diff --git a/providers/alidns/aliDnsProvider.go b/providers/alidns/aliDnsProvider.go index 6068ebb1c..2a7943210 100644 --- a/providers/alidns/aliDnsProvider.go +++ b/providers/alidns/aliDnsProvider.go @@ -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 diff --git a/providers/alidns/api.go b/providers/alidns/api.go index d11a3403b..74e0bed69 100644 --- a/providers/alidns/api.go +++ b/providers/alidns/api.go @@ -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() diff --git a/providers/alidns/convert.go b/providers/alidns/convert.go index ab1f3942e..706efc659 100644 --- a/providers/alidns/convert.go +++ b/providers/alidns/convert.go @@ -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 +}