dnscontrol/providers/cscglobal/dns.go
2023-10-22 13:56:13 -04:00

246 lines
7.1 KiB
Go

package cscglobal
import (
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
)
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (client *providerClient) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
records, err := client.getZoneRecordsAll(domain)
if err != nil {
return nil, err
}
// Convert them to DNScontrol's native format:
existingRecords := []*models.RecordConfig{}
// Option 1: One long list. If your provider returns one long list,
// convert each one to RecordType like this:
// for _, rr := range records {
// existingRecords = append(existingRecords, nativeToRecord(rr, domain))
//}
// Option 2: Grouped records. Sometimes the provider returns one item per
// label. Each item contains a list of all the records at that label.
// You'll need to split them out into one RecordConfig for each record. An
// example of this is the ROUTE53 provider.
// for _, rg := range records {
// for _, rr := range rg {
// existingRecords = append(existingRecords, nativeToRecords(rg, rr, domain)...)
// }
// }
// Option 3: Something else. In this case, we get a big massive structure
// which needs to be broken up. Still, we're generating a list of
// RecordConfig structures.
defaultTTL := records.Soa.TTL
for _, rr := range records.A {
existingRecords = append(existingRecords, nativeToRecordA(rr, domain, defaultTTL))
}
for _, rr := range records.Cname {
existingRecords = append(existingRecords, nativeToRecordCNAME(rr, domain, defaultTTL))
}
for _, rr := range records.Aaaa {
existingRecords = append(existingRecords, nativeToRecordAAAA(rr, domain, defaultTTL))
}
for _, rr := range records.Txt {
existingRecords = append(existingRecords, nativeToRecordTXT(rr, domain, defaultTTL))
}
for _, rr := range records.Mx {
existingRecords = append(existingRecords, nativeToRecordMX(rr, domain, defaultTTL))
}
for _, rr := range records.Ns {
existingRecords = append(existingRecords, nativeToRecordNS(rr, domain, defaultTTL))
}
for _, rr := range records.Srv {
existingRecords = append(existingRecords, nativeToRecordSRV(rr, domain, defaultTTL))
}
for _, rr := range records.Caa {
existingRecords = append(existingRecords, nativeToRecordCAA(rr, domain, defaultTTL))
}
return existingRecords, nil
}
func (client *providerClient) GetNameservers(domain string) ([]*models.Nameserver, error) {
nss, err := client.getNameservers(domain)
if err != nil {
return nil, err
}
return models.ToNameservers(nss)
}
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
func (client *providerClient) GetZoneRecordsCorrections(dc *models.DomainConfig, foundRecords models.Records) ([]*models.Correction, error) {
//txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
toReport, creates, dels, modifications, err := diff.NewCompat(dc).IncrementalDiff(foundRecords)
if err != nil {
return nil, err
}
// Start corrections with the reports
corrections := diff.GenerateMessageCorrections(toReport)
// 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())
}
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
}
func makePurge(domainname string, cor diff.Correlation) zoneResourceRecordEdit {
var existingTarget string
switch cor.Existing.Type {
case "TXT":
existingTarget = strings.Join(cor.Existing.TxtStrings, "")
default:
existingTarget = cor.Existing.GetTargetField()
}
zer := zoneResourceRecordEdit{
Action: "PURGE",
RecordType: cor.Existing.Type,
CurrentKey: cor.Existing.Name,
CurrentValue: existingTarget,
}
if cor.Existing.Type == "CAA" {
var tagValue = cor.Existing.CaaTag
//printer.Printf("DEBUG: CAA TAG = %q\n", tagValue)
zer.CurrentTag = &tagValue
}
return zer
}
func makeAdd(domainname string, cre diff.Correlation) zoneResourceRecordEdit {
rec := cre.Desired
var recTarget string
switch rec.Type {
case "TXT":
recTarget = strings.Join(rec.TxtStrings, "")
default:
recTarget = rec.GetTargetField()
}
zer := zoneResourceRecordEdit{
Action: "ADD",
RecordType: rec.Type,
NewKey: rec.Name,
NewValue: recTarget,
NewTTL: rec.TTL,
}
switch rec.Type {
case "CAA":
var tagValue = rec.CaaTag
var flagValue = rec.CaaFlag
zer.NewTag = &tagValue
zer.NewFlag = &flagValue
case "MX":
zer.NewPriority = rec.MxPreference
case "SRV":
zer.NewPriority = rec.SrvPriority
zer.NewWeight = rec.SrvWeight
zer.NewPort = rec.SrvPort
case "TXT":
zer.NewValue = strings.Join(rec.TxtStrings, "")
default: // "A", "CNAME", "NS"
// Nothing to do.
}
return zer
}
func makeEdit(domainname string, m diff.Correlation) zoneResourceRecordEdit {
old, rec := m.Existing, m.Desired
// TODO: Assert that old.Type == rec.Type
// TODO: Assert that old.Name == rec.Name
var oldTarget, recTarget string
switch old.Type {
case "TXT":
oldTarget = strings.Join(old.TxtStrings, "")
recTarget = strings.Join(rec.TxtStrings, "")
default:
oldTarget = old.GetTargetField()
recTarget = rec.GetTargetField()
}
zer := zoneResourceRecordEdit{
Action: "EDIT",
RecordType: old.Type,
CurrentKey: old.Name,
CurrentValue: oldTarget,
}
if oldTarget != recTarget {
zer.NewValue = recTarget
}
if old.TTL != rec.TTL {
zer.NewTTL = rec.TTL
}
switch old.Type {
case "CAA":
var tagValue = old.CaaTag
zer.CurrentTag = &tagValue
if old.CaaTag != rec.CaaTag || old.CaaFlag != rec.CaaFlag || old.TTL != rec.TTL {
// If anything changed, we need to update both tag and flag.
zer.NewTag = &(rec.CaaTag)
zer.NewFlag = &(rec.CaaFlag)
}
case "MX":
if old.MxPreference != rec.MxPreference {
zer.NewPriority = rec.MxPreference
}
case "SRV":
zer.NewWeight = rec.SrvWeight
zer.NewPort = rec.SrvPort
zer.NewPriority = rec.SrvPriority
default: // "A", "CNAME", "NS", "TXT"
// Nothing to do.
}
return zer
}