Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions integrationTest/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,25 @@ func makeTests() []*TestGroup {
ovhdmarc("_dmarc", "v=DMARC1; p=none; rua=mailto:dmarc@example.com")),
),

// CLOUDNS features

testgroup("CLOUDNS geodns tests",
only("CLOUDNS"),
tc("Add record with geodns code", withMeta(a("@", "1.2.3.4"), map[string]string{
"cloudns_geodns_code": "US",
})),
tc("Update record with default geodns code", withMeta(a("@", "1.2.3.4"), map[string]string{
"cloudns_geodns_code": "DEFAULT",
})),
tc("Update record with geodns code", withMeta(a("@", "1.2.3.4"), map[string]string{
"cloudns_geodns_code": "BR",
})),
tc("Delete metadata from record", a("@", "1.2.3.4")),
tc("Update a record with the value DEFAULT after removing the metadata should do nothing", withMeta(a("@", "1.2.3.4"), map[string]string{
"cloudns_geodns_code": "DEFAULT",
})).ExpectNoChanges(),
),

// PORKBUN features

testgroup("PORKBUN_URLFWD tests",
Expand Down
1 change: 1 addition & 0 deletions providers/cloudns/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type domainRecord struct {
Protocol string `json:"protocol"`
TTL string `json:"ttl"`
Status int8 `json:"status"`
GeodnsCode string `json:"geodns-location-code,omitempty"`
CaaFlag string `json:"caa_flag,omitempty"`
CaaTag string `json:"caa_type,omitempty"`
CaaValue string `json:"caa_value,omitempty"`
Expand Down
142 changes: 137 additions & 5 deletions providers/cloudns/cloudnsProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import (
"strconv"
"strings"

"github.com/fatih/color"

"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/providers"
"github.com/miekg/dns/dnsutil"
"golang.org/x/time/rate"
Expand Down Expand Up @@ -148,19 +152,46 @@ func (c *cloudnsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
return nil, 0, err
}

toReport, create, del, modify, actualChangeCount, err := diff.NewCompat(dc).IncrementalDiff(existingRecords)
instructions, actualChangeCount, err := diff2.ByRecord(existingRecords, dc, compareMetadata)
if err != nil {
return nil, 0, err
}

var (
reportMsgs []string
create diff.Changeset
del diff.Changeset
modify diff.Changeset
)
for _, inst := range instructions {
cor := diff.Correlation{}
switch inst.Type {
case diff2.REPORT:
reportMsgs = append(reportMsgs, inst.Msgs...)
case diff2.CREATE:
cor.Desired = inst.New[0]
create = append(create, cor)
case diff2.CHANGE:
cor.Existing = inst.Old[0]
cor.Desired = inst.New[0]
modify = append(modify, cor)
case diff2.DELETE:
cor.Existing = inst.Old[0]
del = append(del, cor)
default:
panic(fmt.Sprintf("unhandled inst.Type %s", inst.Type))
}
}

// Start corrections with the reports
corrections := diff.GenerateMessageCorrections(toReport)
corrections := diff.GenerateMessageCorrections(reportMsgs)
corrections = append(corrections, dnssecFixes...)

// 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),
Msg: fmt.Sprintf("%s%s, ClouDNS ID: %s", m.String(), addMetadataCorrection(m.Existing, m.Desired), id),
F: func() error {
return c.deleteRecord(domainID, id)
},
Expand Down Expand Up @@ -193,7 +224,7 @@ func (c *cloudnsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
}

corr := &models.Correction{
Msg: m.String(),
Msg: fmt.Sprintf("%s%s", m.String(), addMetadataCorrection(m.Existing, m.Desired)),
F: func() error {
return c.createRecord(domainID, req)
},
Expand Down Expand Up @@ -228,7 +259,7 @@ func (c *cloudnsProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, exi
}

corr := &models.Correction{
Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id),
Msg: fmt.Sprintf("%s%s, ClouDNS ID: %s: ", m.String(), addMetadataCorrection(m.Existing, m.Desired), id),
F: func() error {
return c.modifyRecord(domainID, id, req)
},
Expand Down Expand Up @@ -311,6 +342,17 @@ func toRc(domain string, r *domainRecord) (*models.RecordConfig, error) {
}
rc.SetLabel(r.Host, domain)

// Add metadata for GeoDNS
// Note: By default, it works only with A, AAAA, CNAME, NAPTR or SRV record
// but you can ask the support for others type of record and they enable it
// for your ClouDNS account.
if r.GeodnsCode != "" {
if rc.Metadata == nil {
rc.Metadata = map[string]string{}
}
rc.Metadata[metaGeodnsCode] = r.GeodnsCode;
}

var err error
switch rtype := r.Type; rtype { // #rtype_variations
case "TXT":
Expand Down Expand Up @@ -391,6 +433,15 @@ func toReq(rc *models.RecordConfig) (requestParams, error) {
req["host"] = ""
}

// Add metadata for GeoDNS
// Note: By default, it works only with A, AAAA, CNAME, NAPTR or SRV record
// but you can ask the support for others type of record and they enable it
// for your ClouDNS account.
geodnsCodeFromMetadataValue, geodnsCodeFromMetadataExist := rc.Metadata[metaGeodnsCode]
if geodnsCodeFromMetadataExist == true {
req["geodns-code"] = geodnsCodeFromMetadataValue;
}

switch rc.Type { // #rtype_variations
case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "ALIAS", "CNAME", "WR", "DNAME":
// Nothing special.
Expand Down Expand Up @@ -445,3 +496,84 @@ func toReq(rc *models.RecordConfig) (requestParams, error) {

return req, nil
}

func addMetadataCorrection(existingRc *models.RecordConfig, desiredRc *models.RecordConfig) string {
if existingRc == nil {
if desiredRc.Metadata != nil {
geodnsCodeFromMetadataValue, geodnsCodeFromMetadataExist := desiredRc.Metadata[metaGeodnsCode]
if geodnsCodeFromMetadataExist == true {
return color.GreenString(fmt.Sprintf(" location=%s", geodnsCodeFromMetadataValue))
}
}

return ""
}
if desiredRc == nil {
if existingRc.Metadata != nil {
geodnsCodeFromMetadataValue, geodnsCodeFromMetadataExist := existingRc.Metadata[metaGeodnsCode]
if geodnsCodeFromMetadataExist == true {
return color.RedString(fmt.Sprintf(" location=%s", geodnsCodeFromMetadataValue))
}
}

return ""
}

if compareMetadata(existingRc) == compareMetadata(desiredRc) {
return ""
}

// By default, the value is "DEFAULT"
// To compare geodns metadata, we replace the value "DEFAULT" with an empty string
// Here, we replace the empty string with "DEFAULT", so the end user can see the
// real value send to the provider.

geodnsCodeFromExistingRcMetadataValue, geodnsCodeFromExistingRcMetadataExist := existingRc.Metadata[metaGeodnsCode]
geodnsCodeFromDesiredRcMetadataValue, geodnsCodeFromDesiredRcMetadataExist := desiredRc.Metadata[metaGeodnsCode]

if geodnsCodeFromExistingRcMetadataExist == false {
geodnsCodeFromExistingRcMetadataValue = "DEFAULT"
}

if geodnsCodeFromDesiredRcMetadataExist == false {
geodnsCodeFromDesiredRcMetadataValue = "DEFAULT"
}

return color.YellowString(", (location=%s) -> (location=%s)", geodnsCodeFromExistingRcMetadataValue, geodnsCodeFromDesiredRcMetadataValue)
}

func compareMetadata(rc *models.RecordConfig) string {
if rc.Metadata == nil || len(rc.Metadata) == 0 {
return ""
}

// To compare GeoDNS metadata, we must temporary remove the value "DEFAULT".
// - DNS record without GeoDNS return ""
// - DNS record with GeoDNS return "DEFAULT" as empty value
val, exist := rc.Metadata[metaGeodnsCode]
if exist == true && val == "DEFAULT" {
delete(rc.Metadata, metaGeodnsCode)
}

// If there is no metadata left, we return an empty string (like the first if)
// Useful for the case where a DNS record has the "DEFAULT" GeoDNS value
// or the account doesn't have GeoDNS feature enabled
if len(rc.Metadata) == 0 {
return ""
}

// json.Marshal always serialize all fields in alphabetical order,
// so the metadata string will be consistent between runs
result, err := json.Marshal(rc.Metadata)
if err != nil {
printer.Warnf("Cannot serialize metadata of record %s", rc)
return ""
}

// Restore the metadata value
if exist == true && val == "DEFAULT" {
rc.Metadata[metaGeodnsCode] = "DEFAULT"
}

return string(result)
}
6 changes: 6 additions & 0 deletions providers/cloudns/metaConstants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cloudns

const (
// Only used for GeoDNS feature
metaGeodnsCode = "cloudns_geodns_code"
)