mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
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
This commit is contained in:
@ -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
|
||||
|
@ -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 {
|
||||
|
58
commands/previewPush_test.go
Normal file
58
commands/previewPush_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user