diff --git a/commands/commands.go b/commands/commands.go index bdc9dea06..b01f043ab 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/StackExchange/dnscontrol/v3/models" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/js" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/urfave/cli/v2" @@ -57,6 +58,11 @@ func Run(v string) int { Usage: "Enable JS fetch(), dangerous on untrusted code!", Destination: &js.EnableFetch, }, + &cli.BoolFlag{ + Name: "diff2", + Usage: "Enable replacement diff algorithm", + Destination: &diff2.EnableDiff2, + }, } sort.Sort(cli.CommandsByName(commands)) app.Commands = commands diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 8998529f3..2f56ac0f6 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -12,6 +12,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/credsfile" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/nameservers" "github.com/StackExchange/dnscontrol/v3/pkg/normalize" "github.com/StackExchange/dnscontrol/v3/providers" @@ -29,6 +30,8 @@ var enableCFWorkers = flag.Bool("cfworkers", true, "Set false to disable CF work func init() { testing.Init() + + flag.BoolVar(&diff2.EnableDiff2, "diff2", false, "enable diff2") flag.Parse() } diff --git a/pkg/diff2/flag.go b/pkg/diff2/flag.go new file mode 100644 index 000000000..6576d9271 --- /dev/null +++ b/pkg/diff2/flag.go @@ -0,0 +1,4 @@ +package diff2 + +// EnableDiff2 is true to activate the experimental diff2 algorithm. +var EnableDiff2 bool diff --git a/providers/akamaiedgedns/akamaiEdgeDnsProvider.go b/providers/akamaiedgedns/akamaiEdgeDnsProvider.go index 1e613943a..c6a80ca3d 100644 --- a/providers/akamaiedgedns/akamaiEdgeDnsProvider.go +++ b/providers/akamaiedgedns/akamaiEdgeDnsProvider.go @@ -16,6 +16,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -121,101 +122,108 @@ func (a *edgeDNSProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) - keysToUpdate, err := (diff.New(dc)).ChangedGroups(existingRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - existingRecordsMap := make(map[models.RecordKey][]*models.RecordConfig) - for _, r := range existingRecords { - key := models.RecordKey{NameFQDN: r.NameFQDN, Type: r.Type} - existingRecordsMap[key] = append(existingRecordsMap[key], r) - } + keysToUpdate, err := (diff.New(dc)).ChangedGroups(existingRecords) + if err != nil { + return nil, err + } - desiredRecordsMap := dc.Records.GroupedByKey() + existingRecordsMap := make(map[models.RecordKey][]*models.RecordConfig) + for _, r := range existingRecords { + key := models.RecordKey{NameFQDN: r.NameFQDN, Type: r.Type} + existingRecordsMap[key] = append(existingRecordsMap[key], r) + } - // Deletes must occur first. For example, if replacing a existing CNAME with an A of the same name: - // DELETE CNAME foo.example.net - // must occur before - // CREATE A foo.example.net - // because both an A and a CNAME for the same name is not allowed. + desiredRecordsMap := dc.Records.GroupedByKey() - corrections := []*models.Correction{} // deletes first - lastCorrections := []*models.Correction{} // creates and replaces last + // Deletes must occur first. For example, if replacing a existing CNAME with an A of the same name: + // DELETE CNAME foo.example.net + // must occur before + // CREATE A foo.example.net + // because both an A and a CNAME for the same name is not allowed. - for key, msg := range keysToUpdate { - existing, okExisting := existingRecordsMap[key] - desired, okDesired := desiredRecordsMap[key] + lastCorrections := []*models.Correction{} // creates and replaces last - if okExisting && !okDesired { - // In the existing map but not in the desired map: Delete - corrections = append(corrections, &models.Correction{ - Msg: strings.Join(msg, "\n "), - F: func() error { - return deleteRecordset(existing, dc.Name) - }, - }) - printer.Debugf("deleteRecordset: %s %s\n", key.NameFQDN, key.Type) - for _, rdata := range existing { - printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) - } - } else if !okExisting && okDesired { - // Not in the existing map but in the desired map: Create - lastCorrections = append(lastCorrections, &models.Correction{ - Msg: strings.Join(msg, "\n "), - F: func() error { - return createRecordset(desired, dc.Name) - }, - }) - printer.Debugf("createRecordset: %s %s\n", key.NameFQDN, key.Type) - for _, rdata := range desired { - printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) - } - } 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 { - return replaceRecordset(desired, dc.Name) - }, - }) - printer.Debugf("replaceRecordset: %s %s\n", key.NameFQDN, key.Type) - for _, rdata := range desired { - printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) + 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{ + Msg: strings.Join(msg, "\n "), + F: func() error { + return deleteRecordset(existing, dc.Name) + }, + }) + printer.Debugf("deleteRecordset: %s %s\n", key.NameFQDN, key.Type) + for _, rdata := range existing { + printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) + } + } else if !okExisting && okDesired { + // Not in the existing map but in the desired map: Create + lastCorrections = append(lastCorrections, &models.Correction{ + Msg: strings.Join(msg, "\n "), + F: func() error { + return createRecordset(desired, dc.Name) + }, + }) + printer.Debugf("createRecordset: %s %s\n", key.NameFQDN, key.Type) + for _, rdata := range desired { + printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) + } + } 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 { + return replaceRecordset(desired, dc.Name) + }, + }) + printer.Debugf("replaceRecordset: %s %s\n", key.NameFQDN, key.Type) + for _, rdata := range desired { + printer.Debugf(" Rdata: %s\n", rdata.GetTargetCombined()) + } } } + + // Deletes first, then creates and replaces + corrections = append(corrections, lastCorrections...) + + // AutoDnsSec correction + existingAutoDNSSecEnabled, err := isAutoDNSSecEnabled(dc.Name) + if err != nil { + return nil, err + } + + desiredAutoDNSSecEnabled := dc.AutoDNSSEC == "on" + + if !existingAutoDNSSecEnabled && desiredAutoDNSSecEnabled { + // Existing false (disabled), Desired true (enabled) + corrections = append(corrections, &models.Correction{ + Msg: "Enable AutoDnsSec\n", + F: func() error { + return autoDNSSecEnable(true, dc.Name) + }, + }) + printer.Debugf("autoDNSSecEnable: Enable AutoDnsSec for zone %s\n", dc.Name) + } else if existingAutoDNSSecEnabled && !desiredAutoDNSSecEnabled { + // Existing true (enabled), Desired false (disabled) + corrections = append(corrections, &models.Correction{ + Msg: "Disable AutoDnsSec\n", + F: func() error { + return autoDNSSecEnable(false, dc.Name) + }, + }) + printer.Debugf("autoDNSSecEnable: Disable AutoDnsSec for zone %s\n", dc.Name) + } + + return corrections, nil } - // Deletes first, then creates and replaces - corrections = append(corrections, lastCorrections...) - - // AutoDnsSec correction - existingAutoDNSSecEnabled, err := isAutoDNSSecEnabled(dc.Name) - if err != nil { - return nil, err - } - - desiredAutoDNSSecEnabled := dc.AutoDNSSEC == "on" - - if !existingAutoDNSSecEnabled && desiredAutoDNSSecEnabled { - // Existing false (disabled), Desired true (enabled) - corrections = append(corrections, &models.Correction{ - Msg: "Enable AutoDnsSec\n", - F: func() error { - return autoDNSSecEnable(true, dc.Name) - }, - }) - printer.Debugf("autoDNSSecEnable: Enable AutoDnsSec for zone %s\n", dc.Name) - } else if existingAutoDNSSecEnabled && !desiredAutoDNSSecEnabled { - // Existing true (enabled), Desired false (disabled) - corrections = append(corrections, &models.Correction{ - Msg: "Disable AutoDnsSec\n", - F: func() error { - return autoDNSSecEnable(false, dc.Name) - }, - }) - printer.Debugf("autoDNSSecEnable: Disable AutoDnsSec for zone %s\n", dc.Name) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/autodns/autoDnsProvider.go b/providers/autodns/autoDnsProvider.go index e23a2b6ae..d6f8a0b3a 100644 --- a/providers/autodns/autoDnsProvider.go +++ b/providers/autodns/autoDnsProvider.go @@ -11,6 +11,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -91,92 +92,98 @@ func (api *autoDNSProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - unchanged, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - - for _, m := range unchanged { - changes = append(changes, m.Desired) - } - - for _, m := range del { - // Just notify, these records don't have to be deleted explicitly - printer.Debugf(m.String()) - } - - for _, m := range create { - printer.Debugf(m.String()) - changes = append(changes, m.Desired) - } - - for _, m := range modify { - printer.Debugf("mod") - printer.Debugf(m.String()) - changes = append(changes, m.Desired) - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - if len(create) > 0 || len(del) > 0 || len(modify) > 0 { - corrections = append(corrections, - &models.Correction{ - Msg: "Zone update for " + domain, - F: func() error { - zoneTTL := uint32(0) - nameServers := []*models.Nameserver{} - resourceRecords := []*ResourceRecord{} + differ := diff.New(dc) + unchanged, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err + } - for _, record := range changes { - // NS records for the APEX should be handled differently - if record.Type == "NS" && record.Name == "@" { - nameServers = append(nameServers, &models.Nameserver{ - Name: strings.TrimSuffix(record.GetTargetField(), "."), - }) + for _, m := range unchanged { + changes = append(changes, m.Desired) + } - zoneTTL = record.TTL - } else { - resourceRecord := &ResourceRecord{ - Name: record.Name, - TTL: int64(record.TTL), - Type: record.Type, - Value: record.GetTargetField(), + for _, m := range del { + // Just notify, these records don't have to be deleted explicitly + printer.Debugf(m.String()) + } + + for _, m := range create { + printer.Debugf(m.String()) + changes = append(changes, m.Desired) + } + + for _, m := range modify { + printer.Debugf("mod") + printer.Debugf(m.String()) + changes = append(changes, m.Desired) + } + + if len(create) > 0 || len(del) > 0 || len(modify) > 0 { + corrections = append(corrections, + &models.Correction{ + Msg: "Zone update for " + domain, + F: func() error { + zoneTTL := uint32(0) + nameServers := []*models.Nameserver{} + resourceRecords := []*ResourceRecord{} + + for _, record := range changes { + // NS records for the APEX should be handled differently + if record.Type == "NS" && record.Name == "@" { + nameServers = append(nameServers, &models.Nameserver{ + Name: strings.TrimSuffix(record.GetTargetField(), "."), + }) + + zoneTTL = record.TTL + } else { + resourceRecord := &ResourceRecord{ + Name: record.Name, + TTL: int64(record.TTL), + Type: record.Type, + Value: record.GetTargetField(), + } + + if resourceRecord.Name == "@" { + resourceRecord.Name = "" + } + + if record.Type == "MX" { + resourceRecord.Pref = int32(record.MxPreference) + } + + if record.Type == "SRV" { + resourceRecord.Value = fmt.Sprintf( + "%d %d %d %s", + record.SrvPriority, + record.SrvWeight, + record.SrvPort, + record.GetTargetField(), + ) + } + + resourceRecords = append(resourceRecords, resourceRecord) } - - if resourceRecord.Name == "@" { - resourceRecord.Name = "" - } - - if record.Type == "MX" { - resourceRecord.Pref = int32(record.MxPreference) - } - - if record.Type == "SRV" { - resourceRecord.Value = fmt.Sprintf( - "%d %d %d %s", - record.SrvPriority, - record.SrvWeight, - record.SrvPort, - record.GetTargetField(), - ) - } - - resourceRecords = append(resourceRecords, resourceRecord) } - } - err := api.updateZone(domain, resourceRecords, nameServers, zoneTTL) + err := api.updateZone(domain, resourceRecords, nameServers, zoneTTL) - if err != nil { - return fmt.Errorf(err.Error()) - } + if err != nil { + return fmt.Errorf(err.Error()) + } - return nil - }, - }) + return nil + }, + }) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/axfrddns/axfrddnsProvider.go b/providers/axfrddns/axfrddnsProvider.go index d8b70ff49..97943986b 100644 --- a/providers/axfrddns/axfrddnsProvider.go +++ b/providers/axfrddns/axfrddnsProvider.go @@ -25,6 +25,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -346,100 +347,107 @@ func (c *axfrddnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod models.PostProcessRecords(foundRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, del, mod, err := differ.IncrementalDiff(foundRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - buf := &bytes.Buffer{} - // Print a list of changes. Generate an actual change that is the zone - changes := false - for _, i := range create { - changes = true - fmt.Fprintln(buf, i) - } - for _, i := range del { - changes = true - fmt.Fprintln(buf, i) - } - for _, i := range mod { - changes = true - fmt.Fprintln(buf, i) - } - msg := fmt.Sprintf("DDNS UPDATES to '%s' (primary master: '%s'). Changes:\n%s", dc.Name, c.master, buf) + differ := diff.New(dc) + _, create, del, mod, err := differ.IncrementalDiff(foundRecords) + if err != nil { + return nil, err + } - corrections := []*models.Correction{} - if changes { + buf := &bytes.Buffer{} + // Print a list of changes. Generate an actual change that is the zone + changes := false + for _, i := range create { + changes = true + fmt.Fprintln(buf, i) + } + for _, i := range del { + changes = true + fmt.Fprintln(buf, i) + } + for _, i := range mod { + changes = true + fmt.Fprintln(buf, i) + } + msg := fmt.Sprintf("DDNS UPDATES to '%s' (primary master: '%s'). Changes:\n%s", dc.Name, c.master, buf) - corrections = append(corrections, - &models.Correction{ - Msg: msg, - F: func() error { + if changes { - // An RFC2136-compliant server must silently ignore an - // update that inserts a non-CNAME RRset when a CNAME RR - // with the same name is present in the zone (and - // vice-versa). Therefore we prefer to first remove records - // and then insert new ones. - // - // Compliant servers must also silently ignore an update - // that removes the last NS record of a zone. Therefore we - // don't want to remove all NS records before inserting a - // new one. For the particular case of NS record, we prefer - // to insert new records before ot remove old ones. - // - // This remarks does not apply for "modified" NS records, as - // updates are processed one-by-one. - // - // This provider does not allow modifying the TTL of an NS - // record in a zone that defines only one NS. That would - // would require removing the single NS record, before - // adding the new one. But who does that anyway? + corrections = append(corrections, + &models.Correction{ + Msg: msg, + F: func() error { - update := new(dns.Msg) - update.SetUpdate(dc.Name + ".") - update.Id = uint16(c.rand.Intn(math.MaxUint16)) - for _, c := range create { - if c.Desired.Type == "NS" { + // An RFC2136-compliant server must silently ignore an + // update that inserts a non-CNAME RRset when a CNAME RR + // with the same name is present in the zone (and + // vice-versa). Therefore we prefer to first remove records + // and then insert new ones. + // + // Compliant servers must also silently ignore an update + // that removes the last NS record of a zone. Therefore we + // don't want to remove all NS records before inserting a + // new one. For the particular case of NS record, we prefer + // to insert new records before ot remove old ones. + // + // This remarks does not apply for "modified" NS records, as + // updates are processed one-by-one. + // + // This provider does not allow modifying the TTL of an NS + // record in a zone that defines only one NS. That would + // would require removing the single NS record, before + // adding the new one. But who does that anyway? + + update := new(dns.Msg) + update.SetUpdate(dc.Name + ".") + update.Id = uint16(c.rand.Intn(math.MaxUint16)) + for _, c := range create { + if c.Desired.Type == "NS" { + update.Insert([]dns.RR{c.Desired.ToRR()}) + } + } + for _, c := range del { + update.Remove([]dns.RR{c.Existing.ToRR()}) + } + for _, c := range mod { + update.Remove([]dns.RR{c.Existing.ToRR()}) update.Insert([]dns.RR{c.Desired.ToRR()}) } - } - for _, c := range del { - update.Remove([]dns.RR{c.Existing.ToRR()}) - } - for _, c := range mod { - update.Remove([]dns.RR{c.Existing.ToRR()}) - update.Insert([]dns.RR{c.Desired.ToRR()}) - } - for _, c := range create { - if c.Desired.Type != "NS" { - update.Insert([]dns.RR{c.Desired.ToRR()}) + for _, c := range create { + if c.Desired.Type != "NS" { + update.Insert([]dns.RR{c.Desired.ToRR()}) + } } - } - client := new(dns.Client) - client.Net = c.updateMode - client.Timeout = dnsTimeout - if c.updateKey != nil { - client.TsigSecret = - map[string]string{c.updateKey.id: c.updateKey.secret} - update.SetTsig(c.updateKey.id, c.updateKey.algo, 300, time.Now().Unix()) - } + client := new(dns.Client) + client.Net = c.updateMode + client.Timeout = dnsTimeout + if c.updateKey != nil { + client.TsigSecret = + map[string]string{c.updateKey.id: c.updateKey.secret} + update.SetTsig(c.updateKey.id, c.updateKey.algo, 300, time.Now().Unix()) + } - msg, _, err := client.Exchange(update, c.master) - if err != nil { - return err - } - if msg.MsgHdr.Rcode != 0 { - return fmt.Errorf("[Error] AXFRDDNS: nameserver refused to update the zone: %s (%d)", - dns.RcodeToString[msg.MsgHdr.Rcode], - msg.MsgHdr.Rcode) - } + msg, _, err := client.Exchange(update, c.master) + if err != nil { + return err + } + if msg.MsgHdr.Rcode != 0 { + return fmt.Errorf("[Error] AXFRDDNS: nameserver refused to update the zone: %s (%d)", + dns.RcodeToString[msg.MsgHdr.Rcode], + msg.MsgHdr.Rcode) + } - return nil - }, - }) + return nil + }, + }) + } + return corrections, nil } + + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/azuredns/azureDnsProvider.go b/providers/azuredns/azureDnsProvider.go index 7a7c5d032..9d44e8fdf 100644 --- a/providers/azuredns/azureDnsProvider.go +++ b/providers/azuredns/azureDnsProvider.go @@ -13,6 +13,7 @@ import ( "github.com/Azure/go-autorest/autorest/to" "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -188,8 +189,6 @@ func (a *azurednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod return nil, err } - var corrections []*models.Correction - existingRecords, records, zoneName, err := a.getExistingRecords(dc.Name) if err != nil { return nil, err @@ -197,133 +196,141 @@ func (a *azurednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - namesToUpdate, err := differ.ChangedGroups(existingRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - if len(namesToUpdate) == 0 { - return nil, nil - } - - updates := map[models.RecordKey][]*models.RecordConfig{} - - for k := range namesToUpdate { - updates[k] = nil - for _, rc := range dc.Records { - if rc.Key() == k { - updates[k] = append(updates[k], rc) - } + differ := diff.New(dc) + namesToUpdate, err := differ.ChangedGroups(existingRecords) + if err != nil { + return nil, err } - } - for k, recs := range updates { - if len(recs) == 0 { - var rrset *adns.RecordSet - for _, r := range records { - if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN { - n1, err := nativeToRecordType(r.Type) - if err != nil { - return nil, err - } - n2, err := nativeToRecordType(to.StringPtr(k.Type)) - if err != nil { - return nil, err - } - if n1 == n2 { - rrset = r - break - } + if len(namesToUpdate) == 0 { + return nil, nil + } + + updates := map[models.RecordKey][]*models.RecordConfig{} + + for k := range namesToUpdate { + updates[k] = nil + for _, rc := range dc.Records { + if rc.Key() == k { + updates[k] = append(updates[k], rc) } } - if rrset != nil { + } + + for k, recs := range updates { + if len(recs) == 0 { + var rrset *adns.RecordSet + for _, r := range records { + if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN { + n1, err := nativeToRecordType(r.Type) + if err != nil { + return nil, err + } + n2, err := nativeToRecordType(to.StringPtr(k.Type)) + if err != nil { + return nil, err + } + if n1 == n2 { + rrset = r + break + } + } + } + if rrset != nil { + corrections = append(corrections, + &models.Correction{ + Msg: strings.Join(namesToUpdate[k], "\n"), + F: func() error { + ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second) + defer cancel() + rt, err := nativeToRecordType(rrset.Type) + if err != nil { + return err + } + _, err = a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, *rrset.Name, rt, nil) + if err != nil { + return err + } + return nil + }, + }) + } else { + return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type) + } + } else { + rrset, recordType, err := a.recordToNative(k, recs) + if err != nil { + return nil, err + } + var recordName string + for _, r := range recs { + i := int64(r.TTL) + rrset.Properties.TTL = &i // TODO: make sure that ttls are consistent within a set + recordName = r.Name + } + + for _, r := range records { + existingRecordType, err := nativeToRecordType(r.Type) + if err != nil { + return nil, err + } + changedRecordType, err := nativeToRecordType(to.StringPtr(k.Type)) + if err != nil { + return nil, err + } + if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN && (changedRecordType == adns.RecordTypeCNAME || existingRecordType == adns.RecordTypeCNAME) { + if existingRecordType == adns.RecordTypeA || existingRecordType == adns.RecordTypeAAAA || changedRecordType == adns.RecordTypeA || changedRecordType == adns.RecordTypeAAAA { //CNAME cannot coexist with an A or AA + corrections = append(corrections, + &models.Correction{ + Msg: strings.Join(namesToUpdate[k], "\n"), + F: func() error { + ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second) + defer cancel() + _, err := a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, recordName, existingRecordType, nil) + if err != nil { + return err + } + return nil + }, + }) + } + } + } + corrections = append(corrections, &models.Correction{ Msg: strings.Join(namesToUpdate[k], "\n"), F: func() error { ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second) defer cancel() - rt, err := nativeToRecordType(rrset.Type) - if err != nil { - return err - } - _, err = a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, *rrset.Name, rt, nil) + _, err := a.recordsClient.CreateOrUpdate(ctx, *a.resourceGroup, zoneName, recordName, recordType, *rrset, nil) if err != nil { return err } return nil }, }) - } else { - return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type) } - } else { - rrset, recordType, err := a.recordToNative(k, recs) - if err != nil { - return nil, err - } - var recordName string - for _, r := range recs { - i := int64(r.TTL) - rrset.Properties.TTL = &i // TODO: make sure that ttls are consistent within a set - recordName = r.Name - } - - for _, r := range records { - existingRecordType, err := nativeToRecordType(r.Type) - if err != nil { - return nil, err - } - changedRecordType, err := nativeToRecordType(to.StringPtr(k.Type)) - if err != nil { - return nil, err - } - if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN && (changedRecordType == adns.RecordTypeCNAME || existingRecordType == adns.RecordTypeCNAME) { - if existingRecordType == adns.RecordTypeA || existingRecordType == adns.RecordTypeAAAA || changedRecordType == adns.RecordTypeA || changedRecordType == adns.RecordTypeAAAA { //CNAME cannot coexist with an A or AA - corrections = append(corrections, - &models.Correction{ - Msg: strings.Join(namesToUpdate[k], "\n"), - F: func() error { - ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second) - defer cancel() - _, err := a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, recordName, existingRecordType, nil) - if err != nil { - return err - } - return nil - }, - }) - } - } - } - - corrections = append(corrections, - &models.Correction{ - Msg: strings.Join(namesToUpdate[k], "\n"), - F: func() error { - ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second) - defer cancel() - _, err := a.recordsClient.CreateOrUpdate(ctx, *a.resourceGroup, zoneName, recordName, recordType, *rrset, nil) - if err != nil { - return err - } - return nil - }, - }) } + + // Sort the records for cosmetic reasons: It just makes a long list + // of deletes or adds easier to read if they are in sorted order. + // That said, it may be risky to sort them (sort key is the text + // message "Msg") if there are deletes that must happen before adds. + // Reading the above code it isn't clear that any of the updates are + // order-dependent. That said, all the tests pass. + // If in the future this causes a bug, we can either just remove + // this next line, or (even better) put any order-dependent + // operations in a single models.Correction{}. + sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) }) + + return corrections, nil } - // Sort the records for cosmetic reasons: It just makes a long list - // of deletes or adds easier to read if they are in sorted order. - // That said, it may be risky to sort them (sort key is the text - // message "Msg") if there are deletes that must happen before adds. - // Reading the above code it isn't clear that any of the updates are - // order-dependent. That said, all the tests pass. - // If in the future this causes a bug, we can either just remove - // this next line, or (even better) put any order-dependent - // operations in a single models.Correction{}. - sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) }) + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 7a8d41129..096b0b28f 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -24,6 +24,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/prettyzone" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" @@ -243,72 +244,79 @@ func (c *bindProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models. models.PostProcessRecords(foundRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, del, mod, err := differ.IncrementalDiff(foundRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - buf := &bytes.Buffer{} - // Print a list of changes. Generate an actual change that is the zone - changes := false - for _, i := range create { - changes = true - if c.zoneFileFound { - fmt.Fprintln(buf, i) + differ := diff.New(dc) + _, create, del, mod, err := differ.IncrementalDiff(foundRecords) + if err != nil { + return nil, err } - } - for _, i := range del { - changes = true - if c.zoneFileFound { - fmt.Fprintln(buf, i) + + buf := &bytes.Buffer{} + // Print a list of changes. Generate an actual change that is the zone + changes := false + for _, i := range create { + changes = true + if c.zoneFileFound { + fmt.Fprintln(buf, i) + } } - } - for _, i := range mod { - changes = true - if c.zoneFileFound { - fmt.Fprintln(buf, i) + for _, i := range del { + changes = true + if c.zoneFileFound { + fmt.Fprintln(buf, i) + } } + for _, i := range mod { + changes = true + if c.zoneFileFound { + fmt.Fprintln(buf, i) + } + } + + var msg string + if c.zoneFileFound { + msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s'. Changes:\n%s", dc.Name, buf) + } else { + msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s' (new file with %d records)\n", dc.Name, len(create)) + } + + if changes { + + // We only change the serial number if there is a change. + desiredSoa.SoaSerial = nextSerial + + corrections = append(corrections, + &models.Correction{ + Msg: msg, + F: func() error { + printer.Printf("WRITING ZONEFILE: %v\n", c.zonefile) + zf, err := os.Create(c.zonefile) + if err != nil { + return fmt.Errorf("could not create zonefile: %w", err) + } + // Beware that if there are any fake types, then they will + // be commented out on write, but we don't reverse that when + // reading, so there will be a diff on every invocation. + err = prettyzone.WriteZoneFileRC(zf, dc.Records, dc.Name, 0, comments) + + if err != nil { + return fmt.Errorf("failed WriteZoneFile: %w", err) + } + err = zf.Close() + if err != nil { + return fmt.Errorf("closing: %w", err) + } + return nil + }, + }) + } + + return corrections, nil } - var msg string - if c.zoneFileFound { - msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s'. Changes:\n%s", dc.Name, buf) - } else { - msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s' (new file with %d records)\n", dc.Name, len(create)) - } - - corrections := []*models.Correction{} - if changes { - - // We only change the serial number if there is a change. - desiredSoa.SoaSerial = nextSerial - - corrections = append(corrections, - &models.Correction{ - Msg: msg, - F: func() error { - printer.Printf("WRITING ZONEFILE: %v\n", c.zonefile) - zf, err := os.Create(c.zonefile) - if err != nil { - return fmt.Errorf("could not create zonefile: %w", err) - } - // Beware that if there are any fake types, then they will - // be commented out on write, but we don't reverse that when - // reading, so there will be a diff on every invocation. - err = prettyzone.WriteZoneFileRC(zf, dc.Records, dc.Name, 0, comments) - - if err != nil { - return fmt.Errorf("failed WriteZoneFile: %w", err) - } - err = zf.Close() - if err != nil { - return fmt.Errorf("closing: %w", err) - } - return nil - }, - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index b5c598ea4..14194b8b4 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -9,6 +9,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/transform" "github.com/StackExchange/dnscontrol/v3/providers" @@ -222,99 +223,107 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m // Therefore, whether the string is 1 octet or thousands, just store it as // one string in the first element of .TxtStrings. - differ := diff.New(dc, getProxyMetadata) - _, create, del, mod, err := differ.IncrementalDiff(records) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - corrections := []*models.Correction{} + differ := diff.New(dc, getProxyMetadata) + _, create, del, mod, err := differ.IncrementalDiff(records) + if err != nil { + return nil, err + } - for _, d := range del { - ex := d.Existing - if ex.Type == "PAGE_RULE" { - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return c.deletePageRule(ex.Original.(cloudflare.PageRule).ID, id) }, - }) - } else if ex.Type == "WORKER_ROUTE" { - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return c.deleteWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id) }, - }) - } else { - corr := c.deleteRec(ex.Original.(cloudflare.DNSRecord), id) - // DS records must always have a corresponding NS record. - // Therefore, we remove DS records before any NS records. - if d.Existing.Type == "DS" { - corrections = append([]*models.Correction{corr}, corrections...) + corrections := []*models.Correction{} + + for _, d := range del { + ex := d.Existing + if ex.Type == "PAGE_RULE" { + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return c.deletePageRule(ex.Original.(cloudflare.PageRule).ID, id) }, + }) + } else if ex.Type == "WORKER_ROUTE" { + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return c.deleteWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id) }, + }) } else { - corrections = append(corrections, corr) + corr := c.deleteRec(ex.Original.(cloudflare.DNSRecord), id) + // DS records must always have a corresponding NS record. + // Therefore, we remove DS records before any NS records. + if d.Existing.Type == "DS" { + corrections = append([]*models.Correction{corr}, corrections...) + } else { + corrections = append(corrections, corr) + } } } - } - for _, d := range create { - des := d.Desired - if des.Type == "PAGE_RULE" { - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return c.createPageRule(id, des.GetTargetField()) }, - }) - } else if des.Type == "WORKER_ROUTE" { - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return c.createWorkerRoute(id, des.GetTargetField()) }, - }) - } else { - corr := c.createRec(des, id) - // DS records must always have a corresponding NS record. - // Therefore, we create NS records before any DS records. - if d.Desired.Type == "NS" { - corrections = append(corr, corrections...) + for _, d := range create { + des := d.Desired + if des.Type == "PAGE_RULE" { + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return c.createPageRule(id, des.GetTargetField()) }, + }) + } else if des.Type == "WORKER_ROUTE" { + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return c.createWorkerRoute(id, des.GetTargetField()) }, + }) } else { - corrections = append(corrections, corr...) + corr := c.createRec(des, id) + // DS records must always have a corresponding NS record. + // Therefore, we create NS records before any DS records. + if d.Desired.Type == "NS" { + corrections = append(corr, corrections...) + } else { + corrections = append(corrections, corr...) + } } } - } - for _, d := range mod { - rec := d.Desired - ex := d.Existing - if rec.Type == "PAGE_RULE" { + for _, d := range mod { + rec := d.Desired + ex := d.Existing + if rec.Type == "PAGE_RULE" { + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return c.updatePageRule(ex.Original.(cloudflare.PageRule).ID, id, rec.GetTargetField()) }, + }) + } else if rec.Type == "WORKER_ROUTE" { + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { + return c.updateWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id, rec.GetTargetField()) + }, + }) + } else { + e := ex.Original.(cloudflare.DNSRecord) + proxy := e.Proxiable && rec.Metadata[metaProxy] != "off" + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return c.modifyRecord(id, e.ID, proxy, rec) }, + }) + } + } + + // Add universalSSL change to corrections when needed + if changed, newState, err := c.checkUniversalSSL(dc, id); err == nil && changed { + var newStateString string + if newState { + newStateString = "enabled" + } else { + newStateString = "disabled" + } corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return c.updatePageRule(ex.Original.(cloudflare.PageRule).ID, id, rec.GetTargetField()) }, - }) - } else if rec.Type == "WORKER_ROUTE" { - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { - return c.updateWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id, rec.GetTargetField()) - }, - }) - } else { - e := ex.Original.(cloudflare.DNSRecord) - proxy := e.Proxiable && rec.Metadata[metaProxy] != "off" - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return c.modifyRecord(id, e.ID, proxy, rec) }, + Msg: fmt.Sprintf("Universal SSL will be %s for this domain.", newStateString), + F: func() error { return c.changeUniversalSSL(id, newState) }, }) } + + return corrections, nil } - // Add universalSSL change to corrections when needed - if changed, newState, err := c.checkUniversalSSL(dc, id); err == nil && changed { - var newStateString string - if newState { - newStateString = "enabled" - } else { - newStateString = "disabled" - } - corrections = append(corrections, &models.Correction{ - Msg: fmt.Sprintf("Universal SSL will be %s for this domain.", newStateString), - F: func() error { return c.changeUniversalSSL(id, newState) }, - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/cloudns/cloudnsProvider.go b/providers/cloudns/cloudnsProvider.go index 4e5864693..bba381aab 100644 --- a/providers/cloudns/cloudnsProvider.go +++ b/providers/cloudns/cloudnsProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/miekg/dns/dnsutil" ) @@ -103,84 +104,90 @@ func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode record.TTL = fixTTL(record.TTL) } - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // Deletes first so changing type works etc. - for _, m := range del { - id := m.Existing.Original.(*domainRecord).ID - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, ClouDNS ID: %s", m.String(), id), - F: func() error { - return c.deleteRecord(domainID, id) - }, + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err } - // at ClouDNS, we MUST have a NS for a DS - // So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error - if m.Existing.Type == "DS" { - // type DS is prepended - so executed first - corrections = append([]*models.Correction{corr}, corrections...) - } else { + + // Deletes first so changing type works etc. + for _, m := range del { + id := m.Existing.Original.(*domainRecord).ID + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, ClouDNS ID: %s", m.String(), id), + F: func() error { + return c.deleteRecord(domainID, id) + }, + } + // at ClouDNS, we MUST have a NS for a DS + // So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error + if m.Existing.Type == "DS" { + // type DS is prepended - so executed first + corrections = append([]*models.Correction{corr}, corrections...) + } else { + corrections = append(corrections, corr) + } + } + + var createCorrections []*models.Correction + for _, m := range create { + req, err := toReq(m.Desired) + if err != nil { + return nil, err + } + + // ClouDNS does not require the trailing period to be specified when creating an NS record where the A or AAAA record exists in the zone. + // So, modify it to remove the trailing period. + if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") { + req["record"] = strings.TrimSuffix(req["record"], ".") + } + + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + return c.createRecord(domainID, req) + }, + } + // at ClouDNS, we MUST have a NS for a DS + // So, when creating, we must create the NS first, otherwise creating the DS throws an error + if m.Desired.Type == "NS" { + // type NS is prepended - so executed first + createCorrections = append([]*models.Correction{corr}, createCorrections...) + } else { + createCorrections = append(createCorrections, corr) + } + } + corrections = append(corrections, createCorrections...) + + for _, m := range modify { + id := m.Existing.Original.(*domainRecord).ID + req, err := toReq(m.Desired) + if err != nil { + return nil, err + } + + // ClouDNS does not require the trailing period to be specified when updating an NS record where the A or AAAA record exists in the zone. + // So, modify it to remove the trailing period. + if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") { + req["record"] = strings.TrimSuffix(req["record"], ".") + } + + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id), + F: func() error { + return c.modifyRecord(domainID, id, req) + }, + } corrections = append(corrections, corr) } + + return corrections, nil } - var createCorrections []*models.Correction - for _, m := range create { - req, err := toReq(m.Desired) - if err != nil { - return nil, err - } - - // ClouDNS does not require the trailing period to be specified when creating an NS record where the A or AAAA record exists in the zone. - // So, modify it to remove the trailing period. - if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") { - req["record"] = strings.TrimSuffix(req["record"], ".") - } - - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - return c.createRecord(domainID, req) - }, - } - // at ClouDNS, we MUST have a NS for a DS - // So, when creating, we must create the NS first, otherwise creating the DS throws an error - if m.Desired.Type == "NS" { - // type NS is prepended - so executed first - createCorrections = append([]*models.Correction{corr}, createCorrections...) - } else { - createCorrections = append(createCorrections, corr) - } - } - corrections = append(corrections, createCorrections...) - - for _, m := range modify { - id := m.Existing.Original.(*domainRecord).ID - req, err := toReq(m.Desired) - if err != nil { - return nil, err - } - - // ClouDNS does not require the trailing period to be specified when updating an NS record where the A or AAAA record exists in the zone. - // So, modify it to remove the trailing period. - if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") { - req["record"] = strings.TrimSuffix(req["record"], ".") - } - - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id), - F: func() error { - return c.modifyRecord(domainID, id, req) - }, - } - corrections = append(corrections, corr) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/cscglobal/dns.go b/providers/cscglobal/dns.go index 846b04a4f..f74277527 100644 --- a/providers/cscglobal/dns.go +++ b/providers/cscglobal/dns.go @@ -5,6 +5,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" ) // GetZoneRecords gets the records of a zone and returns them in RecordConfig format. @@ -116,74 +117,82 @@ func (client *providerClient) GenerateDomainCorrections(dc *models.DomainConfig, models.PostProcessRecords(foundRecords) //txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // How to generate corrections? - - // (1) Most providers take individual deletes, creates, and - // modifications: - - // // Generate changes. - // corrections := []*models.Correction{} - // for _, del := range dels { - // corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del)) - // } - // for _, cre := range creates { - // corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...) - // } - // for _, m := range modifications { - // corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m)) - // } - // return corrections, nil - - // (2) Some providers upload the entire zone every time. Look at - // GetDomainCorrections for BIND and NAMECHEAP for inspiration. - - // (3) Others do something entirely different. Like CSCGlobal: - - // CSCGlobal has a unique API. A list of edits is sent in one API - // call. Edits aren't permitted if an existing edit is being - // processed. Therefore, before we do an edit we block until the - // previous edit is done executing. - - var edits []zoneResourceRecordEdit - var descriptions []string - for _, del := range dels { - edits = append(edits, makePurge(dc.Name, del)) - descriptions = append(descriptions, del.String()) - } - for _, cre := range creates { - edits = append(edits, makeAdd(dc.Name, cre)) - descriptions = append(descriptions, cre.String()) - } - for _, m := range modifications { - edits = append(edits, makeEdit(dc.Name, m)) - descriptions = append(descriptions, m.String()) - } - corrections := []*models.Correction{} - if len(edits) > 0 { - c := &models.Correction{ - Msg: "\t" + strings.Join(descriptions, "\n\t"), - F: func() error { - // CSCGlobal's API only permits one pending update at a time. - // Therefore we block until any outstanding updates are done. - // We also clear out any failures, since (and I can't believe - // I'm writing this) any time something fails, the failure has - // to be cleared out with an additional API call. - - err := client.clearRequests(dc.Name) - if err != nil { - return err - } - return client.sendZoneEditRequest(dc.Name, edits) - }, + differ := diff.New(dc) + _, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords) + if err != nil { + return nil, err } - corrections = append(corrections, c) + + // How to generate corrections? + + // (1) Most providers take individual deletes, creates, and + // modifications: + + // // Generate changes. + // corrections := []*models.Correction{} + // for _, del := range dels { + // corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del)) + // } + // for _, cre := range creates { + // corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...) + // } + // for _, m := range modifications { + // corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m)) + // } + // return corrections, nil + + // (2) Some providers upload the entire zone every time. Look at + // GetDomainCorrections for BIND and NAMECHEAP for inspiration. + + // (3) Others do something entirely different. Like CSCGlobal: + + // CSCGlobal has a unique API. A list of edits is sent in one API + // call. Edits aren't permitted if an existing edit is being + // processed. Therefore, before we do an edit we block until the + // previous edit is done executing. + + var edits []zoneResourceRecordEdit + var descriptions []string + for _, del := range dels { + edits = append(edits, makePurge(dc.Name, del)) + descriptions = append(descriptions, del.String()) + } + for _, cre := range creates { + edits = append(edits, makeAdd(dc.Name, cre)) + descriptions = append(descriptions, cre.String()) + } + for _, m := range modifications { + edits = append(edits, makeEdit(dc.Name, m)) + descriptions = append(descriptions, m.String()) + } + corrections := []*models.Correction{} + if len(edits) > 0 { + c := &models.Correction{ + Msg: "\t" + strings.Join(descriptions, "\n\t"), + F: func() error { + // CSCGlobal's API only permits one pending update at a time. + // Therefore we block until any outstanding updates are done. + // We also clear out any failures, since (and I can't believe + // I'm writing this) any time something fails, the failure has + // to be cleared out with an additional API call. + + err := client.clearRequests(dc.Name) + if err != nil { + return err + } + return client.sendZoneEditRequest(dc.Name, edits) + }, + } + corrections = append(corrections, c) + } + return corrections, nil } + + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/desec/desecProvider.go b/providers/desec/desecProvider.go index 0c40e0d26..00008a534 100644 --- a/providers/desec/desecProvider.go +++ b/providers/desec/desecProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -164,82 +165,88 @@ func PrepDesiredRecords(dc *models.DomainConfig, minTTL uint32) { // made. func (c *desecProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) { - var corrections = []*models.Correction{} + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // diff existing vs. current. - differ := diff.New(dc) - keysToUpdate, err := differ.ChangedGroups(existing) - if err != nil { - return nil, err - } - if len(keysToUpdate) == 0 { - return nil, nil - } + // diff existing vs. current. + differ := diff.New(dc) + keysToUpdate, err := differ.ChangedGroups(existing) + if err != nil { + return nil, err + } + if len(keysToUpdate) == 0 { + return nil, nil + } - desiredRecords := dc.Records.GroupedByKey() - var rrs []resourceRecord - buf := &bytes.Buffer{} - // For any key with an update, delete or replace those records. - for label := range keysToUpdate { - if _, ok := desiredRecords[label]; !ok { - //we could not find this RecordKey in the desiredRecords - //this means it must be deleted - for i, msg := range keysToUpdate[label] { - if i == 0 { - rc := resourceRecord{} - rc.Type = label.Type - rc.Records = make([]string, 0) // empty array of records should delete this rrset - rc.TTL = 3600 - shortname := dnsutil.TrimDomainName(label.NameFQDN, dc.Name) - if shortname == "@" { - shortname = "" + desiredRecords := dc.Records.GroupedByKey() + var rrs []resourceRecord + buf := &bytes.Buffer{} + // For any key with an update, delete or replace those records. + for label := range keysToUpdate { + if _, ok := desiredRecords[label]; !ok { + //we could not find this RecordKey in the desiredRecords + //this means it must be deleted + for i, msg := range keysToUpdate[label] { + if i == 0 { + rc := resourceRecord{} + rc.Type = label.Type + rc.Records = make([]string, 0) // empty array of records should delete this rrset + rc.TTL = 3600 + shortname := dnsutil.TrimDomainName(label.NameFQDN, dc.Name) + if shortname == "@" { + shortname = "" + } + rc.Subname = shortname + fmt.Fprintln(buf, msg) + rrs = append(rrs, rc) + } else { + //just add the message + fmt.Fprintln(buf, msg) } - rc.Subname = shortname - fmt.Fprintln(buf, msg) - rrs = append(rrs, rc) - } else { - //just add the message - fmt.Fprintln(buf, msg) } - } - } else { - //it must be an update or create, both can be done with the same api call. - ns := recordsToNative(desiredRecords[label], dc.Name) - if len(ns) > 1 { - panic("we got more than one resource record to create / modify") - } - for i, msg := range keysToUpdate[label] { - if i == 0 { - rrs = append(rrs, ns[0]) - fmt.Fprintln(buf, msg) - } else { - //noop just for printing the additional messages - fmt.Fprintln(buf, msg) + } else { + //it must be an update or create, both can be done with the same api call. + ns := recordsToNative(desiredRecords[label], dc.Name) + if len(ns) > 1 { + panic("we got more than one resource record to create / modify") + } + for i, msg := range keysToUpdate[label] { + if i == 0 { + rrs = append(rrs, ns[0]) + fmt.Fprintln(buf, msg) + } else { + //noop just for printing the additional messages + fmt.Fprintln(buf, msg) + } } } } - } - msg := fmt.Sprintf("Changes:\n%s", buf) - corrections = append(corrections, - &models.Correction{ - Msg: msg, - F: func() error { - rc := rrs - err := c.upsertRR(rc, dc.Name) - if err != nil { - return err - } - return nil - }, - }) + msg := fmt.Sprintf("Changes:\n%s", buf) + corrections = append(corrections, + &models.Correction{ + Msg: msg, + F: func() error { + rc := rrs + err := c.upsertRR(rc, dc.Name) + if err != nil { + return err + } + return nil + }, + }) - // NB(tlim): This sort is just to make updates look pretty. It is - // cosmetic. The risk here is that there may be some updates that - // require a specific order (for example a delete before an add). - // However the code doesn't seem to have such situation. All tests - // pass. That said, if this breaks anything, the easiest fix might - // be to just remove the sort. - sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) }) + // NB(tlim): This sort is just to make updates look pretty. It is + // cosmetic. The risk here is that there may be some updates that + // require a specific order (for example a delete before an add). + // However the code doesn't seem to have such situation. All tests + // pass. That said, if this breaks anything, the easiest fix might + // be to just remove the sort. + sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) }) + + return corrections, nil + } + + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go index 7a8dc68e2..4fcec4b81 100644 --- a/providers/digitalocean/digitaloceanProvider.go +++ b/providers/digitalocean/digitaloceanProvider.go @@ -10,6 +10,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/digitalocean/godo" @@ -145,67 +146,73 @@ func (api *digitaloceanProvider) GetDomainCorrections(dc *models.DomainConfig) ( models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err + } + + // Deletes first so changing type works etc. + for _, m := range delete { + id := m.Existing.Original.(*godo.DomainRecord).ID + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id), + F: func() error { + retry: + resp, err := api.client.Domains.DeleteRecord(ctx, dc.Name, id) + if err != nil { + if pauseAndRetry(resp) { + goto retry + } + } + return err + }, + } + corrections = append(corrections, corr) + } + for _, m := range create { + req := toReq(dc, m.Desired) + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + retry: + _, resp, err := api.client.Domains.CreateRecord(ctx, dc.Name, req) + if err != nil { + if pauseAndRetry(resp) { + goto retry + } + } + return err + }, + } + corrections = append(corrections, corr) + } + for _, m := range modify { + id := m.Existing.Original.(*godo.DomainRecord).ID + req := toReq(dc, m.Desired) + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id), + F: func() error { + retry: + _, resp, err := api.client.Domains.EditRecord(ctx, dc.Name, id, req) + if err != nil { + if pauseAndRetry(resp) { + goto retry + } + } + return err + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } - var corrections = []*models.Correction{} - - // Deletes first so changing type works etc. - for _, m := range delete { - id := m.Existing.Original.(*godo.DomainRecord).ID - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id), - F: func() error { - retry: - resp, err := api.client.Domains.DeleteRecord(ctx, dc.Name, id) - if err != nil { - if pauseAndRetry(resp) { - goto retry - } - } - return err - }, - } - corrections = append(corrections, corr) - } - for _, m := range create { - req := toReq(dc, m.Desired) - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - retry: - _, resp, err := api.client.Domains.CreateRecord(ctx, dc.Name, req) - if err != nil { - if pauseAndRetry(resp) { - goto retry - } - } - return err - }, - } - corrections = append(corrections, corr) - } - for _, m := range modify { - id := m.Existing.Original.(*godo.DomainRecord).ID - req := toReq(dc, m.Desired) - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id), - F: func() error { - retry: - _, resp, err := api.client.Domains.EditRecord(ctx, dc.Name, id, req) - if err != nil { - if pauseAndRetry(resp) { - goto retry - } - } - return err - }, - } - corrections = append(corrections, corr) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go index 138814d2b..5f576e263 100644 --- a/providers/dnsimple/dnsimpleProvider.go +++ b/providers/dnsimple/dnsimpleProvider.go @@ -11,6 +11,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/providers" dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple" @@ -141,6 +142,7 @@ func (c *dnsimpleProvider) GetZoneRecords(domain string) (models.Records, error) // GetDomainCorrections returns corrections that update a domain. func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { var corrections []*models.Correction + err := dc.Punycode() if err != nil { return nil, err @@ -163,36 +165,43 @@ func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod // Normalize models.PostProcessRecords(actual) - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(actual) - if err != nil { - return nil, err + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(actual) + if err != nil { + return nil, err + } + + for _, del := range del { + rec := del.Existing.Original.(dnsimpleapi.ZoneRecord) + corrections = append(corrections, &models.Correction{ + Msg: del.String(), + F: c.deleteRecordFunc(rec.ID, dc.Name), + }) + } + + for _, cre := range create { + rec := cre.Desired + corrections = append(corrections, &models.Correction{ + Msg: cre.String(), + F: c.createRecordFunc(rec, dc.Name), + }) + } + + for _, mod := range modify { + old := mod.Existing.Original.(dnsimpleapi.ZoneRecord) + rec := mod.Desired + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: c.updateRecordFunc(&old, rec, dc.Name), + }) + } + + return corrections, nil } - for _, del := range del { - rec := del.Existing.Original.(dnsimpleapi.ZoneRecord) - corrections = append(corrections, &models.Correction{ - Msg: del.String(), - F: c.deleteRecordFunc(rec.ID, dc.Name), - }) - } - - for _, cre := range create { - rec := cre.Desired - corrections = append(corrections, &models.Correction{ - Msg: cre.String(), - F: c.createRecordFunc(rec, dc.Name), - }) - } - - for _, mod := range modify { - old := mod.Existing.Original.(dnsimpleapi.ZoneRecord) - rec := mod.Desired - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: c.updateRecordFunc(&old, rec, dc.Name), - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/dnsmadeeasy/dnsMadeEasyProvider.go b/providers/dnsmadeeasy/dnsMadeEasyProvider.go index 1f1e2c856..8bfb504d1 100644 --- a/providers/dnsmadeeasy/dnsMadeEasyProvider.go +++ b/providers/dnsmadeeasy/dnsMadeEasyProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -100,73 +101,79 @@ func (api *dnsMadeEasyProvider) GetDomainCorrections(dc *models.DomainConfig) ([ models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - var deleteRecordIds []int - deleteDescription := []string{"Batch deletion of records:"} - for _, m := range del { - originalRecordID := m.Existing.Original.(*recordResponseDataEntry).ID - deleteRecordIds = append(deleteRecordIds, originalRecordID) - deleteDescription = append(deleteDescription, m.String()) - } - - if len(deleteRecordIds) > 0 { - corr := &models.Correction{ - Msg: strings.Join(deleteDescription, "\n\t"), - F: func() error { - return api.deleteRecords(domain.ID, deleteRecordIds) - }, + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err } - corrections = append(corrections, corr) - } - var createRecords []recordRequestData - createDescription := []string{"Batch creation of records:"} - for _, m := range create { - record := fromRecordConfig(m.Desired) - createRecords = append(createRecords, *record) - createDescription = append(createDescription, m.String()) - } - - if len(createRecords) > 0 { - corr := &models.Correction{ - Msg: strings.Join(createDescription, "\n\t"), - F: func() error { - return api.createRecords(domain.ID, createRecords) - }, + var deleteRecordIds []int + deleteDescription := []string{"Batch deletion of records:"} + for _, m := range del { + originalRecordID := m.Existing.Original.(*recordResponseDataEntry).ID + deleteRecordIds = append(deleteRecordIds, originalRecordID) + deleteDescription = append(deleteDescription, m.String()) } - corrections = append(corrections, corr) - } - var modifyRecords []recordRequestData - modifyDescription := []string{"Batch modification of records:"} - for _, m := range modify { - originalRecord := m.Existing.Original.(*recordResponseDataEntry) - - record := fromRecordConfig(m.Desired) - record.ID = originalRecord.ID - record.GtdLocation = originalRecord.GtdLocation - - modifyRecords = append(modifyRecords, *record) - modifyDescription = append(modifyDescription, m.String()) - } - - if len(modifyRecords) > 0 { - corr := &models.Correction{ - Msg: strings.Join(modifyDescription, "\n\t"), - F: func() error { - return api.updateRecords(domain.ID, modifyRecords) - }, + if len(deleteRecordIds) > 0 { + corr := &models.Correction{ + Msg: strings.Join(deleteDescription, "\n\t"), + F: func() error { + return api.deleteRecords(domain.ID, deleteRecordIds) + }, + } + corrections = append(corrections, corr) } - corrections = append(corrections, corr) + + var createRecords []recordRequestData + createDescription := []string{"Batch creation of records:"} + for _, m := range create { + record := fromRecordConfig(m.Desired) + createRecords = append(createRecords, *record) + createDescription = append(createDescription, m.String()) + } + + if len(createRecords) > 0 { + corr := &models.Correction{ + Msg: strings.Join(createDescription, "\n\t"), + F: func() error { + return api.createRecords(domain.ID, createRecords) + }, + } + corrections = append(corrections, corr) + } + + var modifyRecords []recordRequestData + modifyDescription := []string{"Batch modification of records:"} + for _, m := range modify { + originalRecord := m.Existing.Original.(*recordResponseDataEntry) + + record := fromRecordConfig(m.Desired) + record.ID = originalRecord.ID + record.GtdLocation = originalRecord.GtdLocation + + modifyRecords = append(modifyRecords, *record) + modifyDescription = append(modifyDescription, m.String()) + } + + if len(modifyRecords) > 0 { + corr := &models.Correction{ + Msg: strings.Join(modifyDescription, "\n\t"), + F: func() error { + return api.updateRecords(domain.ID, modifyRecords) + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/domainnameshop/dns.go b/providers/domainnameshop/dns.go index 7b699f30c..432582266 100644 --- a/providers/domainnameshop/dns.go +++ b/providers/domainnameshop/dns.go @@ -7,6 +7,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" ) func (api *domainNameShopProvider) GetZoneRecords(domain string) (models.Records, error) { @@ -46,61 +47,67 @@ func (api *domainNameShopProvider) GetDomainCorrections(dc *models.DomainConfig) record.TTL = fixTTL(record.TTL) } - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - var corrections = []*models.Correction{} - - // Delete record - for _, r := range delete { - domainID := r.Existing.Original.(*domainNameShopRecord).DomainID - recordID := strconv.Itoa(r.Existing.Original.(*domainNameShopRecord).ID) - - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, record id: %s", r.String(), recordID), - F: func() error { return api.deleteRecord(domainID, recordID) }, - } - corrections = append(corrections, corr) - } - - // Create records - for _, r := range create { - // Retrieve the domain name that is targeted. I.e. example.com instead of sub.example.com - domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1) - - dnsR, err := api.fromRecordConfig(domainName, r.Desired) + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) if err != nil { return nil, err } - corr := &models.Correction{ - Msg: r.String(), - F: func() error { return api.CreateRecord(domainName, dnsR) }, + // Delete record + for _, r := range delete { + domainID := r.Existing.Original.(*domainNameShopRecord).DomainID + recordID := strconv.Itoa(r.Existing.Original.(*domainNameShopRecord).ID) + + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, record id: %s", r.String(), recordID), + F: func() error { return api.deleteRecord(domainID, recordID) }, + } + corrections = append(corrections, corr) } - corrections = append(corrections, corr) + // Create records + for _, r := range create { + // Retrieve the domain name that is targeted. I.e. example.com instead of sub.example.com + domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1) + + dnsR, err := api.fromRecordConfig(domainName, r.Desired) + if err != nil { + return nil, err + } + + corr := &models.Correction{ + Msg: r.String(), + F: func() error { return api.CreateRecord(domainName, dnsR) }, + } + + corrections = append(corrections, corr) + } + + for _, r := range modify { + domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1) + + dnsR, err := api.fromRecordConfig(domainName, r.Desired) + if err != nil { + return nil, err + } + + dnsR.ID = r.Existing.Original.(*domainNameShopRecord).ID + + corr := &models.Correction{ + Msg: r.String(), + F: func() error { return api.UpdateRecord(dnsR) }, + } + + corrections = append(corrections, corr) + } + + return corrections, nil } - for _, r := range modify { - domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1) - - dnsR, err := api.fromRecordConfig(domainName, r.Desired) - if err != nil { - return nil, err - } - - dnsR.ID = r.Existing.Original.(*domainNameShopRecord).ID - - corr := &models.Correction{ - Msg: r.String(), - F: func() error { return api.UpdateRecord(dnsR) }, - } - - corrections = append(corrections, corr) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/exoscale/exoscaleProvider.go b/providers/exoscale/exoscaleProvider.go index a7cdfdba7..4773fd707 100644 --- a/providers/exoscale/exoscaleProvider.go +++ b/providers/exoscale/exoscaleProvider.go @@ -12,6 +12,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -189,38 +190,44 @@ func (c *exoscaleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod // Normalize models.PostProcessRecords(existingRecords) - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err + } + + for _, del := range delete { + record := del.Existing.Original.(*egoscale.DNSDomainRecord) + corrections = append(corrections, &models.Correction{ + Msg: del.String(), + F: c.deleteRecordFunc(*record.ID, domainID), + }) + } + + for _, cre := range create { + rc := cre.Desired + corrections = append(corrections, &models.Correction{ + Msg: cre.String(), + F: c.createRecordFunc(rc, domainID), + }) + } + + for _, mod := range modify { + old := mod.Existing.Original.(*egoscale.DNSDomainRecord) + new := mod.Desired + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: c.updateRecordFunc(old, new, domainID), + }) + } + + return corrections, nil } - var corrections = []*models.Correction{} - - for _, del := range delete { - record := del.Existing.Original.(*egoscale.DNSDomainRecord) - corrections = append(corrections, &models.Correction{ - Msg: del.String(), - F: c.deleteRecordFunc(*record.ID, domainID), - }) - } - - for _, cre := range create { - rc := cre.Desired - corrections = append(corrections, &models.Correction{ - Msg: cre.String(), - F: c.createRecordFunc(rc, domainID), - }) - } - - for _, mod := range modify { - old := mod.Existing.Original.(*egoscale.DNSDomainRecord) - new := mod.Desired - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: c.updateRecordFunc(old, new, domainID), - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/gandiv5/gandi_v5Provider.go b/providers/gandiv5/gandi_v5Provider.go index b186fb679..fa9d43bbf 100644 --- a/providers/gandiv5/gandi_v5Provider.go +++ b/providers/gandiv5/gandi_v5Provider.go @@ -23,6 +23,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -226,113 +227,119 @@ func (client *gandiv5Provider) GenerateDomainCorrections(dc *models.DomainConfig debugRecords("GenDC input", existing) } - var corrections = []*models.Correction{} - txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - // diff existing vs. current. - differ := diff.New(dc) - keysToUpdate, err := differ.ChangedGroups(existing) - if err != nil { - return nil, err - } - if client.debug { - diff.DebugKeyMapMap("GenDC diff", keysToUpdate) - } - if len(keysToUpdate) == 0 { - return nil, nil - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // Regroup data by FQDN. ChangedGroups returns data grouped by label:RType tuples. - affectedLabels, msgsForLabel := gatherAffectedLabels(keysToUpdate) - _, desiredRecords := dc.Records.GroupedByFQDN() - doesLabelExist := existing.FQDNMap() + // diff existing vs. current. + differ := diff.New(dc) + keysToUpdate, err := differ.ChangedGroups(existing) + if err != nil { + return nil, err + } + if client.debug { + diff.DebugKeyMapMap("GenDC diff", keysToUpdate) + } + if len(keysToUpdate) == 0 { + return nil, nil + } - g := gandi.NewLiveDNSClient(config.Config{ - APIKey: client.apikey, - SharingID: client.sharingid, - Debug: client.debug, - }) + // Regroup data by FQDN. ChangedGroups returns data grouped by label:RType tuples. + affectedLabels, msgsForLabel := gatherAffectedLabels(keysToUpdate) + _, desiredRecords := dc.Records.GroupedByFQDN() + doesLabelExist := existing.FQDNMap() - // For any key with an update, delete or replace those records. - for label := range affectedLabels { - if len(desiredRecords[label]) == 0 { - // No records matching this key? This can only mean that all - // the records were deleted. Delete them. + g := gandi.NewLiveDNSClient(config.Config{ + APIKey: client.apikey, + SharingID: client.sharingid, + Debug: client.debug, + }) - msgs := strings.Join(msgsForLabel[label], "\n") - domain := dc.Name - shortname := dnsutil.TrimDomainName(label, dc.Name) - corrections = append(corrections, - &models.Correction{ - Msg: msgs, - F: func() error { - err := g.DeleteDomainRecordsByName(domain, shortname) - if err != nil { - return err - } - return nil - }, - }) + // For any key with an update, delete or replace those records. + for label := range affectedLabels { + if len(desiredRecords[label]) == 0 { + // No records matching this key? This can only mean that all + // the records were deleted. Delete them. - } else { - // Replace all the records at a label with our new records. - - // Generate the new data in Gandi's format. - ns := recordsToNative(desiredRecords[label], dc.Name) - - if doesLabelExist[label] { - // Records exist for this label. Replace them with what we have. - - msg := strings.Join(msgsForLabel[label], "\n") + msgs := strings.Join(msgsForLabel[label], "\n") domain := dc.Name shortname := dnsutil.TrimDomainName(label, dc.Name) corrections = append(corrections, &models.Correction{ - Msg: msg, + Msg: msgs, F: func() error { - res, err := g.UpdateDomainRecordsByName(domain, shortname, ns) + err := g.DeleteDomainRecordsByName(domain, shortname) if err != nil { - return fmt.Errorf("%+v: %w", res, err) + return err } return nil }, }) } else { - // First time putting data on this label. Create it. + // Replace all the records at a label with our new records. + + // Generate the new data in Gandi's format. + ns := recordsToNative(desiredRecords[label], dc.Name) + + if doesLabelExist[label] { + // Records exist for this label. Replace them with what we have. - // We have to create the label one rtype at a time. - for _, n := range ns { msg := strings.Join(msgsForLabel[label], "\n") domain := dc.Name shortname := dnsutil.TrimDomainName(label, dc.Name) - rtype := n.RrsetType - ttl := n.RrsetTTL - values := n.RrsetValues corrections = append(corrections, &models.Correction{ Msg: msg, F: func() error { - res, err := g.CreateDomainRecord(domain, shortname, rtype, ttl, values) + res, err := g.UpdateDomainRecordsByName(domain, shortname, ns) if err != nil { return fmt.Errorf("%+v: %w", res, err) } return nil }, }) + + } else { + // First time putting data on this label. Create it. + + // We have to create the label one rtype at a time. + for _, n := range ns { + msg := strings.Join(msgsForLabel[label], "\n") + domain := dc.Name + shortname := dnsutil.TrimDomainName(label, dc.Name) + rtype := n.RrsetType + ttl := n.RrsetTTL + values := n.RrsetValues + corrections = append(corrections, + &models.Correction{ + Msg: msg, + F: func() error { + res, err := g.CreateDomainRecord(domain, shortname, rtype, ttl, values) + if err != nil { + return fmt.Errorf("%+v: %w", res, err) + } + return nil + }, + }) + } } } } + + // NB(tlim): This sort is just to make updates look pretty. It is + // cosmetic. The risk here is that there may be some updates that + // require a specific order (for example a delete before an add). + // However the code doesn't seem to have such situation. All tests + // pass. That said, if this breaks anything, the easiest fix might + // be to just remove the sort. + sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) }) + + return corrections, nil } - // NB(tlim): This sort is just to make updates look pretty. It is - // cosmetic. The risk here is that there may be some updates that - // require a specific order (for example a delete before an add). - // However the code doesn't seem to have such situation. All tests - // pass. That said, if this breaks anything, the easiest fix might - // be to just remove the sort. - sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) }) + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/gcloud/gcloudProvider.go b/providers/gcloud/gcloudProvider.go index 4f8abef6e..de46d1b23 100644 --- a/providers/gcloud/gcloudProvider.go +++ b/providers/gcloud/gcloudProvider.go @@ -11,6 +11,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -204,77 +205,85 @@ func (g *gcloudProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - // first collect keys that have changed - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, fmt.Errorf("incdiff error: %w", err) - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - changedKeys := map[key]bool{} - desc := "" - for _, c := range create { - desc += fmt.Sprintln(c) - changedKeys[keyForRec(c.Desired)] = true - } - for _, d := range delete { - desc += fmt.Sprintln(d) - changedKeys[keyForRec(d.Existing)] = true - } - for _, m := range modify { - desc += fmt.Sprintln(m) - changedKeys[keyForRec(m.Existing)] = true - } - if len(changedKeys) == 0 { - return nil, nil - } - chg := &gdns.Change{Kind: "dns#change"} - for ck := range changedKeys { - // remove old version (if present) - if old, ok := oldRRs[ck]; ok { - chg.Deletions = append(chg.Deletions, old) + // first collect keys that have changed + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, fmt.Errorf("incdiff error: %w", err) } - // collect records to replace with - newRRs := &gdns.ResourceRecordSet{ - Name: ck.Name, - Type: ck.Type, - Kind: "dns#resourceRecordSet", + + changedKeys := map[key]bool{} + desc := "" + for _, c := range create { + desc += fmt.Sprintln(c) + changedKeys[keyForRec(c.Desired)] = true } - for _, r := range dc.Records { - if keyForRec(r) == ck { - newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined()) - newRRs.Ttl = int64(r.TTL) + for _, d := range delete { + desc += fmt.Sprintln(d) + changedKeys[keyForRec(d.Existing)] = true + } + for _, m := range modify { + desc += fmt.Sprintln(m) + changedKeys[keyForRec(m.Existing)] = true + } + if len(changedKeys) == 0 { + return nil, nil + } + chg := &gdns.Change{Kind: "dns#change"} + for ck := range changedKeys { + // remove old version (if present) + if old, ok := oldRRs[ck]; ok { + chg.Deletions = append(chg.Deletions, old) + } + // collect records to replace with + newRRs := &gdns.ResourceRecordSet{ + Name: ck.Name, + Type: ck.Type, + Kind: "dns#resourceRecordSet", + } + for _, r := range dc.Records { + if keyForRec(r) == ck { + newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined()) + newRRs.Ttl = int64(r.TTL) + } + } + if len(newRRs.Rrdatas) > 0 { + chg.Additions = append(chg.Additions, newRRs) } } - if len(newRRs.Rrdatas) > 0 { - chg.Additions = append(chg.Additions, newRRs) + + // FIXME(tlim): Google will return an error if too many changes are + // specified in a single request. We should split up very large + // batches. This can be reliably reproduced with the 1201 + // integration test. The error you get is: + // googleapi: Error 403: The change would exceed quota for additions per change., quotaExceeded + //log.Printf("PAUSE STT = %+v %v\n", err, resp) + //log.Printf("PAUSE ERR = %+v %v\n", err, resp) + + runChange := func() error { + retry: + resp, err := g.client.Changes.Create(g.project, zoneName, chg).Do() + if retryNeeded(resp, err) { + goto retry + } + if err != nil { + return fmt.Errorf("runChange error: %w", err) + } + return nil } + + return []*models.Correction{{ + Msg: desc, + F: runChange, + }}, nil } - // FIXME(tlim): Google will return an error if too many changes are - // specified in a single request. We should split up very large - // batches. This can be reliably reproduced with the 1201 - // integration test. The error you get is: - // googleapi: Error 403: The change would exceed quota for additions per change., quotaExceeded - //log.Printf("PAUSE STT = %+v %v\n", err, resp) - //log.Printf("PAUSE ERR = %+v %v\n", err, resp) + // Insert Future diff2 version here. - runChange := func() error { - retry: - resp, err := g.client.Changes.Create(g.project, zoneName, chg).Do() - if retryNeeded(resp, err) { - goto retry - } - if err != nil { - return fmt.Errorf("runChange error: %w", err) - } - return nil - } - - return []*models.Correction{{ - Msg: desc, - F: runChange, - }}, nil + return corrections, nil } func nativeToRecord(set *gdns.ResourceRecordSet, rec, origin string) (*models.RecordConfig, error) { diff --git a/providers/gcore/gcoreProvider.go b/providers/gcore/gcoreProvider.go index 635fa713e..546c043d3 100644 --- a/providers/gcore/gcoreProvider.go +++ b/providers/gcore/gcoreProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" dnssdk "github.com/G-Core/gcore-dns-sdk-go" @@ -151,86 +152,92 @@ func generateChangeMsg(updates []string) string { // made. func (c *gcoreProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) { - var corrections = []*models.Correction{} + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // diff existing vs. current. - differ := diff.New(dc) - keysToUpdate, err := differ.ChangedGroups(existing) - if err != nil { - return nil, err - } - if len(keysToUpdate) == 0 { - return nil, nil - } - - desiredRecords := dc.Records.GroupedByKey() - existingRecords := existing.GroupedByKey() - - // First pass: delete records to avoid coexisting of conflicting types - for label := range keysToUpdate { - if _, ok := desiredRecords[label]; !ok { - // record deleted in update - // Copy all params to avoid overwrites - zone := dc.Name - name := label.NameFQDN - typ := label.Type - msg := generateChangeMsg(keysToUpdate[label]) - corrections = append(corrections, &models.Correction{ - Msg: msg, - F: func() error { - return c.provider.DeleteRRSet(c.ctx, zone, name, typ) - }, - }) + // diff existing vs. current. + differ := diff.New(dc) + keysToUpdate, err := differ.ChangedGroups(existing) + if err != nil { + return nil, err } - } - - // Second pass: create and update records - for label := range keysToUpdate { - if _, ok := desiredRecords[label]; !ok { - // record deleted in update - // do nothing here - - } else if _, ok := existingRecords[label]; !ok { - // record created in update - record := recordsToNative(desiredRecords[label], label) - if record == nil { - panic("No records matching label") - } - - // Copy all params to avoid overwrites - zone := dc.Name - name := label.NameFQDN - typ := label.Type - rec := *record - msg := generateChangeMsg(keysToUpdate[label]) - corrections = append(corrections, &models.Correction{ - Msg: msg, - F: func() error { - return c.provider.CreateRRSet(c.ctx, zone, name, typ, rec) - }, - }) - - } else { - // record modified in update - record := recordsToNative(desiredRecords[label], label) - if record == nil { - panic("No records matching label") - } - - // Copy all params to avoid overwrites - zone := dc.Name - name := label.NameFQDN - typ := label.Type - rec := *record - msg := generateChangeMsg(keysToUpdate[label]) - corrections = append(corrections, &models.Correction{ - Msg: msg, - F: func() error { - return c.provider.UpdateRRSet(c.ctx, zone, name, typ, rec) - }, - }) + if len(keysToUpdate) == 0 { + return nil, nil } + + desiredRecords := dc.Records.GroupedByKey() + existingRecords := existing.GroupedByKey() + + // First pass: delete records to avoid coexisting of conflicting types + for label := range keysToUpdate { + if _, ok := desiredRecords[label]; !ok { + // record deleted in update + // Copy all params to avoid overwrites + zone := dc.Name + name := label.NameFQDN + typ := label.Type + msg := generateChangeMsg(keysToUpdate[label]) + corrections = append(corrections, &models.Correction{ + Msg: msg, + F: func() error { + return c.provider.DeleteRRSet(c.ctx, zone, name, typ) + }, + }) + } + } + + // Second pass: create and update records + for label := range keysToUpdate { + if _, ok := desiredRecords[label]; !ok { + // record deleted in update + // do nothing here + + } else if _, ok := existingRecords[label]; !ok { + // record created in update + record := recordsToNative(desiredRecords[label], label) + if record == nil { + panic("No records matching label") + } + + // Copy all params to avoid overwrites + zone := dc.Name + name := label.NameFQDN + typ := label.Type + rec := *record + msg := generateChangeMsg(keysToUpdate[label]) + corrections = append(corrections, &models.Correction{ + Msg: msg, + F: func() error { + return c.provider.CreateRRSet(c.ctx, zone, name, typ, rec) + }, + }) + + } else { + // record modified in update + record := recordsToNative(desiredRecords[label], label) + if record == nil { + panic("No records matching label") + } + + // Copy all params to avoid overwrites + zone := dc.Name + name := label.NameFQDN + typ := label.Type + rec := *record + msg := generateChangeMsg(keysToUpdate[label]) + corrections = append(corrections, &models.Correction{ + Msg: msg, + F: func() error { + return c.provider.UpdateRRSet(c.ctx, zone, name, typ, rec) + }, + }) + } + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/hedns/hednsProvider.go b/providers/hedns/hednsProvider.go index 37bbc4268..75afbca90 100644 --- a/providers/hedns/hednsProvider.go +++ b/providers/hedns/hednsProvider.go @@ -17,6 +17,7 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/pquerna/otp/totp" @@ -177,7 +178,6 @@ func (c *hednsProvider) GetNameservers(_ string) ([]*models.Nameserver, error) { // GetDomainCorrections returns a list of corrections for the domain. func (c *hednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { - var corrections []*models.Correction err := dc.Punycode() if err != nil { @@ -204,48 +204,56 @@ func (c *hednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models models.PostProcessRecords(prunedRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, toCreate, toDelete, toModify, err := differ.IncrementalDiff(prunedRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - for _, del := range toDelete { - record := del.Existing - corrections = append(corrections, &models.Correction{ - Msg: del.String(), - F: func() error { return c.deleteZoneRecord(record) }, - }) - } - - for _, cre := range toCreate { - record := cre.Desired - record.Original = Record{ - ZoneName: dc.Name, - ZoneID: zoneID, - RecordName: cre.Desired.Name, + differ := diff.New(dc) + _, toCreate, toDelete, toModify, err := differ.IncrementalDiff(prunedRecords) + if err != nil { + return nil, err } - corrections = append(corrections, &models.Correction{ - Msg: cre.String(), - F: func() error { return c.editZoneRecord(record, true) }, - }) - } - for _, mod := range toModify { - record := mod.Desired - record.Original = Record{ - ZoneName: dc.Name, - ZoneID: zoneID, - RecordID: mod.Existing.Original.(Record).RecordID, - RecordName: mod.Desired.Name, + for _, del := range toDelete { + record := del.Existing + corrections = append(corrections, &models.Correction{ + Msg: del.String(), + F: func() error { return c.deleteZoneRecord(record) }, + }) } - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: func() error { return c.editZoneRecord(record, false) }, - }) + + for _, cre := range toCreate { + record := cre.Desired + record.Original = Record{ + ZoneName: dc.Name, + ZoneID: zoneID, + RecordName: cre.Desired.Name, + } + corrections = append(corrections, &models.Correction{ + Msg: cre.String(), + F: func() error { return c.editZoneRecord(record, true) }, + }) + } + + for _, mod := range toModify { + record := mod.Desired + record.Original = Record{ + ZoneName: dc.Name, + ZoneID: zoneID, + RecordID: mod.Existing.Original.(Record).RecordID, + RecordName: mod.Desired.Name, + } + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: func() error { return c.editZoneRecord(record, false) }, + }) + } + + return corrections, err } - return corrections, err + // Insert Future diff2 version here. + + return corrections, nil } // GetZoneRecords returns all the records for the given domain diff --git a/providers/hetzner/hetznerProvider.go b/providers/hetzner/hetznerProvider.go index 3526f81bd..f244a64e1 100644 --- a/providers/hetzner/hetznerProvider.go +++ b/providers/hetzner/hetznerProvider.go @@ -7,6 +7,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -101,65 +102,71 @@ func (api *hetznerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - zone, err := api.getZone(domain) - if err != nil { - return nil, err - } - - for _, m := range del { - record := m.Existing.Original.(*record) - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - return api.deleteRecord(*record) - }, + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err } - corrections = append(corrections, corr) + + zone, err := api.getZone(domain) + if err != nil { + return nil, err + } + + for _, m := range del { + record := m.Existing.Original.(*record) + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + return api.deleteRecord(*record) + }, + } + corrections = append(corrections, corr) + } + + var createRecords []record + createDescription := []string{"Batch creation of records:"} + for _, m := range create { + record := fromRecordConfig(m.Desired, zone) + createRecords = append(createRecords, *record) + createDescription = append(createDescription, m.String()) + } + if len(createRecords) > 0 { + corr := &models.Correction{ + Msg: strings.Join(createDescription, "\n\t"), + F: func() error { + return api.bulkCreateRecords(createRecords) + }, + } + corrections = append(corrections, corr) + } + + var modifyRecords []record + modifyDescription := []string{"Batch modification of records:"} + for _, m := range modify { + id := m.Existing.Original.(*record).ID + record := fromRecordConfig(m.Desired, zone) + record.ID = id + modifyRecords = append(modifyRecords, *record) + modifyDescription = append(modifyDescription, m.String()) + } + if len(modifyRecords) > 0 { + corr := &models.Correction{ + Msg: strings.Join(modifyDescription, "\n\t"), + F: func() error { + return api.bulkUpdateRecords(modifyRecords) + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } - var createRecords []record - createDescription := []string{"Batch creation of records:"} - for _, m := range create { - record := fromRecordConfig(m.Desired, zone) - createRecords = append(createRecords, *record) - createDescription = append(createDescription, m.String()) - } - if len(createRecords) > 0 { - corr := &models.Correction{ - Msg: strings.Join(createDescription, "\n\t"), - F: func() error { - return api.bulkCreateRecords(createRecords) - }, - } - corrections = append(corrections, corr) - } - - var modifyRecords []record - modifyDescription := []string{"Batch modification of records:"} - for _, m := range modify { - id := m.Existing.Original.(*record).ID - record := fromRecordConfig(m.Desired, zone) - record.ID = id - modifyRecords = append(modifyRecords, *record) - modifyDescription = append(modifyDescription, m.String()) - } - if len(modifyRecords) > 0 { - corr := &models.Correction{ - Msg: strings.Join(modifyDescription, "\n\t"), - F: func() error { - return api.bulkUpdateRecords(modifyRecords) - }, - } - corrections = append(corrections, corr) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/hexonet/records.go b/providers/hexonet/records.go index 59150670a..ce1d3a569 100644 --- a/providers/hexonet/records.go +++ b/providers/hexonet/records.go @@ -10,6 +10,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" ) @@ -73,62 +74,70 @@ func (n *HXClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr models.PostProcessRecords(actual) txtutil.SplitSingleLongTxt(dc.Records) - differ := diff.New(dc) - _, create, del, mod, err := differ.IncrementalDiff(actual) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - corrections := []*models.Correction{} - - buf := &bytes.Buffer{} - // Print a list of changes. Generate an actual change that is the zone - changes := false - params := map[string]interface{}{} - delrridx := 0 - addrridx := 0 - for _, cre := range create { - changes = true - fmt.Fprintln(buf, cre) - rec := cre.Desired - recordString, err := n.createRecordString(rec, dc.Name) + differ := diff.New(dc) + _, create, del, mod, err := differ.IncrementalDiff(actual) if err != nil { - return corrections, err + return nil, err } - params[fmt.Sprintf("ADDRR%d", addrridx)] = recordString - addrridx++ - } - for _, d := range del { - changes = true - fmt.Fprintln(buf, d) - rec := d.Existing.Original.(*HXRecord) - params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(rec, dc.Name) - delrridx++ - } - for _, chng := range mod { - changes = true - fmt.Fprintln(buf, chng) - old := chng.Existing.Original.(*HXRecord) - new := chng.Desired - params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(old, dc.Name) - newRecordString, err := n.createRecordString(new, dc.Name) - if err != nil { - return corrections, err - } - params[fmt.Sprintf("ADDRR%d", addrridx)] = newRecordString - addrridx++ - delrridx++ - } - msg := fmt.Sprintf("GENERATE_ZONEFILE: %s\n", dc.Name) + buf.String() - if changes { - corrections = append(corrections, &models.Correction{ - Msg: msg, - F: func() error { - return n.updateZoneBy(params, dc.Name) - }, - }) + corrections := []*models.Correction{} + + buf := &bytes.Buffer{} + // Print a list of changes. Generate an actual change that is the zone + changes := false + params := map[string]interface{}{} + delrridx := 0 + addrridx := 0 + for _, cre := range create { + changes = true + fmt.Fprintln(buf, cre) + rec := cre.Desired + recordString, err := n.createRecordString(rec, dc.Name) + if err != nil { + return corrections, err + } + params[fmt.Sprintf("ADDRR%d", addrridx)] = recordString + addrridx++ + } + for _, d := range del { + changes = true + fmt.Fprintln(buf, d) + rec := d.Existing.Original.(*HXRecord) + params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(rec, dc.Name) + delrridx++ + } + for _, chng := range mod { + changes = true + fmt.Fprintln(buf, chng) + old := chng.Existing.Original.(*HXRecord) + new := chng.Desired + params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(old, dc.Name) + newRecordString, err := n.createRecordString(new, dc.Name) + if err != nil { + return corrections, err + } + params[fmt.Sprintf("ADDRR%d", addrridx)] = newRecordString + addrridx++ + delrridx++ + } + msg := fmt.Sprintf("GENERATE_ZONEFILE: %s\n", dc.Name) + buf.String() + + if changes { + corrections = append(corrections, &models.Correction{ + Msg: msg, + F: func() error { + return n.updateZoneBy(params, dc.Name) + }, + }) + } + return corrections, nil } + + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/hostingde/hostingdeProvider.go b/providers/hostingde/hostingdeProvider.go index 49bc6234c..8caca4904 100644 --- a/providers/hostingde/hostingdeProvider.go +++ b/providers/hostingde/hostingdeProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -125,35 +126,43 @@ func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m return nil, err } - differ := diff.New(dc) - _, create, del, mod, err := differ.IncrementalDiff(records) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // NOPURGE - if dc.KeepUnknown { - del = []diff.Correlation{} - } + differ := diff.New(dc) + _, create, del, mod, err := differ.IncrementalDiff(records) + if err != nil { + return nil, err + } - msg := []string{} - for _, c := range append(del, append(create, mod...)...) { - msg = append(msg, c.String()) - } + // NOPURGE + if dc.KeepUnknown { + del = []diff.Correlation{} + } - if len(create) == 0 && len(del) == 0 && len(mod) == 0 { - return nil, nil - } + msg := []string{} + for _, c := range append(del, append(create, mod...)...) { + msg = append(msg, c.String()) + } - corrections := []*models.Correction{ - { - Msg: fmt.Sprintf("\n%s", strings.Join(msg, "\n")), - F: func() error { - return hp.updateRecords(dc.Name, create, del, mod) + if len(create) == 0 && len(del) == 0 && len(mod) == 0 { + return nil, nil + } + + corrections := []*models.Correction{ + { + Msg: fmt.Sprintf("\n%s", strings.Join(msg, "\n")), + F: func() error { + return hp.updateRecords(dc.Name, create, del, mod) + }, }, - }, + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/inwx/inwxProvider.go b/providers/inwx/inwxProvider.go index ac2f2ab8c..76994fb92 100644 --- a/providers/inwx/inwxProvider.go +++ b/providers/inwx/inwxProvider.go @@ -9,6 +9,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -247,31 +248,37 @@ func (api *inwxAPI) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor return nil, err } - corrections := []*models.Correction{} + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - for _, d := range create { - des := d.Desired - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return api.createRecord(dc.Name, des) }, - }) - } - for _, d := range del { - existingID := d.Existing.Original.(goinwx.NameserverRecord).ID - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return api.deleteRecord(existingID) }, - }) - } - for _, d := range mod { - rec := d.Desired - existingID := d.Existing.Original.(goinwx.NameserverRecord).ID - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return api.updateRecord(existingID, rec) }, - }) + for _, d := range create { + des := d.Desired + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return api.createRecord(dc.Name, des) }, + }) + } + for _, d := range del { + existingID := d.Existing.Original.(goinwx.NameserverRecord).ID + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return api.deleteRecord(existingID) }, + }) + } + for _, d := range mod { + rec := d.Desired + existingID := d.Existing.Original.(goinwx.NameserverRecord).ID + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return api.updateRecord(existingID, rec) }, + }) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/linode/linodeProvider.go b/providers/linode/linodeProvider.go index fb30502f2..b35463674 100644 --- a/providers/linode/linodeProvider.go +++ b/providers/linode/linodeProvider.go @@ -11,6 +11,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/miekg/dns/dnsutil" "golang.org/x/oauth2" @@ -155,72 +156,78 @@ func (api *linodeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod record.TTL = fixTTL(record.TTL) } - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // Deletes first so changing type works etc. - for _, m := range del { - id := m.Existing.Original.(*domainRecord).ID - if id == 0 { // Skip ID 0, these are the default nameservers always present - continue + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err } - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, Linode ID: %d", m.String(), id), - F: func() error { - return api.deleteRecord(domainID, id) - }, + + // Deletes first so changing type works etc. + for _, m := range del { + id := m.Existing.Original.(*domainRecord).ID + if id == 0 { // Skip ID 0, these are the default nameservers always present + continue + } + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, Linode ID: %d", m.String(), id), + F: func() error { + return api.deleteRecord(domainID, id) + }, + } + corrections = append(corrections, corr) } - corrections = append(corrections, corr) - } - for _, m := range create { - req, err := toReq(dc, m.Desired) - if err != nil { - return nil, err - } - j, err := json.Marshal(req) - if err != nil { - return nil, err - } - corr := &models.Correction{ - Msg: fmt.Sprintf("%s: %s", m.String(), string(j)), - F: func() error { - record, err := api.createRecord(domainID, req) - if err != nil { - return err - } - // TTL isn't saved when creating a record, so we will need to modify it immediately afterwards - return api.modifyRecord(domainID, record.ID, req) - }, - } - corrections = append(corrections, corr) - } - for _, m := range modify { - id := m.Existing.Original.(*domainRecord).ID - if id == 0 { // Skip ID 0, these are the default nameservers always present - continue - } - req, err := toReq(dc, m.Desired) - if err != nil { - return nil, err - } - j, err := json.Marshal(req) - if err != nil { - return nil, err - } - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, Linode ID: %d: %s", m.String(), id, string(j)), - F: func() error { - return api.modifyRecord(domainID, id, req) - }, - } - corrections = append(corrections, corr) + for _, m := range create { + req, err := toReq(dc, m.Desired) + if err != nil { + return nil, err + } + j, err := json.Marshal(req) + if err != nil { + return nil, err + } + corr := &models.Correction{ + Msg: fmt.Sprintf("%s: %s", m.String(), string(j)), + F: func() error { + record, err := api.createRecord(domainID, req) + if err != nil { + return err + } + // TTL isn't saved when creating a record, so we will need to modify it immediately afterwards + return api.modifyRecord(domainID, record.ID, req) + }, + } + corrections = append(corrections, corr) + } + for _, m := range modify { + id := m.Existing.Original.(*domainRecord).ID + if id == 0 { // Skip ID 0, these are the default nameservers always present + continue + } + req, err := toReq(dc, m.Desired) + if err != nil { + return nil, err + } + j, err := json.Marshal(req) + if err != nil { + return nil, err + } + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, Linode ID: %d: %s", m.String(), id, string(j)), + F: func() error { + return api.modifyRecord(domainID, id, req) + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/msdns/corrections.go b/providers/msdns/corrections.go index b3c326b3c..f56572ec8 100644 --- a/providers/msdns/corrections.go +++ b/providers/msdns/corrections.go @@ -3,6 +3,7 @@ package msdns import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" ) @@ -13,25 +14,33 @@ func (client *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig, models.PostProcessRecords(foundRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords) + if err != nil { + return nil, err + } + + // Generate changes. + corrections := []*models.Correction{} + for _, del := range dels { + corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del)) + } + for _, cre := range creates { + corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...) + } + for _, m := range modifications { + corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m)) + } + return corrections, nil + } - // Generate changes. - corrections := []*models.Correction{} - for _, del := range dels { - corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del)) - } - for _, cre := range creates { - corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...) - } - for _, m := range modifications { - corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m)) - } + // Insert Future diff2 version here. + return corrections, nil - } func (client *msdnsProvider) deleteRec(dnsserver, domainname string, cor diff.Correlation) *models.Correction { diff --git a/providers/namecheap/namecheapProvider.go b/providers/namecheap/namecheapProvider.go index c49d67385..b7f7c7acf 100644 --- a/providers/namecheap/namecheapProvider.go +++ b/providers/namecheap/namecheapProvider.go @@ -9,6 +9,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/providers" nc "github.com/billputer/go-namecheap" @@ -190,40 +191,48 @@ func (n *namecheapProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo // Normalize models.PostProcessRecords(actual) - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(actual) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(actual) + if err != nil { + return nil, err + } + + // // because namecheap doesn't have selective create, delete, modify, + // // we bundle them all up to send at once. We *do* want to see the + // // changes though + + var desc []string + for _, i := range create { + desc = append(desc, "\n"+i.String()) + } + for _, i := range delete { + desc = append(desc, "\n"+i.String()) + } + for _, i := range modify { + desc = append(desc, "\n"+i.String()) + } + + msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc) + corrections := []*models.Correction{} + + // only create corrections if there are changes + if len(desc) > 0 { + corrections = append(corrections, + &models.Correction{ + Msg: msg, + F: func() error { + return n.generateRecords(dc) + }, + }) + } + + return corrections, nil } - // // because namecheap doesn't have selective create, delete, modify, - // // we bundle them all up to send at once. We *do* want to see the - // // changes though - - var desc []string - for _, i := range create { - desc = append(desc, "\n"+i.String()) - } - for _, i := range delete { - desc = append(desc, "\n"+i.String()) - } - for _, i := range modify { - desc = append(desc, "\n"+i.String()) - } - - msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc) - corrections := []*models.Correction{} - - // only create corrections if there are changes - if len(desc) > 0 { - corrections = append(corrections, - &models.Correction{ - Msg: msg, - F: func() error { - return n.generateRecords(dc) - }, - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go index 2d81a8a22..f3d6e24f8 100644 --- a/providers/namedotcom/records.go +++ b/providers/namedotcom/records.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/namedotcom/go/namecom" ) @@ -53,36 +54,44 @@ func (n *namedotcomProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m // Normalize models.PostProcessRecords(actual) - differ := diff.New(dc) - _, create, del, mod, err := differ.IncrementalDiff(actual) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, create, del, mod, err := differ.IncrementalDiff(actual) + if err != nil { + return nil, err + } + + corrections := []*models.Correction{} + + for _, d := range del { + rec := d.Existing.Original.(*namecom.Record) + c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.ID, dc.Name) }} + corrections = append(corrections, c) + } + for _, cre := range create { + rec := cre.Desired + c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }} + corrections = append(corrections, c) + } + for _, chng := range mod { + old := chng.Existing.Original.(*namecom.Record) + new := chng.Desired + c := &models.Correction{Msg: chng.String(), F: func() error { + err := n.deleteRecord(old.ID, dc.Name) + if err != nil { + return err + } + return n.createRecord(new, dc.Name) + }} + corrections = append(corrections, c) + } + return corrections, nil } - corrections := []*models.Correction{} + // Insert Future diff2 version here. - for _, d := range del { - rec := d.Existing.Original.(*namecom.Record) - c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.ID, dc.Name) }} - corrections = append(corrections, c) - } - for _, cre := range create { - rec := cre.Desired - c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }} - corrections = append(corrections, c) - } - for _, chng := range mod { - old := chng.Existing.Original.(*namecom.Record) - new := chng.Desired - c := &models.Correction{Msg: chng.String(), F: func() error { - err := n.deleteRecord(old.ID, dc.Name) - if err != nil { - return err - } - return n.createRecord(new, dc.Name) - }} - corrections = append(corrections, c) - } return corrections, nil } diff --git a/providers/netcup/netcupProvider.go b/providers/netcup/netcupProvider.go index 328bd0c30..6de9921bf 100644 --- a/providers/netcup/netcupProvider.go +++ b/providers/netcup/netcupProvider.go @@ -6,6 +6,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -100,48 +101,54 @@ func (api *netcupProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod // no need for txtutil.SplitSingleLongTxt in function GetDomainCorrections // txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // Deletes first so changing type works etc. - for _, m := range del { - req := m.Existing.Original.(*record) - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, Netcup ID: %s", m.String(), req.ID), - F: func() error { - return api.deleteRecord(domain, req) - }, + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err } - corrections = append(corrections, corr) + + // Deletes first so changing type works etc. + for _, m := range del { + req := m.Existing.Original.(*record) + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, Netcup ID: %s", m.String(), req.ID), + F: func() error { + return api.deleteRecord(domain, req) + }, + } + corrections = append(corrections, corr) + } + + for _, m := range create { + req := fromRecordConfig(m.Desired) + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + return api.createRecord(domain, req) + }, + } + corrections = append(corrections, corr) + } + for _, m := range modify { + id := m.Existing.Original.(*record).ID + req := fromRecordConfig(m.Desired) + req.ID = id + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, Netcup ID: %s: ", m.String(), id), + F: func() error { + return api.modifyRecord(domain, req) + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } - for _, m := range create { - req := fromRecordConfig(m.Desired) - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - return api.createRecord(domain, req) - }, - } - corrections = append(corrections, corr) - } - for _, m := range modify { - id := m.Existing.Original.(*record).ID - req := fromRecordConfig(m.Desired) - req.ID = id - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, Netcup ID: %s: ", m.String(), id), - F: func() error { - return api.modifyRecord(domain, req) - }, - } - corrections = append(corrections, corr) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/netlify/netlifyProvider.go b/providers/netlify/netlifyProvider.go index 1feaeafb3..219c95191 100644 --- a/providers/netlify/netlifyProvider.go +++ b/providers/netlify/netlifyProvider.go @@ -7,6 +7,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -176,7 +177,7 @@ func removeOtherApexNS(dc *models.DomainConfig) { } func (n *netlifyProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { - var corrections []*models.Correction + err := dc.Punycode() if err != nil { return nil, err @@ -192,58 +193,66 @@ func (n *netlifyProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode txtutil.SplitSingleLongTxt(dc.Records) // Auto split long TXT records removeOtherApexNS(dc) - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(records) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - zone, err := n.getZone(dc.Name) - if err != nil { - return nil, err - } - - // Deletes first so changing type works etc. - for _, m := range del { - id := m.Existing.Original.(*dnsRecord).ID - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - return n.deleteDNSRecord(zone.ID, id) - }, + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(records) + if err != nil { + return nil, err } - corrections = append(corrections, corr) - } - for _, m := range create { - req := toReq(m.Desired) - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - _, err := n.createDNSRecord(zone.ID, req) - return err - }, + zone, err := n.getZone(dc.Name) + if err != nil { + return nil, err } - corrections = append(corrections, corr) - } - for _, m := range modify { - id := m.Existing.Original.(*dnsRecord).ID - req := toReq(m.Desired) - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - if err := n.deleteDNSRecord(zone.ID, id); err != nil { + // Deletes first so changing type works etc. + for _, m := range del { + id := m.Existing.Original.(*dnsRecord).ID + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + return n.deleteDNSRecord(zone.ID, id) + }, + } + corrections = append(corrections, corr) + } + + for _, m := range create { + req := toReq(m.Desired) + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + _, err := n.createDNSRecord(zone.ID, req) return err - } - - _, err := n.createDNSRecord(zone.ID, req) - return err - }, + }, + } + corrections = append(corrections, corr) } - corrections = append(corrections, corr) + + for _, m := range modify { + id := m.Existing.Original.(*dnsRecord).ID + req := toReq(m.Desired) + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + if err := n.deleteDNSRecord(zone.ID, id); err != nil { + return err + } + + _, err := n.createDNSRecord(zone.ID, req) + return err + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/ns1/ns1Provider.go b/providers/ns1/ns1Provider.go index 16724855f..086e2f983 100644 --- a/providers/ns1/ns1Provider.go +++ b/providers/ns1/ns1Provider.go @@ -10,6 +10,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dns" @@ -162,45 +163,53 @@ func (n *nsone) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correct // Normalize models.PostProcessRecords(existingRecords) - differ := diff.New(dc) - changedGroups, err := differ.ChangedGroups(existingRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - corrections := []*models.Correction{} - - if dnssecCorrections := n.getDomainCorrectionsDNSSEC(domain, dc.AutoDNSSEC); dnssecCorrections != nil { - corrections = append(corrections, dnssecCorrections) - } - - // each name/type is given to the api as a unit. - for k, descs := range changedGroups { - key := k - - desc := strings.Join(descs, "\n") - _, current := existingGrouped[k] - recs, wanted := desiredGrouped[k] - if wanted && !current { - // pure addition - corrections = append(corrections, &models.Correction{ - Msg: desc, - F: func() error { return n.add(recs, dc.Name) }, - }) - } else if current && !wanted { - // pure deletion - corrections = append(corrections, &models.Correction{ - Msg: desc, - F: func() error { return n.remove(key, dc.Name) }, - }) - } else { - // modification - corrections = append(corrections, &models.Correction{ - Msg: desc, - F: func() error { return n.modify(recs, dc.Name) }, - }) + differ := diff.New(dc) + changedGroups, err := differ.ChangedGroups(existingRecords) + if err != nil { + return nil, err } + + corrections := []*models.Correction{} + + if dnssecCorrections := n.getDomainCorrectionsDNSSEC(domain, dc.AutoDNSSEC); dnssecCorrections != nil { + corrections = append(corrections, dnssecCorrections) + } + + // each name/type is given to the api as a unit. + for k, descs := range changedGroups { + key := k + + desc := strings.Join(descs, "\n") + _, current := existingGrouped[k] + recs, wanted := desiredGrouped[k] + if wanted && !current { + // pure addition + corrections = append(corrections, &models.Correction{ + Msg: desc, + F: func() error { return n.add(recs, dc.Name) }, + }) + } else if current && !wanted { + // pure deletion + corrections = append(corrections, &models.Correction{ + Msg: desc, + F: func() error { return n.remove(key, dc.Name) }, + }) + } else { + // modification + corrections = append(corrections, &models.Correction{ + Msg: desc, + F: func() error { return n.modify(recs, dc.Name) }, + }) + } + } + return corrections, nil } + + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/oracle/oracleProvider.go b/providers/oracle/oracleProvider.go index c65ed058b..95b775185 100644 --- a/providers/oracle/oracleProvider.go +++ b/providers/oracle/oracleProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -239,75 +240,83 @@ func (o *oracleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model } } - differ := diff.New(dc) - _, create, dels, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - /* - Oracle's API doesn't have a way to update an existing record. - You can either update an existing RRSet, Domain (FQDN), or Zone in which you have to supply - the entire desired state, or you can patch specifying ADD/REMOVE actions. - Oracle's API is also increadibly slow, so updating individual RRSets is unbearably slow - for any size zone. - */ - - corrections := []*models.Correction{} - - if len(create) > 0 { - createRecords := models.Records{} - desc := "" - for _, d := range create { - createRecords = append(createRecords, d.Desired) - desc += d.String() + "\n" + differ := diff.New(dc) + _, create, dels, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err } - desc = desc[:len(desc)-1] - corrections = append(corrections, &models.Correction{ - Msg: desc, - F: func() error { - return o.patch(createRecords, nil, domain) - }, - }) - } + /* + Oracle's API doesn't have a way to update an existing record. + You can either update an existing RRSet, Domain (FQDN), or Zone in which you have to supply + the entire desired state, or you can patch specifying ADD/REMOVE actions. + Oracle's API is also increadibly slow, so updating individual RRSets is unbearably slow + for any size zone. + */ - if len(dels) > 0 { - deleteRecords := models.Records{} - desc := "" - for _, d := range dels { - deleteRecords = append(deleteRecords, d.Existing) - desc += d.String() + "\n" + corrections := []*models.Correction{} + + if len(create) > 0 { + createRecords := models.Records{} + desc := "" + for _, d := range create { + createRecords = append(createRecords, d.Desired) + desc += d.String() + "\n" + } + desc = desc[:len(desc)-1] + + corrections = append(corrections, &models.Correction{ + Msg: desc, + F: func() error { + return o.patch(createRecords, nil, domain) + }, + }) } - desc = desc[:len(desc)-1] - corrections = append(corrections, &models.Correction{ - Msg: desc, - F: func() error { - return o.patch(nil, deleteRecords, domain) - }, - }) - } + if len(dels) > 0 { + deleteRecords := models.Records{} + desc := "" + for _, d := range dels { + deleteRecords = append(deleteRecords, d.Existing) + desc += d.String() + "\n" + } + desc = desc[:len(desc)-1] - if len(modify) > 0 { - createRecords := models.Records{} - deleteRecords := models.Records{} - desc := "" - for _, d := range modify { - createRecords = append(createRecords, d.Desired) - deleteRecords = append(deleteRecords, d.Existing) - desc += d.String() + "\n" + corrections = append(corrections, &models.Correction{ + Msg: desc, + F: func() error { + return o.patch(nil, deleteRecords, domain) + }, + }) } - desc = desc[:len(desc)-1] - corrections = append(corrections, &models.Correction{ - Msg: desc, - F: func() error { - return o.patch(createRecords, deleteRecords, domain) - }, - }) + if len(modify) > 0 { + createRecords := models.Records{} + deleteRecords := models.Records{} + desc := "" + for _, d := range modify { + createRecords = append(createRecords, d.Desired) + deleteRecords = append(deleteRecords, d.Existing) + desc += d.String() + "\n" + } + desc = desc[:len(desc)-1] + + corrections = append(corrections, &models.Correction{ + Msg: desc, + F: func() error { + return o.patch(createRecords, deleteRecords, domain) + }, + }) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/ovh/ovhProvider.go b/providers/ovh/ovhProvider.go index e6e91325d..0379744b0 100644 --- a/providers/ovh/ovhProvider.go +++ b/providers/ovh/ovhProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/ovh/go-ovh/ovh" ) @@ -128,47 +129,55 @@ func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C // Normalize models.PostProcessRecords(actual) - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(actual) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(actual) + if err != nil { + return nil, err + } + + corrections := []*models.Correction{} + + for _, del := range delete { + rec := del.Existing.Original.(*Record) + corrections = append(corrections, &models.Correction{ + Msg: del.String(), + F: c.deleteRecordFunc(rec.ID, dc.Name), + }) + } + + for _, cre := range create { + rec := cre.Desired + corrections = append(corrections, &models.Correction{ + Msg: cre.String(), + F: c.createRecordFunc(rec, dc.Name), + }) + } + + for _, mod := range modify { + oldR := mod.Existing.Original.(*Record) + newR := mod.Desired + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: c.updateRecordFunc(oldR, newR, dc.Name), + }) + } + + if len(corrections) > 0 { + corrections = append(corrections, &models.Correction{ + Msg: "REFRESH zone " + dc.Name, + F: func() error { + return c.refreshZone(dc.Name) + }, + }) + } + + return corrections, nil } - corrections := []*models.Correction{} - - for _, del := range delete { - rec := del.Existing.Original.(*Record) - corrections = append(corrections, &models.Correction{ - Msg: del.String(), - F: c.deleteRecordFunc(rec.ID, dc.Name), - }) - } - - for _, cre := range create { - rec := cre.Desired - corrections = append(corrections, &models.Correction{ - Msg: cre.String(), - F: c.createRecordFunc(rec, dc.Name), - }) - } - - for _, mod := range modify { - oldR := mod.Existing.Original.(*Record) - newR := mod.Desired - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: c.updateRecordFunc(oldR, newR, dc.Name), - }) - } - - if len(corrections) > 0 { - corrections = append(corrections, &models.Correction{ - Msg: "REFRESH zone " + dc.Name, - F: func() error { - return c.refreshZone(dc.Name) - }, - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/packetframe/packetframeProvider.go b/providers/packetframe/packetframeProvider.go index 53c4e4132..907c042ed 100644 --- a/providers/packetframe/packetframeProvider.go +++ b/providers/packetframe/packetframeProvider.go @@ -10,6 +10,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -123,62 +124,68 @@ func (api *packetframeProvider) GetDomainCorrections(dc *models.DomainConfig) ([ // Normalize models.PostProcessRecords(existingRecords) - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - for _, m := range create { - req, err := toReq(zone.ID, dc, m.Desired) + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(existingRecords) if err != nil { return nil, err } - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - _, err := api.createRecord(req) - return err - }, + + for _, m := range create { + req, err := toReq(zone.ID, dc, m.Desired) + if err != nil { + return nil, err + } + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + _, err := api.createRecord(req) + return err + }, + } + corrections = append(corrections, corr) } - corrections = append(corrections, corr) + + for _, m := range delete { + original := m.Existing.Original.(*domainRecord) + if original.ID == "0" { // Skip the default nameservers + continue + } + + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + err := api.deleteRecord(zone.ID, original.ID) + return err + }, + } + corrections = append(corrections, corr) + } + + for _, m := range modify { + original := m.Existing.Original.(*domainRecord) + if original.ID == "0" { // Skip the default nameservers + continue + } + + req, _ := toReq(zone.ID, dc, m.Desired) + req.ID = original.ID + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + err := api.modifyRecord(req) + return err + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } - for _, m := range delete { - original := m.Existing.Original.(*domainRecord) - if original.ID == "0" { // Skip the default nameservers - continue - } - - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - err := api.deleteRecord(zone.ID, original.ID) - return err - }, - } - corrections = append(corrections, corr) - } - - for _, m := range modify { - original := m.Existing.Original.(*domainRecord) - if original.ID == "0" { // Skip the default nameservers - continue - } - - req, _ := toReq(zone.ID, dc, m.Desired) - req.ID = original.ID - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - err := api.modifyRecord(req) - return err - }, - } - corrections = append(corrections, corr) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/porkbun/porkbunProvider.go b/providers/porkbun/porkbunProvider.go index e46cc859d..16d6ac9e5 100644 --- a/providers/porkbun/porkbunProvider.go +++ b/providers/porkbun/porkbunProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/providers" ) @@ -98,57 +99,63 @@ func (c *porkbunProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode record.TTL = fixTTL(record.TTL) } - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - // Deletes first so changing type works etc. - for _, m := range del { - id := m.Existing.Original.(*domainRecord).ID - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, porkbun ID: %s", m.String(), id), - F: func() error { - return c.deleteRecord(dc.Name, id) - }, - } - corrections = append(corrections, corr) - } - - for _, m := range create { - req, err := toReq(m.Desired) + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) if err != nil { return nil, err } - corr := &models.Correction{ - Msg: m.String(), - F: func() error { - return c.createRecord(dc.Name, req) - }, - } - corrections = append(corrections, corr) - } - - for _, m := range modify { - id := m.Existing.Original.(*domainRecord).ID - req, err := toReq(m.Desired) - if err != nil { - return nil, err + // Deletes first so changing type works etc. + for _, m := range del { + id := m.Existing.Original.(*domainRecord).ID + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, porkbun ID: %s", m.String(), id), + F: func() error { + return c.deleteRecord(dc.Name, id) + }, + } + corrections = append(corrections, corr) } - corr := &models.Correction{ - Msg: fmt.Sprintf("%s, porkbun ID: %s: ", m.String(), id), - F: func() error { - return c.modifyRecord(dc.Name, id, req) - }, + for _, m := range create { + req, err := toReq(m.Desired) + if err != nil { + return nil, err + } + + corr := &models.Correction{ + Msg: m.String(), + F: func() error { + return c.createRecord(dc.Name, req) + }, + } + corrections = append(corrections, corr) } - corrections = append(corrections, corr) + + for _, m := range modify { + id := m.Existing.Original.(*domainRecord).ID + req, err := toReq(m.Desired) + if err != nil { + return nil, err + } + + corr := &models.Correction{ + Msg: fmt.Sprintf("%s, porkbun ID: %s: ", m.String(), id), + F: func() error { + return c.modifyRecord(dc.Name, id, req) + }, + } + corrections = append(corrections, corr) + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/powerdns/dns.go b/providers/powerdns/dns.go index 8284b9709..7d09cfc9f 100644 --- a/providers/powerdns/dns.go +++ b/providers/powerdns/dns.go @@ -7,6 +7,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/mittwald/go-powerdns/apis/zones" "github.com/mittwald/go-powerdns/pdnshttp" ) @@ -48,7 +49,6 @@ func (dsp *powerdnsProvider) GetZoneRecords(domain string) (models.Records, erro // GetDomainCorrections returns a list of corrections to update a domain. func (dsp *powerdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { - var corrections []*models.Correction // get current zone records curRecords, err := dsp.GetZoneRecords(dc.Name) @@ -62,65 +62,73 @@ func (dsp *powerdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m } models.PostProcessRecords(curRecords) - // create record diff by group - keysToUpdate, err := (diff.New(dc)).ChangedGroups(curRecords) - if err != nil { - return nil, err - } - desiredRecords := dc.Records.GroupedByKey() + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - var cuCorrections []*models.Correction - var dCorrections []*models.Correction + // create record diff by group + keysToUpdate, err := (diff.New(dc)).ChangedGroups(curRecords) + if err != nil { + return nil, err + } + desiredRecords := dc.Records.GroupedByKey() - // add create/update and delete corrections separately - for label, msgs := range keysToUpdate { - labelName := label.NameFQDN + "." - labelType := label.Type - msgJoined := strings.Join(msgs, "\n ") + var cuCorrections []*models.Correction + var dCorrections []*models.Correction - if _, ok := desiredRecords[label]; !ok { - // no record found so delete it - dCorrections = append(dCorrections, &models.Correction{ - Msg: msgJoined, - F: func() error { - return dsp.client.Zones().RemoveRecordSetFromZone(context.Background(), dsp.ServerName, dc.Name, labelName, labelType) - }, - }) - } else { - // record found so create or update it - ttl := desiredRecords[label][0].TTL - var records []zones.Record - for _, recordContent := range desiredRecords[label] { - records = append(records, zones.Record{ - Content: recordContent.GetTargetCombined(), + // add create/update and delete corrections separately + for label, msgs := range keysToUpdate { + labelName := label.NameFQDN + "." + labelType := label.Type + msgJoined := strings.Join(msgs, "\n ") + + if _, ok := desiredRecords[label]; !ok { + // no record found so delete it + dCorrections = append(dCorrections, &models.Correction{ + Msg: msgJoined, + F: func() error { + return dsp.client.Zones().RemoveRecordSetFromZone(context.Background(), dsp.ServerName, dc.Name, labelName, labelType) + }, + }) + } else { + // record found so create or update it + ttl := desiredRecords[label][0].TTL + var records []zones.Record + for _, recordContent := range desiredRecords[label] { + records = append(records, zones.Record{ + Content: recordContent.GetTargetCombined(), + }) + } + cuCorrections = append(cuCorrections, &models.Correction{ + Msg: msgJoined, + F: func() error { + return dsp.client.Zones().AddRecordSetToZone(context.Background(), dsp.ServerName, dc.Name, zones.ResourceRecordSet{ + Name: labelName, + Type: labelType, + TTL: int(ttl), + Records: records, + ChangeType: zones.ChangeTypeReplace, + }) + }, }) } - cuCorrections = append(cuCorrections, &models.Correction{ - Msg: msgJoined, - F: func() error { - return dsp.client.Zones().AddRecordSetToZone(context.Background(), dsp.ServerName, dc.Name, zones.ResourceRecordSet{ - Name: labelName, - Type: labelType, - TTL: int(ttl), - Records: records, - ChangeType: zones.ChangeTypeReplace, - }) - }, - }) } + + // append corrections in the right order + // delete corrections must be run first to avoid correlations with existing RR + corrections = append(corrections, dCorrections...) + corrections = append(corrections, cuCorrections...) + + // DNSSec corrections + dnssecCorrections, err := dsp.getDNSSECCorrections(dc) + if err != nil { + return nil, err + } + corrections = append(corrections, dnssecCorrections...) + + return corrections, nil } - // append corrections in the right order - // delete corrections must be run first to avoid correlations with existing RR - corrections = append(corrections, dCorrections...) - corrections = append(corrections, cuCorrections...) - - // DNSSec corrections - dnssecCorrections, err := dsp.getDNSSECCorrections(dc) - if err != nil { - return nil, err - } - corrections = append(corrections, dnssecCorrections...) + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go index e4c4dbf5e..1f03c74d0 100644 --- a/providers/route53/route53Provider.go +++ b/providers/route53/route53Provider.go @@ -13,6 +13,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" "github.com/StackExchange/dnscontrol/v3/providers" @@ -261,8 +262,6 @@ func (r *route53Provider) getZoneRecords(zone r53Types.HostedZone) (models.Recor func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { dc.Punycode() - var corrections = []*models.Correction{} - zone, err := r.getZone(dc) if err != nil { return nil, err @@ -284,108 +283,134 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - // diff - differ := diff.New(dc, getAliasMap) - namesToUpdate, err := differ.ChangedGroups(existingRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - if len(namesToUpdate) == 0 { - return nil, nil - } + // diff + differ := diff.New(dc, getAliasMap) + namesToUpdate, err := differ.ChangedGroups(existingRecords) + if err != nil { + return nil, err + } - updates := map[models.RecordKey][]*models.RecordConfig{} + if len(namesToUpdate) == 0 { + return nil, nil + } - // for each name we need to update, collect relevant records from our desired domain state - for k := range namesToUpdate { - updates[k] = nil - for _, rc := range dc.Records { - if rc.Key() == k { - updates[k] = append(updates[k], rc) + updates := map[models.RecordKey][]*models.RecordConfig{} + + // for each name we need to update, collect relevant records from our desired domain state + for k := range namesToUpdate { + updates[k] = nil + for _, rc := range dc.Records { + if rc.Key() == k { + updates[k] = append(updates[k], rc) + } } } - } - // updateOrder is the order that the updates will happen. - // The order should be sorted by NameFQDN, then Type, with R53_ALIAS_* - // types sorted after all other types. R53_ALIAS_* needs to be last - // because they are order dependent (aliases must refer to labels - // that already exist). - var updateOrder []models.RecordKey - // Collect the keys - for k := range updates { - updateOrder = append(updateOrder, k) - } - // Sort themm - sort.Slice(updateOrder, func(i, j int) bool { - if updateOrder[i].Type == updateOrder[j].Type { + // updateOrder is the order that the updates will happen. + // The order should be sorted by NameFQDN, then Type, with R53_ALIAS_* + // types sorted after all other types. R53_ALIAS_* needs to be last + // because they are order dependent (aliases must refer to labels + // that already exist). + var updateOrder []models.RecordKey + // Collect the keys + for k := range updates { + updateOrder = append(updateOrder, k) + } + // Sort themm + sort.Slice(updateOrder, func(i, j int) bool { + if updateOrder[i].Type == updateOrder[j].Type { + return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN + } + + if strings.HasPrefix(updateOrder[i].Type, "R53_ALIAS_") { + return false + } + if strings.HasPrefix(updateOrder[j].Type, "R53_ALIAS_") { + return true + } + + if updateOrder[i].NameFQDN == updateOrder[j].NameFQDN { + return updateOrder[i].Type < updateOrder[j].Type + } return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN - } + }) - if strings.HasPrefix(updateOrder[i].Type, "R53_ALIAS_") { - return false - } - if strings.HasPrefix(updateOrder[j].Type, "R53_ALIAS_") { - return true - } + // we collect all changes into one of two categories now: + // pure deletions where we delete an entire record set, + // or changes where we upsert an entire record set. + dels := []r53Types.Change{} + delDesc := []string{} + changes := []r53Types.Change{} + changeDesc := []string{} - if updateOrder[i].NameFQDN == updateOrder[j].NameFQDN { - return updateOrder[i].Type < updateOrder[j].Type - } - return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN - }) - - // we collect all changes into one of two categories now: - // pure deletions where we delete an entire record set, - // or changes where we upsert an entire record set. - dels := []r53Types.Change{} - delDesc := []string{} - changes := []r53Types.Change{} - changeDesc := []string{} - - for _, k := range updateOrder { - recs := updates[k] - // If there are no records in our desired state for a key, this - // indicates we should delete all records at that key. - if len(recs) == 0 { - // To delete, we submit the original resource set we got from r53. - var ( - rrset r53Types.ResourceRecordSet - found bool - ) - // Find the original resource set: - for _, r := range r.originalRecords { - if unescape(r.Name) == k.NameFQDN && (string(r.Type) == k.Type || k.Type == "R53_ALIAS_"+string(r.Type)) { - rrset = r - found = true - break + for _, k := range updateOrder { + recs := updates[k] + // If there are no records in our desired state for a key, this + // indicates we should delete all records at that key. + if len(recs) == 0 { + // To delete, we submit the original resource set we got from r53. + var ( + rrset r53Types.ResourceRecordSet + found bool + ) + // Find the original resource set: + for _, r := range r.originalRecords { + if unescape(r.Name) == k.NameFQDN && (string(r.Type) == k.Type || k.Type == "R53_ALIAS_"+string(r.Type)) { + rrset = r + found = true + break + } } - } - if !found { - // This should not happen. - return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type) - } - // Assemble the change and add it to the list: - chg := r53Types.Change{ - Action: r53Types.ChangeActionDelete, - ResourceRecordSet: &rrset, - } - dels = append(dels, chg) - delDesc = append(delDesc, strings.Join(namesToUpdate[k], "\n")) - } else { - // If it isn't a delete, it must be either a change or create. In - // either case, we build a new record set from the desired state and - // UPSERT it. - - if strings.HasPrefix(k.Type, "R53_ALIAS_") { - // Each R53_ALIAS_* requires an individual change. - if len(recs) != 1 { - log.Fatal("Only one R53_ALIAS_ permitted on a label") + if !found { + // This should not happen. + return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type) } - for _, r := range recs { - rrset := aliasToRRSet(zone, r) - rrset.Name = aws.String(k.NameFQDN) + // Assemble the change and add it to the list: + chg := r53Types.Change{ + Action: r53Types.ChangeActionDelete, + ResourceRecordSet: &rrset, + } + dels = append(dels, chg) + delDesc = append(delDesc, strings.Join(namesToUpdate[k], "\n")) + } else { + // If it isn't a delete, it must be either a change or create. In + // either case, we build a new record set from the desired state and + // UPSERT it. + + if strings.HasPrefix(k.Type, "R53_ALIAS_") { + // Each R53_ALIAS_* requires an individual change. + if len(recs) != 1 { + log.Fatal("Only one R53_ALIAS_ permitted on a label") + } + for _, r := range recs { + rrset := aliasToRRSet(zone, r) + rrset.Name = aws.String(k.NameFQDN) + // Assemble the change and add it to the list: + chg := r53Types.Change{ + Action: r53Types.ChangeActionUpsert, + ResourceRecordSet: rrset, + } + changes = append(changes, chg) + changeDesc = append(changeDesc, strings.Join(namesToUpdate[k], "\n")) + } + } else { + // All other keys combine their updates into one rrset: + rrset := &r53Types.ResourceRecordSet{ + Name: aws.String(k.NameFQDN), + Type: r53Types.RRType(k.Type), + } + for _, r := range recs { + val := r.GetTargetCombined() + rr := r53Types.ResourceRecord{ + Value: aws.String(val), + } + rrset.ResourceRecords = append(rrset.ResourceRecords, rr) + i := int64(r.TTL) + rrset.TTL = &i // TODO: make sure that ttls are consistent within a set + } // Assemble the change and add it to the list: chg := r53Types.Change{ Action: r53Types.ChangeActionUpsert, @@ -394,79 +419,61 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode changes = append(changes, chg) changeDesc = append(changeDesc, strings.Join(namesToUpdate[k], "\n")) } - } else { - // All other keys combine their updates into one rrset: - rrset := &r53Types.ResourceRecordSet{ - Name: aws.String(k.NameFQDN), - Type: r53Types.RRType(k.Type), - } - for _, r := range recs { - val := r.GetTargetCombined() - rr := r53Types.ResourceRecord{ - Value: aws.String(val), - } - rrset.ResourceRecords = append(rrset.ResourceRecords, rr) - i := int64(r.TTL) - rrset.TTL = &i // TODO: make sure that ttls are consistent within a set - } - // Assemble the change and add it to the list: - chg := r53Types.Change{ - Action: r53Types.ChangeActionUpsert, - ResourceRecordSet: rrset, - } - changes = append(changes, chg) - changeDesc = append(changeDesc, strings.Join(namesToUpdate[k], "\n")) + } - } - } - addCorrection := func(msg string, req *r53.ChangeResourceRecordSetsInput) { - corrections = append(corrections, - &models.Correction{ - Msg: msg, - F: func() error { - var err error - req.HostedZoneId = zone.Id - withRetry(func() error { - _, err = r.client.ChangeResourceRecordSets(context.Background(), req) + addCorrection := func(msg string, req *r53.ChangeResourceRecordSetsInput) { + corrections = append(corrections, + &models.Correction{ + Msg: msg, + F: func() error { + var err error + req.HostedZoneId = zone.Id + withRetry(func() error { + _, err = r.client.ChangeResourceRecordSets(context.Background(), req) + return err + }) return err - }) - return err - }, - }) + }, + }) + } + + batcher := newChangeBatcher(dels) + for batcher.Next() { + start, end := batcher.Batch() + batch := dels[start:end] + descBatchStr := "\n" + strings.Join(delDesc[start:end], "\n") + "\n" + req := &r53.ChangeResourceRecordSetsInput{ + ChangeBatch: &r53Types.ChangeBatch{Changes: batch}, + } + addCorrection(descBatchStr, req) + } + if err := batcher.Err(); err != nil { + return nil, err + } + + batcher = newChangeBatcher(changes) + for batcher.Next() { + start, end := batcher.Batch() + batch := changes[start:end] + descBatchStr := "\n" + strings.Join(changeDesc[start:end], "\n") + "\n" + req := &r53.ChangeResourceRecordSetsInput{ + ChangeBatch: &r53Types.ChangeBatch{Changes: batch}, + } + addCorrection(descBatchStr, req) + } + if err := batcher.Err(); err != nil { + return nil, err + } + + return corrections, nil + } - batcher := newChangeBatcher(dels) - for batcher.Next() { - start, end := batcher.Batch() - batch := dels[start:end] - descBatchStr := "\n" + strings.Join(delDesc[start:end], "\n") + "\n" - req := &r53.ChangeResourceRecordSetsInput{ - ChangeBatch: &r53Types.ChangeBatch{Changes: batch}, - } - addCorrection(descBatchStr, req) - } - if err := batcher.Err(); err != nil { - return nil, err - } - - batcher = newChangeBatcher(changes) - for batcher.Next() { - start, end := batcher.Batch() - batch := changes[start:end] - descBatchStr := "\n" + strings.Join(changeDesc[start:end], "\n") + "\n" - req := &r53.ChangeResourceRecordSetsInput{ - ChangeBatch: &r53Types.ChangeBatch{Changes: batch}, - } - addCorrection(descBatchStr, req) - } - if err := batcher.Err(); err != nil { - return nil, err - } + // Insert Future diff2 version here. return corrections, nil - } func nativeToRecords(set r53Types.ResourceRecordSet, origin string) ([]*models.RecordConfig, error) { diff --git a/providers/rwth/dns.go b/providers/rwth/dns.go index 8c5edd2a3..8112ef9bb 100644 --- a/providers/rwth/dns.go +++ b/providers/rwth/dns.go @@ -5,6 +5,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/txtutil" ) @@ -51,44 +52,50 @@ func (api *rwthProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model models.PostProcessRecords(existingRecords) txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(existingRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - for _, d := range create { - des := d.Desired - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return api.createRecord(dc.Name, des) }, - }) - } - for _, d := range del { - existingRecord := d.Existing.Original.(RecordReply) - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return api.destroyRecord(existingRecord) }, - }) - } - for _, d := range modify { - rec := d.Desired - existingID := d.Existing.Original.(RecordReply).ID - corrections = append(corrections, &models.Correction{ - Msg: d.String(), - F: func() error { return api.updateRecord(existingID, *rec) }, - }) + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(existingRecords) + if err != nil { + return nil, err + } + + for _, d := range create { + des := d.Desired + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return api.createRecord(dc.Name, des) }, + }) + } + for _, d := range del { + existingRecord := d.Existing.Original.(RecordReply) + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return api.destroyRecord(existingRecord) }, + }) + } + for _, d := range modify { + rec := d.Desired + existingID := d.Existing.Original.(RecordReply).ID + corrections = append(corrections, &models.Correction{ + Msg: d.String(), + F: func() error { return api.updateRecord(existingID, *rec) }, + }) + } + + // And deploy if any corrections were applied + if len(corrections) > 0 { + corrections = append(corrections, &models.Correction{ + Msg: fmt.Sprintf("Deploy zone %s", domain), + F: func() error { return api.deployZone(domain) }, + }) + } + + return corrections, nil } - // And deploy if any corrections were applied - if len(corrections) > 0 { - corrections = append(corrections, &models.Correction{ - Msg: fmt.Sprintf("Deploy zone %s", domain), - F: func() error { return api.deployZone(domain) }, - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/softlayer/softlayerProvider.go b/providers/softlayer/softlayerProvider.go index 18050d943..c968313ec 100644 --- a/providers/softlayer/softlayerProvider.go +++ b/providers/softlayer/softlayerProvider.go @@ -8,6 +8,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/pkg/printer" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/softlayer/softlayer-go/datatypes" @@ -67,7 +68,6 @@ func (s *softlayerProvider) GetZoneRecords(domain string) (models.Records, error // GetDomainCorrections returns corrections to update a domain. func (s *softlayerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { - corrections := []*models.Correction{} domain, err := s.getDomain(&dc.Name) @@ -81,33 +81,41 @@ func (s *softlayerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo return nil, err } - _, create, delete, modify, err := diff.New(dc).IncrementalDiff(actual) - if err != nil { - return nil, err + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives + + _, create, delete, modify, err := diff.New(dc).IncrementalDiff(actual) + if err != nil { + return nil, err + } + + for _, del := range delete { + existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord) + corrections = append(corrections, &models.Correction{ + Msg: del.String(), + F: s.deleteRecordFunc(*existing.Id), + }) + } + + for _, cre := range create { + corrections = append(corrections, &models.Correction{ + Msg: cre.String(), + F: s.createRecordFunc(cre.Desired, domain), + }) + } + + for _, mod := range modify { + existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord) + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: s.updateRecordFunc(&existing, mod.Desired), + }) + } + + return corrections, nil } - for _, del := range delete { - existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord) - corrections = append(corrections, &models.Correction{ - Msg: del.String(), - F: s.deleteRecordFunc(*existing.Id), - }) - } - - for _, cre := range create { - corrections = append(corrections, &models.Correction{ - Msg: cre.String(), - F: s.createRecordFunc(cre.Desired, domain), - }) - } - - for _, mod := range modify { - existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord) - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: s.updateRecordFunc(&existing, mod.Desired), - }) - } + // Insert Future diff2 version here. return corrections, nil } diff --git a/providers/transip/transipProvider.go b/providers/transip/transipProvider.go index 2f0d0c305..726f2e397 100644 --- a/providers/transip/transipProvider.go +++ b/providers/transip/transipProvider.go @@ -7,6 +7,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/transip/gotransip/v6" "github.com/transip/gotransip/v6/domain" @@ -79,7 +80,6 @@ func init() { } func (n *transipProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { - var corrections []*models.Correction curRecords, err := n.GetZoneRecords(dc.Name) if err != nil { @@ -94,69 +94,77 @@ func (n *transipProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode models.PostProcessRecords(curRecords) - differ := diff.New(dc) - _, create, del, modify, err := differ.IncrementalDiff(curRecords) - if err != nil { - return nil, err - } + var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - for _, del := range del { - entry, err := recordToNative(del.Existing) + differ := diff.New(dc) + _, create, del, modify, err := differ.IncrementalDiff(curRecords) if err != nil { return nil, err } - corrections = append(corrections, &models.Correction{ - Msg: del.String(), - F: func() error { return n.domains.RemoveDNSEntry(dc.Name, entry) }, - }) - } - - for _, cre := range create { - entry, err := recordToNative(cre.Desired) - if err != nil { - return nil, err - } - - corrections = append(corrections, &models.Correction{ - Msg: cre.String(), - F: func() error { return n.domains.AddDNSEntry(dc.Name, entry) }, - }) - } - - for _, mod := range modify { - targetEntry, err := recordToNative(mod.Desired) - if err != nil { - return nil, err - } - - // TransIP identifies records by (Label, TTL Type), we can only update it if only the contents - // has changed. Otherwise we delete the old record and create the new one - if canUpdateDNSEntry(mod.Desired, mod.Existing) { - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: func() error { return n.domains.UpdateDNSEntry(dc.Name, targetEntry) }, - }) - } else { - oldEntry, err := recordToNative(mod.Existing) + for _, del := range del { + entry, err := recordToNative(del.Existing) if err != nil { return nil, err } - corrections = append(corrections, - &models.Correction{ - Msg: mod.String() + "[1/2]", - F: func() error { return n.domains.RemoveDNSEntry(dc.Name, oldEntry) }, - }, - &models.Correction{ - Msg: mod.String() + "[2/2]", - F: func() error { return n.domains.AddDNSEntry(dc.Name, targetEntry) }, - }, - ) + corrections = append(corrections, &models.Correction{ + Msg: del.String(), + F: func() error { return n.domains.RemoveDNSEntry(dc.Name, entry) }, + }) } + for _, cre := range create { + entry, err := recordToNative(cre.Desired) + if err != nil { + return nil, err + } + + corrections = append(corrections, &models.Correction{ + Msg: cre.String(), + F: func() error { return n.domains.AddDNSEntry(dc.Name, entry) }, + }) + } + + for _, mod := range modify { + targetEntry, err := recordToNative(mod.Desired) + if err != nil { + return nil, err + } + + // TransIP identifies records by (Label, TTL Type), we can only update it if only the contents + // has changed. Otherwise we delete the old record and create the new one + if canUpdateDNSEntry(mod.Desired, mod.Existing) { + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: func() error { return n.domains.UpdateDNSEntry(dc.Name, targetEntry) }, + }) + } else { + oldEntry, err := recordToNative(mod.Existing) + if err != nil { + return nil, err + } + + corrections = append(corrections, + &models.Correction{ + Msg: mod.String() + "[1/2]", + F: func() error { return n.domains.RemoveDNSEntry(dc.Name, oldEntry) }, + }, + &models.Correction{ + Msg: mod.String() + "[2/2]", + F: func() error { return n.domains.AddDNSEntry(dc.Name, targetEntry) }, + }, + ) + } + + } + + return corrections, nil } + // Insert Future diff2 version here. + return corrections, nil } diff --git a/providers/vultr/vultrProvider.go b/providers/vultr/vultrProvider.go index a72e4c7f3..93b8ff378 100644 --- a/providers/vultr/vultrProvider.go +++ b/providers/vultr/vultrProvider.go @@ -11,6 +11,7 @@ import ( "github.com/StackExchange/dnscontrol/v3/models" "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/pkg/diff2" "github.com/StackExchange/dnscontrol/v3/providers" "github.com/vultr/govultr/v2" ) @@ -117,44 +118,50 @@ func (api *vultrProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode models.PostProcessRecords(curRecords) - differ := diff.New(dc) - _, create, delete, modify, err := differ.IncrementalDiff(curRecords) - if err != nil { - return nil, err - } - var corrections []*models.Correction + if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives - for _, mod := range delete { - id := mod.Existing.Original.(govultr.DomainRecord).ID - corrections = append(corrections, &models.Correction{ - Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id), - F: func() error { - return api.client.DomainRecord.Delete(context.Background(), dc.Name, id) - }, - }) + differ := diff.New(dc) + _, create, delete, modify, err := differ.IncrementalDiff(curRecords) + if err != nil { + return nil, err + } + + for _, mod := range delete { + id := mod.Existing.Original.(govultr.DomainRecord).ID + corrections = append(corrections, &models.Correction{ + Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id), + F: func() error { + return api.client.DomainRecord.Delete(context.Background(), dc.Name, id) + }, + }) + } + + for _, mod := range create { + r := toVultrRecord(dc, mod.Desired, "0") + corrections = append(corrections, &models.Correction{ + Msg: mod.String(), + F: func() error { + _, err := api.client.DomainRecord.Create(context.Background(), dc.Name, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority}) + return err + }, + }) + } + + for _, mod := range modify { + r := toVultrRecord(dc, mod.Desired, mod.Existing.Original.(govultr.DomainRecord).ID) + corrections = append(corrections, &models.Correction{ + Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), r.ID), + F: func() error { + return api.client.DomainRecord.Update(context.Background(), dc.Name, r.ID, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority}) + }, + }) + } + + return corrections, nil } - for _, mod := range create { - r := toVultrRecord(dc, mod.Desired, "0") - corrections = append(corrections, &models.Correction{ - Msg: mod.String(), - F: func() error { - _, err := api.client.DomainRecord.Create(context.Background(), dc.Name, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority}) - return err - }, - }) - } - - for _, mod := range modify { - r := toVultrRecord(dc, mod.Desired, mod.Existing.Original.(govultr.DomainRecord).ID) - corrections = append(corrections, &models.Correction{ - Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), r.ID), - F: func() error { - return api.client.DomainRecord.Update(context.Background(), dc.Name, r.ID, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority}) - }, - }) - } + // Insert Future diff2 version here. return corrections, nil }