mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
CLOUDFLARE: Adopt diff2 (#2040)
This commit is contained in:
@ -154,11 +154,11 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := c.getDomainID(dc.Name)
|
||||
domainID, err := c.getDomainID(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records, err := c.getRecordsForDomain(id, dc.Name)
|
||||
records, err := c.getRecordsForDomain(domainID, dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -176,7 +176,7 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
}
|
||||
|
||||
if c.manageRedirects {
|
||||
prs, err := c.getPageRules(id, dc.Name)
|
||||
prs, err := c.getPageRules(domainID, dc.Name)
|
||||
//printer.Printf("GET PAGE RULES:\n")
|
||||
//for i, p := range prs {
|
||||
// printer.Printf("%03d: %q\n", i, p.GetTargetField())
|
||||
@ -188,7 +188,7 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
}
|
||||
|
||||
if c.manageWorkers {
|
||||
wrs, err := c.getWorkerRoutes(id, dc.Name)
|
||||
wrs, err := c.getWorkerRoutes(domainID, dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -224,7 +224,7 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
// one string in the first element of .TxtStrings.
|
||||
|
||||
var corrections []*models.Correction
|
||||
if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
|
||||
if !diff2.EnableDiff2 {
|
||||
|
||||
differ := diff.New(dc, getProxyMetadata)
|
||||
_, create, del, mod, err := differ.IncrementalDiff(records)
|
||||
@ -239,15 +239,15 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
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) },
|
||||
F: func() error { return c.deletePageRule(ex.Original.(cloudflare.PageRule).ID, domainID) },
|
||||
})
|
||||
} 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) },
|
||||
F: func() error { return c.deleteWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, domainID) },
|
||||
})
|
||||
} else {
|
||||
corr := c.deleteRec(ex.Original.(cloudflare.DNSRecord), id)
|
||||
corr := c.deleteRec(ex.Original.(cloudflare.DNSRecord), domainID)
|
||||
// DS records must always have a corresponding NS record.
|
||||
// Therefore, we remove DS records before any NS records.
|
||||
if d.Existing.Type == "DS" {
|
||||
@ -262,15 +262,15 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
if des.Type == "PAGE_RULE" {
|
||||
corrections = append(corrections, &models.Correction{
|
||||
Msg: d.String(),
|
||||
F: func() error { return c.createPageRule(id, des.GetTargetField()) },
|
||||
F: func() error { return c.createPageRule(domainID, 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()) },
|
||||
F: func() error { return c.createWorkerRoute(domainID, des.GetTargetField()) },
|
||||
})
|
||||
} else {
|
||||
corr := c.createRec(des, id)
|
||||
corr := c.createRec(des, domainID)
|
||||
// DS records must always have a corresponding NS record.
|
||||
// Therefore, we create NS records before any DS records.
|
||||
if d.Desired.Type == "NS" {
|
||||
@ -287,13 +287,15 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
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()) },
|
||||
F: func() error {
|
||||
return c.updatePageRule(ex.Original.(cloudflare.PageRule).ID, domainID, 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())
|
||||
return c.updateWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, domainID, rec.GetTargetField())
|
||||
},
|
||||
})
|
||||
} else {
|
||||
@ -301,13 +303,13 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
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) },
|
||||
F: func() error { return c.modifyRecord(domainID, e.ID, proxy, rec) },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add universalSSL change to corrections when needed
|
||||
if changed, newState, err := c.checkUniversalSSL(dc, id); err == nil && changed {
|
||||
if changed, newState, err := c.checkUniversalSSL(dc, domainID); err == nil && changed {
|
||||
var newStateString string
|
||||
if newState {
|
||||
newStateString = "enabled"
|
||||
@ -316,17 +318,154 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
|
||||
}
|
||||
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) },
|
||||
F: func() error { return c.changeUniversalSSL(domainID, newState) },
|
||||
})
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
// Insert Future diff2 version here.
|
||||
// Cloudflare is a "ByRecord" API.
|
||||
instructions, err := diff2.ByRecord(records, dc, genComparable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, inst := range instructions {
|
||||
|
||||
addToFront := false
|
||||
var corrs []*models.Correction
|
||||
|
||||
domainID := domainID
|
||||
msg := inst.Msgs[0]
|
||||
|
||||
switch inst.Type {
|
||||
case diff2.CREATE:
|
||||
createRec := inst.New[0]
|
||||
corrs = c.mkCreateCorrection(createRec, domainID, msg)
|
||||
// DS records must always have a corresponding NS record.
|
||||
// Therefore, we create NS records before any DS records.
|
||||
addToFront = createRec.Type == "NS"
|
||||
case diff2.CHANGE:
|
||||
newrec := inst.New[0]
|
||||
oldrec := inst.Old[0]
|
||||
corrs = c.mkChangeCorrection(oldrec, newrec, domainID, msg)
|
||||
case diff2.DELETE:
|
||||
deleteRec := inst.Old[0]
|
||||
deleteRecType := deleteRec.Type
|
||||
deleteRecOrig := deleteRec.Original
|
||||
corrs = c.mkDeleteCorrection(deleteRecType, deleteRecOrig, domainID, msg)
|
||||
// DS records must always have a corresponding NS record.
|
||||
// Therefore, we remove DS records before any NS records.
|
||||
addToFront = deleteRecType == "DS"
|
||||
}
|
||||
|
||||
if addToFront {
|
||||
corrections = append(corrs, corrections...)
|
||||
} else {
|
||||
corrections = append(corrections, corrs...)
|
||||
}
|
||||
}
|
||||
|
||||
// Add universalSSL change when needed
|
||||
if changed, newState, err := c.checkUniversalSSL(dc, domainID); 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(domainID, newState) },
|
||||
})
|
||||
}
|
||||
|
||||
return corrections, nil
|
||||
}
|
||||
|
||||
func genComparable(rec *models.RecordConfig) string {
|
||||
//fmt.Printf("DEBUG: genComparable called %v:%v meta=%+v\n", rec.Type, rec.GetLabel(), rec.Metadata)
|
||||
if rec.Type == "A" || rec.Type == "AAAA" || rec.Type == "CNAME" {
|
||||
proxy := rec.Metadata[metaProxy]
|
||||
if proxy != "" {
|
||||
//return "proxy=" + rec.Metadata[metaProxy]
|
||||
return "proxy=" + proxy
|
||||
//return fmt.Sprintf("proxy:%v=%s", rec.Type, proxy)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *cloudflareProvider) mkCreateCorrection(newrec *models.RecordConfig, domainID, msg string) []*models.Correction {
|
||||
switch newrec.Type {
|
||||
case "PAGE_RULE":
|
||||
return []*models.Correction{{
|
||||
Msg: msg,
|
||||
F: func() error { return c.createPageRule(domainID, newrec.GetTargetField()) },
|
||||
}}
|
||||
case "WORKER_ROUTE":
|
||||
return []*models.Correction{{
|
||||
Msg: msg,
|
||||
F: func() error { return c.createWorkerRoute(domainID, newrec.GetTargetField()) },
|
||||
}}
|
||||
default:
|
||||
return c.createRecDiff2(newrec, domainID, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cloudflareProvider) mkChangeCorrection(oldrec, newrec *models.RecordConfig, domainID string, msg string) []*models.Correction {
|
||||
switch newrec.Type {
|
||||
case "PAGE_RULE":
|
||||
return []*models.Correction{{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
return c.updatePageRule(oldrec.Original.(cloudflare.PageRule).ID, domainID, newrec.GetTargetField())
|
||||
},
|
||||
}}
|
||||
case "WORKER_ROUTE":
|
||||
return []*models.Correction{{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
return c.updateWorkerRoute(oldrec.Original.(cloudflare.WorkerRoute).ID, domainID, newrec.GetTargetField())
|
||||
},
|
||||
}}
|
||||
default:
|
||||
e := oldrec.Original.(cloudflare.DNSRecord)
|
||||
proxy := e.Proxiable && newrec.Metadata[metaProxy] != "off"
|
||||
return []*models.Correction{{
|
||||
Msg: msg,
|
||||
F: func() error { return c.modifyRecord(domainID, e.ID, proxy, newrec) },
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cloudflareProvider) mkDeleteCorrection(recType string, origRec any, domainID string, msg string) []*models.Correction {
|
||||
var idTxt string
|
||||
switch recType {
|
||||
case "PAGE_RULE":
|
||||
idTxt = origRec.(cloudflare.PageRule).ID
|
||||
case "WORKER_ROUTE":
|
||||
idTxt = origRec.(cloudflare.WorkerRoute).ID
|
||||
default:
|
||||
idTxt = origRec.(cloudflare.DNSRecord).ID
|
||||
}
|
||||
msg = msg + fmt.Sprintf(" id=%v", idTxt)
|
||||
|
||||
correction := &models.Correction{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
switch recType {
|
||||
case "PAGE_RULE":
|
||||
return c.deletePageRule(origRec.(cloudflare.PageRule).ID, domainID)
|
||||
case "WORKER_ROUTE":
|
||||
return c.deleteWorkerRoute(origRec.(cloudflare.WorkerRoute).ID, domainID)
|
||||
default:
|
||||
return c.deleteDNSRecord(origRec.(cloudflare.DNSRecord), domainID)
|
||||
}
|
||||
},
|
||||
}
|
||||
return []*models.Correction{correction}
|
||||
}
|
||||
|
||||
func checkNSModifications(dc *models.DomainConfig) {
|
||||
@ -669,6 +808,7 @@ func (c *cloudflareProvider) nativeToRecord(domain string, cr cloudflare.DNSReco
|
||||
rc := &models.RecordConfig{
|
||||
TTL: uint32(cr.TTL),
|
||||
Original: cr,
|
||||
Metadata: map[string]string{},
|
||||
}
|
||||
rc.SetLabelFromFQDN(cr.Name, domain)
|
||||
|
||||
@ -677,6 +817,16 @@ func (c *cloudflareProvider) nativeToRecord(domain string, cr cloudflare.DNSReco
|
||||
cr.Type = "TXT"
|
||||
}
|
||||
|
||||
if cr.Type == "A" || cr.Type == "AAAA" || cr.Type == "CNAME" {
|
||||
if cr.Proxied != nil {
|
||||
if *(cr.Proxied) {
|
||||
rc.Metadata[metaProxy] = "on"
|
||||
} else {
|
||||
rc.Metadata[metaProxy] = "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch rType := cr.Type; rType { // #rtype_variations
|
||||
case "MX":
|
||||
if err := rc.SetTargetMX(*cr.Priority, cr.Content); err != nil {
|
||||
|
@ -44,6 +44,10 @@ func (c *cloudflareProvider) getRecordsForDomain(id string, domain string) ([]*m
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (c *cloudflareProvider) deleteDNSRecord(rec cloudflare.DNSRecord, domainID string) error {
|
||||
return c.cfClient.DeleteDNSRecord(context.Background(), domainID, rec.ID)
|
||||
}
|
||||
|
||||
// create a correction to delete a record
|
||||
func (c *cloudflareProvider) deleteRec(rec cloudflare.DNSRecord, domainID string) *models.Correction {
|
||||
return &models.Correction{
|
||||
@ -168,6 +172,70 @@ func (c *cloudflareProvider) createRec(rec *models.RecordConfig, domainID string
|
||||
return arr
|
||||
}
|
||||
|
||||
func (c *cloudflareProvider) createRecDiff2(rec *models.RecordConfig, domainID string, msg string) []*models.Correction {
|
||||
|
||||
content := rec.GetTargetField()
|
||||
if rec.Metadata[metaOriginalIP] != "" {
|
||||
content = rec.Metadata[metaOriginalIP]
|
||||
}
|
||||
prio := ""
|
||||
if rec.Type == "MX" {
|
||||
prio = fmt.Sprintf(" %d ", rec.MxPreference)
|
||||
}
|
||||
if rec.Type == "TXT" {
|
||||
content = rec.GetTargetTXTJoined()
|
||||
}
|
||||
if rec.Type == "DS" {
|
||||
content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest)
|
||||
}
|
||||
if msg == "" {
|
||||
msg = fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL, prio, content)
|
||||
}
|
||||
if rec.Metadata[metaProxy] == "on" {
|
||||
msg = msg + fmt.Sprintf("\nACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField())
|
||||
}
|
||||
arr := []*models.Correction{{
|
||||
Msg: msg,
|
||||
F: func() error {
|
||||
cf := cloudflare.DNSRecord{
|
||||
Name: rec.GetLabel(),
|
||||
Type: rec.Type,
|
||||
TTL: int(rec.TTL),
|
||||
Content: content,
|
||||
Priority: &rec.MxPreference,
|
||||
}
|
||||
if rec.Type == "SRV" {
|
||||
cf.Data = cfSrvData(rec)
|
||||
cf.Name = rec.GetLabelFQDN()
|
||||
} else if rec.Type == "CAA" {
|
||||
cf.Data = cfCaaData(rec)
|
||||
cf.Name = rec.GetLabelFQDN()
|
||||
cf.Content = ""
|
||||
} else if rec.Type == "TLSA" {
|
||||
cf.Data = cfTlsaData(rec)
|
||||
cf.Name = rec.GetLabelFQDN()
|
||||
} else if rec.Type == "SSHFP" {
|
||||
cf.Data = cfSshfpData(rec)
|
||||
cf.Name = rec.GetLabelFQDN()
|
||||
} else if rec.Type == "DS" {
|
||||
cf.Data = cfDSData(rec)
|
||||
}
|
||||
resp, err := c.cfClient.CreateDNSRecord(context.Background(), domainID, cf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Records are created with the proxy off. If proxy should be
|
||||
// enabled, we do a second API call.
|
||||
resultID := resp.Result.ID
|
||||
if rec.Metadata[metaProxy] == "on" {
|
||||
return c.modifyRecord(domainID, resultID, true, rec)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
return arr
|
||||
}
|
||||
|
||||
func (c *cloudflareProvider) modifyRecord(domainID, recID string, proxied bool, rec *models.RecordConfig) error {
|
||||
if domainID == "" || recID == "" {
|
||||
return fmt.Errorf("cannot modify record if domain or record id are empty")
|
||||
|
Reference in New Issue
Block a user