dnscontrol/commands/previewPush.go
Tom Limoncelli 304515d137
FEATURE: Remove ppreview/ppush and cmode=legacy (#3412)
Co-authored-by: Jeffrey Cafferata <jeffrey@jcid.nl>
2025-02-05 15:25:30 -05:00

267 lines
9.1 KiB
Go

package commands
import (
"fmt"
"os"
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/notifications"
"github.com/StackExchange/dnscontrol/v4/providers"
)
// ReportItem is a record of corrections for a particular domain/provider/registrar.
type ReportItem struct {
Domain string `json:"domain"`
Corrections int `json:"corrections"`
Provider string `json:"provider,omitempty"`
Registrar string `json:"registrar,omitempty"`
}
// InitializeProviders takes (fully processed) configuration and instantiates all providers and returns them.
func InitializeProviders(cfg *models.DNSConfig, providerConfigs map[string]map[string]string, notifyFlag bool) (notify notifications.Notifier, err error) {
var notificationCfg map[string]string
defer func() {
notify = notifications.Init(notificationCfg)
}()
if notifyFlag {
notificationCfg = providerConfigs["notifications"]
}
isNonDefault := map[string]bool{}
for name, vals := range providerConfigs {
// add "_exclude_from_defaults":"true" to a provider to exclude it from being run unless
// -providers=all or -providers=name
if vals["_exclude_from_defaults"] == "true" {
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 notify, err
}
registrars := map[string]providers.Registrar{}
dnsProviders := map[string]providers.DNSServiceProvider{}
for _, d := range cfg.Domains {
if registrars[d.RegistrarName] == nil {
rCfg := cfg.RegistrarsByName[d.RegistrarName]
r, err := providers.CreateRegistrar(rCfg.Type, providerConfigs[d.RegistrarName])
if err != nil {
return nil, err
}
registrars[d.RegistrarName] = r
}
d.RegistrarInstance.Driver = registrars[d.RegistrarName]
d.RegistrarInstance.IsDefault = !isNonDefault[d.RegistrarName]
for _, pInst := range d.DNSProviderInstances {
if dnsProviders[pInst.Name] == nil {
dCfg := cfg.DNSProvidersByName[pInst.Name]
prov, err := providers.CreateDNSProvider(dCfg.Type, providerConfigs[dCfg.Name], dCfg.Metadata)
if err != nil {
return nil, err
}
dnsProviders[pInst.Name] = prov
}
pInst.Driver = dnsProviders[pInst.Name]
pInst.IsDefault = !isNonDefault[pInst.Name]
}
}
return notify, err
}
// 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://docs.dnscontrol.org/commands/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 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
}
}