REFACTOR: Add diff2 enable flag to all DNS providers (#1851)

This should enable the diff2 code to be inserted with good "git blame" results for new code.  I'm adding this early to catch any problems early.
This commit is contained in:
Tom Limoncelli 2022-12-11 15:02:58 -05:00 committed by GitHub
parent fe03b29ab2
commit b0f2945510
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 2419 additions and 2100 deletions

View file

@ -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

View file

@ -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()
}

4
pkg/diff2/flag.go Normal file
View file

@ -0,0 +1,4 @@
package diff2
// EnableDiff2 is true to activate the experimental diff2 algorithm.
var EnableDiff2 bool

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}