From 9e6d642e35e7c72815c8e6094e68d851614fb1a2 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 8 May 2022 14:23:45 -0400 Subject: [PATCH] NEW FEATURE: Moving provider TYPE from dnsconfig.js to creds.json (#1500) Fixes https://github.com/StackExchange/dnscontrol/issues/1457 * New-style creds.json implememented backwards compatible * Update tests * Update docs * Assume new-style TYPE --- commands/getZones.go | 42 +++- commands/previewPush.go | 214 ++++++++++++++++- commands/previewPush_test.go | 58 +++++ docs/_functions/global/D.md | 10 +- docs/_functions/global/DEFAULTS.md | 2 +- docs/_functions/global/FETCH.md | 4 +- docs/_functions/global/NewDnsProvider.md | 36 ++- docs/_functions/global/NewRegistrar.md | 36 ++- docs/assets/creds.json-example.txt | 2 + docs/assets/dnsconfig.js-example.txt | 4 +- docs/check-creds.md | 18 +- docs/creds-json.md | 132 ++++++++++- docs/get-zones.md | 31 ++- docs/getting-started.md | 58 +++-- docs/nameservers.md | 16 +- docs/v316.md | 220 ++++++++++++++++++ pkg/credsfile/providerConfig.go | 16 +- pkg/js/helpers.js | 42 +++- pkg/js/js_test.go | 7 + pkg/js/parse_tests/041-newstyleproviders.js | 31 +++ pkg/js/parse_tests/041-newstyleproviders.json | 19 ++ pkg/normalize/validate.go | 2 +- providers/providers.go | 57 ++++- 23 files changed, 949 insertions(+), 108 deletions(-) create mode 100644 commands/previewPush_test.go create mode 100644 docs/v316.md create mode 100755 pkg/js/parse_tests/041-newstyleproviders.js create mode 100644 pkg/js/parse_tests/041-newstyleproviders.json diff --git a/commands/getZones.go b/commands/getZones.go index eb64ebc50..865056e59 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -22,11 +22,21 @@ var _ = cmd(catUtils, func() *cli.Command { Action: func(ctx *cli.Context) error { if ctx.NArg() < 3 { return cli.Exit("Arguments should be: credskey providername zone(s) (Ex: r53 ROUTE53 example.com)", 1) - } args.CredName = ctx.Args().Get(0) - args.ProviderName = ctx.Args().Get(1) + arg1 := ctx.Args().Get(1) + args.ProviderName = arg1 + // In v4.0, skip the first args.ZoneNames if it it equals "-". args.ZoneNames = ctx.Args().Slice()[2:] + + if arg1 != "" && arg1 != "-" { + // NB(tlim): In v4.0 this "if" can be removed. + fmt.Fprintf(os.Stderr, "WARNING: To retain compatibility in future versions, please change %q to %q. See %q\n", + arg1, "-", + "https://stackexchange.github.io/dnscontrol/get-zones.html", + ) + } + return exit(GetZone(args)) }, Flags: args.flags(), @@ -73,12 +83,21 @@ var _ = cmd(catUtils, func() *cli.Command { Name: "check-creds", Usage: "Do a small operation to verify credentials (stand-alone)", Action: func(ctx *cli.Context) error { - if ctx.NArg() != 2 { - return cli.Exit("Arguments should be: credskey providername (Ex: r53 ROUTE53)", 1) - + var arg0, arg1 string + // This takes one or two command-line args. + // Starting in v3.16: Using it with 2 args will generate a warning. + // Starting in v4.0: Using it with 2 args might be an error. + if ctx.NArg() == 1 { + arg0 = ctx.Args().Get(0) + arg1 = "" + } else if ctx.NArg() == 2 { + arg0 = ctx.Args().Get(0) + arg1 = ctx.Args().Get(1) + } else { + return cli.Exit("Arguments should be: credskey [providername] (Ex: r53 ROUTE53)", 1) } - args.CredName = ctx.Args().Get(0) - args.ProviderName = ctx.Args().Get(1) + args.CredName = arg0 + args.ProviderName = arg1 args.ZoneNames = []string{"all"} args.OutputFormat = "nameonly" return exit(GetZone(args)) @@ -95,8 +114,9 @@ ARGUMENTS: provider: The name of the provider (second parameter to NewDnsProvider() in dnsconfig.js) EXAMPLES: - dnscontrol get-zones myr53 ROUTE53 - dnscontrol get-zones --out=/dev/null myr53 ROUTE53`, + dnscontrol check-creds myr53 ROUTE53 # Pre v3.16, or pre-v4.0 for backwards-compatibility + dnscontrol check-creds myr53 + dnscontrol check-creds --out=/dev/null myr53 && echo Success`, } }()) @@ -104,7 +124,7 @@ EXAMPLES: type GetZoneArgs struct { GetCredentialsArgs // Args related to creds.json CredName string // key in creds.json - ProviderName string // provider name: BIND, GANDI_V5, etc or "-" + ProviderName string // provider type: BIND, GANDI_V5, etc or "-" (NB(tlim): In 4.0, this field goes away.) ZoneNames []string // The zones to get OutputFormat string // Output format OutputFile string // Filename to send output ("" means stdout) @@ -144,7 +164,7 @@ func GetZone(args GetZoneArgs) error { } provider, err := providers.CreateDNSProvider(args.ProviderName, providerConfigs[args.CredName], nil) if err != nil { - return fmt.Errorf("failed GetZone CreateDNSProvider: %w", err) + return fmt.Errorf("failed GetZone CDP: %w", err) } // decide which zones we need to convert diff --git a/commands/previewPush.go b/commands/previewPush.go index 4e1703d23..f1d188d2c 100644 --- a/commands/previewPush.go +++ b/commands/previewPush.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "strings" "github.com/urfave/cli/v2" @@ -99,10 +100,6 @@ func run(args PreviewArgs, push bool, interactive bool, out printer.CLI) error { if err != nil { return err } - errs := normalize.ValidateAndNormalizeConfig(cfg) - if PrintValidationErrors(errs) { - return fmt.Errorf("exiting due to validation errors") - } providerConfigs, err := credsfile.LoadProviderConfigs(args.CredsFile) if err != nil { return err @@ -111,6 +108,11 @@ func run(args PreviewArgs, push bool, interactive bool, out printer.CLI) error { if err != nil { return err } + + errs := normalize.ValidateAndNormalizeConfig(cfg) + if PrintValidationErrors(errs) { + return fmt.Errorf("exiting due to validation errors") + } anyErrors := false totalCorrections := 0 DomainLoop: @@ -200,6 +202,16 @@ func InitializeProviders(cfg *models.DNSConfig, providerConfigs map[string]map[s isNonDefault[name] = true } } + + // Populate provider type ids based on values from creds.json: + msgs, err := populateProviderTypes(cfg, providerConfigs) + if len(msgs) != 0 { + fmt.Fprintln(os.Stderr, strings.Join(msgs, "\n")) + } + if err != nil { + return + } + registrars := map[string]providers.Registrar{} dnsProviders := map[string]providers.DNSServiceProvider{} for _, d := range cfg.Domains { @@ -229,6 +241,200 @@ func InitializeProviders(cfg *models.DNSConfig, providerConfigs map[string]map[s return } +// providerTypeFieldName is the name of the field in creds.json that specifies the provider type id. +const providerTypeFieldName = "TYPE" + +// url is the documentation URL to list in the warnings related to missing provider type ids. +const url = "https://stackexchange.github.io/dnscontrol/creds-json" + +// populateProviderTypes scans a DNSConfig for blank provider types and fills them in based on providerConfigs. +// That is, if the provider type is "-" or "", we take that as an flag +// that means this value should be replaced by the type found in creds.json. +func populateProviderTypes(cfg *models.DNSConfig, providerConfigs map[string]map[string]string) ([]string, error) { + var msgs []string + + for i := range cfg.Registrars { + pType := cfg.Registrars[i].Type + pName := cfg.Registrars[i].Name + nt, warnMsg, err := refineProviderType(pName, pType, providerConfigs[pName], "NewRegistrar") + cfg.Registrars[i].Type = nt + if warnMsg != "" { + msgs = append(msgs, warnMsg) + } + if err != nil { + return msgs, err + } + } + + for i := range cfg.DNSProviders { + pName := cfg.DNSProviders[i].Name + pType := cfg.DNSProviders[i].Type + nt, warnMsg, err := refineProviderType(pName, pType, providerConfigs[pName], "NewDnsProvider") + cfg.DNSProviders[i].Type = nt + if warnMsg != "" { + msgs = append(msgs, warnMsg) + } + if err != nil { + return msgs, err + } + } + + // Update these fields set by // commands/commands.go:preloadProviders(). + // This is probably a layering violation. That said, the + // fundamental problem here is that we're storing the provider + // instances by string name, not by a pointer to a struct. We + // should clean that up someday. + for _, domain := range cfg.Domains { // For each domain.. + for _, provider := range domain.DNSProviderInstances { // For each provider... + pName := provider.ProviderBase.Name + pType := provider.ProviderBase.ProviderType + nt, warnMsg, err := refineProviderType(pName, pType, providerConfigs[pName], "NewDnsProvider") + provider.ProviderBase.ProviderType = nt + if warnMsg != "" { + msgs = append(msgs, warnMsg) + } + if err != nil { + return msgs, err + } + } + p := domain.RegistrarInstance + pName := p.Name + pType := p.ProviderType + nt, warnMsg, err := refineProviderType(pName, pType, providerConfigs[pName], "NewRegistrar") + p.ProviderType = nt + if warnMsg != "" { + msgs = append(msgs, warnMsg) + } + if err != nil { + return msgs, err + } + } + + return uniqueStrings(msgs), nil +} + +// uniqueStrings takes an unsorted slice of strings and returns the +// unique strings, in the order they first appeared in the list. +func uniqueStrings(stringSlice []string) []string { + keys := make(map[string]bool) + list := []string{} + for _, entry := range stringSlice { + if _, ok := keys[entry]; !ok { + keys[entry] = true + list = append(list, entry) + } + } + return list +} + +func refineProviderType(credEntryName string, t string, credFields map[string]string, source string) (replacementType string, warnMsg string, err error) { + + // t="" and t="-" are processed the same. Standardize on "-" to reduce the number of cases to check. + if t == "" { + t = "-" + } + + // Use cases: + // + // type credsType + // ---- --------- + // - or "" GANDI lookup worked. Nothing to say. + // - or "" - or "" ERROR "creds.json has invalid or missing data" + // GANDI "" WARNING "Working but.... Please fix as follows..." + // GANDI GANDI INFO "working but unneeded: clean up as follows..." + // GANDI NAMEDOT ERROR "error mismatched: please fix as follows..." + + // ERROR: Invalid. + // WARNING: Required change to remain compatible with 4.0 + // INFO: Post-4.0 cleanups or other non-required changes. + + if t != "-" { + // Old-style, dnsconfig.js specifies the type explicitly. + // This is supported but we suggest updates for future compatibility. + + // If credFields is nil, that means there was no entry in creds.json: + if credFields == nil { + // Warn the user to update creds.json in preparation for 4.0: + // In 4.0 this should be an error. We could default to a + // provider such as "NONE" but I suspect it would be confusing + // to users to see references to a provider name that they did + // not specify. + return t, fmt.Sprintf(`WARNING: For future compatibility, add this entry creds.json: %q: { %q: %q }, (See %s#missing)`, + credEntryName, providerTypeFieldName, t, + url, + ), nil + } + + switch ct := credFields[providerTypeFieldName]; ct { + case "": + // Warn the user to update creds.json in preparation for 4.0: + // In 4.0 this should be an error. + return t, fmt.Sprintf(`WARNING: For future compatibility, update the %q entry in creds.json by adding: %q: %q, (See %s#missing)`, + credEntryName, + providerTypeFieldName, t, + url, + ), nil + case "-": + // This should never happen. The user is specifying "-" in a place that it shouldn't be used. + return "-", "", fmt.Errorf(`ERROR: creds.json entry %q has invalid %q value %q (See %s#hyphen)`, + credEntryName, providerTypeFieldName, ct, + url, + ) + case t: + // creds.json file is compatible with and dnsconfig.js can be updated. + return ct, fmt.Sprintf(`INFO: In dnsconfig.js %s(%q, %q) can be simplified to %s(%q) (See %s#cleanup)`, + source, credEntryName, t, + source, credEntryName, + url, + ), nil + default: + // creds.json lists a TYPE but it doesn't match what's in dnsconfig.js! + return t, "", fmt.Errorf(`ERROR: Mismatch found! creds.json entry %q has %q set to %q but dnsconfig.js specifies %s(%q, %q) (See %s#mismatch)`, + credEntryName, + providerTypeFieldName, ct, + source, credEntryName, t, + url, + ) + } + } + + // t == "-" + // New-style, dnsconfig.js does not specify the type (t == "") or a + // command line tool accepted "-" as a positional argument for + // backwards compatibility. + + // If credFields is nil, that means there was no entry in creds.json: + if credFields == nil { + return "", "", fmt.Errorf(`ERROR: creds.json is missing an entry called %q. Suggestion: %q: { %q: %q }, (See %s#missing)`, + credEntryName, + credEntryName, providerTypeFieldName, "FILL_IN_PROVIDER_TYPE", + url, + ) + } + + // New-style, dnsconfig.js doesn't specifies the type. It will be + // looked up in creds.json. + switch ct := credFields[providerTypeFieldName]; ct { + case "": + return ct, "", fmt.Errorf(`ERROR: creds.json entry %q is missing: %q: %q, (See %s#fixcreds)`, + credEntryName, + providerTypeFieldName, "FILL_IN_PROVIDER_TYPE", + url, + ) + case "-": + // This should never happen. The user is confused and specified "-" in the wrong place! + return "-", "", fmt.Errorf(`ERROR: creds.json entry %q has invalid %q value %q (See %s#hyphen)`, + credEntryName, + providerTypeFieldName, ct, + url, + ) + default: + // use the value in creds.json (this should be the normal case) + return ct, "", nil + } + +} + func printOrRunCorrections(domain string, provider string, corrections []*models.Correction, out printer.CLI, push bool, interactive bool, notifier notifications.Notifier) (anyErrors bool) { anyErrors = false if len(corrections) == 0 { diff --git a/commands/previewPush_test.go b/commands/previewPush_test.go new file mode 100644 index 000000000..92a95fbe5 --- /dev/null +++ b/commands/previewPush_test.go @@ -0,0 +1,58 @@ +package commands + +import ( + "strings" + "testing" +) + +func Test_refineProviderType(t *testing.T) { + + var mapEmpty map[string]string + mapTypeMissing := map[string]string{"otherfield": "othervalue"} + mapTypeFoo := map[string]string{"TYPE": "FOO"} + mapTypeBar := map[string]string{"TYPE": "BAR"} + mapTypeHyphen := map[string]string{"TYPE": "-"} + + type args struct { + t string + credFields map[string]string + } + tests := []struct { + name string + args args + wantReplacementType string + wantWarnMsgPrefix string + wantErr bool + }{ + {"fooEmp", args{"FOO", mapEmpty}, "FOO", "WARN", false}, // 3.x: Provide compatibility suggestion. 4.0: hard error + {"fooMis", args{"FOO", mapTypeMissing}, "FOO", "WARN", false}, // 3.x: Provide compatibility suggestion. 4.0: hard error + {"fooHyp", args{"FOO", mapTypeHyphen}, "-", "", true}, // Error: Invalid creds.json data. + {"fooFoo", args{"FOO", mapTypeFoo}, "FOO", "INFO", false}, // Suggest cleanup. + {"fooBar", args{"FOO", mapTypeBar}, "FOO", "", true}, // Error: Mismatched! + + {"hypEmp", args{"-", mapEmpty}, "", "", true}, // Hard error. creds.json entry is missing type. + {"hypMis", args{"-", mapTypeMissing}, "", "", true}, // Hard error. creds.json entry is missing type. + {"hypHyp", args{"-", mapTypeHyphen}, "-", "", true}, // Hard error: Invalid creds.json data. + {"hypFoo", args{"-", mapTypeFoo}, "FOO", "", false}, // normal + {"hypBar", args{"-", mapTypeBar}, "BAR", "", false}, // normal + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr && (tt.wantWarnMsgPrefix != "") { + t.Error("refineProviderType() bad test data. Prefix should be \"\" if wantErr is set") + } + gotReplacementType, gotWarnMsg, err := refineProviderType("foo", tt.args.t, tt.args.credFields, "FOO") + if !strings.HasPrefix(gotWarnMsg, tt.wantWarnMsgPrefix) { + t.Errorf("refineProviderType() gotWarnMsg = %q, wanted prefix %q", gotWarnMsg, tt.wantWarnMsgPrefix) + } + if (err != nil) != tt.wantErr { + t.Errorf("refineProviderType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotReplacementType != tt.wantReplacementType { + t.Errorf("refineProviderType() gotReplacementType = %q, want %q (warn,msg)=(%q,%s)", gotReplacementType, tt.wantReplacementType, gotWarnMsg, err) + } + }) + } +} diff --git a/docs/_functions/global/D.md b/docs/_functions/global/D.md index 0144ba194..afb762234 100644 --- a/docs/_functions/global/D.md +++ b/docs/_functions/global/D.md @@ -19,8 +19,8 @@ Modifier arguments are processed according to type as follows: {% capture example %} ```js -var REGISTRAR = NewRegistrar("name.com", "NAMEDOTCOM"); -var r53 = NewDnsProvider("R53","ROUTE53"); +var REGISTRAR = NewRegistrar("name.com"); +var r53 = NewDnsProvider("R53"); // simple domain D("example.com", REGISTRAR, DnsProvider(r53), @@ -60,9 +60,9 @@ To differentiate the different domains, specify the domains as {% capture example %} ```js -var REG = NewRegistrar("Third-Party", "NONE"); -var DNS_INSIDE = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI"); -var DNS_OUTSIDE = NewDnsProvider("bind", "BIND"); +var REG = NewRegistrar("Third-Party"); +var DNS_INSIDE = NewDnsProvider("Cloudflare"); +var DNS_OUTSIDE = NewDnsProvider("bind"); D("example.com!inside", REG, DnsProvider(DNS_INSIDE), A("www", "10.10.10.10") diff --git a/docs/_functions/global/DEFAULTS.md b/docs/_functions/global/DEFAULTS.md index 036a3bb4a..128cc27c8 100644 --- a/docs/_functions/global/DEFAULTS.md +++ b/docs/_functions/global/DEFAULTS.md @@ -9,7 +9,7 @@ arguments passed as if they were the first modifiers in the argument list. {% capture example %} ```js -var COMMON = NewDnsProvider("foo","BIND"); +var COMMON = NewDnsProvider("foo"); // we want to create backup zone files for all domains, but not actually register them. // also create a default TTL DEFAULTS( DnsProvider(COMMON,0), DefaultTTL(1000)); diff --git a/docs/_functions/global/FETCH.md b/docs/_functions/global/FETCH.md index e998b41f0..009c46cdf 100644 --- a/docs/_functions/global/FETCH.md +++ b/docs/_functions/global/FETCH.md @@ -20,8 +20,8 @@ Otherwise the syntax of `FETCH` is the same as `fetch`. {% capture example %} ```js -var REG_NONE = NewRegistrar('none', 'NONE'); -var DNS_BIND = NewDnsProvider('bind', 'BIND'); +var REG_NONE = NewRegistrar('none'); +var DNS_BIND = NewDnsProvider('bind'); D('example.com', REG_NONE, DnsProvider(DNS_BIND), [ A('@', '1.2.3.4'), diff --git a/docs/_functions/global/NewDnsProvider.md b/docs/_functions/global/NewDnsProvider.md index 343655c96..e263aa462 100644 --- a/docs/_functions/global/NewDnsProvider.md +++ b/docs/_functions/global/NewDnsProvider.md @@ -7,20 +7,36 @@ parameters: return: string --- -NewDnsProvider registers a new DNS Service Provider. The name can be any string value you would like to use. -The type must match a valid dns provider type identifier (see [provider page.]({{site.github.url}}/provider-list)) +NewDnsProvider activates a DNS Service Provider (DSP) specified in creds.json. +A DSP stores a DNS zone's records and provides DNS service for the zone (i.e. +answers on port 53 to queries related to the zone). -Metadata is an optional object, that will only be used by certain providers. See [individual provider docs]({{site.github.url}}/provider-list) for specific details. +* `name` must match the name of an entry in `creds.json`. +* `type` specifies a valid DNS provider type identifier listed on the [provider page.]({{site.github.url}}/provider-list). + * Starting with v3.16, the type is optional. If it is absent, the `TYPE` field in `creds.json` is used instead. You can leave it out. (Thanks to JavaScript magic, you can leave it out even when there are more fields). + * Starting with v4.0, specifying the type may be an error. Please add the `TYPE` field to `creds.json` and remove this parameter from `dnsconfig.js` to prepare. +* `meta` is a way to send additional parameters to the provider. It is optional and only certain providers use it. See the [individual provider docs]({{site.github.url}}/provider-list) for details. -This function will return the name as a string so that you may assign it to a variable to use inside [D](#D) directives. +This function will return an opaque string that should be assigned to a variable name for use in [D](#D) directives. + +Prior to v3.16: -{% capture example %} ```js -var REGISTRAR = NewRegistrar("name.com", "NAMEDOTCOM"); -var R53 = NewDnsProvider("r53", "ROUTE53"); +var REG_MYNDC = NewRegistrar("mynamedotcom", "NAMEDOTCOM"); +var DNS_MYAWS = NewDnsProvider("myaws", "ROUTE53"); -D("example.com", REGISTRAR, DnsProvider(R53), A("@","1.2.3.4")); +D("example.com", REG_MYNDC, DnsProvider(DNS_MYAWS), + A("@","1.2.3.4") +); ``` -{% endcapture %} -{% include example.html content=example %} +In v3.16 and later: + +```js +var REG_MYNDC = NewRegistrar("mynamedotcom"); +var DNS_MYAWS = NewDnsProvider("myaws"); + +D("example.com", REG_MYNDC, DnsProvider(DNS_MYAWS), + A("@","1.2.3.4") +); +``` diff --git a/docs/_functions/global/NewRegistrar.md b/docs/_functions/global/NewRegistrar.md index 7df7bace7..0fcdea4fa 100644 --- a/docs/_functions/global/NewRegistrar.md +++ b/docs/_functions/global/NewRegistrar.md @@ -7,20 +7,36 @@ parameters: return: string --- -NewRegistrar registers a registrar provider. The name can be any string value you would like to use. -The type must match a valid registrar provider type identifier (see [provider page.]({{site.github.url}}/provider-list)) +NewRegistrar activates a Registrar Provider specified in `creds.json`. +A registrar maintains the domain's registration and delegation (i.e. the +nameservers for the domain). DNSControl only manages the delegation. -Metadata is an optional object, that will only be used by certain providers. See [individual provider docs]({{site.github.url}}/provider-list) for specific details. +* `name` must match the name of an entry in `creds.json`. +* `type` specifies a valid DNS provider type identifier listed on the [provider page.]({{site.github.url}}/provider-list). + * Starting with v3.16, the type is optional. If it is absent, the `TYPE` field in `creds.json` is used instead. You can leave it out. (Thanks to JavaScript magic, you can leave it out even when there are more fields). + * Starting with v4.0, specifying the type may be an error. Please add the `TYPE` field to `creds.json` and remove this parameter from `dnsconfig.js` to prepare. +* `meta` is a way to send additional parameters to the provider. It is optional and only certain providers use it. See the [individual provider docs]({{site.github.url}}/provider-list) for details. -This function will return the name as a string so that you may assign it to a variable to use inside [D](#D) directives. +This function will return an opaque string that should be assigned to a variable name for use in [D](#D) directives. + +Prior to v3.16: -{% capture example %} ```js -var REGISTRAR = NewRegistrar("name.com", "NAMEDOTCOM"); -var r53 = NewDnsProvider("R53","ROUTE53"); +var REG_MYNDC = NewRegistrar("mynamedotcom", "NAMEDOTCOM"); +var DNS_MYAWS = NewDnsProvider("myaws", "ROUTE53"); -D("example.com", REGISTRAR, DnsProvider(r53), A("@","1.2.3.4")); +D("example.com", REG_MYNDC, DnsProvider(DNS_MYAWS), + A("@","1.2.3.4") +); ``` -{% endcapture %} -{% include example.html content=example %} +In v3.16 and later: + +```js +var REG_MYNDC = NewRegistrar("mynamedotcom"); +var DNS_MYAWS = NewDnsProvider("myaws"); + +D("example.com", REG_MYNDC, DnsProvider(DNS_MYAWS), + A("@","1.2.3.4") +); +``` diff --git a/docs/assets/creds.json-example.txt b/docs/assets/creds.json-example.txt index ac6bb4352..a20ed9fe2 100644 --- a/docs/assets/creds.json-example.txt +++ b/docs/assets/creds.json-example.txt @@ -1,7 +1,9 @@ { "bind": { + "TYPE": "BIND" }, "r53_ACCOUNTNAME": { + "TYPE": "ROUTE53", "KeyId": "change_to_your_keyid", "SecretKey": "change_to_your_secretkey", "Token": "optional_sts_token" diff --git a/docs/assets/dnsconfig.js-example.txt b/docs/assets/dnsconfig.js-example.txt index c8710d80f..a798929c0 100644 --- a/docs/assets/dnsconfig.js-example.txt +++ b/docs/assets/dnsconfig.js-example.txt @@ -4,8 +4,8 @@ // Providers: -var REG_NONE = NewRegistrar('none', 'NONE'); // No registrar. -var DNS_BIND = NewDnsProvider('bind', 'BIND'); // ISC BIND. +var REG_NONE = NewRegistrar('none'); // No registrar. +var DNS_BIND = NewDnsProvider('bind'); // ISC BIND. // Domains: diff --git a/docs/check-creds.md b/docs/check-creds.md index 46a810ba5..1f897ad07 100644 --- a/docs/check-creds.md +++ b/docs/check-creds.md @@ -8,8 +8,7 @@ title: Check-Creds subcommand This is a stand-alone utility to help verify entries in `creds.json`. The command does a trivia operation to verify credentials. If -successful, a list of zones will be output. If not, hopefully you see -verbose error messages. +successful, a list of zones will be output (which may be an empty list). If the credentials or other problems prevent this operation from executing, the exit code will be non-zero and hopefully verbose error messages will be output. Syntax: @@ -22,14 +21,23 @@ ARGUMENTS: credkey: The name used in creds.json (first parameter to NewDnsProvider() in dnsconfig.js) provider: The name of the provider (second parameter to NewDnsProvider() in dnsconfig.js) +Starting in v3.16, "provider" is optional. If it is omitted (or the placeholder value `-` is used), the `TYPE` specified in `creds.json` will be used instead. A warning will be displayed with advice on how to remain compatible with v4.0. + +Starting in v4.0, the "provider" argument is expected to go away. + EXAMPLES: dnscontrol check-creds myr53 ROUTE53 +Starting in v3.16: + dnscontrol check-creds myr53 + dnscontrol check-creds myr53 - + dnscontrol check-creds myr53 ROUTE53 +Starting in v4.0: + dnscontrol check-creds myr53 -This command is the same as: - dnscontrol get-zones --out=/dev/null myr53 ROUTE53 +This command is the same as `get-zones` with `--format=nameonly` # Developer Note This command is not implemented for all providers. -To add this to a provider, implement the get-zones subcommand +To add this to a provider, implement the get-zones subcommand. diff --git a/docs/creds-json.md b/docs/creds-json.md index bcff1e406..47f96a4fa 100644 --- a/docs/creds-json.md +++ b/docs/creds-json.md @@ -13,14 +13,17 @@ Here's a sample file: ```json { "cloudflare_tal": { + "TYPE": "CLOUDFLAREAPI", "apikey": "REDACTED", "apiuser": "REDACTED" }, "inside": { + "TYPE": "BIND", "directory": "inzones", "filenameformat": "db_%T%?_%D" }, "hexonet": { + "TYPE": "HEXONET", "apilogin": "$HEXONET_APILOGIN", "apipassword": "$HEXONET_APIPASSWORD", "debugmode": "$HEXONET_DEBUGMODE", @@ -29,7 +32,7 @@ Here's a sample file: } ``` -# Format +## Format * Primary keys: (e.g. `cloudflare_tal`, `inside`, `hexonet`) * ...refer to the first parameter in the `NewRegistrar()` or `NewDnsProvider()` functions in a dnsconfig.js file. @@ -43,7 +46,130 @@ Here's a sample file: * ...may include any JSON string value including the empty string. * If a subkey starts with `$`, it is taken as an env variable. In the above example, `$HEXONET_APILOGIN` would be replaced by the value of the environment variable `HEXONET_APILOGIN` or the empty string if no such environment variable exists. -# Using a different name +## New in v3.16: + +The special subkey "TYPE" is used to indicate the provider type (NONE, +CLOUDFLAREAPI, GCLOUD, etc). + +Prior to v3.16, the provider type is specified as the second argument +to `NewRegistrar()` and `NewDnsProvider()` in `dnsconfig.js` or as a +command-line argument in tools such as `dnscontrol get-zones`. + +Starting in v3.16, `NewRegistrar()`, and `NewDnsProvider()` no longer +require the provider type to be specified. It may be specified for +backwards compatibility, but a warning will be generated with a +suggestion of how to upgrade to the 4.0 format. Likewise, +command-line tools no longer require the provider type to be +specified, but for backwards compatibility one may specify `-` since +the parameter is positional. + +In 4.0, DNSControl will require the "TYPE" subkey in each `creds.json` +entry. Command line tools will have a backwards-incompatible change to +remove the provider-type as a positional argument. Prior to 4.0, the +various commands will output warnings and suggestions to avoid +compatibility issues during the transition. + +## Error messages + +### Missing + +Message: `WARNING: For future compatibility, add this entry creds.json:...` + +Message: `WARNING: For future compatibility, update the ... entry in creds.json by adding:...` + +These messages indicates that this provider is not mentioned in `creds.json`. In v4.0 +all providers used in `dnsconfig.js` will require an entry in `creds.json`. + +For a smooth transition, please update your `creds.json` file now. + +Here is the minimal entry required: + +```json +{ + "entryName": { + "TYPE": "FILL_IN" + } +} +``` + +### hyphen + +Message: `ERROR: creds.json entry ... has invalid ... value ...` + +This indicates the entry for `creds.json` has a TYPE value that is +invalid i.e. it is the empty string or a hyphen (`-`). + +The fix is to correct the `TYPE` parameter in the `creds.json` entry. +Change it to one of the all caps identifiers in [the service provider list](https://stackexchange.github.io/dnscontrol/provider-list). + + +### cleanup + +Message: `INFO: In dnsconfig.js New*(..., ...) can be simplified to New*(...)` + +This message indicates that the same provider name is specified in +`dnsconfig.js` and `creds.json` and offers a suggestion for reducing +the redundancy. + +The fix is to update `dnsconfig.js` as suggested in the error. +Usually this is to simply remove the second parameter to the function. + +Examples: + + +``` +OLD: var REG_THING = NewRegistrar("thing", "THING"); +NEW: var REG_THING = NewRegistrar("thing"); + +OLD: var REG_THING = NewRegistrar("thing", "THING", { settings: "value" } ); +NEW: var REG_THING = NewRegistrar("thing", { settings: "value" } ); + +OLD: var DNS_MYGANDI = NewDnsProvider("mygandi", "GANDI_V5"); +NEW: var DNS_MYGANDI = NewDnsProvider("mygandi"); + +OLD: var DNS_MYGANDI = NewDnsProvider("mygandi", "GANDI_V5", { settings: "value" } ); +NEW: var DNS_MYGANDI = NewDnsProvider("mygandi", { settings: "value" } ); +``` + +Starting with v3.16 use of an OLD format will trigger warnings with suggestions on how to adopt the NEW format. + +Starting with v4.0 support for the OLD format may be reported as an error. + +Please adopt the NEW format when your installation has eliminated any use of DNSControl pre-3.16. + + +### mismatch + +Message: `ERROR: Mismatch found! creds.json entry ... has ... set to ... but dnsconfig.js specifies New*(..., ...)` + +This indicates that the provider type specifed in `creds.json` does not match the one specifed in `dnsconfig.js` or on the command line. + +The fix is to change one to match the other. + +### fixcreds + +Message: `ERROR: creds.json entry ... is missing ...: ...` + +However no `TYPE` subkey was found in an entry in `creds.json`. +In 3.16 forward, it is required if new-style `NewRegistrar()` or `NewDnsProvider()` was used. +In 4.0 this is required. + +The fix is to add a `TYPE` subkey to the `creds.json` entry. + +### hyphen + +Message: `ERROR: creds.json entry ... has invalid ... value ...` + +This indicates that the type `-` was specified in a `TYPE` value in +`creds.json`. There is no provider named `-` therefore that is +invalid. Perhaps you meant to specify a `-` on a command-line tool? + +The fix is to change the `TYPE` subkey entry in `creds.json` from `-` to +a valid service provider identifier, as listed +in [the service provider list](https://stackexchange.github.io/dnscontrol/provider-list). + + +## Using a different file name The `--creds` flag allows you to specify a different file name. @@ -55,7 +181,7 @@ The `--creds` flag allows you to specify a different file name. * Exceptions: The `x` bit is not checked if the filename ends with `.yaml`, `.yml` or `.json`. * Windows: Executing an external script isn't supported. There's no code that prevents it from trying, but it isn't supported. -# Don't store secrets in a Git repo! +## Don't store secrets in a Git repo! Do NOT store secrets in a Git repository. That is not secure. For example, storing the example `cloudflare_tal` is insecure because anyone with access to diff --git a/docs/get-zones.md b/docs/get-zones.md index e459997f4..e6a702225 100644 --- a/docs/get-zones.md +++ b/docs/get-zones.md @@ -68,6 +68,10 @@ zones at the provider. provider: The name of the provider (second parameter to NewDnsProvider() in dnsconfig.js) zone: One or more zones (domains) to download; or "all". + As of v3.16, `provider` can be `-` to indicate that the provider name is listed in `creds.json` in the `TYPE` field. Doing this will be backwards compatible with an (otherwise) breaking change due in v4.0. + + As of v4.0 (BREAKING CHANGE), you must not specify `provider`. That value is found in the `TYPE` field of the credkey's `creds.json` file. For backwards compatibility, if the first `zone` is `-`, it will be skipped. + FORMATS: --format=js dnsconfig.js format (not perfect, just a decent first draft) --format=djs js with disco commas (leading commas) @@ -92,12 +96,35 @@ The `--ttl` flag only applies to zone/js/djs formats. dnscontrol get-zones gmain GANDI_V5 example.comn other.com dnscontrol get-zones cfmain CLOUDFLAREAPI all dnscontrol get-zones --format=tsv bind BIND example.com - dnscontrol get-zones --format=djs --out=draft.js glcoud GCLOUD example.com`, + dnscontrol get-zones --format=djs --out=draft.js glcoud GCLOUD example.com + +As of v3.16: + # NOTE: When "-" appears as the 2nd argument, it is assumed that the + # creds.json entry has a field TYPE with the provider's type name. + dnscontrol get-zones gmain GANDI_V5 example.comn other.com + dnscontrol get-zones gmain - example.comn other.com + dnscontrol get-zones cfmain CLOUDFLAREAPI all + dnscontrol get-zones cfmain - all + dnscontrol get-zones --format=tsv bind BIND example.com + dnscontrol get-zones --format=tsv bind - example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud GCLOUD example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud - example.com + +As of v4.0: + dnscontrol get-zones gmain example.comn other.com + dnscontrol get-zones cfmain all + dnscontrol get-zones --format=tsv bind example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud example.com + # For backwards compatibility, these are valid until at least v5.0 + dnscontrol get-zones gmain - example.comn other.com + dnscontrol get-zones cfmain - all + dnscontrol get-zones --format=tsv bind - example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud - example.com Read a zonefile, generate a JS file, then use the JS file to see how different it is from the zonefile: - dnscontrol get-zone --format=djs -out=foo.djs bind BIND example.org + dnscontrol get-zone --format=djs -out=foo.djs bind - example.org dnscontrol preview --config foo.js # Developer Notes diff --git a/docs/getting-started.md b/docs/getting-started.md index ba614db61..4ba4adac6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -9,7 +9,7 @@ title: Getting Started ## From source -DNSControl can be built with Go version 1.16 or higher. +DNSControl can be built with Go version 1.18 or higher. The `go get` command will download the source, compile it, and install `dnscontrol` in your `$GOBIN` directory. @@ -48,13 +48,6 @@ Create a directory where you'll be storing your configuration files. We highly recommend storing these files in a Git repo, but for simple tests anything will do. -Note: Do **not** store your creds.json file in Git unencrypted. -That is unsafe. Add `creds.json` to your -`.gitignore` file as a precaution. This file should be encrypted -using something -like [git-crypt](https://www.agwa.name/projects/git-crypt) or -[Blackbox](https://github.com/StackExchange/blackbox). - Create a subdirectory called `zones` in the same directory as the configuration files. (`mkdir zones`). `zones` is where the BIND provider writes the zonefiles it creates. Even if you don't @@ -75,8 +68,8 @@ The file looks like: ```js // Providers: -var REG_NONE = NewRegistrar('none', 'NONE'); // No registrar. -var DNS_BIND = NewDnsProvider('bind', 'BIND'); // ISC BIND. +var REG_NONE = NewRegistrar('none'); // No registrar. +var DNS_BIND = NewDnsProvider('bind'); // ISC BIND. // Domains: @@ -85,30 +78,43 @@ D('example.com', REG_NONE, DnsProvider(DNS_BIND), ); ``` -You may modify this file to match your particular providers and domains. See [the javascript docs]({{site.github.url}}/js) and [the provider docs]({{site.github.url}}/provider-list) for more details. -If you are using other providers, you will likely need to make a `creds.json` file with api tokens and other account information. For example, to use both name.com and Cloudflare, you would have: +Modify this file to match your particular providers and domains. See [the dnsconfig docs]({{site.github.url}}/js) and [the provider docs]({{site.github.url}}/provider-list) for more details. -```js +Create a file called `creds.json` for storing provider configurations (API tokens and other account information). +For example, to use both name.com and Cloudflare, you would have: + +```json { - "cloudflare":{ // provider name to be used in dnsconfig.js - "apitoken": "token" // API token + "cloudflare": { // The provider name used in dnsconfig.js + "TYPE": "CLOUDFLAREAPI", // The provider type identifier + "accountid": "your-cloudflare-account-id", // credentials + "apitoken": "your-cloudflare-api-token" // credentials }, - "namecom":{ // provider name to be used in dnsconfig.js - "apikey": "key", // API Key - "apiuser": "username" // username for name.com - } + "namecom": { // The provider name used in dnsconfig.js + "TYPE": "NAMEDOTCOM", // The provider type identifier + "apikey": "key", // credentials + "apiuser": "username" // credentials + }, + "none": { "TYPE": "NONE" } // The no-op provider } ``` +Note: Do **not** store your creds.json file in Git unencrypted. +That is unsafe. Add `creds.json` to your +`.gitignore` file as a precaution. This file should be encrypted +using something +like [git-crypt](https://www.agwa.name/projects/git-crypt) or +[Blackbox](https://github.com/StackExchange/blackbox). + There are 2 types of providers: A "Registrar" is who you register the domain with. Start with -`REG_NONE`, which is a provider that never talks to or updates the +`NONE`, which is a provider that never talks to or updates the registrar. You can define your registrar later when you want to use advanced features. -The `DnsProvider` is the service that actually provides DNS service -(port 53) and may be the same or different company. Even if both +A "DnsProvider" is the service that actually provides DNS service +(port 53) and may be the same or different as the registrar. Even if both your Registrar and DnsProvider are the same company, two different definitions must be included in `dnsconfig.js`. @@ -128,15 +134,17 @@ The file looks like: ```js { "bind": { + "TYPE": "BIND" }, - "r53_ACCOUNTNAME": { + "r53_accountname": { + "TYPE": "ROUTE53", "KeyId": "change_to_your_keyid", "SecretKey": "change_to_your_secretkey" } } ``` -Ignore the `r53_ACCOUNTNAME` section. It is a placeholder and will be ignored. You +Ignore the `r53_accountname` section. It is a placeholder and will be ignored. You can use it later when you define your first set of API credentials. Note that `creds.json` is a JSON file. JSON is very strict about commas @@ -148,7 +156,7 @@ Python: jq: - jq < creds.json + jq . < creds.json FYI: `creds.json` fields can be read from an environment variable. The field must begin with a `$` followed by the variable name. No other text. For example: diff --git a/docs/nameservers.md b/docs/nameservers.md index 3582184da..71dbcc53b 100644 --- a/docs/nameservers.md +++ b/docs/nameservers.md @@ -20,20 +20,20 @@ All the examples use the variables. Substitute your own. // ========== Registrars: // A typical registrar. -var REG_NAMECOM = NewRegistrar("namedotcom_main", "NAMEDOTCOM"); +var REG_NAMECOM = NewRegistrar("namedotcom_main"); // The "NONE" registrar is a "fake" registrar. // This is useful if the registrar is not supported by DNSControl, // or if you don't want to control the domain's delegation. -var REG_THIRDPARTY = NewRegistrar("ThirdParty", "NONE"); +var REG_THIRDPARTY = NewRegistrar("ThirdParty"); // ========== DNS Providers: -var DNS_NAMECOM = NewDnsProvider("namedotcom_main", "NAMEDOTCOM"); -var DNS_AWS = NewDnsProvider("aws_main", "ROUTE53"); -var DNS_GOOGLE = NewDnsProvider("gcp_main", "GCLOUD"); -var DNS_CLOUDFLARE = NewDnsProvider("cloudflare_main", "CLOUDFLAREAPI"); -var DNS_BIND = NewDnsProvider("bind", "BIND"); +var DNS_NAMECOM = NewDnsProvider("namedotcom_main"); +var DNS_AWS = NewDnsProvider("aws_main"); +var DNS_GOOGLE = NewDnsProvider("gcp_main"); +var DNS_CLOUDFLARE = NewDnsProvider("cloudflare_main"); +var DNS_BIND = NewDnsProvider("bind"); ``` # Typical Delegations @@ -226,7 +226,7 @@ Sometimes you just want to know if something changes! See the DNS-over-HTTPS Provider documentation for more info. ```js -var REG_MONITOR = NewRegistrar('DNS-over-HTTPS', 'DNSOVERHTTPS'); +var REG_MONITOR = NewRegistrar('DNS-over-HTTPS'); D("example1.com", REG_MONITOR, NAMESERVER("ns1.example1.com."), diff --git a/docs/v316.md b/docs/v316.md new file mode 100644 index 000000000..f00e9cfdb --- /dev/null +++ b/docs/v316.md @@ -0,0 +1,220 @@ +--- +layout: default +title: Converting to v3.16 +--- + +# creds.json file format change + +**Feel free to skip to "How do I convert?" if you don't care about the details.** + +Starting in v3.16 the "provider type identifier" (PTI) will be located in +`creds.json` instead of `dnsconfig.js`. The PTI is the all caps string like +`ROUTE53` or `CLOUDFLAREAPI` used to identify a provider's type. + +V3.16 will enable a syntax that is backwards and forwards compatible. The old +syntax will be removed in v4.0. There's no planned release date for v4.0 but +it is expected to be after Dec 31, 2022. + +The change was discussed +in [Request for Comments: Include the provider type in creds.json, remove it from dnsconfig.js](https://github.com/StackExchange/dnscontrol/issues/1457) where we decided "Plan A" would be selected. + +# What does this mean to you? + +In a nutshell, `NewRegistrar()` and `NewDnsProvider()` will lose the 2nd +parameter: + +OLD dnsconfig.js: + +```js +var REG_GANDI = NewRegistrar("gandi", "GANDI_V5"); +var DSP_CF = NewDnsProvider("cloudflare_tal", "CLOUDFLAREAPI"); +``` + +NEW dnsconfig.js: + +```js +var REG_GANDI = NewRegistrar("gandi"); +var DSP_CF = NewDnsProvider("cloudflare_tal"); +``` + +The second paramter (`GANDI_V5` and `CLOUDFLAREAPI` in the +above examples) has moved to `creds.json` instead. It will be in a `TYPE` +field, which all providers have. It can appear in both places for backwards compatibility for now. + +NEW creds.json: + +```json +{ + "gandi": { + "TYPE": "GANDI_V5", << NEW + "apikey": "reacted" + }, + "cloudflare_tal": { + "TYPE": "CLOUDFLAREAPI", << NEW + "apikey": "reacted", + "apiuser": "reacted" + } +} +``` + +In the past, a provider didn't need an entry in `creds.json` if there were no credentials to be stored. Starting in v4.0 all providers must have an entry in `creds.json`. To aid the transition, starting in v3.16 warnings will appear on stdout that direct you to convert to the new format. + +Also to help in the conversion, if no provider named "none" or "bind" exist, ones will be added for you. They look like this: + +```json +{ + "none": { "TYPE": "NONE" }, + "bind": { "TYPE": "BIND" } +} +``` + +# Command line tools + +How does this affect command line tools? + +The following subcommands require the PTI a parameter on the command line: + +* `get-zone` +* `get-zones` +* `check-creds` + +In 3.16, that parameter can be changed to `-` as a placeholder, or removed +entirely if it is the last parameter on the command line. When you omit this +parameter, DNSControl will look find the value in `creds.json` instead. + +In 4.0, that parameter will be removed, though a `-` is permitted for backwards compatibility. + +In other words, if you add the `TYPE` field to `creds.json`, you no longer need +to specify it on the command line. You can specify `-` instead, or leave it +out entirely starting in v4.0. + +For check-creds: + +``` +Starting in v3.16 these forms are valid: + dnscontrol check-creds myr53 + dnscontrol check-creds myr53 - + dnscontrol check-creds myr53 ROUTE53 +Starting in v4.0 this is the only valid form: + dnscontrol check-creds myr53 + # For backwards compatibility, these are valid until at least v5.0: + dnscontrol check-creds myr53 - +``` + +For get-zones/get-zone: + +``` +Starting in v3.16 these forms are valid: + dnscontrol get-zones gmain GANDI_V5 example.comn other.com + dnscontrol get-zones gmain - example.comn other.com + dnscontrol get-zones cfmain CLOUDFLAREAPI all + dnscontrol get-zones cfmain - all + dnscontrol get-zones --format=tsv bind BIND example.com + dnscontrol get-zones --format=tsv bind - example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud GCLOUD example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud - example.com +Starting in v4.0 these forms are valid: + dnscontrol get-zones gmain example.comn other.com + dnscontrol get-zones cfmain all + dnscontrol get-zones --format=tsv bind example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud example.com + # For backwards compatibility, these are valid until at least v5.0: + dnscontrol get-zones gmain - example.comn other.com + dnscontrol get-zones cfmain - all + dnscontrol get-zones --format=tsv bind - example.com + dnscontrol get-zones --format=djs --out=draft.js glcoud - example.com +``` + +# How do I convert? + +## Step 1: Upgrade + +Upgrade to v3.16 or later. If DNSControl is used in many places, do not +continue until they are all at v3.16 or later. + +## Step 2: Edit creds.json + +Now that all uses of DNSControl are on v3.16 or later... + +For each `creds.json` entry, add a field "TYPE" set to the provider type +identifier. This is the all-caps name such as `ROUTE53`, `GCLOUD`, or +`CLOUDFLAREAPI`. + +For example, here is a new-style `creds.json` file with `TYPE` fields added: + +```json +{ + "bind_inside": { + "TYPE": "BIND", << ADDED + "directory": "inzones" + }, + "cloudflare_tal": { + "TYPE": "CLOUDFLAREAPI", << ADDED + "apikey": "redacted", + "apiuser": "redacted" + }, + "gandi": { + "TYPE": "GANDI_V5", << ADDED + "apikey": "redacted" + }, +``` + +## Step 3: Cross-check + +Run `dnscontrol preview` as one normally would. Fix any errors, warnings, or informational messages that appear on stdout. + +Here are some examples: + +``` +WARNING: For future compatibility, update the "namedotcom_main" entry in `creds.json` by adding: "TYPE": "NAMEDOTCOM", (See https://stackexchange.github.io/dnscontrol/creds-json#missing) +``` + +``` +ERROR: Mismatch found! creds.json entry "namedotcom_main" has "TYPE" set to "ROUTE53" but dnsconfig.js specifies New*("namedotcom_main", "NAMEDOTCOM") (See https://stackexchange.github.io/dnscontrol/creds-json#mismatch) +``` + +After you correct some warnings, you may receive information messages like: + +``` +INFO: In dnsconfig.js New*("namedotcom_main", "NAMEDOTCOM") can be simplified to New*("namedotcom_main") (See https://stackexchange.github.io/dnscontrol/creds-json#cleanup) +``` + +Those messages will disappear as you update `dnsconfig.js` in the next step. + +## Step 4: Edit dnsconfig.js + +Remove the 2nd parameter to any NewDnsProvider() or NewRegistrar() functions. + +OLD: + +```js +var REG_NAMEDOTCOM_TAL = NewRegistrar("namedotcom_tal", "NAMEDOTCOM"); +var DNS_GANDI_TAL = NewDnsProvider("gandi_v5_tal", "GANDI_V5"); +``` + +NEW: + +```js +var REG_NAMEDOTCOM_TAL = NewRegistrar("namedotcom_tal"); +var DNS_GANDI_TAL = NewDnsProvider("gandi_v5_tal"); +``` + +Again, run `dnscontrol preview` to verify you setup still works as expected. + +## Step 5: Update any shell scripts + +Any shell scripts or documentation that uses the subcommands `get-zone`, +`get-zones` or `check-creds` should be updated. The "provider type" parameter +should be changed to `-`. If it is the last parameter on the command, it can +be removed. + +It's unlikely you have scripts that use these commands. However you may have +documentation that refers to them and needs to be updated. + +## Step 4: Test + +Run `dnscontrol preview` as one normally would. Fix any errors, warnings, or informational messages that appear on stdout. + +## Step 5: Done! + +That's it! diff --git a/pkg/credsfile/providerConfig.go b/pkg/credsfile/providerConfig.go index 1c60c2920..f873ec459 100644 --- a/pkg/credsfile/providerConfig.go +++ b/pkg/credsfile/providerConfig.go @@ -14,7 +14,6 @@ import ( "github.com/DisposaBoy/JsonConfigReader" "github.com/TomOnTime/utfutil" - "golang.org/x/exp/maps" ) func quotedList(l []string) string { @@ -76,13 +75,16 @@ func LoadProviderConfigs(fname string) (map[string]map[string]string, error) { return nil, err } - ckeys := keysWithColons(maps.Keys(results)) - if len(ckeys) != 0 { - fmt.Printf(`WARNING: In the future, colons in cred entry names will have meaning.`+ - ` Our best advice is to remove the colons for now to avoid future compatibility issues.`+ - ` Specifically these keys: %v`+"\n", - quotedList(ckeys)) + // For backwards compatibility, insert NONE and BIND entries if + // they do not exist. These are the only providers that previously + // did not require entries in creds.json prior to v4.0. + if _, ok := results["none"]; !ok { + results["none"] = map[string]string{"TYPE": "NONE"} } + if _, ok := results["bind"]; !ok { + results["bind"] = map[string]string{"TYPE": "BIND"} + } + return results, nil } diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index 911d12437..b33a29463 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -1,8 +1,5 @@ 'use strict'; -// If you edit this file, you must run `go generate` to embed this -// file in the source code. - // If you are heavily debugging this code, the "-dev" flag will // read this file directly instead of using the output of // `go generate`. You'll still need to run `go generate` before @@ -36,7 +33,27 @@ function getConfiguredDomains() { return conf.domain_names; } -function NewRegistrar(name, type, meta) { +// NewRegistrar returns an registrar object. +// For backwards compatibility, it accepts (name), (name, meta), +// (name, type), (name, type, meta). +function NewRegistrar() { + // For backwards compatibility, this is a wrapper around the legacy + // version of this function. + switch (arguments.length) { + case 1: + return oldNewRegistrar(arguments[0], "-") + case 2: + // x = NewRegistrar("myThing", "THING") + // x = NewRegistrar("myThing", { metakey: metavalue } ) + if (typeof arguments[1] === 'object') { + return oldNewRegistrar(arguments[0], "-", arguments[1]) + } + break; + default: // do nothing + } + return oldNewRegistrar.apply(null, arguments) +} +function oldNewRegistrar(name, type, meta) { if (type) { type == 'MANUAL'; } @@ -46,6 +63,23 @@ function NewRegistrar(name, type, meta) { } function NewDnsProvider(name, type, meta) { + // For backwards compatibility, this is a wrapper around the legacy + // version of this function. + switch (arguments.length) { + case 1: + return oldNewDnsProvider(arguments[0], "-") + case 2: + // x = NewDnsProvider("myThing", "THING") + // x = NewDnsProvider("myThing", { metakey: metavalue } ) + if (typeof arguments[1] === 'object') { + return oldNewDnsProvider(arguments[0], "-", arguments[1]) + } + break; + default: // do nothing + } + return oldNewDnsProvider.apply(null, arguments) +} +function oldNewDnsProvider(name, type, meta) { if (typeof meta === 'object' && 'ip_conversions' in meta) { meta.ip_conversions = format_tt(meta.ip_conversions); } diff --git a/pkg/js/js_test.go b/pkg/js/js_test.go index 9fcab8ef1..b9aceb246 100644 --- a/pkg/js/js_test.go +++ b/pkg/js/js_test.go @@ -54,6 +54,13 @@ func TestParsedFiles(t *testing.T) { // Initialize any DNS providers mentioned. for _, dProv := range conf.DNSProviders { var pcfg = map[string]string{} + + if dProv.Type == "-" { + // Pretend any "look up provider type in creds.json" results + // in a provider type that actually exists. + dProv.Type = "CLOUDFLAREAPI" + } + // Fake out any provider's validation tests. switch dProv.Type { case "CLOUDFLAREAPI": diff --git a/pkg/js/parse_tests/041-newstyleproviders.js b/pkg/js/parse_tests/041-newstyleproviders.js new file mode 100755 index 000000000..53580549a --- /dev/null +++ b/pkg/js/parse_tests/041-newstyleproviders.js @@ -0,0 +1,31 @@ +// Test old-style and new-style New*() functions. + +var REG1 = NewRegistrar("foo1"); +var CF1 = NewDnsProvider("dns1"); + +var REG2a = NewRegistrar("foo2a", "NONE"); +var CF2a = NewDnsProvider("dns2a", "CLOUDFLAREAPI"); + +var REG2b = NewRegistrar("foo2b", { + regmetakey: "reg2b" +}); +var CF2b = NewDnsProvider("dns2b", { + dnsmetakey: "dns2b" +}); + +var REG3 = NewRegistrar("foo3", "MANUAL", { + regmetakey: "reg3" +}); +var CF3 = NewDnsProvider("dns3", "CLOUDFLAREAPI", { + dnsmetakey: "dns3" +}); + +var REG1h = NewRegistrar("foo1h", "-"); +var CF1h = NewDnsProvider("dns1h", "-"); + +var REG2bh = NewRegistrar("foo2bh", "-", { + regmetakey: "reg2bh" +}); +var CF2bh = NewDnsProvider("dns2bh", "-", { + dnsmetakey: "dns2bh" +}); diff --git a/pkg/js/parse_tests/041-newstyleproviders.json b/pkg/js/parse_tests/041-newstyleproviders.json new file mode 100644 index 000000000..b5ab9b07b --- /dev/null +++ b/pkg/js/parse_tests/041-newstyleproviders.json @@ -0,0 +1,19 @@ +{ + "dns_providers": [ + { "name": "dns1", "type": "CLOUDFLAREAPI" }, + { "name": "dns2a", "type": "CLOUDFLAREAPI" }, + { "name": "dns2b", "type": "CLOUDFLAREAPI", "meta": { "dnsmetakey": "dns2b" } }, + { "name": "dns3", "type": "CLOUDFLAREAPI", "meta": { "dnsmetakey": "dns3" } }, + { "name": "dns1h", "type": "CLOUDFLAREAPI" }, + { "name": "dns2bh", "type": "CLOUDFLAREAPI", "meta": { "dnsmetakey": "dns2bh" } } + ], + "domains": [], + "registrars": [ + { "name": "foo1", "type": "-" }, + { "name": "foo2a", "type": "NONE" }, + { "name": "foo2b", "type": "-", "meta": { "regmetakey": "reg2b" } }, + { "name": "foo3", "type": "MANUAL", "meta": { "regmetakey": "reg3" } }, + { "name": "foo1h", "type": "-" }, + { "name": "foo2bh", "type": "-", "meta": { "regmetakey": "reg2bh" } } + ] +} diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index b7da5b6db..26ae6bca0 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -464,7 +464,7 @@ func ValidateAndNormalizeConfig(config *models.DNSConfig) (errs []error) { // At this point we've munged anything that needs to be munged, and // validated anything that can be globally validated. - // Let's ask // the provider if there are any records they can't handle. + // Let's ask the provider if there are any records they can't handle. for _, domain := range config.Domains { // For each domain.. for _, provider := range domain.DNSProviderInstances { // For each provider... if err := providers.AuditRecords(provider.ProviderBase.ProviderType, domain.Records); err != nil { diff --git a/providers/providers.go b/providers/providers.go index 7dc54d75a..bbcd45a48 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -57,7 +57,7 @@ var DNSProviderTypes = map[string]DspFuncs{} // RegisterRegistrarType adds a registrar type to the registry by providing a suitable initialization function. func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...ProviderMetadata) { if _, ok := RegistrarTypes[name]; ok { - log.Fatalf("Cannot register registrar type %s multiple times", name) + log.Fatalf("Cannot register registrar type %q multiple times", name) } RegistrarTypes[name] = init unwrapProviderCapabilities(name, pm) @@ -66,7 +66,7 @@ func RegisterRegistrarType(name string, init RegistrarInitializer, pm ...Provide // RegisterDomainServiceProviderType adds a dsp to the registry with the given initialization function. func RegisterDomainServiceProviderType(name string, fns DspFuncs, pm ...ProviderMetadata) { if _, ok := DNSProviderTypes[name]; ok { - log.Fatalf("Cannot register registrar type %s multiple times", name) + log.Fatalf("Cannot register registrar type %q multiple times", name) } DNSProviderTypes[name] = fns unwrapProviderCapabilities(name, pm) @@ -74,30 +74,71 @@ func RegisterDomainServiceProviderType(name string, fns DspFuncs, pm ...Provider // CreateRegistrar initializes a registrar instance from given credentials. func CreateRegistrar(rType string, config map[string]string) (Registrar, error) { + var err error + rType, err = beCompatible(rType, config) + if err != nil { + return nil, err + } + initer, ok := RegistrarTypes[rType] if !ok { - return nil, fmt.Errorf("registrar type %s not declared", rType) + return nil, fmt.Errorf("No such registrar type: %q", rType) } return initer(config) } // CreateDNSProvider initializes a dns provider instance from given credentials. -func CreateDNSProvider(dType string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) { - p, ok := DNSProviderTypes[dType] +func CreateDNSProvider(providerTypeName string, config map[string]string, meta json.RawMessage) (DNSServiceProvider, error) { + var err error + providerTypeName, err = beCompatible(providerTypeName, config) + if err != nil { + return nil, err + } + + p, ok := DNSProviderTypes[providerTypeName] if !ok { - return nil, fmt.Errorf("DSP type %s not declared", dType) + return nil, fmt.Errorf("No such DNS service provider: %q", providerTypeName) } return p.Initializer(config, meta) } +// beCompatible looks up +func beCompatible(n string, config map[string]string) (string, error) { + // Pre 4.0: If n is a placeholder, substitute the TYPE from creds.json. + // 4.0: Require TYPE from creds.json. + + ct := config["TYPE"] + // If a placeholder value was specified... + if n == "" || n == "-" { + // But no TYPE exists in creds.json... + if ct == "" { + return "-", fmt.Errorf("creds.json entry missing TYPE field") + } + // Otherwise, use the value from creds.json. + return ct, nil + } + + // Pre 4.0: The user specified the name manually. + // Cross check to detect user-error. + if ct != "" && n != ct { + return "", fmt.Errorf("creds.json entry mismatch: specified=%q TYPE=%q", n, ct) + } + // Seems like the user did it the right way. Return the original value. + return n, nil + + // NB(tlim): My hope is that in 4.0 this entire function will simply be the + // following, but I may be wrong: + //return config["TYPE"], nil +} + // AuditRecords calls the RecordAudit function for a provider. func AuditRecords(dType string, rcs models.Records) error { p, ok := DNSProviderTypes[dType] if !ok { - return fmt.Errorf("DSP type %s not declared", dType) + return fmt.Errorf("Unknown DNS service provider type: %q", dType) } if p.RecordAuditor == nil { - return fmt.Errorf("DSP type %s has no RecordAuditor", dType) + return fmt.Errorf("DNS service provider type %q has no RecordAuditor", dType) } return p.RecordAuditor(rcs) }