ALIDNS: Use diff2

This commit is contained in:
artin 2025-12-03 23:49:27 +08:00
parent 988a6596d2
commit 820cde51d1
2 changed files with 51 additions and 54 deletions

View file

@ -1748,7 +1748,7 @@ func makeTests() []*TestGroup {
// IGNORE with changes // IGNORE with changes
testgroup("IGNORE with modify", testgroup("IGNORE with modify",
not("NAMECHEAP", "ALIDNS"), // Will fail until converted to use diff2 module. not("NAMECHEAP"), // Will fail until converted to use diff2 module.
tc("Create some records", tc("Create some records",
a("foo", "1.1.1.1"), a("foo", "1.1.1.1"),
a("foo", "10.10.10.10"), a("foo", "10.10.10.10"),

View file

@ -6,8 +6,7 @@ import (
"strings" "strings"
"github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff" "github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/providers" "github.com/StackExchange/dnscontrol/v4/providers"
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
) )
@ -109,76 +108,74 @@ func (a *aliDnsDsp) GetZoneRecords(domain string, meta map[string]string) (model
return out, nil return out, nil
} }
func removeTrailingDot(record string) string {
return strings.TrimSuffix(record, ".")
}
func deduplicateNameServerTargets(newRecs models.Records) models.Records {
dedupedMap := make(map[string]bool)
var deduped models.Records
for _, rec := range newRecs {
if !dedupedMap[rec.GetTargetField()] {
dedupedMap[rec.GetTargetField()] = true
deduped = append(deduped, rec)
}
}
return deduped
}
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) {
keysToUpdate, toReport, actualChangeCount, err := diff.NewCompat(dc).ChangedGroups(existingRecords) var corrections []*models.Correction
// Azure is a "ByRecordSet" API.
changes, actualChangeCount, err := diff2.ByRecord(existingRecords, dc, nil)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
// Start corrections with the reports
corrections := diff.GenerateMessageCorrections(toReport)
existingRecordsMap := make(map[models.RecordKey][]*models.RecordConfig) for _, change := range changes {
for _, r := range existingRecords { // Copy all param values to local variables to avoid overwrites
key := models.RecordKey{NameFQDN: r.NameFQDN, Type: r.Type} msgs := change.MsgsJoined
existingRecordsMap[key] = append(existingRecordsMap[key], r) dcn := dc.Name
} chaKey := change.Key
desiredRecordsMap := dc.Records.GroupedByKey() if change.Type == diff2.CHANGE || change.Type == diff2.CREATE {
if chaKey.Type == "NS" && dcn == removeTrailingDot(change.Key.NameFQDN) {
change.New = deduplicateNameServerTargets(change.New)
}
}
// Deletes must occur first. For example, if replacing a existing CNAME with an A of the same name: switch change.Type {
// DELETE CNAME foo.example.net case diff2.REPORT:
// must occur before corrections = append(corrections, &models.Correction{Msg: change.MsgsJoined})
// CREATE A foo.example.net case diff2.CREATE:
// because both an A and a CNAME for the same name is not allowed. changeNew := change.New
lastCorrections := []*models.Correction{} // creates and replaces last
for key, msg := range keysToUpdate {
existing, okExisting := existingRecordsMap[key]
desired, okDesired := desiredRecordsMap[key]
if okExisting && !okDesired {
// In the existing map but not in the desired map: Delete
corrections = append(corrections, &models.Correction{ corrections = append(corrections, &models.Correction{
Msg: strings.Join(msg, "\n "), Msg: msgs,
F: func() error { F: func() error {
return a.deleteRecordset(existing, dc.Name) return a.createRecordset(changeNew, dcn)
}, },
}) })
printer.Debugf("deleteRecordset: %s %s\n", key.NameFQDN, key.Type) case diff2.CHANGE:
for _, rdata := range existing { changeNew := change.New
printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) changeExisting := change.Old
} corrections = append(corrections, &models.Correction{
} else if !okExisting && okDesired { Msg: msgs,
// Not in the existing map but in the desired map: Create
lastCorrections = append(lastCorrections, &models.Correction{
Msg: strings.Join(msg, "\n "),
F: func() error { F: func() error {
return a.createRecordset(desired, dc.Name) return a.updateRecordset(changeExisting, changeNew, dcn)
}, },
}) })
printer.Debugf("createRecordset: %s %s\n", key.NameFQDN, key.Type) case diff2.DELETE:
for _, rdata := range desired { corrections = append(corrections, &models.Correction{
printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) Msg: msgs,
}
} else if okExisting && okDesired {
// In the existing map and in the desired map: Replace
lastCorrections = append(lastCorrections, &models.Correction{
Msg: strings.Join(msg, "\n "),
F: func() error { F: func() error {
return a.updateRecordset(existing, desired, dc.Name) return a.deleteRecordset(change.Old, dcn)
}, },
}) })
printer.Debugf("updateRecordset: %s %s\n", key.NameFQDN, key.Type) default:
for _, rdata := range desired { panic(fmt.Sprintf("unhandled change.Type %s", change.Type))
printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined())
}
} }
} }
// Append creates and updates after deletes
corrections = append(corrections, lastCorrections...)
printer.Debugf("Found %d corrections (actualChangeCount=%d)\n", len(corrections), actualChangeCount)
return corrections, actualChangeCount, nil return corrections, actualChangeCount, nil
} }