1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Refactor in preparation to unexport RecordConfig.{Name,NameFQDN,Target} (#337)

* Preparing for the unexport of Name/NameFQDN/Target
* Cleanups
This commit is contained in:
Tom Limoncelli
2018-03-19 17:18:58 -04:00
committed by GitHub
parent cd58d26545
commit a7eba97ada
37 changed files with 298 additions and 270 deletions

View File

@ -107,9 +107,11 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
dom, _ := dc.Copy() dom, _ := dc.Copy()
for _, r := range tst.Records { for _, r := range tst.Records {
rc := models.RecordConfig(*r) rc := models.RecordConfig(*r)
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, domainName) if strings.Contains(rc.GetTargetField(), "**current-domain**") {
if strings.Contains(rc.Target, "**current-domain**") { rc.SetTarget(strings.Replace(rc.GetTargetField(), "**current-domain**", domainName, 1) + ".")
rc.Target = strings.Replace(rc.Target, "**current-domain**", domainName, 1) + "." }
if strings.Contains(rc.GetLabelFQDN(), "**current-domain**") {
rc.SetLabelFromFQDN(strings.Replace(rc.GetLabelFQDN(), "**current-domain**", domainName, 1), domainName)
} }
dom.Records = append(dom.Records, &rc) dom.Records = append(dom.Records, &rc)
} }
@ -205,6 +207,19 @@ type TestCase struct {
type rec models.RecordConfig type rec models.RecordConfig
func (r *rec) GetLabel() string {
return r.Name
}
func (r *rec) SetLabel(label, domain string) {
r.Name = label
r.NameFQDN = dnsutil.AddOrigin(label, "**current-domain**")
}
func (r *rec) SetTarget(target string) {
r.Target = target
}
func a(name, target string) *rec { func a(name, target string) *rec {
return makeRec(name, target, "A") return makeRec(name, target, "A")
} }
@ -273,24 +288,25 @@ func tlsa(name string, usage, selector, matchingtype uint8, target string) *rec
r.TlsaUsage = usage r.TlsaUsage = usage
r.TlsaSelector = selector r.TlsaSelector = selector
r.TlsaMatchingType = matchingtype r.TlsaMatchingType = matchingtype
r.Target = target
return r return r
} }
func ignore(name string) *rec { func ignore(name string) *rec {
return &rec{ r := &rec{
Name: name,
Type: "IGNORE", Type: "IGNORE",
} }
r.SetLabel(name, "**current-domain**")
return r
} }
func makeRec(name, target, typ string) *rec { func makeRec(name, target, typ string) *rec {
return &rec{ r := &rec{
Name: name,
Type: typ, Type: typ,
Target: target,
TTL: 300, TTL: 300,
} }
r.SetLabel(name, "**current-domain**")
r.SetTarget(target)
return r
} }
func (r *rec) ttl(t uint32) *rec { func (r *rec) ttl(t uint32) *rec {
@ -303,7 +319,7 @@ func tc(desc string, recs ...*rec) *TestCase {
var ignored []string var ignored []string
for _, r := range recs { for _, r := range recs {
if r.Type == "IGNORE" { if r.Type == "IGNORE" {
ignored = append(ignored, r.Name) ignored = append(ignored, r.GetLabel())
} else { } else {
records = append(records, r) records = append(records, r)
} }

View File

@ -26,9 +26,9 @@ func TestRR(t *testing.T) {
experiment := RecordConfig{ experiment := RecordConfig{
Type: "A", Type: "A",
Name: "foo", Name: "foo",
NameFQDN: "foo.example.com",
Target: "1.2.3.4", Target: "1.2.3.4",
TTL: 0, TTL: 0,
NameFQDN: "foo.example.com",
MxPreference: 0, MxPreference: 0,
} }
expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4" expected := "foo.example.com.\t300\tIN\tA\t1.2.3.4"
@ -40,9 +40,9 @@ func TestRR(t *testing.T) {
experiment = RecordConfig{ experiment = RecordConfig{
Type: "CAA", Type: "CAA",
Name: "@", Name: "@",
NameFQDN: "example.com",
Target: "mailto:test@example.com", Target: "mailto:test@example.com",
TTL: 300, TTL: 300,
NameFQDN: "example.com",
CaaTag: "iodef", CaaTag: "iodef",
CaaFlag: 1, CaaFlag: 1,
} }
@ -55,9 +55,9 @@ func TestRR(t *testing.T) {
experiment = RecordConfig{ experiment = RecordConfig{
Type: "TLSA", Type: "TLSA",
Name: "@", Name: "@",
NameFQDN: "_443._tcp.example.com",
Target: "abcdef0123456789", Target: "abcdef0123456789",
TTL: 300, TTL: 300,
NameFQDN: "_443._tcp.example.com",
TlsaUsage: 0, TlsaUsage: 0,
TlsaSelector: 0, TlsaSelector: 0,
TlsaMatchingType: 1, TlsaMatchingType: 1,
@ -74,17 +74,17 @@ func TestDowncase(t *testing.T) {
&RecordConfig{Type: "MX", Name: "lower", Target: "targetmx"}, &RecordConfig{Type: "MX", Name: "lower", Target: "targetmx"},
&RecordConfig{Type: "MX", Name: "UPPER", Target: "TARGETMX"}, &RecordConfig{Type: "MX", Name: "UPPER", Target: "TARGETMX"},
}} }}
Downcase(dc.Records) downcase(dc.Records)
if !dc.HasRecordTypeName("MX", "lower") { if !dc.HasRecordTypeName("MX", "lower") {
t.Errorf("%v: expected (%v) got (%v)\n", dc.Records, false, true) t.Errorf("%v: expected (%v) got (%v)\n", dc.Records, false, true)
} }
if !dc.HasRecordTypeName("MX", "upper") { if !dc.HasRecordTypeName("MX", "upper") {
t.Errorf("%v: expected (%v) got (%v)\n", dc.Records, false, true) t.Errorf("%v: expected (%v) got (%v)\n", dc.Records, false, true)
} }
if dc.Records[0].Target != "targetmx" { if dc.Records[0].GetTargetField() != "targetmx" {
t.Errorf("%v: target0 expected (%v) got (%v)\n", dc.Records, "targetmx", dc.Records[0].Target) t.Errorf("%v: target0 expected (%v) got (%v)\n", dc.Records, "targetmx", dc.Records[0].GetTargetField())
} }
if dc.Records[1].Target != "targetmx" { if dc.Records[1].GetTargetField() != "targetmx" {
t.Errorf("%v: target1 expected (%v) got (%v)\n", dc.Records, "targetmx", dc.Records[1].Target) t.Errorf("%v: target1 expected (%v) got (%v)\n", dc.Records, "targetmx", dc.Records[1].GetTargetField())
} }
} }

View File

@ -49,7 +49,7 @@ func (dc *DomainConfig) Copy() (*DomainConfig, error) {
// HasRecordTypeName returns True if there is a record with this rtype and name. // HasRecordTypeName returns True if there is a record with this rtype and name.
func (dc *DomainConfig) HasRecordTypeName(rtype, name string) bool { func (dc *DomainConfig) HasRecordTypeName(rtype, name string) bool {
for _, r := range dc.Records { for _, r := range dc.Records {
if r.Type == rtype && r.Name == name { if r.Type == rtype && r.GetLabel() == name {
return true return true
} }
} }
@ -73,19 +73,17 @@ func (dc *DomainConfig) Filter(f func(r *RecordConfig) bool) {
// - NameFQDN // - NameFQDN
// - Target (CNAME and MX only) // - Target (CNAME and MX only)
func (dc *DomainConfig) Punycode() error { func (dc *DomainConfig) Punycode() error {
var err error
for _, rec := range dc.Records { for _, rec := range dc.Records {
rec.Name, err = idna.ToASCII(rec.Name) t, err := idna.ToASCII(rec.GetLabelFQDN())
if err != nil {
return err
}
rec.NameFQDN, err = idna.ToASCII(rec.NameFQDN)
if err != nil { if err != nil {
return err return err
} }
rec.SetLabelFromFQDN(t, dc.Name)
switch rec.Type { // #rtype_variations switch rec.Type { // #rtype_variations
case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS": case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS":
rec.Target, err = idna.ToASCII(rec.Target) // These rtypes are hostnames, therefore need to be converted (unlike, for example, an AAAA record)
t, err := idna.ToASCII(rec.GetTargetField())
rec.SetTarget(t)
if err != nil { if err != nil {
return err return err
} }

View File

@ -120,6 +120,14 @@ func (rc *RecordConfig) SetLabel(short, origin string) {
} }
} }
// UnsafeSetLabelNull sets the label to "". Normally the FQDN is denoted by .Name being
// "@" however this can be used to violate that assertion. It should only be used
// on copies of a RecordConfig that is being used for non-standard things like
// Marshalling yaml.
func (rc *RecordConfig) UnsafeSetLabelNull() {
rc.Name = ""
}
// SetLabelFromFQDN sets the .Name/.NameFQDN fields given a FQDN and origin. // SetLabelFromFQDN sets the .Name/.NameFQDN fields given a FQDN and origin.
// fqdn may have a trailing "." but it is not required. // fqdn may have a trailing "." but it is not required.
// origin may not have a trailing dot. // origin may not have a trailing dot.
@ -268,11 +276,11 @@ func (r Records) GroupedByLabel() ([]string, map[string]Records) {
// PostProcessRecords does any post-processing of the downloaded DNS records. // PostProcessRecords does any post-processing of the downloaded DNS records.
func PostProcessRecords(recs []*RecordConfig) { func PostProcessRecords(recs []*RecordConfig) {
Downcase(recs) downcase(recs)
} }
// Downcase converts all labels and targets to lowercase in a list of RecordConfig. // Downcase converts all labels and targets to lowercase in a list of RecordConfig.
func Downcase(recs []*RecordConfig) { func downcase(recs []*RecordConfig) {
for _, r := range recs { for _, r := range recs {
r.Name = strings.ToLower(r.Name) r.Name = strings.ToLower(r.Name)
r.NameFQDN = strings.ToLower(r.NameFQDN) r.NameFQDN = strings.ToLower(r.NameFQDN)

View File

@ -11,7 +11,7 @@ import (
func (rc *RecordConfig) SetTargetCAA(flag uint8, tag string, target string) error { func (rc *RecordConfig) SetTargetCAA(flag uint8, tag string, target string) error {
rc.CaaTag = tag rc.CaaTag = tag
rc.CaaFlag = flag rc.CaaFlag = flag
rc.Target = target rc.SetTarget(target)
if rc.Type == "" { if rc.Type == "" {
rc.Type = "CAA" rc.Type = "CAA"
} }

View File

@ -10,7 +10,7 @@ import (
// SetTargetMX sets the MX fields. // SetTargetMX sets the MX fields.
func (rc *RecordConfig) SetTargetMX(pref uint16, target string) error { func (rc *RecordConfig) SetTargetMX(pref uint16, target string) error {
rc.MxPreference = pref rc.MxPreference = pref
rc.Target = target rc.SetTarget(target)
if rc.Type == "" { if rc.Type == "" {
rc.Type = "MX" rc.Type = "MX"
} }

View File

@ -12,7 +12,7 @@ func (rc *RecordConfig) SetTargetSRV(priority, weight, port uint16, target strin
rc.SrvPriority = priority rc.SrvPriority = priority
rc.SrvWeight = weight rc.SrvWeight = weight
rc.SrvPort = port rc.SrvPort = port
rc.Target = target rc.SetTarget(target)
if rc.Type == "" { if rc.Type == "" {
rc.Type = "SRV" rc.Type = "SRV"
} }

View File

@ -12,7 +12,7 @@ func (rc *RecordConfig) SetTargetTLSA(usage, selector, matchingtype uint8, targe
rc.TlsaUsage = usage rc.TlsaUsage = usage
rc.TlsaSelector = selector rc.TlsaSelector = selector
rc.TlsaMatchingType = matchingtype rc.TlsaMatchingType = matchingtype
rc.Target = target rc.SetTarget(target)
if rc.Type == "" { if rc.Type == "" {
rc.Type = "TLSA" rc.Type = "TLSA"
} }

View File

@ -2,7 +2,7 @@ package models
// SetTargetTXT sets the TXT fields when there is 1 string. // SetTargetTXT sets the TXT fields when there is 1 string.
func (rc *RecordConfig) SetTargetTXT(s string) error { func (rc *RecordConfig) SetTargetTXT(s string) error {
rc.Target = s rc.SetTarget(s)
rc.TxtStrings = []string{s} rc.TxtStrings = []string{s}
if rc.Type == "" { if rc.Type == "" {
rc.Type = "TXT" rc.Type = "TXT"
@ -15,7 +15,7 @@ func (rc *RecordConfig) SetTargetTXT(s string) error {
// SetTargetTXTs sets the TXT fields when there are many strings. // SetTargetTXTs sets the TXT fields when there are many strings.
func (rc *RecordConfig) SetTargetTXTs(s []string) error { func (rc *RecordConfig) SetTargetTXTs(s []string) error {
rc.Target = s[0] rc.SetTarget(s[0])
rc.TxtStrings = s rc.TxtStrings = s
if rc.Type == "" { if rc.Type == "" {
rc.Type = "TXT" rc.Type = "TXT"

View File

@ -109,11 +109,11 @@ func (rc *RecordConfig) SetTarget(target string) error {
// SetTargetIP sets the target to an IP, verifying this is an appropriate rtype. // SetTargetIP sets the target to an IP, verifying this is an appropriate rtype.
func (rc *RecordConfig) SetTargetIP(ip net.IP) error { func (rc *RecordConfig) SetTargetIP(ip net.IP) error {
// TODO(tlim): Verify the rtype is appropriate for an IP. // TODO(tlim): Verify the rtype is appropriate for an IP.
rc.Target = ip.String() rc.SetTarget(ip.String())
return nil return nil
} }
// // SetTargetFQDN sets the target to an IP, verifying this is an appropriate rtype. // // SetTargetFQDN sets the target to a string, verifying this is an appropriate rtype.
// func (rc *RecordConfig) SetTargetFQDN(target string) error { // func (rc *RecordConfig) SetTargetFQDN(target string) error {
// // TODO(tlim): Verify the rtype is appropriate for an hostname. // // TODO(tlim): Verify the rtype is appropriate for an hostname.
// rc.Target = target // rc.Target = target

View File

@ -8,7 +8,6 @@ import (
"strconv" "strconv"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/miekg/dns/dnsutil"
) )
// DetermineNameservers will find all nameservers we should use for a domain. It follows the following rules: // DetermineNameservers will find all nameservers we should use for a domain. It follows the following rules:
@ -52,15 +51,16 @@ func AddNSRecords(dc *models.DomainConfig) {
for _, ns := range dc.Nameservers { for _, ns := range dc.Nameservers {
rc := &models.RecordConfig{ rc := &models.RecordConfig{
Type: "NS", Type: "NS",
Name: "@",
Target: ns.Name,
Metadata: map[string]string{}, Metadata: map[string]string{},
TTL: ttl, TTL: ttl,
} }
if !strings.HasSuffix(rc.Target, ".") { rc.SetLabel("@", dc.Name)
rc.Target += "." t := ns.Name
if !strings.HasSuffix(t, ".") {
rc.SetTarget(t + ".")
} }
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, dc.Name) rc.SetTarget(t)
dc.Records = append(dc.Records, rc) dc.Records = append(dc.Records, rc)
} }
} }

View File

@ -3,7 +3,6 @@ package normalize
import ( import (
"strings" "strings"
"github.com/miekg/dns/dnsutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
@ -27,13 +26,13 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
return []error{err} return []error{err}
} }
} }
rec, err = spflib.Parse(txt.Target, cache) rec, err = spflib.Parse(txt.GetTargetField(), cache)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
continue continue
} }
} }
if flatten, ok := txt.Metadata["flatten"]; ok && strings.HasPrefix(txt.Target, "v=spf1") { if flatten, ok := txt.Metadata["flatten"]; ok && strings.HasPrefix(txt.GetTargetField(), "v=spf1") {
rec = rec.Flatten(flatten) rec = rec.Flatten(flatten)
err = txt.SetTargetTXT(rec.TXT()) err = txt.SetTargetTXT(rec.TXT())
if err != nil { if err != nil {
@ -44,7 +43,7 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
// now split if needed // now split if needed
if split, ok := txt.Metadata["split"]; ok { if split, ok := txt.Metadata["split"]; ok {
if !strings.Contains(split, "%d") { if !strings.Contains(split, "%d") {
errs = append(errs, Warning{errors.Errorf("Split format `%s` in `%s` is not proper format (should have %%d in it)", split, txt.NameFQDN)}) errs = append(errs, Warning{errors.Errorf("Split format `%s` in `%s` is not proper format (should have %%d in it)", split, txt.GetLabelFQDN())})
continue continue
} }
recs := rec.TXTSplit(split + "." + domain.Name) recs := rec.TXTSplit(split + "." + domain.Name)
@ -54,8 +53,7 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
} else { } else {
cp, _ := txt.Copy() cp, _ := txt.Copy()
cp.SetTargetTXT(v) cp.SetTargetTXT(v)
cp.NameFQDN = k cp.SetLabelFromFQDN(k, domain.Name)
cp.Name = dnsutil.TrimDomainName(k, domain.Name)
domain.Records = append(domain.Records, cp) domain.Records = append(domain.Records, cp)
} }
} }

View File

@ -6,6 +6,12 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
) )
func makeRC(label, domain, target string, rc models.RecordConfig) *models.RecordConfig {
rc.SetLabel(label, domain)
rc.SetTarget(target)
return &rc
}
func TestImportTransform(t *testing.T) { func TestImportTransform(t *testing.T) {
const transformDouble = "0.0.0.0~1.1.1.1~~9.0.0.0,10.0.0.0" const transformDouble = "0.0.0.0~1.1.1.1~~9.0.0.0,10.0.0.0"
@ -13,15 +19,15 @@ func TestImportTransform(t *testing.T) {
src := &models.DomainConfig{ src := &models.DomainConfig{
Name: "stackexchange.com", Name: "stackexchange.com",
Records: []*models.RecordConfig{ Records: []*models.RecordConfig{
{Type: "A", Name: "*", NameFQDN: "*.stackexchange.com", Target: "0.0.2.2"}, makeRC("*", "stackexchange.com", "0.0.2.2", models.RecordConfig{Type: "A"}),
{Type: "A", Name: "www", NameFQDN: "www.stackexchange.com", Target: "0.0.1.1"}, makeRC("www", "stackexchange.com", "0.0.1.1", models.RecordConfig{Type: "A"}),
}, },
} }
dst := &models.DomainConfig{ dst := &models.DomainConfig{
Name: "internal", Name: "internal",
Records: []*models.RecordConfig{ Records: []*models.RecordConfig{
{Type: "A", Name: "*.stackexchange.com", NameFQDN: "*.stackexchange.com.internal", Target: "0.0.3.3", Metadata: map[string]string{"transform_table": transformSingle}}, makeRC("*.stackexchange.com", "*.stackexchange.com.internal", "0.0.3.3", models.RecordConfig{Type: "A", Metadata: map[string]string{"transform_table": transformSingle}}),
{Type: "IMPORT_TRANSFORM", Name: "@", NameFQDN: "internal", Target: "stackexchange.com", Metadata: map[string]string{"transform_table": transformDouble}}, makeRC("@", "internal", "stackexchange.com", models.RecordConfig{Type: "IMPORT_TRANSFORM", Metadata: map[string]string{"transform_table": transformDouble}}),
}, },
} }
cfg := &models.DNSConfig{ cfg := &models.DNSConfig{

View File

@ -66,7 +66,7 @@ func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []strin
if !ok { if !ok {
cType := providers.GetCustomRecordType(rec.Type) cType := providers.GetCustomRecordType(rec.Type)
if cType == nil { if cType == nil {
return errors.Errorf("Unsupported record type (%v) domain=%v name=%v", rec.Type, domain, rec.Name) return errors.Errorf("Unsupported record type (%v) domain=%v name=%v", rec.Type, domain, rec.GetLabel())
} }
for _, providerType := range pTypes { for _, providerType := range pTypes {
if providerType != cType.Provider { if providerType != cType.Provider {
@ -125,11 +125,11 @@ func checkLabel(label string, rType string, domain string, meta map[string]strin
// checkTargets returns true if rec.Target is valid for the rec.Type. // checkTargets returns true if rec.Target is valid for the rec.Type.
func checkTargets(rec *models.RecordConfig, domain string) (errs []error) { func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
label := rec.Name label := rec.GetLabel()
target := rec.Target target := rec.GetTargetField()
check := func(e error) { check := func(e error) {
if e != nil { if e != nil {
err := errors.Errorf("In %s %s.%s: %s", rec.Type, rec.Name, domain, e.Error()) err := errors.Errorf("In %s %s.%s: %s", rec.Type, rec.GetLabel(), domain, e.Error())
if _, ok := e.(Warning); ok { if _, ok := e.(Warning); ok {
err = Warning{err} err = Warning{err}
} }
@ -166,7 +166,7 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
return return
} }
errs = append(errs, errors.Errorf("checkTargets: Unimplemented record type (%v) domain=%v name=%v", errs = append(errs, errors.Errorf("checkTargets: Unimplemented record type (%v) domain=%v name=%v",
rec.Type, domain, rec.Name)) rec.Type, domain, rec.GetLabel()))
} }
return return
} }
@ -189,13 +189,13 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra
// 4. For As, change the target as described the transforms. // 4. For As, change the target as described the transforms.
for _, rec := range srcDomain.Records { for _, rec := range srcDomain.Records {
if dstDomain.HasRecordTypeName(rec.Type, rec.NameFQDN) { if dstDomain.HasRecordTypeName(rec.Type, rec.GetLabelFQDN()) {
continue continue
} }
newRec := func() *models.RecordConfig { newRec := func() *models.RecordConfig {
rec2, _ := rec.Copy() rec2, _ := rec.Copy()
rec2.Name = rec2.NameFQDN newlabel := rec2.GetLabelFQDN()
rec2.NameFQDN = dnsutil.AddOrigin(rec2.Name, dstDomain.Name) rec2.SetLabelFromFQDN(newlabel, dstDomain.Name)
if ttl != 0 { if ttl != 0 {
rec2.TTL = ttl rec2.TTL = ttl
} }
@ -203,25 +203,25 @@ func importTransform(srcDomain, dstDomain *models.DomainConfig, transforms []tra
} }
switch rec.Type { // #rtype_variations switch rec.Type { // #rtype_variations
case "A": case "A":
trs, err := transform.TransformIPToList(net.ParseIP(rec.Target), transforms) trs, err := transform.TransformIPToList(net.ParseIP(rec.GetTargetField()), transforms)
if err != nil { if err != nil {
return errors.Errorf("import_transform: TransformIP(%v, %v) returned err=%s", rec.Target, transforms, err) return errors.Errorf("import_transform: TransformIP(%v, %v) returned err=%s", rec.GetTargetField(), transforms, err)
} }
for _, tr := range trs { for _, tr := range trs {
r := newRec() r := newRec()
r.Target = tr.String() r.SetTarget(tr.String())
dstDomain.Records = append(dstDomain.Records, r) dstDomain.Records = append(dstDomain.Records, r)
} }
case "CNAME": case "CNAME":
r := newRec() r := newRec()
r.Target = transformCNAME(r.Target, srcDomain.Name, dstDomain.Name) r.SetTarget(transformCNAME(r.GetTargetField(), srcDomain.Name, dstDomain.Name))
dstDomain.Records = append(dstDomain.Records, r) dstDomain.Records = append(dstDomain.Records, r)
case "MX", "NS", "SRV", "TXT", "CAA", "TLSA": case "MX", "NS", "SRV", "TXT", "CAA", "TLSA":
// Not imported. // Not imported.
continue continue
default: default:
return errors.Errorf("import_transform: Unimplemented record type %v (%v)", return errors.Errorf("import_transform: Unimplemented record type %v (%v)",
rec.Type, rec.Name) rec.Type, rec.GetLabel())
} }
} }
return nil return nil
@ -276,7 +276,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
if err := validateRecordTypes(rec, domain.Name, pTypes); err != nil { if err := validateRecordTypes(rec, domain.Name, pTypes); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
if err := checkLabel(rec.Name, rec.Type, domain.Name, rec.Metadata); err != nil { if err := checkLabel(rec.GetLabel(), rec.Type, domain.Name, rec.Metadata); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
if errs2 := checkTargets(rec, domain.Name); errs2 != nil { if errs2 := checkTargets(rec, domain.Name); errs2 != nil {
@ -285,14 +285,16 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
// Canonicalize Targets. // Canonicalize Targets.
if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NS" { if rec.Type == "CNAME" || rec.Type == "MX" || rec.Type == "NS" {
rec.Target = dnsutil.AddOrigin(rec.Target, domain.Name+".") rec.SetTarget(dnsutil.AddOrigin(rec.GetTargetField(), domain.Name+"."))
} else if rec.Type == "A" || rec.Type == "AAAA" { } else if rec.Type == "A" || rec.Type == "AAAA" {
rec.Target = net.ParseIP(rec.Target).String() rec.SetTarget(net.ParseIP(rec.GetTargetField()).String())
} else if rec.Type == "PTR" { } else if rec.Type == "PTR" {
var err error var err error
if rec.Name, err = transform.PtrNameMagic(rec.Name, domain.Name); err != nil { var name string
if name, err = transform.PtrNameMagic(rec.GetLabel(), domain.Name); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
rec.SetLabel(name, domain.Name)
} else if rec.Type == "CAA" { } else if rec.Type == "CAA" {
if rec.CaaTag != "issue" && rec.CaaTag != "issuewild" && rec.CaaTag != "iodef" { if rec.CaaTag != "issue" && rec.CaaTag != "issuewild" && rec.CaaTag != "iodef" {
errs = append(errs, errors.Errorf("CAA tag %s is invalid", rec.CaaTag)) errs = append(errs, errors.Errorf("CAA tag %s is invalid", rec.CaaTag))
@ -300,26 +302,26 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
} else if rec.Type == "TLSA" { } else if rec.Type == "TLSA" {
if rec.TlsaUsage < 0 || rec.TlsaUsage > 3 { if rec.TlsaUsage < 0 || rec.TlsaUsage > 3 {
errs = append(errs, errors.Errorf("TLSA Usage %d is invalid in record %s (domain %s)", errs = append(errs, errors.Errorf("TLSA Usage %d is invalid in record %s (domain %s)",
rec.TlsaUsage, rec.Name, domain.Name)) rec.TlsaUsage, rec.GetLabel(), domain.Name))
} }
if rec.TlsaSelector < 0 || rec.TlsaSelector > 1 { if rec.TlsaSelector < 0 || rec.TlsaSelector > 1 {
errs = append(errs, errors.Errorf("TLSA Selector %d is invalid in record %s (domain %s)", errs = append(errs, errors.Errorf("TLSA Selector %d is invalid in record %s (domain %s)",
rec.TlsaSelector, rec.Name, domain.Name)) rec.TlsaSelector, rec.GetLabel(), domain.Name))
} }
if rec.TlsaMatchingType < 0 || rec.TlsaMatchingType > 2 { if rec.TlsaMatchingType < 0 || rec.TlsaMatchingType > 2 {
errs = append(errs, errors.Errorf("TLSA MatchingType %d is invalid in record %s (domain %s)", errs = append(errs, errors.Errorf("TLSA MatchingType %d is invalid in record %s (domain %s)",
rec.TlsaMatchingType, rec.Name, domain.Name)) rec.TlsaMatchingType, rec.GetLabel(), domain.Name))
} }
} else if rec.Type == "TXT" && len(txtMultiDissenters) != 0 && len(rec.TxtStrings) > 1 { } else if rec.Type == "TXT" && len(txtMultiDissenters) != 0 && len(rec.TxtStrings) > 1 {
// There are providers that don't support TXTMulti yet there is // There are providers that don't support TXTMulti yet there is
// a TXT record with multiple strings: // a TXT record with multiple strings:
errs = append(errs, errs = append(errs,
errors.Errorf("TXT records with multiple strings (label %v domain: %v) not supported by %s", errors.Errorf("TXT records with multiple strings (label %v domain: %v) not supported by %s",
rec.Name, domain.Name, strings.Join(txtMultiDissenters, ","))) rec.GetLabel(), domain.Name, strings.Join(txtMultiDissenters, ",")))
} }
// Populate FQDN: // Populate FQDN:
rec.NameFQDN = dnsutil.AddOrigin(rec.Name, domain.Name) //rec.NameFQDN = dnsutil.AddOrigin(rec.GetLabel(), domain.Name)
} }
} }
@ -337,7 +339,7 @@ func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
errs = append(errs, err) errs = append(errs, err)
continue continue
} }
err = importTransform(config.FindDomain(rec.Target), domain, table, rec.TTL) err = importTransform(config.FindDomain(rec.GetTargetField()), domain, table, rec.TTL)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
@ -375,15 +377,15 @@ func checkCNAMEs(dc *models.DomainConfig) (errs []error) {
cnames := map[string]bool{} cnames := map[string]bool{}
for _, r := range dc.Records { for _, r := range dc.Records {
if r.Type == "CNAME" { if r.Type == "CNAME" {
if cnames[r.Name] { if cnames[r.GetLabel()] {
errs = append(errs, errors.Errorf("Cannot have multiple CNAMEs with same name: %s", r.NameFQDN)) errs = append(errs, errors.Errorf("Cannot have multiple CNAMEs with same name: %s", r.GetLabelFQDN()))
} }
cnames[r.Name] = true cnames[r.GetLabel()] = true
} }
} }
for _, r := range dc.Records { for _, r := range dc.Records {
if cnames[r.Name] && r.Type != "CNAME" { if cnames[r.GetLabel()] && r.Type != "CNAME" {
errs = append(errs, errors.Errorf("Cannot have CNAME and %s record with same name: %s", r.Type, r.NameFQDN)) errs = append(errs, errors.Errorf("Cannot have CNAME and %s record with same name: %s", r.Type, r.GetLabelFQDN()))
} }
} }
return return
@ -433,21 +435,21 @@ func applyRecordTransforms(domain *models.DomainConfig) error {
if err != nil { if err != nil {
return err return err
} }
ip := net.ParseIP(rec.Target) // ip already validated above ip := net.ParseIP(rec.GetTargetField()) // ip already validated above
newIPs, err := transform.TransformIPToList(net.ParseIP(rec.Target), table) newIPs, err := transform.TransformIPToList(net.ParseIP(rec.GetTargetField()), table)
if err != nil { if err != nil {
return err return err
} }
for i, newIP := range newIPs { for i, newIP := range newIPs {
if i == 0 && !newIP.Equal(ip) { if i == 0 && !newIP.Equal(ip) {
rec.Target = newIP.String() // replace target of first record if different rec.SetTarget(newIP.String()) // replace target of first record if different
} else if i > 0 { } else if i > 0 {
// any additional ips need identical records with the alternate ip added to the domain // any additional ips need identical records with the alternate ip added to the domain
copy, err := rec.Copy() copy, err := rec.Copy()
if err != nil { if err != nil {
return err return err
} }
copy.Target = newIP.String() copy.SetTarget(newIP.String())
domain.Records = append(domain.Records, copy) domain.Records = append(domain.Records, copy)
} }
} }

View File

@ -113,12 +113,14 @@ func Test_transform_cname(t *testing.T) {
func TestNSAtRoot(t *testing.T) { func TestNSAtRoot(t *testing.T) {
// do not allow ns records for @ // do not allow ns records for @
rec := &models.RecordConfig{Name: "test", Type: "NS", Target: "ns1.name.com."} rec := &models.RecordConfig{Type: "NS"}
rec.SetLabel("test", "foo.com")
rec.SetTarget("ns1.name.com.")
errs := checkTargets(rec, "foo.com") errs := checkTargets(rec, "foo.com")
if len(errs) > 0 { if len(errs) > 0 {
t.Error("Expect no error with ns record on subdomain") t.Error("Expect no error with ns record on subdomain")
} }
rec.Name = "@" rec.SetLabel("@", "foo.com")
errs = checkTargets(rec, "foo.com") errs = checkTargets(rec, "foo.com")
if len(errs) != 1 { if len(errs) != 1 {
t.Error("Expect error with ns record on @") t.Error("Expect error with ns record on @")
@ -138,7 +140,7 @@ func TestTransforms(t *testing.T) {
for i, test := range tests { for i, test := range tests {
dc := &models.DomainConfig{ dc := &models.DomainConfig{
Records: []*models.RecordConfig{ Records: []*models.RecordConfig{
{Type: "A", Target: test.givenIP, Metadata: map[string]string{"transform": transform}}, makeRC("f", "example.tld", test.givenIP, models.RecordConfig{Type: "A", Metadata: map[string]string{"transform": transform}}),
}, },
} }
err := applyRecordTransforms(dc) err := applyRecordTransforms(dc)
@ -151,8 +153,8 @@ func TestTransforms(t *testing.T) {
continue continue
} }
for r, rec := range dc.Records { for r, rec := range dc.Records {
if rec.Target != test.expectedRecords[r] { if rec.GetTargetField() != test.expectedRecords[r] {
t.Errorf("test %d at index %d: records don't match. Expect %s but found %s.", i, r, test.expectedRecords[r], rec.Target) t.Errorf("test %d at index %d: records don't match. Expect %s but found %s.", i, r, test.expectedRecords[r], rec.GetTargetField())
continue continue
} }
} }
@ -160,7 +162,9 @@ func TestTransforms(t *testing.T) {
} }
func TestCNAMEMutex(t *testing.T) { func TestCNAMEMutex(t *testing.T) {
var recA = &models.RecordConfig{Type: "CNAME", Name: "foo", NameFQDN: "foo.example.com", Target: "example.com."} var recA = &models.RecordConfig{Type: "CNAME"}
recA.SetLabel("foo", "foo.example.com")
recA.SetTarget("example.com.")
tests := []struct { tests := []struct {
rType string rType string
name string name string
@ -173,7 +177,9 @@ func TestCNAMEMutex(t *testing.T) {
} }
for _, tst := range tests { for _, tst := range tests {
t.Run(fmt.Sprintf("%s %s", tst.rType, tst.name), func(t *testing.T) { t.Run(fmt.Sprintf("%s %s", tst.rType, tst.name), func(t *testing.T) {
var recB = &models.RecordConfig{Type: tst.rType, Name: tst.name, NameFQDN: tst.name + ".example.com", Target: "example2.com."} var recB = &models.RecordConfig{Type: tst.rType}
recB.SetLabel(tst.name, "example.com")
recB.SetTarget("example2.com.")
dc := &models.DomainConfig{ dc := &models.DomainConfig{
Name: "example.com", Name: "example.com",
Records: []*models.RecordConfig{recA, recB}, Records: []*models.RecordConfig{recA, recB},
@ -196,7 +202,7 @@ func TestCAAValidation(t *testing.T) {
Name: "example.com", Name: "example.com",
RegistrarName: "BIND", RegistrarName: "BIND",
Records: []*models.RecordConfig{ Records: []*models.RecordConfig{
{Name: "@", NameFQDN: "example.com", Type: "CAA", CaaTag: "invalid", Target: "example.com"}, makeRC("@", "example.com", "example.com", models.RecordConfig{Type: "CAA", CaaTag: "invalid"}),
}, },
}, },
}, },
@ -214,7 +220,8 @@ func TestTLSAValidation(t *testing.T) {
Name: "_443._tcp.example.com", Name: "_443._tcp.example.com",
RegistrarName: "BIND", RegistrarName: "BIND",
Records: []*models.RecordConfig{ Records: []*models.RecordConfig{
{Name: "_443._tcp", NameFQDN: "_443._tcp._443._tcp.example.com", Type: "TLSA", TlsaUsage: 4, TlsaSelector: 1, TlsaMatchingType: 1, Target: "abcdef0"}, makeRC("_443._tcp", "_443._tcp.example.com", "abcdef0", models.RecordConfig{
Type: "TLSA", TlsaUsage: 4, TlsaSelector: 1, TlsaMatchingType: 1}),
}, },
}, },
}, },

View File

@ -11,7 +11,6 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
"github.com/TomOnTime/utfutil" "github.com/TomOnTime/utfutil"
"github.com/miekg/dns/dnsutil"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -35,7 +34,7 @@ func (c *adProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Co
dc.Filter(func(r *models.RecordConfig) bool { dc.Filter(func(r *models.RecordConfig) bool {
if r.Type != "A" && r.Type != "CNAME" { if r.Type != "A" && r.Type != "CNAME" {
log.Printf("WARNING: Active Directory only manages A and CNAME records. Won't consider %s %s", r.Type, r.NameFQDN) log.Printf("WARNING: Active Directory only manages A and CNAME records. Won't consider %s %s", r.Type, r.GetLabelFQDN())
return false return false
} }
return true return true
@ -162,18 +161,16 @@ func (c *adProvider) getExistingRecords(domainname string) ([]*models.RecordConf
} }
func (r *RecordConfigJson) unpackRecord(origin string) *models.RecordConfig { func (r *RecordConfigJson) unpackRecord(origin string) *models.RecordConfig {
rc := models.RecordConfig{} rc := models.RecordConfig{
Type: r.Type,
rc.Name = strings.ToLower(r.Name) TTL: r.TTL,
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, origin) }
rc.Type = r.Type rc.SetLabel(r.Name, origin)
rc.TTL = r.TTL switch rtype := rc.Type; rtype { // #rtype_variations
case "A", "AAAA":
switch rc.Type { // #rtype_variations rc.SetTarget(r.Data)
case "A":
rc.Target = r.Data
case "CNAME": case "CNAME":
rc.Target = strings.ToLower(r.Data) rc.SetTarget(strings.ToLower(r.Data))
case "NS", "SOA": case "NS", "SOA":
return nil return nil
default: default:
@ -197,12 +194,12 @@ Get-DnsServerResourceRecord -ComputerName REPLACE_WITH_COMPUTER_NAME -ZoneName $
// generatePowerShellCreate generates PowerShell commands to ADD a record. // generatePowerShellCreate generates PowerShell commands to ADD a record.
func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.RecordConfig) string { func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.RecordConfig) string {
content := rec.Target content := rec.GetTargetField()
text := "\r\n" // Skip a line. text := "\r\n" // Skip a line.
text += fmt.Sprintf("Add-DnsServerResourceRecord%s", rec.Type) text += fmt.Sprintf("Add-DnsServerResourceRecord%s", rec.Type)
text += fmt.Sprintf(` -ComputerName "%s"`, c.adServer) text += fmt.Sprintf(` -ComputerName "%s"`, c.adServer)
text += fmt.Sprintf(` -ZoneName "%s"`, domainname) text += fmt.Sprintf(` -ZoneName "%s"`, domainname)
text += fmt.Sprintf(` -Name "%s"`, rec.Name) text += fmt.Sprintf(` -Name "%s"`, rec.GetLabel())
text += fmt.Sprintf(` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL) text += fmt.Sprintf(` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL)
switch rec.Type { // #rtype_variations switch rec.Type { // #rtype_variations
case "CNAME": case "CNAME":
@ -210,9 +207,10 @@ func (c *adProvider) generatePowerShellCreate(domainname string, rec *models.Rec
case "A": case "A":
text += fmt.Sprintf(` -IPv4Address "%s"`, content) text += fmt.Sprintf(` -IPv4Address "%s"`, content)
case "NS": case "NS":
text = fmt.Sprintf("\r\n"+`echo "Skipping NS update (%v %v)"`+"\r\n", rec.Name, rec.Target) text = fmt.Sprintf("\r\n"+`echo "Skipping NS update (%v %v)"`+"\r\n", rec.GetLabel(), rec.GetTargetDebug())
default: default:
panic(errors.Errorf("generatePowerShellCreate() does not yet handle recType=%s recName=%#v content=%#v)", rec.Type, rec.Name, content)) panic(errors.Errorf("generatePowerShellCreate() does not yet handle recType=%s recName=%#v content=%#v)",
rec.Type, rec.GetLabel(), content))
// We panic so that we quickly find any switch statements // We panic so that we quickly find any switch statements
// that have not been updated for a new RR type. // that have not been updated for a new RR type.
} }
@ -286,7 +284,7 @@ func (c *adProvider) generatePowerShellDelete(domainname, recName, recType, cont
func (c *adProvider) createRec(domainname string, rec *models.RecordConfig) []*models.Correction { func (c *adProvider) createRec(domainname string, rec *models.RecordConfig) []*models.Correction {
arr := []*models.Correction{ arr := []*models.Correction{
{ {
Msg: fmt.Sprintf("CREATE record: %s %s ttl(%d) %s", rec.Name, rec.Type, rec.TTL, rec.Target), Msg: fmt.Sprintf("CREATE record: %s %s ttl(%d) %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField()),
F: func() error { F: func() error {
return c.powerShellDoCommand(c.generatePowerShellCreate(domainname, rec), true) return c.powerShellDoCommand(c.generatePowerShellCreate(domainname, rec), true)
}}, }},
@ -299,16 +297,16 @@ func (c *adProvider) modifyRec(domainname string, m diff.Correlation) *models.Co
return &models.Correction{ return &models.Correction{
Msg: m.String(), Msg: m.String(),
F: func() error { F: func() error {
return c.powerShellDoCommand(c.generatePowerShellModify(domainname, rec.Name, rec.Type, old.Target, rec.Target, old.TTL, rec.TTL), true) return c.powerShellDoCommand(c.generatePowerShellModify(domainname, rec.GetLabel(), rec.Type, old.GetTargetField(), rec.GetTargetField(), old.TTL, rec.TTL), true)
}, },
} }
} }
func (c *adProvider) deleteRec(domainname string, rec *models.RecordConfig) *models.Correction { func (c *adProvider) deleteRec(domainname string, rec *models.RecordConfig) *models.Correction {
return &models.Correction{ return &models.Correction{
Msg: fmt.Sprintf("DELETE record: %s %s ttl(%d) %s", rec.Name, rec.Type, rec.TTL, rec.Target), Msg: fmt.Sprintf("DELETE record: %s %s ttl(%d) %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField()),
F: func() error { F: func() error {
return c.powerShellDoCommand(c.generatePowerShellDelete(domainname, rec.Name, rec.Type, rec.Target), true) return c.powerShellDoCommand(c.generatePowerShellDelete(domainname, rec.GetLabel(), rec.Type, rec.GetTargetField()), true)
}, },
} }
} }

View File

@ -7,6 +7,12 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
) )
func makeRC(label, domain, target string, rc models.RecordConfig) *models.RecordConfig {
rc.SetLabel(label, domain)
rc.SetTarget(target)
return &rc
}
func TestGetExistingRecords(t *testing.T) { func TestGetExistingRecords(t *testing.T) {
cf := &adProvider{} cf := &adProvider{}
@ -17,11 +23,11 @@ func TestGetExistingRecords(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expected := []*models.RecordConfig{ expected := []*models.RecordConfig{
{Name: "@", NameFQDN: "test2", Type: "A", TTL: 600, Target: "10.166.2.11"}, makeRC("@", "test2", "10.166.2.11", models.RecordConfig{Type: "A", TTL: 600}),
//{Name: "_msdcs", NameFQDN: "_msdcs.test2", Type: "NS", TTL: 300, Target: "other_record"}, // Will be filtered. //makeRC("_msdcs", "test2", "other_record", models.RecordConfig{Type: "NS", TTL: 300}), // Will be filtered.
{Name: "co-devsearch02", NameFQDN: "co-devsearch02.test2", Type: "A", TTL: 3600, Target: "10.8.2.64"}, makeRC("co-devsearch02", "test2", "10.8.2.64", models.RecordConfig{Type: "A", TTL: 3600}),
{Name: "co-devservice01", NameFQDN: "co-devservice01.test2", Type: "A", TTL: 1200, Target: "10.8.2.48"}, // Downcased. makeRC("co-devservice01", "test2", "10.8.2.48", models.RecordConfig{Type: "A", TTL: 1200}), // Downcased.
{Name: "yum", NameFQDN: "yum.test2", Type: "A", TTL: 3600, Target: "10.8.0.59"}, makeRC("yum", "test2", "10.8.0.59", models.RecordConfig{Type: "A", TTL: 3600}),
} }
actualS := "" actualS := ""

View File

@ -127,7 +127,7 @@ func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordCo
} }
newSerial = v.Serial newSerial = v.Serial
//if (dnsutil.TrimDomainName(rc.Name, origin+".") == "@") && replaceSerial != 0 { //if (dnsutil.TrimDomainName(rc.Name, origin+".") == "@") && replaceSerial != 0 {
if rc.Name == "@" && replaceSerial != 0 { if rc.GetLabel() == "@" && replaceSerial != 0 {
newSerial = replaceSerial newSerial = replaceSerial
} }
panicInvalid(rc.SetTarget( panicInvalid(rc.SetTarget(

View File

@ -16,14 +16,25 @@ func newDomainConfig() *models.DomainConfig {
} }
} }
func makeRCmeta(meta map[string]string) *models.RecordConfig {
rc := models.RecordConfig{
Type: "A",
Metadata: meta,
}
rc.SetLabel("foo", "example.tld")
rc.SetTarget("1.2.3.4")
return &rc
}
func TestPreprocess_BoolValidation(t *testing.T) { func TestPreprocess_BoolValidation(t *testing.T) {
cf := &CloudflareApi{} cf := &CloudflareApi{}
domain := newDomainConfig() domain := newDomainConfig()
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "on"}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{metaProxy: "on"}))
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "fUll"}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{metaProxy: "fUll"}))
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{}))
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "Off"}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{metaProxy: "Off"}))
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "off"}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{metaProxy: "off"}))
err := cf.preprocessConfig(domain) err := cf.preprocessConfig(domain)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -51,9 +62,9 @@ func TestPreprocess_DefaultProxy(t *testing.T) {
cf := &CloudflareApi{} cf := &CloudflareApi{}
domain := newDomainConfig() domain := newDomainConfig()
domain.Metadata[metaProxyDefault] = "full" domain.Metadata[metaProxyDefault] = "full"
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "on"}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{metaProxy: "on"}))
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{metaProxy: "off"}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{metaProxy: "off"}))
domain.Records = append(domain.Records, &models.RecordConfig{Type: "A", Target: "1.2.3.4", Metadata: map[string]string{}}) domain.Records = append(domain.Records, makeRCmeta(map[string]string{}))
err := cf.preprocessConfig(domain) err := cf.preprocessConfig(domain)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -97,7 +108,8 @@ func TestIpRewriting(t *testing.T) {
NewBases: []net.IP{net.ParseIP("255.255.255.0")}, NewBases: []net.IP{net.ParseIP("255.255.255.0")},
NewIPs: nil}} NewIPs: nil}}
for _, tst := range tests { for _, tst := range tests {
rec := &models.RecordConfig{Type: "A", Target: tst.Given, Metadata: map[string]string{metaProxy: tst.Proxy}} rec := &models.RecordConfig{Type: "A", Metadata: map[string]string{metaProxy: tst.Proxy}}
rec.SetTarget(tst.Given)
domain.Records = append(domain.Records, rec) domain.Records = append(domain.Records, rec)
} }
err := cf.preprocessConfig(domain) err := cf.preprocessConfig(domain)
@ -106,8 +118,8 @@ func TestIpRewriting(t *testing.T) {
} }
for i, tst := range tests { for i, tst := range tests {
rec := domain.Records[i] rec := domain.Records[i]
if rec.Target != tst.Expected { if rec.GetTargetField() != tst.Expected {
t.Fatalf("At index %d, expected target of %s, but found %s.", i, tst.Expected, rec.Target) t.Fatalf("At index %d, expected target of %s, but found %s.", i, tst.Expected, rec.GetTargetField())
} }
if tst.Proxy == "full" && tst.Given != tst.Expected && rec.Metadata[metaOriginalIP] != tst.Given { if tst.Proxy == "full" && tst.Given != tst.Expected && rec.Metadata[metaOriginalIP] != tst.Given {
t.Fatalf("At index %d, expected original_ip to be set", i) t.Fatalf("At index %d, expected original_ip to be set", i)

View File

@ -77,7 +77,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
if d.matchIgnored(e.GetLabel()) { if d.matchIgnored(e.GetLabel()) {
log.Printf("Ignoring record %s %s due to IGNORE", e.GetLabel(), e.Type) log.Printf("Ignoring record %s %s due to IGNORE", e.GetLabel(), e.Type)
} else { } else {
k := key{e.NameFQDN, e.Type} k := key{e.GetLabelFQDN(), e.Type}
existingByNameAndType[k] = append(existingByNameAndType[k], e) existingByNameAndType[k] = append(existingByNameAndType[k], e)
} }
} }
@ -85,7 +85,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
if d.matchIgnored(dr.GetLabel()) { if d.matchIgnored(dr.GetLabel()) {
panic(fmt.Sprintf("Trying to update/add IGNOREd record: %s %s", dr.GetLabel(), dr.Type)) panic(fmt.Sprintf("Trying to update/add IGNOREd record: %s %s", dr.GetLabel(), dr.Type))
} else { } else {
k := key{dr.NameFQDN, dr.Type} k := key{dr.GetLabelFQDN(), dr.Type}
desiredByNameAndType[k] = append(desiredByNameAndType[k], dr) desiredByNameAndType[k] = append(desiredByNameAndType[k], dr)
} }
} }
@ -106,7 +106,7 @@ func (d *differ) IncrementalDiff(existing []*models.RecordConfig) (unchanged, cr
for i := len(existingRecords) - 1; i >= 0; i-- { for i := len(existingRecords) - 1; i >= 0; i-- {
ex := existingRecords[i] ex := existingRecords[i]
for j, de := range desiredRecords { for j, de := range desiredRecords {
if de.Target == ex.Target { if de.GetTargetField() == ex.GetTargetField() {
// they're either identical or should be a modification of each other (ttl or metadata changes) // they're either identical or should be a modification of each other (ttl or metadata changes)
if d.content(de) == d.content(ex) { if d.content(de) == d.content(ex) {
unchanged = append(unchanged, Correlation{d, ex, de}) unchanged = append(unchanged, Correlation{d, ex, de})
@ -197,12 +197,12 @@ func (d *differ) ChangedGroups(existing []*models.RecordConfig) map[models.Recor
func (c Correlation) String() string { func (c Correlation) String() string {
if c.Existing == nil { if c.Existing == nil {
return fmt.Sprintf("CREATE %s %s %s", c.Desired.Type, c.Desired.NameFQDN, c.d.content(c.Desired)) return fmt.Sprintf("CREATE %s %s %s", c.Desired.Type, c.Desired.GetLabelFQDN(), c.d.content(c.Desired))
} }
if c.Desired == nil { if c.Desired == nil {
return fmt.Sprintf("DELETE %s %s %s", c.Existing.Type, c.Existing.NameFQDN, c.d.content(c.Existing)) return fmt.Sprintf("DELETE %s %s %s", c.Existing.Type, c.Existing.GetLabelFQDN(), c.d.content(c.Existing))
} }
return fmt.Sprintf("MODIFY %s %s: (%s) -> (%s)", c.Existing.Type, c.Existing.NameFQDN, c.d.content(c.Existing), c.d.content(c.Desired)) return fmt.Sprintf("MODIFY %s %s: (%s) -> (%s)", c.Existing.Type, c.Existing.GetLabelFQDN(), c.d.content(c.Existing), c.d.content(c.Desired))
} }
func sortedKeys(m map[string]*models.RecordConfig) []string { func sortedKeys(m map[string]*models.RecordConfig) []string {

View File

@ -7,20 +7,19 @@ import (
"testing" "testing"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/miekg/dns/dnsutil"
) )
func myRecord(s string) *models.RecordConfig { func myRecord(s string) *models.RecordConfig {
parts := strings.Split(s, " ") parts := strings.Split(s, " ")
ttl, _ := strconv.ParseUint(parts[2], 10, 32) ttl, _ := strconv.ParseUint(parts[2], 10, 32)
return &models.RecordConfig{ r := &models.RecordConfig{
Name: parts[0],
NameFQDN: dnsutil.AddOrigin(parts[0], "example.com"),
Type: parts[1], Type: parts[1],
TTL: uint32(ttl), TTL: uint32(ttl),
Target: parts[3],
Metadata: map[string]string{}, Metadata: map[string]string{},
} }
r.SetLabel(parts[0], "example.com")
r.SetTarget(parts[3])
return r
} }
func TestAdditionsOnly(t *testing.T) { func TestAdditionsOnly(t *testing.T) {

View File

@ -203,7 +203,6 @@ func toRc(dc *models.DomainConfig, r *godo.DomainRecord) *models.RecordConfig {
t := &models.RecordConfig{ t := &models.RecordConfig{
Type: r.Type, Type: r.Type,
Target: target,
TTL: uint32(r.TTL), TTL: uint32(r.TTL),
MxPreference: uint16(r.Priority), MxPreference: uint16(r.Priority),
SrvPriority: uint16(r.Priority), SrvPriority: uint16(r.Priority),
@ -212,6 +211,7 @@ func toRc(dc *models.DomainConfig, r *godo.DomainRecord) *models.RecordConfig {
Original: r, Original: r,
} }
t.SetLabelFromFQDN(name, dc.Name) t.SetLabelFromFQDN(name, dc.Name)
t.SetTarget(target)
switch rtype := r.Type; rtype { switch rtype := r.Type; rtype {
case "TXT": case "TXT":
t.SetTargetTXTString(target) t.SetTargetTXTString(target)

View File

@ -10,7 +10,6 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
"github.com/miekg/dns/dnsutil"
"github.com/pkg/errors" "github.com/pkg/errors"
dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple" dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple"
@ -281,7 +280,7 @@ func (c *DnsimpleApi) createRecordFunc(rc *models.RecordConfig, domainName strin
return err return err
} }
record := dnsimpleapi.ZoneRecord{ record := dnsimpleapi.ZoneRecord{
Name: dnsutil.TrimDomainName(rc.NameFQDN, domainName), Name: rc.GetLabel(),
Type: rc.Type, Type: rc.Type,
Content: rc.GetTargetCombined(), Content: rc.GetTargetCombined(),
TTL: int(rc.TTL), TTL: int(rc.TTL),
@ -327,7 +326,7 @@ func (c *DnsimpleApi) updateRecordFunc(old *dnsimpleapi.ZoneRecord, rc *models.R
} }
record := dnsimpleapi.ZoneRecord{ record := dnsimpleapi.ZoneRecord{
Name: dnsutil.TrimDomainName(rc.NameFQDN, domainName), Name: rc.GetLabel(),
Type: rc.Type, Type: rc.Type,
Content: rc.GetTargetCombined(), Content: rc.GetTargetCombined(),
TTL: int(rc.TTL), TTL: int(rc.TTL),
@ -374,7 +373,7 @@ func removeOtherNS(dc *models.DomainConfig) {
for _, rec := range dc.Records { for _, rec := range dc.Records {
if rec.Type == "NS" { if rec.Type == "NS" {
// apex NS inside dnsimple are expected. // apex NS inside dnsimple are expected.
if rec.NameFQDN == dc.Name && strings.HasSuffix(rec.GetTargetField(), ".dnsimple.com.") { if rec.GetLabelFQDN() == dc.Name && strings.HasSuffix(rec.GetTargetField(), ".dnsimple.com.") {
continue continue
} }
fmt.Printf("Warning: dnsimple.com does not allow NS records to be modified. %s will not be added.\n", rec.GetTargetField()) fmt.Printf("Warning: dnsimple.com does not allow NS records to be modified. %s will not be added.\n", rec.GetTargetField())

View File

@ -99,11 +99,11 @@ func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
return nil, errors.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL) return nil, errors.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL)
} }
if rec.Type == "TXT" { if rec.Type == "TXT" {
rec.Target = "\"" + rec.Target + "\"" // FIXME(tlim): Should do proper quoting. rec.SetTarget("\"" + rec.GetTargetField() + "\"") // FIXME(tlim): Should do proper quoting.
} }
if rec.Type == "NS" && rec.GetLabel() == "@" { if rec.Type == "NS" && rec.GetLabel() == "@" {
if !strings.HasSuffix(rec.Target, ".gandi.net.") { if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") {
log.Printf("WARNING: Gandi does not support changing apex NS records. %s will not be added.", rec.Target) log.Printf("WARNING: Gandi does not support changing apex NS records. %s will not be added.", rec.GetTargetField())
} }
continue continue
} }

View File

@ -223,31 +223,31 @@ func (c *liveClient) recordsToInfo(records models.Records) (models.Records, []*g
for _, rec := range records { for _, rec := range records {
if rec.TTL < 300 { if rec.TTL < 300 {
log.Printf("WARNING: Gandi does not support ttls < 300. %s will not be set to %d.", rec.NameFQDN, rec.TTL) log.Printf("WARNING: Gandi does not support ttls < 300. %s will not be set to %d.", rec.GetLabelFQDN(), rec.TTL)
rec.TTL = 300 rec.TTL = 300
} }
if rec.TTL > 2592000 { if rec.TTL > 2592000 {
return nil, nil, errors.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL) return nil, nil, errors.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL)
} }
if rec.Type == "NS" && rec.Name == "@" { if rec.Type == "NS" && rec.GetLabel() == "@" {
if !strings.HasSuffix(rec.Target, ".gandi.net.") { if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") {
log.Printf("WARNING: Gandi does not support changing apex NS records. %s will not be added.", rec.Target) log.Printf("WARNING: Gandi does not support changing apex NS records. %s will not be added.", rec.GetTargetField())
} }
continue continue
} }
r, ok := recordSets[rec.Name][rec.Type] r, ok := recordSets[rec.GetLabel()][rec.Type]
if !ok { if !ok {
_, ok := recordSets[rec.Name] _, ok := recordSets[rec.GetLabel()]
if !ok { if !ok {
recordSets[rec.Name] = map[string]*gandiliverecord.Info{} recordSets[rec.GetLabel()] = map[string]*gandiliverecord.Info{}
} }
r = &gandiliverecord.Info{ r = &gandiliverecord.Info{
Type: rec.Type, Type: rec.Type,
Name: rec.Name, Name: rec.GetLabel(),
TTL: int64(rec.TTL), TTL: int64(rec.TTL),
} }
recordInfos = append(recordInfos, r) recordInfos = append(recordInfos, r)
recordSets[rec.Name][rec.Type] = r recordSets[rec.GetLabel()][rec.Type] = r
} else { } else {
if r.TTL != int64(rec.TTL) { if r.TTL != int64(rec.TTL) {
log.Printf( log.Printf(

View File

@ -8,6 +8,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func makeRC(label, domain, target string, rc models.RecordConfig) *models.RecordConfig {
rc.SetLabel(label, domain)
rc.SetTarget(target)
return &rc
}
func TestRecordConfigFromInfo(t *testing.T) { func TestRecordConfigFromInfo(t *testing.T) {
for _, data := range []struct { for _, data := range []struct {
@ -22,20 +27,14 @@ func TestRecordConfigFromInfo(t *testing.T) {
Values: []string{"127.0.0.1", "127.1.0.1"}, Values: []string{"127.0.0.1", "127.1.0.1"},
}, },
[]*models.RecordConfig{ []*models.RecordConfig{
{ makeRC("www", "example.com", "127.0.0.1", models.RecordConfig{
NameFQDN: "www.example.com",
Name: "www",
Type: "A", Type: "A",
Target: "127.0.0.1",
TTL: 500, TTL: 500,
}, }),
{ makeRC("www", "example.com", "127.1.0.1", models.RecordConfig{
NameFQDN: "www.example.com",
Name: "www",
Type: "A", Type: "A",
Target: "127.1.0.1",
TTL: 500, TTL: 500,
}, }),
}, },
}, },
{ {
@ -46,14 +45,11 @@ func TestRecordConfigFromInfo(t *testing.T) {
Values: []string{"\"test 2\"", "\"test message test message test message\""}, Values: []string{"\"test 2\"", "\"test message test message test message\""},
}, },
[]*models.RecordConfig{ []*models.RecordConfig{
{ makeRC("www", "example.com", "test 2", models.RecordConfig{
NameFQDN: "www.example.com",
Name: "www",
Type: "TXT", Type: "TXT",
Target: "test 2",
TxtStrings: []string{"test 2", "test message test message test message"}, TxtStrings: []string{"test 2", "test message test message test message"},
TTL: 500, TTL: 500,
}, }),
}, },
}, },
{ {
@ -65,24 +61,18 @@ func TestRecordConfigFromInfo(t *testing.T) {
Values: []string{"0 issue \"www.certinomis.com\"", "0 issuewild \"buypass.com\""}, Values: []string{"0 issue \"www.certinomis.com\"", "0 issuewild \"buypass.com\""},
}, },
[]*models.RecordConfig{ []*models.RecordConfig{
{ makeRC("www", "example.com", "www.certinomis.com", models.RecordConfig{
NameFQDN: "www.example.com",
Name: "www",
Type: "CAA", Type: "CAA",
Target: "www.certinomis.com",
CaaFlag: 0, CaaFlag: 0,
CaaTag: "issue", CaaTag: "issue",
TTL: 500, TTL: 500,
}, }),
{ makeRC("www", "example.com", "buypass.com", models.RecordConfig{
NameFQDN: "www.example.com",
Name: "www",
Type: "CAA", Type: "CAA",
Target: "buypass.com",
CaaFlag: 0, CaaFlag: 0,
CaaTag: "issuewild", CaaTag: "issuewild",
TTL: 500, TTL: 500,
}, }),
}, },
}, },
{ {
@ -93,16 +83,13 @@ func TestRecordConfigFromInfo(t *testing.T) {
Values: []string{"20 0 5060 backupbox.example.com."}, Values: []string{"20 0 5060 backupbox.example.com."},
}, },
[]*models.RecordConfig{ []*models.RecordConfig{
{ makeRC("test", "example.com", "backupbox.example.com.", models.RecordConfig{
NameFQDN: "test.example.com",
Name: "test",
Type: "SRV", Type: "SRV",
Target: "backupbox.example.com.",
SrvPriority: 20, SrvPriority: 20,
SrvWeight: 0, SrvWeight: 0,
SrvPort: 5060, SrvPort: 5060,
TTL: 500, TTL: 500,
}, }),
}, },
}, },
{ {
@ -113,22 +100,16 @@ func TestRecordConfigFromInfo(t *testing.T) {
Values: []string{"50 fb.mail.gandi.net.", "10 spool.mail.gandi.net."}, Values: []string{"50 fb.mail.gandi.net.", "10 spool.mail.gandi.net."},
}, },
[]*models.RecordConfig{ []*models.RecordConfig{
{ makeRC("mail", "example.com", "fb.mail.gandi.net.", models.RecordConfig{
NameFQDN: "mail.example.com",
Name: "mail",
Type: "MX", Type: "MX",
MxPreference: 50, MxPreference: 50,
Target: "fb.mail.gandi.net.",
TTL: 500, TTL: 500,
}, }),
{ makeRC("mail", "example.com", "spool.mail.gandi.net.", models.RecordConfig{
NameFQDN: "mail.example.com",
Name: "mail",
Type: "MX", Type: "MX",
MxPreference: 10, MxPreference: 10,
Target: "spool.mail.gandi.net.",
TTL: 500, TTL: 500,
}, }),
}, },
}, },
} { } {

View File

@ -104,7 +104,7 @@ func keyFor(r *gdns.ResourceRecordSet) key {
return key{Type: r.Type, Name: r.Name} return key{Type: r.Type, Name: r.Name}
} }
func keyForRec(r *models.RecordConfig) key { func keyForRec(r *models.RecordConfig) key {
return key{Type: r.Type, Name: r.NameFQDN + "."} return key{Type: r.Type, Name: r.GetLabelFQDN() + "."}
} }
func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {

View File

@ -136,10 +136,10 @@ func (api *LinodeApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
// https://github.com/linode/manager/blob/edd99dc4e1be5ab8190f243c3dbf8b830716255e/src/constants.js#L184 // https://github.com/linode/manager/blob/edd99dc4e1be5ab8190f243c3dbf8b830716255e/src/constants.js#L184
for _, name := range defaultNameServerNames { for _, name := range defaultNameServerNames {
rc := &models.RecordConfig{ rc := &models.RecordConfig{
NameFQDN: dc.Name,
Type: "NS", Type: "NS",
Original: &domainRecord{}, Original: &domainRecord{},
} }
rc.SetLabelFromFQDN(dc.Name, dc.Name)
rc.SetTarget(name) rc.SetTarget(name)
existingRecords = append(existingRecords, rc) existingRecords = append(existingRecords, rc)
@ -249,7 +249,7 @@ func toRc(dc *models.DomainConfig, r *domainRecord) *models.RecordConfig {
func toReq(dc *models.DomainConfig, rc *models.RecordConfig) (*recordEditRequest, error) { func toReq(dc *models.DomainConfig, rc *models.RecordConfig) (*recordEditRequest, error) {
req := &recordEditRequest{ req := &recordEditRequest{
Type: rc.Type, Type: rc.Type,
Name: dnsutil.TrimDomainName(rc.NameFQDN, dc.Name), Name: rc.GetLabel(),
Target: rc.GetTargetField(), Target: rc.GetTargetField(),
TTL: int(rc.TTL), TTL: int(rc.TTL),
Priority: 0, Priority: 0,

View File

@ -14,7 +14,6 @@ import (
"github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
nc "github.com/billputer/go-namecheap" nc "github.com/billputer/go-namecheap"
"github.com/miekg/dns/dnsutil"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -125,9 +124,9 @@ func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor
// namecheap does not allow setting @ NS with basic DNS // namecheap does not allow setting @ NS with basic DNS
dc.Filter(func(r *models.RecordConfig) bool { dc.Filter(func(r *models.RecordConfig) bool {
if r.Type == "NS" && r.Name == "@" { if r.Type == "NS" && r.GetLabel() == "@" {
if !strings.HasSuffix(r.Target, "registrar-servers.com.") { if !strings.HasSuffix(r.GetTargetField(), "registrar-servers.com.") {
fmt.Println("\n", r.Target, "Namecheap does not support changing apex NS records. Skipping.") fmt.Println("\n", r.GetTargetField(), "Namecheap does not support changing apex NS records. Skipping.")
} }
return false return false
} }
@ -150,13 +149,13 @@ func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor
continue continue
} }
rec := &models.RecordConfig{ rec := &models.RecordConfig{
NameFQDN: dnsutil.AddOrigin(r.Name, dc.Name),
Type: r.Type, Type: r.Type,
Target: r.Address,
TTL: uint32(r.TTL), TTL: uint32(r.TTL),
MxPreference: uint16(r.MXPref), MxPreference: uint16(r.MXPref),
Original: r, Original: r,
} }
rec.SetLabel(r.Name, dc.Name)
rec.SetTarget(r.Address)
actual = append(actual, rec) actual = append(actual, rec)
} }
@ -204,12 +203,11 @@ func (n *Namecheap) generateRecords(dc *models.DomainConfig) error {
id := 1 id := 1
for _, r := range dc.Records { for _, r := range dc.Records {
name := dnsutil.TrimDomainName(r.NameFQDN, dc.Name)
rec := nc.DomainDNSHost{ rec := nc.DomainDNSHost{
ID: id, ID: id,
Name: name, Name: r.GetLabel(),
Type: r.Type, Type: r.Type,
Address: r.Target, Address: r.GetTargetField(),
MXPref: int(r.MxPreference), MXPref: int(r.MxPreference),
TTL: int(r.TTL), TTL: int(r.TTL),
} }

View File

@ -8,8 +8,6 @@ import (
"github.com/namedotcom/go/namecom" "github.com/namedotcom/go/namecom"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/miekg/dns/dnsutil"
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
) )
@ -76,7 +74,7 @@ func (n *NameCom) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corre
func checkNSModifications(dc *models.DomainConfig) { func checkNSModifications(dc *models.DomainConfig) {
newList := make([]*models.RecordConfig, 0, len(dc.Records)) newList := make([]*models.RecordConfig, 0, len(dc.Records))
for _, rec := range dc.Records { for _, rec := range dc.Records {
if rec.Type == "NS" && rec.NameFQDN == dc.Name { if rec.Type == "NS" && rec.GetLabel() == "@" {
continue // Apex NS records are automatically created for the domain's nameservers and cannot be managed otherwise via the name.com API. continue // Apex NS records are automatically created for the domain's nameservers and cannot be managed otherwise via the name.com API.
} }
newList = append(newList, rec) newList = append(newList, rec)
@ -147,9 +145,9 @@ func (n *NameCom) getRecords(domain string) ([]*namecom.Record, error) {
func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error { func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error {
record := &namecom.Record{ record := &namecom.Record{
DomainName: domain, DomainName: domain,
Host: dnsutil.TrimDomainName(rc.NameFQDN, domain), Host: rc.GetLabel(),
Type: rc.Type, Type: rc.Type,
Answer: rc.Target, Answer: rc.GetTargetField(),
TTL: rc.TTL, TTL: rc.TTL,
Priority: uint32(rc.MxPreference), Priority: uint32(rc.MxPreference),
} }
@ -159,7 +157,7 @@ func (n *NameCom) createRecord(rc *models.RecordConfig, domain string) error {
case "TXT": case "TXT":
record.Answer = encodeTxt(rc.TxtStrings) record.Answer = encodeTxt(rc.TxtStrings)
case "SRV": case "SRV":
record.Answer = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.Target) record.Answer = fmt.Sprintf("%d %d %v", rc.SrvWeight, rc.SrvPort, rc.GetTargetField())
record.Priority = uint32(rc.SrvPriority) record.Priority = uint32(rc.SrvPriority)
default: default:
panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type)) panic(fmt.Sprintf("createRecord rtype %v unimplemented", rc.Type))

View File

@ -178,7 +178,7 @@ func parseLeaf(results models.Records, k string, v interface{}, origin string) (
case "port": // SRV case "port": // SRV
newRc.SrvPort = uint16(v4.(int)) newRc.SrvPort = uint16(v4.(int))
case "value": // MX case "value": // MX
newRc.Target = v4.(string) newRc.SetTarget(v4.(string))
} }
} }
//fmt.Printf("parseLeaf: append %v\n", newRc) //fmt.Printf("parseLeaf: append %v\n", newRc)

View File

@ -56,7 +56,9 @@ func TestYamlWrite(t *testing.T) {
m.AddFunc("json", minjson.Minify) m.AddFunc("json", minjson.Minify)
t.Run(f.Name(), func(t *testing.T) { t.Run(f.Name(), func(t *testing.T) {
content, err := ioutil.ReadFile(filepath.Join(testDir, f.Name())) fname := filepath.Join(testDir, f.Name())
fmt.Printf("Filename: %v\n", fname)
content, err := ioutil.ReadFile(fname)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -48,14 +48,14 @@ func (z *genYamlData) Less(i, j int) bool {
case "NS", "TXT", "TLSA": case "NS", "TXT", "TLSA":
// pass through. // pass through.
case "A": case "A":
ta2, tb2 := net.ParseIP(a.Target), net.ParseIP(b.Target) ta2, tb2 := net.ParseIP(a.GetTargetField()), net.ParseIP(b.GetTargetField())
ipa, ipb := ta2.To4(), tb2.To4() ipa, ipb := ta2.To4(), tb2.To4()
if ipa == nil || ipb == nil { if ipa == nil || ipb == nil {
log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2) log.Fatalf("should not happen: IPs are not 4 bytes: %#v %#v", ta2, tb2)
} }
return bytes.Compare(ipa, ipb) == -1 return bytes.Compare(ipa, ipb) == -1
case "AAAA": case "AAAA":
ta2, tb2 := net.ParseIP(a.Target), net.ParseIP(b.Target) ta2, tb2 := net.ParseIP(a.GetTargetField()), net.ParseIP(b.GetTargetField())
ipa, ipb := ta2.To16(), tb2.To16() ipa, ipb := ta2.To16(), tb2.To16()
return bytes.Compare(ipa, ipb) == -1 return bytes.Compare(ipa, ipb) == -1
case "MX": case "MX":
@ -75,7 +75,7 @@ func (z *genYamlData) Less(i, j int) bool {
return pa < pb return pa < pb
} }
case "PTR": case "PTR":
pa, pb := a.Target, b.Target pa, pb := a.GetTargetField(), b.GetTargetField()
if pa != pb { if pa != pb {
return pa < pb return pa < pb
} }

View File

@ -28,8 +28,9 @@ func WriteYaml(w io.Writer, records models.Records, origin string) error {
recsCopy = append(recsCopy, r) recsCopy = append(recsCopy, r)
} }
for _, r := range recsCopy { for _, r := range recsCopy {
if r.Name == "@" { if r.GetLabel() == "@" {
r.Name = "" //r.Name = ""
r.UnsafeSetLabelNull()
} }
} }
@ -156,7 +157,7 @@ func sameType(records models.Records) bool {
func oneLabel(records models.Records) yaml.MapItem { func oneLabel(records models.Records) yaml.MapItem {
item := yaml.MapItem{ item := yaml.MapItem{
// a yaml.MapItem is a YAML map that retains the key order. // a yaml.MapItem is a YAML map that retains the key order.
Key: records[0].Name, Key: records[0].GetLabel(),
} }
// Special case labels with a single record: // Special case labels with a single record:
if len(records) == 1 { if len(records) == 1 {

View File

@ -112,7 +112,7 @@ func (c *ovhProvider) deleteRecordFunc(id int64, fqdn string) func() error {
func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) func() error { func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) func() error {
return func() error { return func() error {
record := Record{ record := Record{
SubDomain: dnsutil.TrimDomainName(rc.NameFQDN, fqdn), SubDomain: dnsutil.TrimDomainName(rc.GetLabelFQDN(), fqdn),
FieldType: rc.Type, FieldType: rc.Type,
Target: rc.GetTargetCombined(), Target: rc.GetTargetCombined(),
TTL: rc.TTL, TTL: rc.TTL,
@ -130,7 +130,7 @@ func (c *ovhProvider) createRecordFunc(rc *models.RecordConfig, fqdn string) fun
func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqdn string) func() error { func (c *ovhProvider) updateRecordFunc(old *Record, rc *models.RecordConfig, fqdn string) func() error {
return func() error { return func() error {
record := Record{ record := Record{
SubDomain: dnsutil.TrimDomainName(rc.NameFQDN, fqdn), SubDomain: rc.GetLabel(),
FieldType: rc.Type, FieldType: rc.Type,
Target: rc.GetTargetCombined(), Target: rc.GetTargetCombined(),
TTL: rc.TTL, TTL: rc.TTL,

View File

@ -9,7 +9,6 @@ import (
"github.com/StackExchange/dnscontrol/models" "github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers" "github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/providers/diff" "github.com/StackExchange/dnscontrol/providers/diff"
"github.com/miekg/dns/dnsutil"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/datatypes"
@ -129,10 +128,10 @@ func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.
recConfig := &models.RecordConfig{ recConfig := &models.RecordConfig{
Type: recType, Type: recType,
Target: *record.Data,
TTL: uint32(*record.Ttl), TTL: uint32(*record.Ttl),
Original: record, Original: record,
} }
recConfig.SetTarget(*record.Data)
switch recType { switch recType {
case "SRV": case "SRV":
@ -153,21 +152,16 @@ func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.
if record.Service != nil { if record.Service != nil {
service = *record.Service service = *record.Service
} }
recConfig.SetLabel(fmt.Sprintf("%s.%s", service, strings.ToLower(protocol)), *domain.Name)
recConfig.Name = fmt.Sprintf("%s.%s", service, strings.ToLower(protocol))
case "MX": case "MX":
if record.MxPriority != nil { if record.MxPriority != nil {
recConfig.MxPreference = uint16(*record.MxPriority) recConfig.MxPreference = uint16(*record.MxPriority)
} }
fallthrough fallthrough
default: default:
recConfig.Name = *record.Host recConfig.SetLabel(*record.Host, *domain.Name)
} }
recConfig.NameFQDN = dnsutil.AddOrigin(recConfig.Name, *domain.Name)
actual = append(actual, recConfig) actual = append(actual, recConfig)
} }
@ -180,7 +174,7 @@ func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.
func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error { func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error {
var ttl, preference, domainID int = int(desired.TTL), int(desired.MxPreference), *domain.Id var ttl, preference, domainID int = int(desired.TTL), int(desired.MxPreference), *domain.Id
var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort) var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort)
var host, data, newType string = desired.Name, desired.Target, desired.Type var host, data, newType string = desired.GetLabel(), desired.GetTargetField(), desired.Type
var err error var err error
srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`) srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`)
@ -260,13 +254,15 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
service := services.GetDnsDomainResourceRecordMxTypeService(s.Session) service := services.GetDnsDomainResourceRecordMxTypeService(s.Session)
updated := datatypes.Dns_Domain_ResourceRecord_MxType{} updated := datatypes.Dns_Domain_ResourceRecord_MxType{}
if desired.Name != *existing.Host { label := desired.GetLabel()
updated.Host = &desired.Name if label != *existing.Host {
updated.Host = &label
changes = true changes = true
} }
if desired.Target != *existing.Data { target := desired.GetTargetField()
updated.Data = &desired.Target if target != *existing.Data {
updated.Data = &target
changes = true changes = true
} }
@ -290,13 +286,15 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session) service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session)
updated := datatypes.Dns_Domain_ResourceRecord_SrvType{} updated := datatypes.Dns_Domain_ResourceRecord_SrvType{}
if desired.Name != *existing.Host { label := desired.GetLabel()
updated.Host = &desired.Name if label != *existing.Host {
updated.Host = &label
changes = true changes = true
} }
if desired.Target != *existing.Data { target := desired.GetTargetField()
updated.Data = &desired.Target if target != *existing.Data {
updated.Data = &target
changes = true changes = true
} }
@ -333,13 +331,15 @@ func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceReco
service := services.GetDnsDomainResourceRecordService(s.Session) service := services.GetDnsDomainResourceRecordService(s.Session)
updated := datatypes.Dns_Domain_ResourceRecord{} updated := datatypes.Dns_Domain_ResourceRecord{}
if desired.Name != *existing.Host { label := desired.GetLabel()
updated.Host = &desired.Name if label != *existing.Host {
updated.Host = &label
changes = true changes = true
} }
if desired.Target != *existing.Data { target := desired.GetTargetField()
updated.Data = &desired.Target if target != *existing.Data {
updated.Data = &target
changes = true changes = true
} }

View File

@ -234,7 +234,7 @@ func toRecordConfig(dc *models.DomainConfig, r *vultr.DNSRecord) (*models.Record
// toVultrRecord converts a RecordConfig converted by toRecordConfig back to a Vultr DNSRecord #rtype_variations // toVultrRecord converts a RecordConfig converted by toRecordConfig back to a Vultr DNSRecord #rtype_variations
func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig) *vultr.DNSRecord { func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig) *vultr.DNSRecord {
name := dnsutil.TrimDomainName(rc.NameFQDN, dc.Name) name := rc.GetLabel()
// Vultr uses a blank string to represent the apex domain // Vultr uses a blank string to represent the apex domain
if name == "@" { if name == "@" {
name = "" name = ""
@ -267,17 +267,16 @@ func toVultrRecord(dc *models.DomainConfig, rc *models.RecordConfig) *vultr.DNSR
TTL: int(rc.TTL), TTL: int(rc.TTL),
Priority: priority, Priority: priority,
} }
switch rtype := rc.Type; rtype { // #rtype_variations
if rc.Type == "SRV" { case "SRV":
target := rc.Target target := rc.GetTargetField()
if strings.HasSuffix(target, ".") { if strings.HasSuffix(target, ".") {
target = target[:len(target)-1] target = target[:len(target)-1]
} }
r.Data = fmt.Sprintf("%v %v %s", rc.SrvWeight, rc.SrvPort, target) r.Data = fmt.Sprintf("%v %v %s", rc.SrvWeight, rc.SrvPort, target)
} case "CAA":
if rc.Type == "CAA" {
r.Data = fmt.Sprintf(`%v %s "%s"`, rc.CaaFlag, rc.CaaTag, rc.GetTargetField()) r.Data = fmt.Sprintf(`%v %s "%s"`, rc.CaaFlag, rc.CaaTag, rc.GetTargetField())
default:
} }
return r return r