ALIDNS: Fix NS modified

This commit is contained in:
artin 2025-12-04 02:39:55 +08:00
parent 9b7bbbabe1
commit 528a8105eb
3 changed files with 61 additions and 23 deletions

View file

@ -25,7 +25,7 @@ var features = providers.DocumentationNotes{
providers.CanAutoDNSSEC: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.DocOfficiallySupported: providers.Cannot(),
providers.DocDualHost: providers.Cannot(),
providers.DocDualHost: providers.Can("Alibaba Cloud DNS allows full management of apex NS records"),
providers.DocCreateDomains: providers.Cannot(),
providers.CanUseRoute53Alias: providers.Cannot(),
}
@ -34,7 +34,7 @@ func init() {
const providerName = "ALIDNS"
const providerMaintainer = "@bytemain"
fns := providers.DspFuncs{
Initializer: newAliDnsDsp,
Initializer: newAliDNSDsp,
RecordAuditor: AuditRecords,
}
providers.RegisterDomainServiceProviderType(providerName, fns, features)
@ -52,7 +52,7 @@ func init() {
}
type aliDnsDsp struct {
type aliDNSDsp struct {
client *alidns.Client
domainVersionCache map[string]*domainVersionInfo
cacheMu sync.Mutex
@ -64,7 +64,7 @@ type domainVersionInfo struct {
maxTTL uint32
}
func newAliDnsDsp(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
func newAliDNSDsp(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
accessKeyID := config["access_key_id"]
if accessKeyID == "" {
return nil, fmt.Errorf("creds.json: access_key_id must not be empty")
@ -92,14 +92,22 @@ func newAliDnsDsp(config map[string]string, metadata json.RawMessage) (providers
if err != nil {
return nil, err
}
return &aliDnsDsp{
return &aliDNSDsp{
client: client,
domainVersionCache: make(map[string]*domainVersionInfo),
}, nil
}
func (a *aliDNSDsp) GetNameservers(domain string) ([]*models.Nameserver, error) {
nsStrings, err := a.getNameservers(domain)
if err != nil {
return nil, err
}
return models.ToNameserversStripTD(nsStrings)
}
// GetZoneRecords returns an array of RecordConfig structs for a zone.
func (a *aliDnsDsp) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
func (a *aliDNSDsp) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
// Fetch all pages of domain records.
records, err := a.describeDomainRecordsAll(domain)
if err != nil {
@ -117,19 +125,25 @@ func (a *aliDnsDsp) GetZoneRecords(domain string, meta map[string]string) (model
return nil, err
}
// Skip apex NS records since Alibaba Cloud manages them automatically
// and we cannot modify them (DocDualHost: Cannot)
if rc.Type == "NS" && rc.GetLabel() == "@" {
continue
}
out = append(out, rc)
}
// Alibaba Cloud's DescribeDomainRecords API doesn't return NS records at the apex.
// We need to fetch them separately using getNameservers and add them to the records.
nameservers, err := a.getNameservers(domain)
if err != nil {
return nil, err
}
for _, ns := range nameservers {
rc := nativeToRecordNS(ns, domain)
out = append(out, rc)
}
return out, nil
}
func (a *aliDnsDsp) ListZones() ([]string, error) {
func (a *aliDNSDsp) ListZones() ([]string, error) {
return a.describeDomainsAll()
}
@ -150,7 +164,7 @@ func deduplicateNameServerTargets(newRecs models.Records) models.Records {
}
// PrepDesiredRecords munges any records to best suit this provider.
func (a *aliDnsDsp) PrepDesiredRecords(dc *models.DomainConfig) {
func (a *aliDNSDsp) PrepDesiredRecords(dc *models.DomainConfig) {
versionInfo, err := a.getDomainVersionInfo(dc.Name)
if err != nil {
return
@ -180,12 +194,13 @@ func (a *aliDnsDsp) PrepDesiredRecords(dc *models.DomainConfig) {
dc.Records = recordsToKeep
}
func (a *aliDnsDsp) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, int, error) {
func (a *aliDNSDsp) GetZoneRecordsCorrections(dc *models.DomainConfig, existingRecords models.Records) ([]*models.Correction, int, error) {
// Prepare desired records first to normalize TTLs and avoid warnings
a.PrepDesiredRecords(dc)
var corrections []*models.Correction
// Azure is a "ByRecordSet" API.
// Alibaba Cloud DNS is a "ByRecord" API.
changes, actualChangeCount, err := diff2.ByRecord(existingRecords, dc, nil)
if err != nil {
return nil, 0, err

View file

@ -8,7 +8,7 @@ import (
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
)
func (a *aliDnsDsp) getDomainVersionInfo(domain string) (*domainVersionInfo, error) {
func (a *aliDNSDsp) getDomainVersionInfo(domain string) (*domainVersionInfo, error) {
// Check cache first
a.cacheMu.Lock()
info, ok := a.domainVersionCache[domain]
@ -52,7 +52,8 @@ func (a *aliDnsDsp) getDomainVersionInfo(domain string) (*domainVersionInfo, err
return info, nil
}
func (a *aliDnsDsp) GetNameservers(domain string) ([]*models.Nameserver, error) {
// GetNameservers returns the nameservers for a domain.
func (a *aliDNSDsp) getNameservers(domain string) ([]string, error) {
req := alidns.CreateDescribeDomainInfoRequest()
req.DomainName = domain
@ -61,10 +62,20 @@ func (a *aliDnsDsp) GetNameservers(domain string) ([]*models.Nameserver, error)
return nil, err
}
return models.ToNameservers(resp.DnsServers.DnsServer)
// Add trailing dot to each nameserver to make them FQDNs
nameservers := make([]string, len(resp.DnsServers.DnsServer))
for i, ns := range resp.DnsServers.DnsServer {
if ns != "" && ns[len(ns)-1] != '.' {
nameservers[i] = ns + "."
} else {
nameservers[i] = ns
}
}
return nameservers, nil
}
func (a *aliDnsDsp) deleteRecordset(records []*models.RecordConfig, domainName string) error {
func (a *aliDNSDsp) deleteRecordset(records []*models.RecordConfig, domainName string) error {
for _, r := range records {
req := alidns.CreateDeleteDomainRecordRequest()
original, ok := r.Original.(*alidns.Record)
@ -81,7 +92,7 @@ func (a *aliDnsDsp) deleteRecordset(records []*models.RecordConfig, domainName s
return nil
}
func (a *aliDnsDsp) createRecordset(records []*models.RecordConfig, domainName string) error {
func (a *aliDNSDsp) createRecordset(records []*models.RecordConfig, domainName string) error {
for _, r := range records {
req := alidns.CreateAddDomainRecordRequest()
req.DomainName = domainName
@ -103,7 +114,7 @@ func (a *aliDnsDsp) createRecordset(records []*models.RecordConfig, domainName s
return nil
}
func (a *aliDnsDsp) updateRecordset(existing, desired []*models.RecordConfig, domainName string) error {
func (a *aliDNSDsp) updateRecordset(existing, desired []*models.RecordConfig, domainName string) error {
// Strategy: Delete all existing records, then create all desired records.
// This is the simplest and most reliable approach because:
// 1. The number of records in a recordset may change
@ -121,11 +132,12 @@ func (a *aliDnsDsp) updateRecordset(existing, desired []*models.RecordConfig, do
// describeDomainRecordsAll fetches all domain records for 'domain', handling
// pagination transparently. It returns the slice of *alidns.Record or an error.
func (a *aliDnsDsp) describeDomainRecordsAll(domain string) ([]*alidns.Record, error) {
func (a *aliDNSDsp) describeDomainRecordsAll(domain string) ([]*alidns.Record, error) {
// The SDK returns a slice of value Records (not pointers). We fetch pages
// as values and then convert to pointers before returning.
fetch := func(pageNumber, pageSize int) ([]alidns.Record, int, error) {
req := alidns.CreateDescribeDomainRecordsRequest()
req.Status = "Enable"
req.DomainName = domain
req.PageNumber = requests.NewInteger(pageNumber)
req.PageSize = requests.NewInteger(pageSize)
@ -150,7 +162,7 @@ func (a *aliDnsDsp) describeDomainRecordsAll(domain string) ([]*alidns.Record, e
return out, nil
}
func (a *aliDnsDsp) describeDomainsAll() ([]string, error) {
func (a *aliDNSDsp) describeDomainsAll() ([]string, error) {
// describeDomainsAll fetches all domains in the account, handling pagination.
fetch := func(pageNumber, pageSize int) ([]string, int, error) {
req := alidns.CreateDescribeDomainsRequest()

View file

@ -97,3 +97,14 @@ func recordToNativePriority(r *models.RecordConfig) int64 {
return 0
}
}
// nativeToRecordNS takes a NS record from DNS and returns a native RecordConfig struct.
func nativeToRecordNS(ns string, origin string) *models.RecordConfig {
rc := &models.RecordConfig{
Type: "NS",
TTL: 600,
}
rc.SetLabel("@", origin)
rc.MustSetTarget(ns)
return rc
}