diff --git a/commands/commands.go b/commands/commands.go index fbf1265ac..3ebb051f0 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -303,39 +303,3 @@ func (args *FilterArgs) flags() []cli.Flag { }, } } - -// domainInList takes a domain and a list of domains and returns true if the -// domain is in the list, accounting for wildcards and tags. -func domainInList(domain string, list []string) bool { - for _, item := range list { - if item == domain { - return true - } - if strings.HasPrefix(item, "*") && strings.HasSuffix(domain, item[1:]) { - return true - } - filterDom, filterTag, isFilterTagged := strings.Cut(item, "!") - splitDom, domainTag, isDomainTagged := strings.Cut(domain, "!") - if splitDom == filterDom { - if isDomainTagged { - if filterTag == "*" { - return true - } - if domainTag == "" && !isFilterTagged { - // domain example.com! == filter example.com - return true - } - if isFilterTagged && domainTag == filterTag { - return true - } - } - if isFilterTagged { - if filterTag == "" && !isDomainTagged { - // filter example.com! == domain example.com - return true - } - } - } - } - return false -} diff --git a/commands/commands_test.go b/commands/commands_test.go deleted file mode 100644 index 73f974bbd..000000000 --- a/commands/commands_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package commands - -import "testing" - -func Test_domainInList(t *testing.T) { - type args struct { - domain string - list []string - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "small", - args: args{ - domain: "foo.com", - list: []string{"foo.com"}, - }, - want: true, - }, - { - name: "big", - args: args{ - domain: "foo.com", - list: []string{"example.com", "foo.com", "baz.com"}, - }, - want: true, - }, - { - name: "missing", - args: args{ - domain: "foo.com", - list: []string{"bar.com"}, - }, - want: false, - }, - { - name: "wildcard", - args: args{ - domain: "*.10.in-addr.arpa", - list: []string{"bar.com", "10.in-addr.arpa", "example.com"}, - }, - want: false, - }, - { - name: "wildcardmissing", - args: args{ - domain: "*.10.in-addr.arpa", - list: []string{"bar.com", "6.in-addr.arpa", "example.com"}, - }, - want: false, - }, - { - name: "tagged", - args: args{ - domain: "foo.com!bar", - list: []string{"foo.com"}, - }, - want: false, - }, - { - name: "taggedWildcard", - args: args{ - domain: "foo.com!bar", - list: []string{"foo.com!*"}, - }, - want: true, - }, - { - name: "taggedWildcardMatchesEmpty", - args: args{ - domain: "foo.com!", - list: []string{"foo.com!*"}, - }, - want: true, - }, - { - name: "taggedWildcardNotMatchUntagged", - args: args{ - domain: "foo.com", - list: []string{"foo.com!*"}, - }, - want: false, - }, - { - name: "taggedEmtpy", - args: args{ - domain: "foo.com", - list: []string{"foo.com!"}, - }, - want: true, - }, - { - name: "domainTaggedEmtpy", - args: args{ - domain: "foo.com!", - list: []string{"foo.com"}, - }, - want: true, - }, - { - name: "filterTaggedNoMatch", - args: args{ - domain: "foo.com", - list: []string{"foo.com!foo"}, - }, - want: false, - }, - { - name: "domainTaggedNoMatch", - args: args{ - domain: "foo.com!foo", - list: []string{"foo.com"}, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := domainInList(tt.args.domain, tt.args.list); got != tt.want { - t.Errorf("domainInList() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/commands/getZones.go b/commands/getZones.go index 4839ba343..80c6327e7 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -200,7 +200,10 @@ func GetZone(args GetZoneArgs) error { // fetch all of the records zoneRecs := make([]models.Records, len(zones)) for i, zone := range zones { - recs, err := provider.GetZoneRecords(zone, nil) + recs, err := provider.GetZoneRecords(zone, + map[string]string{ + models.DomainUniqueName: zone, + }) if err != nil { return fmt.Errorf("failed GetZone gzr: %w", err) } diff --git a/commands/gz_test.go b/commands/gz_test.go index 9eb123de3..c53009a07 100644 --- a/commands/gz_test.go +++ b/commands/gz_test.go @@ -63,7 +63,7 @@ func testFormat(t *testing.T, domain, format string) { // Read the expected result want, err := os.ReadFile(expectedFilename) if err != nil { - log.Fatal(fmt.Errorf("can't read expected %q: %w", outfile.Name(), err)) + log.Fatal(fmt.Errorf("can't read expected %q: %w", expectedFilename, err)) } if w, g := string(want), string(got); w != g { diff --git a/commands/ppreviewPush.go b/commands/ppreviewPush.go index f666dbde3..4ee9fbacc 100644 --- a/commands/ppreviewPush.go +++ b/commands/ppreviewPush.go @@ -20,6 +20,7 @@ import ( "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/bindserial" "github.com/StackExchange/dnscontrol/v4/pkg/credsfile" + "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" "github.com/StackExchange/dnscontrol/v4/pkg/nameservers" "github.com/StackExchange/dnscontrol/v4/pkg/normalize" "github.com/StackExchange/dnscontrol/v4/pkg/notifications" @@ -288,7 +289,7 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor continue // Do not emit noise when zone exists } if !started { - out.StartDomain(zone.GetUniqueName()) + out.StartDomain(zone) started = true } skip := skipProvider(provider.Name, providersToProcess) @@ -351,7 +352,7 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor // Now we know what to do, print or do the tasks. out.PrintfIf(fullMode, "PHASE 3: CORRECTIONS\n") for _, zone := range zonesToProcess { - out.StartDomain(zone.GetUniqueName()) + out.StartDomain(zone) // Process DNS provider changes: providersToProcess := whichProvidersToProcess(zone.DNSProviderInstances, args.Providers) @@ -400,29 +401,16 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor return nil } -// func countActions(corrections []*models.Correction) int { -// r := 0 -// for _, c := range corrections { -// if c.F != nil { -// r++ -// } -// } -// return r -//} - // whichZonesToProcess takes a list of DomainConfigs and a filter string and -// returns a list of DomainConfigs whose metadata[DomainUniqueName] matched the +// returns a list of DomainConfigs whose Domain.UniqueName matched the // filter. The filter string is a comma-separated list of domain names. If the // filter string is empty or "all", all domains are returned. func whichZonesToProcess(domains []*models.DomainConfig, filter string) []*models.DomainConfig { - if filter == "" || filter == "all" { - return domains - } + fh := domaintags.CompilePermitList(filter) - permitList := strings.Split(filter, ",") var picked []*models.DomainConfig for _, domain := range domains { - if domainInList(domain.GetUniqueName(), permitList) { + if fh.Permitted(domain.GetUniqueName()) { picked = append(picked, domain) } } diff --git a/commands/ppreviewPush_test.go b/commands/ppreviewPush_test.go index 735749f9b..83dd4c941 100644 --- a/commands/ppreviewPush_test.go +++ b/commands/ppreviewPush_test.go @@ -23,7 +23,7 @@ func Test_whichZonesToProcess(t *testing.T) { } for _, dc := range allDC { - dc.UpdateSplitHorizonNames() + dc.PostProcess() } type args struct { diff --git a/documentation/provider/bind.md b/documentation/provider/bind.md index 0a485320b..5136e0795 100644 --- a/documentation/provider/bind.md +++ b/documentation/provider/bind.md @@ -22,7 +22,8 @@ Example: { "bind": { "TYPE": "BIND", - "directory": "myzones" + "directory": "myzones", + "filenameformat": "%U.zone" } } ``` @@ -89,10 +90,13 @@ file name is the name as specified in the `D()` function plus ".zone". The filenameformat is a string with a few printf-like `%` verbs: - * `%U` the domain name as specified in `D()` - * `%D` the domain name without any split horizon tag (the "example.com" part of "example.com!tag") - * `%T` the split horizon tag, or "" (the "tag" part of "example.com!tag") - * `%?x` this returns `x` if the split horizon tag is non-null, otherwise nothing. `x` can be any printable. + * The domain name without tag (the `example.com` part of `example.com!tag`): + * `%D` as specified in `D()` (no IDN conversion, but downcased) + * `%I` converted to IDN/Punycode (`xn--...`) and downcased. + * `%N` converted to Unicode (downcased first) + * `%T` the split horizon tag, or "" (the `tag` part of `example.com!tag`) + * `%?x` this returns `x` if the split horizon tag is non-null, otherwise nothing. `x` can be any printable but is usually `!`. + * `%U` short for "%I%?!%T". This is the universal, canonical, name for the domain used for comparisons within DNSControl. This is best for filenames which is why it is used in the default. * `%%` `%` * ordinary characters (not `%`) are copied unchanged to the output stream * FYI: format strings must not end with an incomplete `%` or `%?` @@ -101,19 +105,17 @@ Typical values: * `%U.zone` (The default) * `example.com.zone` or `example.com!tag.zone` - * `%T%*U%D.zone` (optional tag and `_` + domain + `.zone`) + * `%T%?_%I.zone` (optional tag and `_` + domain + `.zone`) * `tag_example.com.zone` or `example.com.zone` * `db_%T%?_%D` * `db_inside_example.com` or `db_example.com` - * `db_%D` - * `db_example.com` -The last example will generate the same name for both -`D("example.com!inside")` and `D("example.com!outside")`. This -assumes two BIND providers are configured in `creds.json`, each with -a different `directory` setting. Otherwise `dnscontrol` will write -both domains to the same file, flapping between the two back and -forth. +{% hint style="warning" %} +**Warning** DNSControl will not warn you if two zones generate the same +filename. Instead, each will write to the same place. The content would end up +flapping back and forth between the two. The best way to prevent this is to +always include the tag (`%T`) or use `%U` which includes the tag. +{% endhint %} (new in v4.2.0) `dnscontrol push` will create subdirectories along the path to the filename. This includes both the portion of the path created by the diff --git a/integrationTest/helpers_integration_test.go b/integrationTest/helpers_integration_test.go index 2385b796c..a289513f2 100644 --- a/integrationTest/helpers_integration_test.go +++ b/integrationTest/helpers_integration_test.go @@ -40,7 +40,7 @@ func getDomainConfigWithNameservers(t *testing.T, prv providers.DNSServiceProvid dc := &models.DomainConfig{ Name: domainName, } - dc.UpdateSplitHorizonNames() + dc.PostProcess() // fix up nameservers ns, err := prv.GetNameservers(domainName) @@ -148,6 +148,8 @@ func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.Doma return } + //fmt.Printf("DEBUG: Running test %q: Names %q %q %q\n", desc, dom.Name, dom.NameRaw, dom.NameUnicode) + // get and run corrections for first time _, corrections, actualChangeCount, err := zonerecs.CorrectZoneRecords(prv, dom) if err != nil { diff --git a/models/dns.go b/models/dns.go index 524a3e007..384aea4d7 100644 --- a/models/dns.go +++ b/models/dns.go @@ -105,17 +105,10 @@ type Correction struct { Msg string } -// DomainContainingFQDN finds the best domain from the dns config for the given record fqdn. -// It will chose the domain whose name is the longest suffix match for the fqdn. -func (config *DNSConfig) DomainContainingFQDN(fqdn string) *DomainConfig { - fqdn = strings.TrimSuffix(fqdn, ".") - longestLength := 0 - var d *DomainConfig - for _, dom := range config.Domains { - if (dom.Name == fqdn || strings.HasSuffix(fqdn, "."+dom.Name)) && len(dom.Name) > longestLength { - longestLength = len(dom.Name) - d = dom - } +// PostProcess performs and post-processing required after running dnsconfig.js and loading the result. +func (config *DNSConfig) PostProcess() error { + for _, domain := range config.Domains { + domain.PostProcess() } - return d + return nil } diff --git a/models/domain.go b/models/domain.go index f978b8868..845b1b460 100644 --- a/models/domain.go +++ b/models/domain.go @@ -2,28 +2,33 @@ package models import ( "fmt" - "strings" "sync" + "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" "github.com/qdm12/reprint" "golang.org/x/net/idna" ) const ( - // DomainUniqueName is the full `example.com!tag` name` - DomainUniqueName = "dnscontrol_uniquename" - // DomainTag is the tag part of `example.com!tag` name - DomainTag = "dnscontrol_tag" + DomainTag = "dnscontrol_tag" // A copy of DomainConfig.Tag + DomainUniqueName = "dnscontrol_uniquename" // A copy of DomainConfig.UniqueName + DomainNameRaw = "dnscontrol_nameraw" // A copy of DomainConfig.NameRaw + DomainNameIDN = "dnscontrol_nameidn" // A copy of DomainConfig.NameIDN + DomainNameUnicode = "dnscontrol_nameunicode" // A copy of DomainConfig.NameUnicode ) // DomainConfig describes a DNS domain (technically a DNS zone). type DomainConfig struct { - Name string `json:"name"` // NO trailing "." + Name string `json:"name"` // NO trailing "." Converted to IDN (punycode) early in the pipeline. + NameRaw string `json:"-"` // name as entered by user in dnsconfig.js + NameUnicode string `json:"-"` // name in Unicode format + + Tag string `json:"tag,omitempty"` // Split horizon tag. + UniqueName string `json:"-"` // .Name + "!" + .Tag + RegistrarName string `json:"registrar"` DNSProviderNames map[string]int `json:"dnsProviders"` - // Metadata[DomainUniqueName] // .Name + "!" + .Tag - // Metadata[DomainTag] // split horizon tag Metadata map[string]string `json:"meta,omitempty"` Records Records `json:"records"` Nameservers []*Nameserver `json:"nameservers,omitempty"` @@ -56,43 +61,39 @@ type DomainConfig struct { pendingPopulateCorrections map[string][]*Correction // Corrections for zone creations at each provider } -// GetSplitHorizonNames returns the domain's name, uniquename, and tag. -func (dc *DomainConfig) GetSplitHorizonNames() (name, uniquename, tag string) { - return dc.Name, dc.Metadata[DomainUniqueName], dc.Metadata[DomainTag] -} - -// GetUniqueName returns the domain's uniquename. -func (dc *DomainConfig) GetUniqueName() (uniquename string) { - return dc.Metadata[DomainUniqueName] -} - -// UpdateSplitHorizonNames updates the split horizon fields -// (uniquename and tag) based on name. -func (dc *DomainConfig) UpdateSplitHorizonNames() { - name, unique, tag := dc.GetSplitHorizonNames() - - if unique == "" { - unique = name - } - - if tag == "" { - l := strings.SplitN(name, "!", 2) - if len(l) == 2 { - name = l[0] - tag = l[1] - } - if tag == "" { - // ensure empty tagged domain is treated as untagged - unique = name - } - } - - dc.Name = name +// PostProcess performs and post-processing required after running dnsconfig.js and loading the result. +// It is called by dns.go's PostProcess() function. +func (dc *DomainConfig) PostProcess() { + // Ensure the metadata map is initialized. if dc.Metadata == nil { dc.Metadata = map[string]string{} } - dc.Metadata[DomainUniqueName] = unique - dc.Metadata[DomainTag] = tag + + // Turn the user-supplied name into the fixed forms. + ff := domaintags.MakeDomainFixForms(dc.Name) + dc.Tag, dc.NameRaw, dc.Name, dc.NameUnicode, dc.UniqueName = ff.Tag, ff.NameRaw, ff.NameIDN, ff.NameUnicode, ff.UniqueName + + // Store the FixForms is Metadata so we don't have to change the signature of every function that might need them. + // This is a bit ugly but avoids a huge refactor. Please avoid using these to make the future refactor easier. + if dc.Tag != "" { + dc.Metadata[DomainTag] = dc.Tag + } + //dc.Metadata[DomainNameRaw] = dc.NameRaw + //dc.Metadata[DomainNameIDN] = dc.Name + //dc.Metadata[DomainNameUnicode] = dc.NameUnicode + dc.Metadata[DomainUniqueName] = dc.UniqueName +} + +// GetSplitHorizonNames returns the domain's name, uniquename, and tag. +// Deprecated: use .Name, .Uniquename, and .Tag directly instead. +func (dc *DomainConfig) GetSplitHorizonNames() (name, uniquename, tag string) { + return dc.Name, dc.UniqueName, dc.Tag +} + +// GetUniqueName returns the domain's uniquename. +// Deprecated: dc.UniqueName directly instead. +func (dc *DomainConfig) GetUniqueName() (uniquename string) { + return dc.UniqueName } // Copy returns a deep copy of the DomainConfig. diff --git a/models/domain_test.go b/models/domain_test.go deleted file mode 100644 index 140b8edff..000000000 --- a/models/domain_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package models - -import ( - "testing" -) - -func Test_UpdateSplitHorizonNames(t *testing.T) { - tests := []struct { - name string - dc *DomainConfig - expected *DomainConfig - }{ - { - name: "testNoTag", - dc: &DomainConfig{ - Name: "example.com", - }, - expected: &DomainConfig{ - Name: "example.com", - Metadata: map[string]string{ - DomainUniqueName: "example.com", - DomainTag: "", - }, - }, - }, - { - name: "testEmptyTag", - dc: &DomainConfig{ - Name: "example.com!", - }, - expected: &DomainConfig{ - Name: "example.com", - Metadata: map[string]string{ - DomainUniqueName: "example.com", - DomainTag: "", - }, - }, - }, - { - name: "testWithTag", - dc: &DomainConfig{ - Name: "example.com!john", - }, - expected: &DomainConfig{ - Name: "example.com", - Metadata: map[string]string{ - DomainUniqueName: "example.com!john", - DomainTag: "john", - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.dc.UpdateSplitHorizonNames() - if tt.dc.Name != tt.expected.Name { - t.Errorf("expected name %s, got %s", tt.expected.Name, tt.dc.Name) - } - if tt.dc.Metadata[DomainUniqueName] != tt.expected.Metadata[DomainUniqueName] { - t.Errorf("expected unique name %s, got %s", tt.expected.Metadata[DomainUniqueName], tt.dc.Metadata[DomainUniqueName]) - } - if tt.dc.Metadata[DomainTag] != tt.expected.Metadata[DomainTag] { - t.Errorf("expected tag %s, got %s", tt.expected.Metadata[DomainTag], tt.dc.Metadata[DomainTag]) - } - }) - } -} diff --git a/models/t_caa.go b/models/t_caa.go index 73ced3a13..53e94a137 100644 --- a/models/t_caa.go +++ b/models/t_caa.go @@ -23,7 +23,7 @@ func (rc *RecordConfig) SetTargetCAA(flag uint8, tag string, target string) erro // Per: https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml#caa-properties excluding reserved tags allowedTags := []string{"issue", "issuewild", "iodef", "contactemail", "contactphone", "issuemail", "issuevmc"} if !slices.Contains(allowedTags, tag) { - return fmt.Errorf("CAA tag (%v) is not one of the valid types.", tag) + return fmt.Errorf("CAA tag (%v) is not one of the valid types", tag) } return nil diff --git a/pkg/domaintags/domaintags.go b/pkg/domaintags/domaintags.go new file mode 100644 index 000000000..adf07d810 --- /dev/null +++ b/pkg/domaintags/domaintags.go @@ -0,0 +1,81 @@ +package domaintags + +import ( + "strings" + + "golang.org/x/net/idna" +) + +// DomainFixedForms stores the various fixed forms of a domain name and tag. +type DomainFixedForms struct { + NameRaw string // "originalinput.com" (name as input by the user, lowercased (no tag)) + NameIDN string // "punycode.com" + NameUnicode string // "unicode.com" (converted to downcase BEFORE unicode conversion) + UniqueName string // "punycode.com!tag" + + Tag string // The tag portion of `example.com!tag` + HasBang bool // Was there a "!" in the input when creating this struct? +} + +// MakeDomainFixedForms turns the user-supplied name into the fixed forms. +// * .Tag: the domain tag (of "example.com!tag") +// * .NameRaw: lowercase version of how the user input the name in dnsconfig.js. +// * .Name: punycode version, downcased. +// * .NameUnicode: unicode version of the name, downcased. +// * .UniqueName: "example.com!tag" unique across the entire config. +func MakeDomainFixForms(n string) DomainFixedForms { + var err error + var tag, nameRaw, nameIDN, nameUnicode, uniqueName string + var hasBang bool + + // Split tag from name. + p := strings.SplitN(n, "!", 2) + if len(p) == 2 { + tag = p[1] + hasBang = true + } else { + tag = "" + hasBang = false + } + + nameRaw = strings.ToLower(p[0]) + if strings.HasPrefix(n, nameRaw) { + // Avoid pointless duplication. + nameRaw = n[0:len(nameRaw)] + } + + nameIDN, err = idna.ToASCII(nameRaw) + if err != nil { + nameIDN = nameRaw // Fallback to raw name on error. + } else { + // Avoid pointless duplication. + if nameIDN == nameRaw { + nameIDN = nameRaw + } + } + + nameUnicode, err = idna.ToUnicode(nameRaw) + if err != nil { + nameUnicode = nameRaw // Fallback to raw name on error. + } else { + // Avoid pointless duplication. + if nameUnicode == nameRaw { + nameUnicode = nameRaw + } + } + + if hasBang { + uniqueName = nameIDN + "!" + tag + } else { + uniqueName = nameIDN + } + + return DomainFixedForms{ + Tag: tag, + NameRaw: nameRaw, + NameIDN: nameIDN, + NameUnicode: nameUnicode, + UniqueName: uniqueName, + HasBang: hasBang, + } +} diff --git a/pkg/domaintags/domaintags_test.go b/pkg/domaintags/domaintags_test.go new file mode 100644 index 000000000..f6c010c33 --- /dev/null +++ b/pkg/domaintags/domaintags_test.go @@ -0,0 +1,133 @@ +package domaintags + +import ( + "testing" +) + +func Test_MakeDomainFixForms(t *testing.T) { + tests := []struct { + name string + input string + wantTag string + wantNameRaw string + wantNameIDN string + wantNameUnicode string + wantUniqueName string + wantHasBang bool + }{ + { + name: "simple domain", + input: "example.com", + wantTag: "", + wantNameRaw: "example.com", + wantNameIDN: "example.com", + wantNameUnicode: "example.com", + wantUniqueName: "example.com", + wantHasBang: false, + }, + { + name: "domain with tag", + input: "example.com!mytag", + wantTag: "mytag", + wantNameRaw: "example.com", + wantNameIDN: "example.com", + wantNameUnicode: "example.com", + wantUniqueName: "example.com!mytag", + wantHasBang: true, + }, + { + name: "domain with empty tag", + input: "example.com!", + wantTag: "", + wantNameRaw: "example.com", + wantNameIDN: "example.com", + wantNameUnicode: "example.com", + wantUniqueName: "example.com!", + wantHasBang: true, + }, + { + name: "unicode domain", + input: "उदाहरण.com", + wantTag: "", + wantNameRaw: "उदाहरण.com", + wantNameIDN: "xn--p1b6ci4b4b3a.com", + wantNameUnicode: "उदाहरण.com", + wantUniqueName: "xn--p1b6ci4b4b3a.com", + wantHasBang: false, + }, + { + name: "unicode domain with tag", + input: "उदाहरण.com!mytag", + wantTag: "mytag", + wantNameRaw: "उदाहरण.com", + wantNameIDN: "xn--p1b6ci4b4b3a.com", + wantNameUnicode: "उदाहरण.com", + wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag", + wantHasBang: true, + }, + { + name: "punycode domain", + input: "xn--p1b6ci4b4b3a.com", + wantTag: "", + wantNameRaw: "xn--p1b6ci4b4b3a.com", + wantNameIDN: "xn--p1b6ci4b4b3a.com", + wantNameUnicode: "उदाहरण.com", + wantUniqueName: "xn--p1b6ci4b4b3a.com", + wantHasBang: false, + }, + { + name: "punycode domain with tag", + input: "xn--p1b6ci4b4b3a.com!mytag", + wantTag: "mytag", + wantNameRaw: "xn--p1b6ci4b4b3a.com", + wantNameIDN: "xn--p1b6ci4b4b3a.com", + wantNameUnicode: "उदाहरण.com", + wantUniqueName: "xn--p1b6ci4b4b3a.com!mytag", + wantHasBang: true, + }, + { + name: "mixed case domain", + input: "Example.COM", + wantTag: "", + wantNameRaw: "example.com", + wantNameIDN: "example.com", + wantNameUnicode: "example.com", + wantUniqueName: "example.com", + wantHasBang: false, + }, + { + name: "mixed case domain with tag", + input: "Example.COM!MyTag", + wantTag: "MyTag", + wantNameRaw: "example.com", + wantNameIDN: "example.com", + wantNameUnicode: "example.com", + wantUniqueName: "example.com!MyTag", + wantHasBang: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MakeDomainFixForms(tt.input) + if got.Tag != tt.wantTag { + t.Errorf("MakeDomainFixForms() gotTag = %v, want %v", got.Tag, tt.wantTag) + } + if got.NameRaw != tt.wantNameRaw { + t.Errorf("MakeDomainFixForms() gotNameRaw = %v, want %v", got.NameRaw, tt.wantNameRaw) + } + if got.NameIDN != tt.wantNameIDN { + t.Errorf("MakeDomainFixForms() gotNameIDN = %v, want %v", got.NameIDN, tt.wantNameIDN) + } + if got.NameUnicode != tt.wantNameUnicode { + t.Errorf("MakeDomainFixForms() gotNameUnicode = %v, want %v", got.NameUnicode, tt.wantNameUnicode) + } + if got.UniqueName != tt.wantUniqueName { + t.Errorf("MakeDomainFixForms() gotUniqueName = %v, want %v", got.UniqueName, tt.wantUniqueName) + } + if got.HasBang != tt.wantHasBang { + t.Errorf("MakeDomainFixForms() gotHasTag = %v, want %v", got.HasBang, tt.wantHasBang) + } + }) + } +} diff --git a/pkg/domaintags/permitlist.go b/pkg/domaintags/permitlist.go new file mode 100644 index 000000000..b9e8e904b --- /dev/null +++ b/pkg/domaintags/permitlist.go @@ -0,0 +1,91 @@ +package domaintags + +import ( + "strings" +) + +type PermitList struct { + // If the permit list is "all" or "". + all bool + items []DomainFixedForms +} + +// CompilePermitList compiles a list of domain strings into a PermitList structure. The +func CompilePermitList(s string) PermitList { + s = strings.TrimSpace(s) + if s == "" || s == "*" || strings.ToLower(s) == "all" { + return PermitList{all: true} + } + + sl := PermitList{} + for _, l := range strings.Split(s, ",") { + l = strings.TrimSpace(l) + if l == "" { // Skip empty entries. They match nothing. + continue + } + ff := MakeDomainFixForms(l) + if ff.HasBang && ff.NameIDN == "" { // Treat empty name as wildcard. + ff.NameIDN = "*" + } + sl.items = append(sl.items, ff) + } + + return sl +} + +func (pl *PermitList) Permitted(domToCheck string) bool { + + // If the permit list is "all", everything is permitted. + if pl.all { + return true + } + + domToCheckFF := MakeDomainFixForms(domToCheck) + + for _, filterItem := range pl.items { + + // Special case: filter=example.com!* does not match example.com (no tag) + if filterItem.Tag == "*" && !domToCheckFF.HasBang { + continue + } + // Special case: filter=example.com!* does not match example.com! (empty tag) + if filterItem.Tag == "*" && domToCheckFF.HasBang && domToCheckFF.Tag == "" { + continue + } + // Special case: filter=example.com! does not match example.com!tag + if filterItem.HasBang && filterItem.Tag == "" && domToCheckFF.HasBang && domToCheckFF.Tag != "" { + continue + } + + // Skip if tags don't match + if (filterItem.Tag != "*") && (domToCheckFF.Tag != filterItem.Tag) { + continue + } + + // Now that we know the tag matches, we can focus on the name. + + // `*!tag` or `*` matches everything. + if filterItem.NameIDN == "*" { + return true + } + + // If the name starts with "*." then match the suffix. + if strings.HasPrefix(filterItem.NameIDN, "*.") { + // example.com matches *.example.com + if domToCheckFF.NameIDN == filterItem.NameIDN[2:] || domToCheckFF.NameUnicode == filterItem.NameUnicode[2:] { + return true + } + // foo.example.com matches *.example.com + if strings.HasSuffix(domToCheckFF.NameIDN, filterItem.NameIDN[1:]) || strings.HasSuffix(domToCheckFF.NameUnicode, filterItem.NameUnicode[1:]) { + return true + } + } + + // No wildcards? Exact match. + if filterItem.NameIDN == domToCheckFF.NameIDN || filterItem.NameUnicode == domToCheckFF.NameUnicode { + return true + } + } + + return false +} diff --git a/pkg/domaintags/permitlist_test.go b/pkg/domaintags/permitlist_test.go new file mode 100644 index 000000000..d6845a50b --- /dev/null +++ b/pkg/domaintags/permitlist_test.go @@ -0,0 +1,110 @@ +package domaintags + +import "testing" + +func TestPermitList_Permitted(t *testing.T) { + // MakeDomainFixForms is not exported, so we can't directly use it here + // to create complex test cases with IDNs easily without duplicating its logic. + // However, the existing tests cover a wide range of practical scenarios. + // For the purpose of this test, we'll assume MakeDomainFixForms works as expected + // and focus on the logic of the Permitted method itself. + + testCases := []struct { + name string + permitList string + domain string + expected bool + }{ + // "all" or empty permit list + {"all permits everything", "all", "example.com", true}, + {"all permits everything with tag", "all", "example.com!tag1", true}, + {"empty string permits everything", "", "example.com", true}, + {"whitespace string permits everything", " ", "example.com", true}, + + // Simple exact matches + {"exact match", "example.com", "example.com", true}, + {"exact match with tag", "example.com!tag1", "example.com!tag1", true}, + {"exact mismatch domain", "example.com", "google.com", false}, + {"exact mismatch tag", "example.com!tag1", "example.com!tag2", false}, + {"exact mismatch domain with tag", "example.com!tag1", "google.com!tag1", false}, + {"domain with tag not in list without tag", "example.com", "example.com!tag1", false}, + {"domain without tag not in list with tag", "example.com!tag1", "example.com", false}, + + // Wildcard domain name + {"wildcard domain matches", "*!tag1", "example.com!tag1", true}, + {"wildcard domain mismatch tag", "*!tag1", "example.com!tag2", false}, + {"wildcard domain no tag", "*!tag1", "example.com", false}, + {"wildcard domain and tag", "*", "example.com!tag1", true}, + {"wildcard domain and tag no tag", "*", "example.com", true}, + + // Wildcard tag + {"wildcard tag matches", "example.com!*", "example.com!tag1", true}, + {"wildcard tag matches no tag", "example.com!*", "example.com", false}, + {"wildcard tag mismatch domain", "example.com!*", "google.com!tag1", false}, + + // Suffix matching + {"suffix match base domain", "*.example.com", "example.com", true}, + {"suffix match subdomain", "*.example.com", "foo.example.com", true}, + {"suffix match another subdomain", "*.example.com", "foo.bar.example.com", true}, + {"suffix mismatch different domain", "*.example.com", "google.com", false}, + {"suffix mismatch partial", "*.example.com", "badexample.com", false}, + {"suffix match with tag", "*.example.com!tag1", "foo.example.com!tag1", true}, + {"suffix match base domain with tag", "*.example.com!tag1", "example.com!tag1", true}, + {"suffix mismatch tag", "*.example.com!tag1", "foo.example.com!tag2", false}, + {"suffix mismatch domain with tag", "*.example.com!tag1", "google.com!tag1", false}, + + // Multiple items in list + {"multiple items first match", "google.com,example.com", "google.com", true}, + {"multiple items second match", "google.com,example.com", "example.com", true}, + {"multiple items no match", "google.com,example.com", "other.com", false}, + {"multiple items with tags match", "google.com!tag1,example.com!tag2", "example.com!tag2", true}, + {"multiple items with tags mismatch", "google.com!tag1,example.com!tag2", "example.com!tag1", false}, + {"multiple complex items match", "a.com,*.b.com!tag1,c.com!*", "foo.b.com!tag1", true}, + {"multiple complex items match 2", "a.com,*.b.com!tag1,c.com!*", "c.com!anytag", true}, + {"multiple complex items no match", "a.com,*.b.com!tag1,c.com!*", "foo.b.com!tag2", false}, + + // IDN/Unicode cases (assuming MakeDomainFixForms works) + {"IDN exact match punycode", "xn--e1a4c.com", "xn--e1a4c.com", true}, // д.com + {"IDN exact match unicode", "д.com", "д.com", true}, + {"IDN mixed match", "xn--d1a.com", "д.com", true}, + {"IDN mixed match reversed", "д.com", "xn--d1a.com", true}, + {"IDN suffix match punycode", "*.xn--e1a4c.com", "sub.xn--e1a4c.com", true}, + {"IDN suffix match unicode", "*.д.com", "sub.д.com", true}, + {"IDN suffix match mixed", "*.xn--d1a.com", "sub.д.com", true}, + {"IDN suffix match mixed reversed", "*.д.com", "sub.xn--d1a.com", true}, + {"IDN suffix match base", "*.д.com", "д.com", true}, + + // Edge cases + {"empty list", " ", "example.com", true}, // TrimSpace makes it "", which is "all" + {"list with empty items", "one.com,,two.com", "one.com", true}, + {"list with empty items 2", "one.com,,two.com", "two.com", true}, + {"list with empty items no match", "one.com,,two.com", "three.com", false}, + {"no match on empty list", "nonexistent", "example.com", false}, + + // Weird backwards compatibility with no tag being different than empty tag + {"empty tag vs no tag mismatch", "example.com", "example.com!foo", false}, + + // testMultiFilterTaggedWildcard + {"testMultiFilterTaggedWildcard_0", "example.com!*", "example.com!", false}, + {"testMultiFilterTaggedWildcard_1", "example.com!*", "example.com", false}, + {"testMultiFilterTaggedWildcard_2", "example.com!*", "example.net", false}, + {"testMultiFilterTaggedWildcard_3", "example.com!*", "example.com!george", true}, + {"testMultiFilterTaggedWildcard_4", "example.com!*", "example.com!john", true}, + + // testFilterEmptyTagAndNoTag + {"testFilterEmptyTagAndNoTag_0", "example.com!,example.com", "example.com!", true}, + {"testFilterEmptyTagAndNoTag_1", "example.com!,example.com", "example.com", true}, + {"testFilterEmptyTagAndNoTag_2", "example.com!,example.com", "example.net", false}, + {"testFilterEmptyTagAndNoTag_3", "example.com!,example.com", "example.com!tag", false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + pl := CompilePermitList(tc.permitList) + got := pl.Permitted(tc.domain) + if got != tc.expected { + t.Errorf("PermitList(%q).Permitted(%q) = %v; want %v", tc.permitList, tc.domain, got, tc.expected) + } + }) + } +} diff --git a/pkg/js/README-parse_tests.md b/pkg/js/README-parse_tests.md index 0db9c1b7a..3010dfe3d 100644 --- a/pkg/js/README-parse_tests.md +++ b/pkg/js/README-parse_tests.md @@ -36,6 +36,8 @@ Back-port the ACTUAL results to the expected results: (This is dangerous. You may be committing buggy results to the "expected" files. Carefully inspect the resulting PR.) ``` +find . -type f -name \*.ACTUAL -print -delete +go test -count=1 ./... cd parse_tests fmtjson *.json *.json.ACTUAL for i in *.ACTUAL ; do f=$(basename $i .ACTUAL) ; cp $i $f ; done diff --git a/pkg/js/js.go b/pkg/js/js.go index 6c614d4c5..52708b9cb 100644 --- a/pkg/js/js.go +++ b/pkg/js/js.go @@ -119,6 +119,12 @@ func ExecuteJavascriptString(script []byte, devMode bool, variables map[string]s if err = json.Unmarshal([]byte(str), conf); err != nil { return nil, err } + + err = conf.PostProcess() + if err != nil { + return nil, err + } + return conf, nil } diff --git a/pkg/js/js_test.go b/pkg/js/js_test.go index 8b16a128b..f42c02e4c 100644 --- a/pkg/js/js_test.go +++ b/pkg/js/js_test.go @@ -18,8 +18,7 @@ import ( ) const ( - testDir = "pkg/js/parse_tests" - errorDir = "pkg/js/error_tests" + testDir = "pkg/js/parse_tests" ) func init() { @@ -49,9 +48,6 @@ func TestParsedFiles(t *testing.T) { if err != nil { t.Fatal(err) } - for _, dc := range conf.Domains { - dc.UpdateSplitHorizonNames() - } errs := normalize.ValidateAndNormalizeConfig(conf) if len(errs) != 0 { @@ -115,8 +111,7 @@ func TestParsedFiles(t *testing.T) { var dCount int for _, dc := range conf.Domains { var zoneFile string - dc.UpdateSplitHorizonNames() - if dc.Metadata[models.DomainTag] != "" { + if dc.Tag != "" { zoneFile = filepath.Join(testDir, testName, dc.GetUniqueName()+".zone") } else { zoneFile = filepath.Join(testDir, testName, dc.Name+".zone") diff --git a/pkg/js/parse_tests/001-basic.json b/pkg/js/parse_tests/001-basic.json index 59609c518..82289bf2e 100644 --- a/pkg/js/parse_tests/001-basic.json +++ b/pkg/js/parse_tests/001-basic.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/002-ttl.json b/pkg/js/parse_tests/002-ttl.json index 5a4147f0d..48bd3db4a 100644 --- a/pkg/js/parse_tests/002-ttl.json +++ b/pkg/js/parse_tests/002-ttl.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/003-meta.json b/pkg/js/parse_tests/003-meta.json index a0b3f52d6..21f988ee3 100644 --- a/pkg/js/parse_tests/003-meta.json +++ b/pkg/js/parse_tests/003-meta.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/004-ips.json b/pkg/js/parse_tests/004-ips.json index 230c750cb..4147e135f 100644 --- a/pkg/js/parse_tests/004-ips.json +++ b/pkg/js/parse_tests/004-ips.json @@ -11,7 +11,6 @@ "Cloudflare": 0 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/005-ignored-records.json b/pkg/js/parse_tests/005-ignored-records.json index 0bb768053..142896949 100644 --- a/pkg/js/parse_tests/005-ignored-records.json +++ b/pkg/js/parse_tests/005-ignored-records.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", @@ -56,7 +55,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "diff2.com" }, "name": "diff2.com", diff --git a/pkg/js/parse_tests/006-transforms.json b/pkg/js/parse_tests/006-transforms.json index f766a1520..2a54d02bf 100644 --- a/pkg/js/parse_tests/006-transforms.json +++ b/pkg/js/parse_tests/006-transforms.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/007-importTransformTTL.json b/pkg/js/parse_tests/007-importTransformTTL.json index d86d8224c..39027b66e 100644 --- a/pkg/js/parse_tests/007-importTransformTTL.json +++ b/pkg/js/parse_tests/007-importTransformTTL.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo1.com" }, "name": "foo1.com", @@ -29,7 +28,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "inny" }, "name": "inny", @@ -54,7 +52,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "com.inny" }, "name": "com.inny", diff --git a/pkg/js/parse_tests/008-import.json b/pkg/js/parse_tests/008-import.json index 8049dfb63..ce6010f13 100644 --- a/pkg/js/parse_tests/008-import.json +++ b/pkg/js/parse_tests/008-import.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/009-reverse.json b/pkg/js/parse_tests/009-reverse.json index 96ec46cf5..911740edd 100644 --- a/pkg/js/parse_tests/009-reverse.json +++ b/pkg/js/parse_tests/009-reverse.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "2.1.in-addr.arpa" }, "name": "2.1.in-addr.arpa", diff --git a/pkg/js/parse_tests/010-alias.json b/pkg/js/parse_tests/010-alias.json index 73a364114..cde8d47e1 100644 --- a/pkg/js/parse_tests/010-alias.json +++ b/pkg/js/parse_tests/010-alias.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/011-cfRedirect.json b/pkg/js/parse_tests/011-cfRedirect.json index 5180a8aa5..be71ee946 100644 --- a/pkg/js/parse_tests/011-cfRedirect.json +++ b/pkg/js/parse_tests/011-cfRedirect.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/012-duration.json b/pkg/js/parse_tests/012-duration.json index a1d6f7cd4..6a1637d6b 100644 --- a/pkg/js/parse_tests/012-duration.json +++ b/pkg/js/parse_tests/012-duration.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/013-mx.json b/pkg/js/parse_tests/013-mx.json index 70cb7c8c0..15b25a8d5 100644 --- a/pkg/js/parse_tests/013-mx.json +++ b/pkg/js/parse_tests/013-mx.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/014-caa.json b/pkg/js/parse_tests/014-caa.json index 14669cee7..c6c6fe922 100644 --- a/pkg/js/parse_tests/014-caa.json +++ b/pkg/js/parse_tests/014-caa.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/015-tlsa.json b/pkg/js/parse_tests/015-tlsa.json index 14e4d71e4..7abbd1a40 100644 --- a/pkg/js/parse_tests/015-tlsa.json +++ b/pkg/js/parse_tests/015-tlsa.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/017-txt.json b/pkg/js/parse_tests/017-txt.json index 670b412c0..1a11556b2 100644 --- a/pkg/js/parse_tests/017-txt.json +++ b/pkg/js/parse_tests/017-txt.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/018-dkim.json b/pkg/js/parse_tests/018-dkim.json index 683ce7555..cfad47f93 100644 --- a/pkg/js/parse_tests/018-dkim.json +++ b/pkg/js/parse_tests/018-dkim.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/019-r53-alias.json b/pkg/js/parse_tests/019-r53-alias.json index 71dc75b8a..f500adc26 100644 --- a/pkg/js/parse_tests/019-r53-alias.json +++ b/pkg/js/parse_tests/019-r53-alias.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/020-complexRequire.json b/pkg/js/parse_tests/020-complexRequire.json index 42bdd3116..41d7226c3 100644 --- a/pkg/js/parse_tests/020-complexRequire.json +++ b/pkg/js/parse_tests/020-complexRequire.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "sortfoo.com" }, "name": "sortfoo.com", diff --git a/pkg/js/parse_tests/021-srv.json b/pkg/js/parse_tests/021-srv.json index a5ed0be7e..70940fe89 100644 --- a/pkg/js/parse_tests/021-srv.json +++ b/pkg/js/parse_tests/021-srv.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/022-sshfp.json b/pkg/js/parse_tests/022-sshfp.json index 682efcef5..a1b74c43c 100644 --- a/pkg/js/parse_tests/022-sshfp.json +++ b/pkg/js/parse_tests/022-sshfp.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/023-ignored-glob-records.json b/pkg/js/parse_tests/023-ignored-glob-records.json index fdacff727..b7774bb8b 100644 --- a/pkg/js/parse_tests/023-ignored-glob-records.json +++ b/pkg/js/parse_tests/023-ignored-glob-records.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/024-json-import.json b/pkg/js/parse_tests/024-json-import.json index 0cf167eb4..f08ec91e2 100644 --- a/pkg/js/parse_tests/024-json-import.json +++ b/pkg/js/parse_tests/024-json-import.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/025-autodnssec.json b/pkg/js/parse_tests/025-autodnssec.json index 587dfe1d7..84a07d4d0 100644 --- a/pkg/js/parse_tests/025-autodnssec.json +++ b/pkg/js/parse_tests/025-autodnssec.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "nothing.com" }, "name": "nothing.com", @@ -15,7 +14,6 @@ "auto_dnssec": "on", "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "with.com" }, "name": "with.com", @@ -26,7 +24,6 @@ "auto_dnssec": "off", "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "without.com" }, "name": "without.com", diff --git a/pkg/js/parse_tests/026-azure-alias.json b/pkg/js/parse_tests/026-azure-alias.json index ca6716335..1fbf6c8fe 100644 --- a/pkg/js/parse_tests/026-azure-alias.json +++ b/pkg/js/parse_tests/026-azure-alias.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/027-ds.json b/pkg/js/parse_tests/027-ds.json index bb13a682a..d2eb195ed 100644 --- a/pkg/js/parse_tests/027-ds.json +++ b/pkg/js/parse_tests/027-ds.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/028-dextend.json b/pkg/js/parse_tests/028-dextend.json index 2f92ae97c..7790dbb69 100644 --- a/pkg/js/parse_tests/028-dextend.json +++ b/pkg/js/parse_tests/028-dextend.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", @@ -38,7 +37,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "bar.foo.com" }, "name": "bar.foo.com", @@ -65,7 +63,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.edu" }, "name": "foo.edu", diff --git a/pkg/js/parse_tests/029-dextendsub.json b/pkg/js/parse_tests/029-dextendsub.json index c241fab13..8c12ca68f 100644 --- a/pkg/js/parse_tests/029-dextendsub.json +++ b/pkg/js/parse_tests/029-dextendsub.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.net" }, "name": "foo.net", @@ -70,7 +69,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.tld" }, "name": "foo.tld", @@ -104,7 +102,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "bar.foo.tld" }, "name": "bar.foo.tld", @@ -138,7 +135,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.help" }, "name": "foo.help", @@ -181,7 +177,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "bar.foo.help" }, "name": "bar.foo.help", @@ -224,7 +219,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.here" }, "name": "foo.here", @@ -283,7 +277,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.com" }, "name": "example.com", @@ -342,10 +335,9 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", - "dnscontrol_uniquename": "d\u00fcsseldorf.example.net" + "dnscontrol_uniquename": "xn--dsseldorf-q9a.example.net" }, - "name": "d\u00fcsseldorf.example.net", + "name": "xn--dsseldorf-q9a.example.net", "records": [ { "filepos": "[line:94:5]", @@ -417,10 +409,9 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", - "dnscontrol_uniquename": "\u00fc.example.net" + "dnscontrol_uniquename": "xn--tda.example.net" }, - "name": "\u00fc.example.net", + "name": "xn--tda.example.net", "records": [ { "filepos": "[line:116:5]", @@ -492,7 +483,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.tld" }, "name": "example.tld", diff --git a/pkg/js/parse_tests/029-dextendsub/düsseldorf.example.net.zone b/pkg/js/parse_tests/029-dextendsub/xn--dsseldorf-q9a.example.net.zone similarity index 100% rename from pkg/js/parse_tests/029-dextendsub/düsseldorf.example.net.zone rename to pkg/js/parse_tests/029-dextendsub/xn--dsseldorf-q9a.example.net.zone diff --git a/pkg/js/parse_tests/029-dextendsub/ü.example.net.zone b/pkg/js/parse_tests/029-dextendsub/xn--tda.example.net.zone similarity index 100% rename from pkg/js/parse_tests/029-dextendsub/ü.example.net.zone rename to pkg/js/parse_tests/029-dextendsub/xn--tda.example.net.zone diff --git a/pkg/js/parse_tests/030-dextenddoc.json b/pkg/js/parse_tests/030-dextenddoc.json index bc7dfa01d..a20ba9f35 100644 --- a/pkg/js/parse_tests/030-dextenddoc.json +++ b/pkg/js/parse_tests/030-dextenddoc.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "domain.tld" }, "name": "domain.tld", diff --git a/pkg/js/parse_tests/031-dextendnames.json b/pkg/js/parse_tests/031-dextendnames.json index a5c74aa5c..8a276646e 100644 --- a/pkg/js/parse_tests/031-dextendnames.json +++ b/pkg/js/parse_tests/031-dextendnames.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "domain.tld" }, "name": "domain.tld", @@ -114,7 +113,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "sub.domain.tld" }, "name": "sub.domain.tld", diff --git a/pkg/js/parse_tests/032-reverseip.json b/pkg/js/parse_tests/032-reverseip.json index 01a12e395..1bf7c471c 100644 --- a/pkg/js/parse_tests/032-reverseip.json +++ b/pkg/js/parse_tests/032-reverseip.json @@ -11,7 +11,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "3.2.1.in-addr.arpa" }, "name": "3.2.1.in-addr.arpa", diff --git a/pkg/js/parse_tests/033-revextend.json b/pkg/js/parse_tests/033-revextend.json index daf2e6154..39d68a929 100644 --- a/pkg/js/parse_tests/033-revextend.json +++ b/pkg/js/parse_tests/033-revextend.json @@ -11,7 +11,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "8.9.in-addr.arpa" }, "name": "8.9.in-addr.arpa", @@ -39,7 +38,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.com" }, "name": "example.com", diff --git a/pkg/js/parse_tests/034-nameserver-ttl.json b/pkg/js/parse_tests/034-nameserver-ttl.json index 52b412f1e..140d21f5c 100644 --- a/pkg/js/parse_tests/034-nameserver-ttl.json +++ b/pkg/js/parse_tests/034-nameserver-ttl.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com", "ns_ttl": "86400" }, @@ -15,7 +14,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "bar.com", "ns_ttl": "300" }, diff --git a/pkg/js/parse_tests/035-naptr.json b/pkg/js/parse_tests/035-naptr.json index 16b745555..58561e529 100644 --- a/pkg/js/parse_tests/035-naptr.json +++ b/pkg/js/parse_tests/035-naptr.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/036-dextendcf.json b/pkg/js/parse_tests/036-dextendcf.json index 7ca254a62..dbf452bdd 100644 --- a/pkg/js/parse_tests/036-dextendcf.json +++ b/pkg/js/parse_tests/036-dextendcf.json @@ -11,7 +11,6 @@ "Cloudflare": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/037-splithor.json b/pkg/js/parse_tests/037-splithor.json index bf2ac2a93..fabc65da5 100644 --- a/pkg/js/parse_tests/037-splithor.json +++ b/pkg/js/parse_tests/037-splithor.json @@ -19,7 +19,6 @@ "otherconfig": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.com" }, "name": "example.com", @@ -66,7 +65,8 @@ "type": "A" } ], - "registrar": "Third-Party" + "registrar": "Third-Party", + "tag": "inside" }, { "dnsProviders": { @@ -86,14 +86,14 @@ "type": "A" } ], - "registrar": "Third-Party" + "registrar": "Third-Party", + "tag": "outside" }, { "dnsProviders": { "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.net" }, "name": "example.net", @@ -147,7 +147,8 @@ "type": "A" } ], - "registrar": "Third-Party" + "registrar": "Third-Party", + "tag": "inside" }, { "dnsProviders": { @@ -181,14 +182,14 @@ "type": "A" } ], - "registrar": "Third-Party" + "registrar": "Third-Party", + "tag": "outside" }, { "dnsProviders": { "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "empty.example.net" }, "name": "empty.example.net", @@ -215,8 +216,7 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", - "dnscontrol_uniquename": "example-b.net" + "dnscontrol_uniquename": "example-b.net!" }, "name": "example-b.net", "records": [ diff --git a/pkg/js/parse_tests/038-soa.json b/pkg/js/parse_tests/038-soa.json index 43aed9b2e..6c63bee83 100644 --- a/pkg/js/parse_tests/038-soa.json +++ b/pkg/js/parse_tests/038-soa.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/039-include.json b/pkg/js/parse_tests/039-include.json index 526cd1338..43e499d12 100644 --- a/pkg/js/parse_tests/039-include.json +++ b/pkg/js/parse_tests/039-include.json @@ -24,7 +24,8 @@ "type": "A" } ], - "registrar": "Third-Party" + "registrar": "Third-Party", + "tag": "external" }, { "dnsProviders": { @@ -51,7 +52,8 @@ "type": "A" } ], - "registrar": "Third-Party" + "registrar": "Third-Party", + "tag": "internal" } ], "registrars": [ diff --git a/pkg/js/parse_tests/040-cfWorkerRoute.json b/pkg/js/parse_tests/040-cfWorkerRoute.json index 479624ab7..9673c9bb7 100644 --- a/pkg/js/parse_tests/040-cfWorkerRoute.json +++ b/pkg/js/parse_tests/040-cfWorkerRoute.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/040-r53-zone.json b/pkg/js/parse_tests/040-r53-zone.json index 1e3d68b35..ee7dd322b 100644 --- a/pkg/js/parse_tests/040-r53-zone.json +++ b/pkg/js/parse_tests/040-r53-zone.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com", "zone_id": "Z2FTEDLFRTZ" }, @@ -37,7 +36,8 @@ "type": "R53_ALIAS" } ], - "registrar": "none" + "registrar": "none", + "tag": "internal" } ], "registrars": [] diff --git a/pkg/js/parse_tests/043-safety.json b/pkg/js/parse_tests/043-safety.json index a6ee5a4f5..82e9d313e 100644 --- a/pkg/js/parse_tests/043-safety.json +++ b/pkg/js/parse_tests/043-safety.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "unsafe.com" }, "name": "unsafe.com", @@ -15,7 +14,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "safe.com" }, "name": "safe.com", diff --git a/pkg/js/parse_tests/044-ensureabsent.json b/pkg/js/parse_tests/044-ensureabsent.json index f0d3745c4..106da8a17 100644 --- a/pkg/js/parse_tests/044-ensureabsent.json +++ b/pkg/js/parse_tests/044-ensureabsent.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "example.com" }, "name": "example.com", diff --git a/pkg/js/parse_tests/045-loc.json b/pkg/js/parse_tests/045-loc.json index 45be3fcb1..4830e3edb 100644 --- a/pkg/js/parse_tests/045-loc.json +++ b/pkg/js/parse_tests/045-loc.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/046-DHCID.json b/pkg/js/parse_tests/046-DHCID.json index 3c62541a1..919805504 100644 --- a/pkg/js/parse_tests/046-DHCID.json +++ b/pkg/js/parse_tests/046-DHCID.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/047-DNAME.json b/pkg/js/parse_tests/047-DNAME.json index d365ec88a..efb252d5f 100644 --- a/pkg/js/parse_tests/047-DNAME.json +++ b/pkg/js/parse_tests/047-DNAME.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/047-SVCB.json b/pkg/js/parse_tests/047-SVCB.json index 8bfe47a6e..92dd4a5d5 100644 --- a/pkg/js/parse_tests/047-SVCB.json +++ b/pkg/js/parse_tests/047-SVCB.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/048-DNSKEY.json b/pkg/js/parse_tests/048-DNSKEY.json index df7863e4f..a16d0518e 100644 --- a/pkg/js/parse_tests/048-DNSKEY.json +++ b/pkg/js/parse_tests/048-DNSKEY.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/049-json5-require.json b/pkg/js/parse_tests/049-json5-require.json index 0cf167eb4..f08ec91e2 100644 --- a/pkg/js/parse_tests/049-json5-require.json +++ b/pkg/js/parse_tests/049-json5-require.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/050-cfSingleRedirect.json b/pkg/js/parse_tests/050-cfSingleRedirect.json index 7f37f3257..380b1022e 100644 --- a/pkg/js/parse_tests/050-cfSingleRedirect.json +++ b/pkg/js/parse_tests/050-cfSingleRedirect.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/051-HASH.json b/pkg/js/parse_tests/051-HASH.json index b58f325ce..befbd9cb4 100644 --- a/pkg/js/parse_tests/051-HASH.json +++ b/pkg/js/parse_tests/051-HASH.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "a9993e364706816aba3e25717850c26c9cd0d89d" }, "name": "a9993e364706816aba3e25717850c26c9cd0d89d", diff --git a/pkg/js/parse_tests/054-b3487_d_extend_rev.json b/pkg/js/parse_tests/054-b3487_d_extend_rev.json index 33f0eb368..166102876 100644 --- a/pkg/js/parse_tests/054-b3487_d_extend_rev.json +++ b/pkg/js/parse_tests/054-b3487_d_extend_rev.json @@ -11,7 +11,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "6.10.in-addr.arpa" }, "name": "6.10.in-addr.arpa", diff --git a/pkg/js/parse_tests/055-b3550-ipv6ptr.json b/pkg/js/parse_tests/055-b3550-ipv6ptr.json index 9c1fd4326..85cd501f5 100644 --- a/pkg/js/parse_tests/055-b3550-ipv6ptr.json +++ b/pkg/js/parse_tests/055-b3550-ipv6ptr.json @@ -11,7 +11,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "d.c.b.a.1.1.0.2.ip6.arpa" }, "name": "d.c.b.a.1.1.0.2.ip6.arpa", @@ -38,7 +37,6 @@ "bind": -1 }, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "8.b.d.0.1.0.0.2.ip6.arpa" }, "name": "8.b.d.0.1.0.0.2.ip6.arpa", diff --git a/pkg/js/parse_tests/056-openpgpkey.json b/pkg/js/parse_tests/056-openpgpkey.json index 7678ddb6c..80f302656 100644 --- a/pkg/js/parse_tests/056-openpgpkey.json +++ b/pkg/js/parse_tests/056-openpgpkey.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/js/parse_tests/057-smimea.json b/pkg/js/parse_tests/057-smimea.json index e57c5d345..bec0486da 100644 --- a/pkg/js/parse_tests/057-smimea.json +++ b/pkg/js/parse_tests/057-smimea.json @@ -4,7 +4,6 @@ { "dnsProviders": {}, "meta": { - "dnscontrol_tag": "", "dnscontrol_uniquename": "foo.com" }, "name": "foo.com", diff --git a/pkg/normalize/importTransform_test.go b/pkg/normalize/importTransform_test.go index 4e2c3bb8b..ffd41ee2a 100644 --- a/pkg/normalize/importTransform_test.go +++ b/pkg/normalize/importTransform_test.go @@ -32,6 +32,10 @@ func TestImportTransform(t *testing.T) { cfg := &models.DNSConfig{ Domains: []*models.DomainConfig{src, dst}, } + err := cfg.PostProcess() + if err != nil { + t.Fatal(err) + } if errs := ValidateAndNormalizeConfig(cfg); len(errs) != 0 { for _, err := range errs { t.Error(err) diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index a787f4dfa..5c19e00b8 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -584,10 +584,6 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) { // processSplitHorizonDomains finds "domain.tld!tag" domains and pre-processes them. func processSplitHorizonDomains(config *models.DNSConfig) error { - // Parse out names and tags. - for _, d := range config.Domains { - d.UpdateSplitHorizonNames() - } // Verify uniquenames are unique seen := map[string]bool{} diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 9826b4eed..9f7063d96 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -13,7 +13,7 @@ import ( // CLI is an abstraction around the CLI. type CLI interface { Printer - StartDomain(domain string) + StartDomain(dc *models.DomainConfig) StartDNSProvider(name string, skip bool) EndProvider(name string, numCorrections int, err error) EndProvider2(name string, numCorrections int) @@ -89,8 +89,12 @@ type ConsolePrinter struct { } // StartDomain is called at the start of each domain. -func (c ConsolePrinter) StartDomain(domain string) { - fmt.Fprintf(c.Writer, "******************** Domain: %s\n", domain) +func (c ConsolePrinter) StartDomain(dc *models.DomainConfig) { + if dc.Name == dc.NameUnicode { + fmt.Fprintf(c.Writer, "******************** Domain: %s\n", dc.Name) + } else { + fmt.Fprintf(c.Writer, "******************** Domain: %s (%s)\n", dc.NameUnicode, dc.Name) + } } // PrintCorrection is called to print/format each correction. diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go index 432f679ed..525e8c74f 100644 --- a/providers/bind/bindProvider.go +++ b/providers/bind/bindProvider.go @@ -24,6 +24,7 @@ import ( "github.com/StackExchange/dnscontrol/v4/models" "github.com/StackExchange/dnscontrol/v4/pkg/bindserial" "github.com/StackExchange/dnscontrol/v4/pkg/diff2" + "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" "github.com/StackExchange/dnscontrol/v4/pkg/prettyzone" "github.com/StackExchange/dnscontrol/v4/pkg/printer" "github.com/StackExchange/dnscontrol/v4/providers" @@ -167,20 +168,23 @@ func (c *bindProvider) GetZoneRecords(domain string, meta map[string]string) (mo if _, err := os.Stat(c.directory); os.IsNotExist(err) { printer.Printf("\nWARNING: BIND directory %q does not exist! (will create)\n", c.directory) } - _, okTag := meta[models.DomainTag] - _, okUnique := meta[models.DomainUniqueName] - if !okTag && !okUnique { - // This layering violation is needed for tests only. - // Otherwise, this is set already. - // Note: In this situation there is no "uniquename" or "tag". - zonefile = filepath.Join(c.directory, - makeFileName(c.filenameformat, domain, domain, "")) - } else { - zonefile = filepath.Join(c.directory, - makeFileName(c.filenameformat, - meta[models.DomainUniqueName], domain, meta[models.DomainTag]), - ) + ff := domaintags.DomainFixedForms{ + Tag: meta[models.DomainTag], + NameRaw: meta[models.DomainNameRaw], + NameIDN: domain, + NameUnicode: meta[models.DomainNameUnicode], + UniqueName: meta[models.DomainUniqueName], } + zonefile = filepath.Join(c.directory, + makeFileName( + c.filenameformat, + ff, + ), + ) + //fmt.Printf("DEBUG: Reading zonefile %q\n", zonefile) + //fmt.Printf("DEBUG: Meta %+v\n", meta) + //fmt.Printf("DEBUG: Domain Names %+v\n", ff) + content, err := os.ReadFile(zonefile) if os.IsNotExist(err) { // If the file doesn't exist, that's not an error. Just informational. @@ -273,8 +277,16 @@ func (c *bindProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, foundR } zonefile = filepath.Join(c.directory, - makeFileName(c.filenameformat, - dc.Metadata[models.DomainUniqueName], dc.Name, dc.Metadata[models.DomainTag]), + makeFileName( + c.filenameformat, + domaintags.DomainFixedForms{ + Tag: dc.Tag, + NameRaw: dc.NameRaw, + NameIDN: dc.Name, + NameUnicode: dc.NameUnicode, + UniqueName: dc.UniqueName, + }, + ), ) // We only change the serial number if there is a change. diff --git a/providers/bind/fnames.go b/providers/bind/fnames.go index 51ab58c89..db7b22514 100644 --- a/providers/bind/fnames.go +++ b/providers/bind/fnames.go @@ -3,18 +3,23 @@ package bind import ( "bytes" "fmt" - "os" "path/filepath" "regexp" "strings" + + "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" ) // makeFileName uses format to generate a zone's filename. See the -func makeFileName(format, uniquename, domain, tag string) string { - // fmt.Printf("DEBUG: makeFileName(%q, %q, %q, %q)\n", format, uniquename, domain, tag) +func makeFileName(format string, ff domaintags.DomainFixedForms) string { + //fmt.Printf("DEBUG: makeFileName(%q, %+v)\n", format, ff) + nameRaw := ff.NameRaw + nameIDN := ff.NameIDN + nameUnicode := ff.NameUnicode + uniquename := ff.UniqueName + tag := ff.Tag if format == "" { - fmt.Fprintf(os.Stderr, "BUG: makeFileName called with null format\n") - return uniquename + panic("BUG: makeFileName called with null format") } var b bytes.Buffer @@ -36,11 +41,17 @@ func makeFileName(format, uniquename, domain, tag string) string { tok = tokens[pos] switch tok { case "D": - b.WriteString(domain) + b.WriteString(nameRaw) case "T": b.WriteString(tag) case "U": b.WriteString(uniquename) + case "I": + b.WriteString(nameIDN) + case "N": + b.WriteString(nameUnicode) + case "%": + b.WriteString("%") case "?": if pos == lastpos { b.WriteString("%(format may not end in %?)") diff --git a/providers/bind/fnames_test.go b/providers/bind/fnames_test.go index 34306cc28..66a0bcb8a 100644 --- a/providers/bind/fnames_test.go +++ b/providers/bind/fnames_test.go @@ -3,12 +3,25 @@ package bind import ( "reflect" "testing" + + "github.com/StackExchange/dnscontrol/v4/pkg/domaintags" ) func Test_makeFileName(t *testing.T) { - uu := "uni" - dd := "domy" - tt := "tagy" + ff := domaintags.DomainFixedForms{ + NameRaw: "raw", + NameIDN: "idn", + NameUnicode: "unicode", + UniqueName: "unique!taga", + Tag: "tagb", + } + tagless := domaintags.DomainFixedForms{ + NameRaw: "raw", + NameIDN: "idn", + NameUnicode: "unicode", + UniqueName: "unique", + Tag: "", + } fmtDefault := "%U.zone" fmtBasic := "%U - %T - %D" fmtBk1 := "db_%U" // Something I've seen in books on DNS @@ -19,35 +32,42 @@ func Test_makeFileName(t *testing.T) { fmtErrorUnk := "literal%o" // Unknown % verb type args struct { - format string - uniquename string - domain string - tag string + format string + ff domaintags.DomainFixedForms } tests := []struct { name string args args want string }{ - {"literal", args{"literal", uu, dd, tt}, "literal"}, - {"basic", args{fmtBasic, uu, dd, tt}, "uni - tagy - domy"}, - {"solo", args{"%D", uu, dd, tt}, "domy"}, - {"front", args{"%Daaa", uu, dd, tt}, "domyaaa"}, - {"tail", args{"bbb%D", uu, dd, tt}, "bbbdomy"}, - {"def", args{fmtDefault, uu, dd, tt}, "uni.zone"}, - {"bk1", args{fmtBk1, uu, dd, tt}, "db_uni"}, - {"bk2", args{fmtBk2, uu, dd, tt}, "db_tagy_domy"}, - {"fanWI", args{fmtFancy, uu, dd, tt}, "tagy_domy.zone"}, - {"fanWO", args{fmtFancy, uu, dd, ""}, "domy.zone"}, - {"errP", args{fmtErrorPct, uu, dd, tt}, "literal%(format may not end in %)"}, - {"errQ", args{fmtErrorOpt, uu, dd, tt}, "literal%(format may not end in %?)"}, - {"errU", args{fmtErrorUnk, uu, dd, tt}, "literal%(unknown %verb %o)"}, + {"literal", args{"literal", ff}, "literal"}, + {"middle", args{"mid%Dle", ff}, "midrawle"}, + {"D", args{"%D", ff}, "raw"}, + {"I", args{"%I", ff}, "idn"}, + {"N", args{"%N", ff}, "unicode"}, + {"T", args{"%T", ff}, "tagb"}, + {"x1", args{"XX%?xYY", ff}, "XXxYY"}, + {"x2", args{"AA%?xBB", tagless}, "AABB"}, + {"U", args{"%U", ff}, "unique!taga"}, + {"percent", args{"%%", ff}, "%"}, + // + {"default", args{fmtDefault, ff}, "unique!taga.zone"}, + {"basic", args{fmtBasic, ff}, "unique!taga - tagb - raw"}, + {"front", args{"%Daaa", ff}, "rawaaa"}, + {"tail", args{"bbb%D", ff}, "bbbraw"}, + {"bk1", args{fmtBk1, ff}, "db_unique!taga"}, + {"bk2", args{fmtBk2, ff}, "db_tagb_raw"}, + {"fanWI", args{fmtFancy, ff}, "tagb_raw.zone"}, + {"fanWO", args{fmtFancy, tagless}, "raw.zone"}, + {"errP", args{fmtErrorPct, ff}, "literal%(format may not end in %)"}, + {"errQ", args{fmtErrorOpt, ff}, "literal%(format may not end in %?)"}, + {"errU", args{fmtErrorUnk, ff}, "literal%(unknown %verb %o)"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := makeFileName(tt.args.format, tt.args.uniquename, tt.args.domain, tt.args.tag); got != tt.want { - t.Errorf("makeFileName() = %v, want %v", got, tt.want) + if got := makeFileName(tt.args.format, tt.args.ff); got != tt.want { + t.Errorf("makeFileName(%q) = %q, want %q", tt.args.format, got, tt.want) } }) } diff --git a/providers/powerdns/diff.go b/providers/powerdns/diff.go index 2208e6bd1..d8622b398 100644 --- a/providers/powerdns/diff.go +++ b/providers/powerdns/diff.go @@ -58,7 +58,7 @@ func (dsp *powerdnsProvider) getDiff2DomainCorrections(dc *models.DomainConfig, } } - domainVariant := dsp.zoneName(dc.Name, dc.Metadata[models.DomainTag]) + domainVariant := dsp.zoneName(dc.Name, dc.Tag) // only append a Correction if there are any, otherwise causes an error when sending an empty rrset if len(rrDeleteSets) > 0 { diff --git a/providers/powerdns/dnssec.go b/providers/powerdns/dnssec.go index efaa2ad52..1a4300546 100644 --- a/providers/powerdns/dnssec.go +++ b/providers/powerdns/dnssec.go @@ -10,7 +10,7 @@ import ( // getDNSSECCorrections returns corrections that update a domain's DNSSEC state. func (dsp *powerdnsProvider) getDNSSECCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { - domainVariant := dsp.zoneName(dc.Name, dc.Metadata[models.DomainTag]) + domainVariant := dsp.zoneName(dc.Name, dc.Tag) zoneCryptokeys, getErr := dsp.client.Cryptokeys().ListCryptokeys(context.Background(), dsp.ServerName, domainVariant) if getErr != nil { if _, ok := getErr.(pdnshttp.ErrNotFound); ok {