NEW FEATURE: Gather data for providers concurrently (#2873)

This commit is contained in:
Tom Limoncelli 2024-03-27 13:54:36 -04:00 committed by GitHub
parent 408a70ec76
commit 68c5e87c89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 933 additions and 67 deletions

View file

@ -159,9 +159,9 @@ release:
## Deprecation warnings
> [!WARNING]
> - **32-bit binaries will no longer be distributed after September 10, 2023.** There is a proposal to stop shipping 32-bit binaries (packages and containers). If no objections are raised by Sept 10, 2023, new releases will not include them. See https://github.com/StackExchange/dnscontrol/issues/2461 for details.
> - **MSDNS maintainer needed!** Without a new volunteer, this DNS provider will lose support after April 2025. See https://github.com/StackExchange/dnscontrol/issues/2878
> - **Call for new volunteer maintainers for NAMEDOTCOM and SOFTLAYER.** These providers have no maintainer. Maintainers respond to PRs and fix bugs in a timely manner, and try to stay on top of protocol changes.
> - **ACME/Let's Encrypt support is frozen and will be removed after December 31, 2022.** The `get-certs` command (renews certs via Let's Encrypt) has no maintainer. There are other projects that do a better job. If you don't use this feature, please do not start. If you do use this feature, please plan on migrating to something else. See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400)
> - **ACME/Let's Encrypt support is frozen and will be removed without notice between now and April 2025.** It has been unsupported since December 2022. If you don't use this feature, do not start. If you do use this feature, migrate ASAP. See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400)
## Install

View file

@ -158,11 +158,11 @@ DNSControl can be installed via packages for macOS, Linux and Windows, or from s
See [dnscontrol-action](https://github.com/koenrh/dnscontrol-action) or [gacts/install-dnscontrol](https://github.com/gacts/install-dnscontrol).
## Deprecation warnings (updated 2024-02-24)
## Deprecation warnings (updated 2024-03-25)
- **32-bit binaries will no longer be distributed after September 10, 2023.** There is a proposal to stop shipping 32-bit binaries (packages and containers). If no objections are raised by Sept 10, 2023, new releases will not include them. See https://github.com/StackExchange/dnscontrol/issues/2461 for details.
- **MSDNS maintainer needed!** Without a new volunteer, this DNS provider will lose support after April 2025. See https://github.com/StackExchange/dnscontrol/issues/2878
- **Call for new volunteer maintainers for NAMEDOTCOM and SOFTLAYER.** These providers have no maintainer. Maintainers respond to PRs and fix bugs in a timely manner, and try to stay on top of protocol changes.
- **ACME/Let's Encrypt support is frozen and will be removed after December 31, 2022.** The `get-certs` command (renews certs via Let's Encrypt) has no maintainer. There are other projects that do a better job. If you don't use this feature, please do not start. If you do use this feature, please plan on migrating to something else. See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400)
- **ACME/Let's Encrypt support is frozen and will be removed without notice between now and April 2025.** It has been unsupported since December 2022. If you don't use this feature, do not start. If you do use this feature, migrate ASAP. See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400)
## More info at our website

783
commands/ppreviewPush.go Normal file
View file

@ -0,0 +1,783 @@
package commands
import (
"cmp"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"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/nameservers"
"github.com/StackExchange/dnscontrol/v4/pkg/normalize"
"github.com/StackExchange/dnscontrol/v4/pkg/notifications"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/zonerecs"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/urfave/cli/v2"
"golang.org/x/exp/slices"
"golang.org/x/net/idna"
)
type zoneCache struct {
cache map[string]*[]string
sync.Mutex
}
var _ = cmd(catMain, func() *cli.Command {
var args PPreviewArgs
return &cli.Command{
Name: "ppreview",
Usage: "read live configuration and identify changes to be made, without applying them",
Action: func(ctx *cli.Context) error {
return exit(PPreview(args))
},
Flags: args.flags(),
}
}())
// PPreviewArgs contains all data/flags needed to run preview, independently of CLI
type PPreviewArgs struct {
GetDNSConfigArgs
GetCredentialsArgs
FilterArgs
Notify bool
WarnChanges bool
ConcurMode string
NoPopulate bool
DePopulate bool
Full bool
}
// 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"`
//}
func (args *PPreviewArgs) flags() []cli.Flag {
flags := args.GetDNSConfigArgs.flags()
flags = append(flags, args.GetCredentialsArgs.flags()...)
flags = append(flags, args.FilterArgs.flags()...)
flags = append(flags, &cli.BoolFlag{
Name: "notify",
Destination: &args.Notify,
Usage: `set to true to send notifications to configured destinations`,
})
flags = append(flags, &cli.BoolFlag{
Name: "expect-no-changes",
Destination: &args.WarnChanges,
Usage: `set to true for non-zero return code if there are changes`,
})
flags = append(flags, &cli.StringFlag{
Name: "cmode",
Destination: &args.ConcurMode,
Value: "default",
Usage: `Which providers to run concurrently: all, default, none`,
Action: func(c *cli.Context, s string) error {
if !slices.Contains([]string{"all", "default", "none"}, s) {
fmt.Printf("%q is not a valid option for --cmode. Valie are: all, default, none", s)
}
return nil
},
})
flags = append(flags, &cli.BoolFlag{
Name: "no-populate",
Destination: &args.NoPopulate,
Usage: `Do not auto-create zones at the provider`,
})
flags = append(flags, &cli.BoolFlag{
Name: "depopulate",
Destination: &args.NoPopulate,
Usage: `Delete unknown zones at provider (dangerous!)`,
})
flags = append(flags, &cli.BoolFlag{
Name: "full",
Destination: &args.Full,
Usage: `Add headings, providers names, notifications of no changes, etc`,
})
flags = append(flags, &cli.IntFlag{
Name: "reportmax",
Hidden: true,
Usage: `Limit the IGNORE/NO_PURGE report to this many lines (Expermental. Will change in the future.)`,
Action: func(ctx *cli.Context, max int) error {
printer.MaxReport = max
return nil
},
})
flags = append(flags, &cli.Int64Flag{
Name: "bindserial",
Destination: &bindserial.ForcedValue,
Usage: `Force BIND serial numbers to this value (for reproducibility)`,
})
return flags
}
var _ = cmd(catMain, func() *cli.Command {
var args PPushArgs
return &cli.Command{
Name: "ppush",
Usage: "identify changes to be made, and perform them",
Action: func(ctx *cli.Context) error {
return exit(PPush(args))
},
Flags: args.flags(),
}
}())
// PPushArgs contains all data/flags needed to run push, independently of CLI
type PPushArgs struct {
PPreviewArgs
Interactive bool
Report string
}
func (args *PPushArgs) flags() []cli.Flag {
flags := args.PPreviewArgs.flags()
flags = append(flags, &cli.BoolFlag{
Name: "i",
Destination: &args.Interactive,
Usage: "Interactive. Confirm or Exclude each correction before they run",
})
flags = append(flags, &cli.StringFlag{
Name: "report",
Destination: &args.Report,
Usage: `Generate a machine-parseable report of performed corrections.`,
})
return flags
}
// PPreview implements the preview subcommand.
func PPreview(args PPreviewArgs) error {
return prun(args, false, false, printer.DefaultPrinter, "")
}
// PPush implements the push subcommand.
func PPush(args PPushArgs) error {
return prun(args.PPreviewArgs, true, args.Interactive, printer.DefaultPrinter, args.Report)
}
var pobsoleteDiff2FlagUsed = false
// run is the main routine common to preview/push
func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, report string) error {
// This is a hack until we have the new printer replacement.
printer.SkinnyReport = !args.Full
fullMode := args.Full
if pobsoleteDiff2FlagUsed {
printer.Println("WARNING: Please remove obsolete --diff2 flag. This will be an error in v5 or later. See https://github.com/StackExchange/dnscontrol/issues/2262")
}
out.PrintfIf(fullMode, "Reading dnsconfig.js or equiv.\n")
cfg, err := GetDNSConfig(args.GetDNSConfigArgs)
if err != nil {
return err
}
out.PrintfIf(fullMode, "Reading creds.json or equiv.\n")
providerConfigs, err := credsfile.LoadProviderConfigs(args.CredsFile)
if err != nil {
return err
}
out.PrintfIf(fullMode, "Creating an in-memory model of 'desired'...\n")
notifier, err := PInitializeProviders(cfg, providerConfigs, args.Notify)
if err != nil {
return err
}
out.PrintfIf(fullMode, "Normalizing and validating 'desired'..\n")
errs := normalize.ValidateAndNormalizeConfig(cfg)
if PrintValidationErrors(errs) {
return fmt.Errorf("exiting due to validation errors")
}
zcache := NewZoneCache()
// Loop over all (or some) zones:
zonesToProcess := whichZonesToProcess(cfg.Domains, args.Domains)
zonesSerial, zonesConcurrent := splitConcurrent(zonesToProcess, args.ConcurMode)
out.PrintfIf(fullMode, "PHASE 1: GATHERING data\n")
var wg sync.WaitGroup
wg.Add(len(zonesConcurrent))
out.Printf("CONCURRENTLY gathering %d zone(s)\n", len(zonesConcurrent))
for _, zone := range optimizeOrder(zonesConcurrent) {
out.PrintfIf(fullMode, "Concurrently gathering: %q\n", zone.Name)
go func(zone *models.DomainConfig, args PPreviewArgs, zcache *zoneCache) {
defer wg.Done()
oneZone(zone, args, zcache)
}(zone, args, zcache)
}
out.Printf("SERIALLY gathering %d zone(s)\n", len(zonesSerial))
for _, zone := range zonesSerial {
out.Printf("Serially Gathering: %q\n", zone.Name)
oneZone(zone, args, zcache)
}
out.PrintfIf(len(zonesConcurrent) > 0, "Waiting for concurrent gathering(s) to complete...")
wg.Wait()
out.PrintfIf(len(zonesConcurrent) > 0, "DONE\n")
// Now we know what to do, print or do the tasks.
out.PrintfIf(fullMode, "PHASE 2: CORRECTIONS\n")
var totalCorrections int
var reportItems []*ReportItem
var anyErrors bool
for _, zone := range zonesToProcess {
out.StartDomain(zone.GetUniqueName())
providersToProcess := whichProvidersToProcess(zone.DNSProviderInstances, args.Providers)
for _, provider := range zone.DNSProviderInstances {
skip := skipProvider(provider.Name, providersToProcess)
out.StartDNSProvider(provider.Name, skip)
if !skip {
corrections := zone.GetCorrections(provider.Name)
totalCorrections += len(corrections)
reportItems = append(reportItems, genReportItem(zone.Name, corrections, provider.Name))
anyErrors = cmp.Or(anyErrors, pprintOrRunCorrections(zone.Name, provider.Name, corrections, out, push, interactive, notifier, report))
out.EndProvider(provider.Name, len(corrections), nil)
}
}
skip := skipProvider(zone.RegistrarInstance.Name, providersToProcess)
out.StartRegistrar(zone.RegistrarName, !skip)
if skip {
corrections := zone.GetCorrections(zone.RegistrarInstance.Name)
totalCorrections += len(corrections)
reportItems = append(reportItems, genReportItem(zone.Name, corrections, zone.RegistrarName))
anyErrors = cmp.Or(anyErrors, pprintOrRunCorrections(zone.Name, zone.RegistrarInstance.Name, corrections, out, push, interactive, notifier, report))
out.EndProvider(zone.RegistrarName, len(corrections), nil)
}
}
if os.Getenv("TEAMCITY_VERSION") != "" {
fmt.Fprintf(os.Stderr, "##teamcity[buildStatus status='SUCCESS' text='%d corrections']", totalCorrections)
}
notifier.Done()
out.Printf("Done. %d corrections.\n", totalCorrections)
err = writeReport(report, reportItems)
if err != nil {
return fmt.Errorf("could not write report")
}
if anyErrors {
return fmt.Errorf("completed with errors")
}
if totalCorrections != 0 && args.WarnChanges {
return fmt.Errorf("there are pending changes")
}
return nil
}
func whichZonesToProcess(domains []*models.DomainConfig, filter string) []*models.DomainConfig {
if filter == "" || filter == "all" {
return domains
}
permitList := strings.Split(filter, ",")
var picked []*models.DomainConfig
for _, domain := range domains {
if domainInList(domain.Name, permitList) {
picked = append(picked, domain)
}
}
return picked
}
// splitConcurrent takes a list of DomainConfigs and returns two lists. The
// first list is the items that do NOT support concurrency. The second is list
// the items that DO support concurrency.
func splitConcurrent(domains []*models.DomainConfig, filter string) (serial []*models.DomainConfig, concurrent []*models.DomainConfig) {
if filter == "none" {
return domains, nil
} else if filter == "all" {
return nil, domains
}
for _, dc := range domains {
if allConcur(dc) {
concurrent = append(concurrent, dc)
} else {
serial = append(serial, dc)
}
}
return
}
// allConcur returns true if its registrar and all DNS providers support
// concurrency. Otherwise false is returned.
func allConcur(dc *models.DomainConfig) bool {
if !providers.ProviderHasCapability(dc.RegistrarInstance.ProviderType, providers.CanConcur) {
//fmt.Printf("WHY? %q: %+v\n", dc.Name, dc.RegistrarInstance)
return false
}
for _, p := range dc.DNSProviderInstances {
if !providers.ProviderHasCapability(p.ProviderType, providers.CanConcur) {
//fmt.Printf("WHY? %q: %+v\n", dc.Name, p)
return false
}
}
return true
}
// optimizeOrder returns a list of DomainConfigs so that they gather fastest.
//
// The current algorithm is based on the heuistic that larger zones (zones with
// the most records) need the most time to be processed. Therefore, the largest
// zones are moved to the front of the list.
// This isn't perfect but it is good enough.
func optimizeOrder(zones []*models.DomainConfig) []*models.DomainConfig {
slices.SortFunc(zones, func(a, b *models.DomainConfig) int {
return len(b.Records) - len(a.Records) // Biggest to smallest.
})
// // For benchmarking. Randomize the list. If you aren't better
// // than random, you might as well not play.
// rand.Shuffle(len(zones), func(i, j int) {
// zones[i], zones[j] = zones[j], zones[i]
// })
return zones
}
func oneZone(zone *models.DomainConfig, args PPreviewArgs, zc *zoneCache) {
// Fix the parent zone's delegation: (if able/needed)
//zone.NameserversMutex.Lock()
delegationCorrections := generateDelegationCorrections(zone, zone.DNSProviderInstances, zone.RegistrarInstance)
//zone.NameserversMutex.Unlock()
// Loop over the (selected) providers configured for that zone:
providersToProcess := whichProvidersToProcess(zone.DNSProviderInstances, args.Providers)
for _, provider := range providersToProcess {
// Populate the zones at the provider (if desired/needed/able):
if !args.NoPopulate {
populateCorrections := generatePopulateCorrections(provider, zone.Name, zc)
zone.StoreCorrections(provider.Name, populateCorrections)
}
// Update the zone's records at the provider:
zoneCor, rep := generateZoneCorrections(zone, provider)
zone.StoreCorrections(provider.Name, rep)
zone.StoreCorrections(provider.Name, zoneCor)
}
// Do the delegation corrections after the zones are updated.
zone.StoreCorrections(zone.RegistrarInstance.Name, delegationCorrections)
}
func whichProvidersToProcess(providers []*models.DNSProviderInstance, filter string) []*models.DNSProviderInstance {
if filter == "all" { // all
return providers
}
permitList := strings.Split(filter, ",")
var picked []*models.DNSProviderInstance
// Just the default providers:
if filter == "" {
for _, provider := range providers {
if provider.IsDefault {
picked = append(picked, provider)
}
}
return picked
}
// Just the exact matches:
for _, provider := range providers {
for _, filterItem := range permitList {
if provider.Name == filterItem {
picked = append(picked, provider)
}
}
}
return picked
}
func skipProvider(name string, providers []*models.DNSProviderInstance) bool {
return !slices.ContainsFunc(providers, func(p *models.DNSProviderInstance) bool {
return p.Name == name
})
}
func genReportItem(zname string, corrections []*models.Correction, pname string) *ReportItem {
// Only count the actions, not the messages.
cnt := 0
for _, cor := range corrections {
if cor.F != nil {
cnt++
}
}
r := ReportItem{
Domain: zname,
Corrections: cnt,
Provider: pname,
}
return &r
}
func pprintOrRunCorrections(zoneName string, providerName string, corrections []*models.Correction, out printer.CLI, push bool, interactive bool, notifier notifications.Notifier, report string) bool {
if len(corrections) == 0 {
return false
}
var anyErrors bool
for i, correction := range corrections {
out.PrintCorrection(i, correction)
var err error
if push {
if interactive && !out.PromptToRun() {
continue
}
if correction.F != nil {
err = correction.F()
if err != nil {
anyErrors = true
}
}
out.EndCorrection(err)
}
notifier.Notify(zoneName, providerName, correction.Msg, err, !push)
}
_ = report // File name to write report to.
return anyErrors
}
func writeReport(report string, reportItems []*ReportItem) error {
// No filename? No report.
if report == "" {
return nil
}
f, err := os.OpenFile(report, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
b, err := json.MarshalIndent(reportItems, "", " ")
if err != nil {
return err
}
if _, err := f.Write(b); err != nil {
return err
}
return nil
}
func generatePopulateCorrections(provider *models.DNSProviderInstance, zoneName string, zcache *zoneCache) []*models.Correction {
lister, ok := provider.Driver.(providers.ZoneLister)
if !ok {
return nil // We can't generate a list. No corrections are possible.
}
z, err := zcache.zoneList(provider.Name, lister)
if err != nil {
return []*models.Correction{{Msg: fmt.Sprintf("zoneList failed for %q: %s", provider.Name, err)}}
}
zones := *z
aceZoneName, _ := idna.ToASCII(zoneName)
if slices.Contains(zones, aceZoneName) {
return nil // zone exists. Nothing to do.
}
creator, ok := provider.Driver.(providers.ZoneCreator)
if !ok {
return []*models.Correction{{Msg: fmt.Sprintf("Zone %q does not exist. Can not create because %q does not implement ZoneCreator", aceZoneName, provider.Name)}}
}
return []*models.Correction{{
Msg: fmt.Sprintf("Create zone '%s' in the '%s' profile", aceZoneName, provider.Name),
F: func() error { return creator.EnsureZoneExists(aceZoneName) },
}}
}
func generateZoneCorrections(zone *models.DomainConfig, provider *models.DNSProviderInstance) ([]*models.Correction, []*models.Correction) {
reports, zoneCorrections, err := zonerecs.CorrectZoneRecords(provider.Driver, zone)
if err != nil {
return []*models.Correction{{Msg: fmt.Sprintf("Domain %q provider %s Error: %s", zone.Name, provider.Name, err)}}, nil
}
return zoneCorrections, reports
}
func generateDelegationCorrections(zone *models.DomainConfig, providers []*models.DNSProviderInstance, _ *models.RegistrarInstance) []*models.Correction {
//fmt.Printf("DEBUG: generateDelegationCorrections start zone=%q nsList = %v\n", zone.Name, zone.Nameservers)
nsList, err := nameservers.DetermineNameserversForProviders(zone, providers, true)
if err != nil {
return msg(fmt.Sprintf("DtermineNS: zone %q; Error: %s", zone.Name, err))
}
zone.Nameservers = nsList
nameservers.AddNSRecords(zone)
if len(zone.Nameservers) == 0 && zone.Metadata["no_ns"] != "true" {
return []*models.Correction{{Msg: fmt.Sprintf("No nameservers declared for domain %q; skipping registrar. Add {no_ns:'true'} to force", zone.Name)}}
}
corrections, err := zone.RegistrarInstance.Driver.GetRegistrarCorrections(zone)
if err != nil {
return msg(fmt.Sprintf("zone %q; Rprovider %q; Error: %s", zone.Name, zone.RegistrarInstance.Name, err))
}
return corrections
}
func msg(s string) []*models.Correction {
return []*models.Correction{{Msg: s}}
}
// PInitializeProviders takes (fully processed) configuration and instantiates all providers and returns them.
func PInitializeProviders(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 := ppopulateProviderTypes(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 {
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
}
// pproviderTypeFieldName is the name of the field in creds.json that specifies the provider type id.
const pproviderTypeFieldName = "TYPE"
// ppurl is the documentation URL to list in the warnings related to missing provider type ids.
const purl = "https://docs.dnscontrol.org/commands/creds-json"
// ppopulateProviderTypes 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 ppopulateProviderTypes(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 := prefineProviderType(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 := prefineProviderType(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 := prefineProviderType(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 := prefineProviderType(pName, pType, providerConfigs[pName], "NewRegistrar")
p.ProviderType = nt
if warnMsg != "" {
msgs = append(msgs, warnMsg)
}
if err != nil {
return msgs, err
}
}
return puniqueStrings(msgs), nil
}
// puniqueStrings takes an unsorted slice of strings and returns the
// unique strings, in the order they first appeared in the list.
func puniqueStrings(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 prefineProviderType(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, pproviderTypeFieldName, t,
purl,
), nil
}
switch ct := credFields[pproviderTypeFieldName]; 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,
pproviderTypeFieldName, t,
purl,
), 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, pproviderTypeFieldName, ct,
purl,
)
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,
purl,
), 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,
pproviderTypeFieldName, ct,
source, credEntryName, t,
purl,
)
}
}
// 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, pproviderTypeFieldName, "FILL_IN_PROVIDER_TYPE",
purl,
)
}
// New-style, dnsconfig.js doesn't specifies the type. It will be
// looked up in creds.json.
switch ct := credFields[pproviderTypeFieldName]; ct {
case "":
return ct, "", fmt.Errorf(`ERROR: creds.json entry %q is missing: %q: %q, (See %s#fixcreds)`,
credEntryName,
pproviderTypeFieldName, "FILL_IN_PROVIDER_TYPE",
purl,
)
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,
pproviderTypeFieldName, ct,
purl,
)
default:
// use the value in creds.json (this should be the normal case)
return ct, "", nil
}
}

View file

@ -230,7 +230,7 @@ func run(args PreviewArgs, push bool, interactive bool, out printer.CLI, report
// Correct the registrar...
nsList, err := nameservers.DetermineNameserversForProviders(domain, providersWithExistingZone)
nsList, err := nameservers.DetermineNameserversForProviders(domain, providersWithExistingZone, false)
if err != nil {
out.Errorf("ERROR: %s\n", err.Error())
return

27
commands/zonecache.go Normal file
View file

@ -0,0 +1,27 @@
package commands
import "github.com/StackExchange/dnscontrol/v4/providers"
func NewZoneCache() *zoneCache {
return &zoneCache{}
}
func (zc *zoneCache) zoneList(name string, lister providers.ZoneLister) (*[]string, error) {
zc.Lock()
defer zc.Unlock()
if zc.cache == nil {
zc.cache = map[string]*[]string{}
}
if v, ok := zc.cache[name]; ok {
return v, nil
}
zones, err := lister.ListZones()
if err != nil {
return nil, err
}
zc.cache[name] = &zones
return &zones, nil
}

View file

@ -17,13 +17,13 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`AKAMAIEDGEDNS`](providers/akamaiedgedns.md) | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❔ | ✅ | ✅ | ✅ |
| [`AUTODNS`](providers/autodns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❔ | ❔ | ❔ | ❌ | ❔ | ✅ | ❌ | ❌ | ❌ | ❔ | ❌ | ❌ | ✅ |
| [`AXFRDDNS`](providers/axfrddns.md) | ❌ | ✅ | ❌ | ❌ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ✅ | ❌ | ❌ | ❌ |
| [`AZURE_DNS`](providers/azure_dns.md) | ✅ | ✅ | ❌ | | ❌ | ✅ | ❔ | ❌ | ❌ | ✅ | ❔ | ✅ | ❌ | ❌ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`AZURE_DNS`](providers/azure_dns.md) | ✅ | ✅ | ❌ | | ❌ | ✅ | ❔ | ❌ | ❌ | ✅ | ❔ | ✅ | ❌ | ❌ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`AZURE_PRIVATE_DNS`](providers/azure_private_dns.md) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❔ | ❌ | ❌ | ✅ | ❔ | ✅ | ❌ | ❌ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`BIND`](providers/bind.md) | ✅ | ✅ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [`BUNNY_DNS`](providers/bunny_dns.md) | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| [`CLOUDFLAREAPI`](providers/cloudflareapi.md) | ✅ | ✅ | ❌ | | ✅ | ✅ | ❔ | ❌ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ❌ | ✅ | ✅ |
| [`CLOUDFLAREAPI`](providers/cloudflareapi.md) | ✅ | ✅ | ❌ | | ✅ | ✅ | ❔ | ❌ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ❌ | ✅ | ✅ |
| [`CLOUDNS`](providers/cloudns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ❔ | ✅ | ✅ |
| [`CSCGLOBAL`](providers/cscglobal.md) | ✅ | ✅ | ✅ | | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ |
| [`CSCGLOBAL`](providers/cscglobal.md) | ✅ | ✅ | ✅ | | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ✅ |
| [`DESEC`](providers/desec.md) | ❌ | ✅ | ❌ | ❌ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ✅ | ✅ |
| [`DIGITALOCEAN`](providers/digitalocean.md) | ❌ | ✅ | ❌ | ❌ | ❔ | ✅ | ❔ | ❌ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ |
| [`DNSIMPLE`](providers/dnsimple.md) | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❔ | ✅ | ✅ | ❌ | ❌ | ❔ | ❌ | ❌ | ✅ |
@ -34,7 +34,7 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`EASYNAME`](providers/easyname.md) | ❌ | ❌ | ✅ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ❔ |
| [`EXOSCALE`](providers/exoscale.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❌ | ❔ | ❔ | ❌ | ❌ | ❔ |
| [`GANDI_V5`](providers/gandi_v5.md) | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ✅ |
| [`GCLOUD`](providers/gcloud.md) | ✅ | ✅ | ❌ | | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`GCLOUD`](providers/gcloud.md) | ✅ | ✅ | ❌ | | ✅ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`GCORE`](providers/gcore.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❔ | ✅ | ❌ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ |
| [`HEDNS`](providers/hedns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❔ | ✅ | ✅ | ✅ |
| [`HETZNER`](providers/hetzner.md) | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ |
@ -59,7 +59,7 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`PORKBUN`](providers/porkbun.md) | ❌ | ✅ | ✅ | ❌ | ✅ | ❔ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ | ❔ | ❌ | ❌ | ✅ |
| [`POWERDNS`](providers/powerdns.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ |
| [`REALTIMEREGISTER`](providers/realtimeregister.md) | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| [`ROUTE53`](providers/route53.md) | ✅ | ✅ | ✅ | | ❌ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`ROUTE53`](providers/route53.md) | ✅ | ✅ | ✅ | | ❌ | ✅ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ |
| [`RWTH`](providers/rwth.md) | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ | ❔ | ❌ | ❌ | ✅ | ❔ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ❌ | ✅ |
| [`SOFTLAYER`](providers/softlayer.md) | ❌ | ✅ | ❌ | ❌ | ❔ | ❔ | ❔ | ❌ | ❔ | ❔ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ❔ |
| [`TRANSIP`](providers/transip.md) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❔ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ✅ |

View file

@ -3,6 +3,7 @@ package models
import (
"fmt"
"strings"
"sync"
"github.com/qdm12/reprint"
"golang.org/x/net/idna"
@ -23,9 +24,10 @@ type DomainConfig struct {
// 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"`
Metadata map[string]string `json:"meta,omitempty"`
Records Records `json:"records"`
Nameservers []*Nameserver `json:"nameservers,omitempty"`
NameserversMutex sync.Mutex `json:"-"`
EnsureAbsent Records `json:"recordsabsent,omitempty"` // ENSURE_ABSENT
KeepUnknown bool `json:"keepunknown,omitempty"` // NO_PURGE
@ -42,6 +44,12 @@ type DomainConfig struct {
// 2. Final driver instances are loaded after we load credentials. Any actual provider interaction requires that.
RegistrarInstance *RegistrarInstance `json:"-"`
DNSProviderInstances []*DNSProviderInstance `json:"-"`
// Pending work to do for each provider. Provider may be a registrar or DSP.
// pendingCorrectionsMutex sync.Mutex
pendingCorrections map[string]([]*Correction) // Work to be done for each provider
pendingCorrectionsOrder []string // Call the providers in this order
pendingCorrectionsMutex sync.Mutex // Protect pendingCorrections*
}
// GetSplitHorizonNames returns the domain's name, uniquename, and tag.
@ -141,3 +149,38 @@ func (dc *DomainConfig) Punycode() error {
}
return nil
}
func (dc *DomainConfig) StoreCorrections(providerName string, corrections []*Correction) {
dc.pendingCorrectionsMutex.Lock()
defer dc.pendingCorrectionsMutex.Unlock()
if dc.pendingCorrections == nil {
// First time storing anything.
dc.pendingCorrections = make(map[string]([]*Correction))
dc.pendingCorrections[providerName] = corrections
dc.pendingCorrectionsOrder = []string{providerName}
} else if c, ok := dc.pendingCorrections[providerName]; !ok {
// First time key used
dc.pendingCorrections[providerName] = corrections
dc.pendingCorrectionsOrder = []string{providerName}
} else {
// Add to existing.
dc.pendingCorrections[providerName] = append(c, corrections...)
dc.pendingCorrectionsOrder = append(dc.pendingCorrectionsOrder, providerName)
}
}
func (dc *DomainConfig) GetCorrections(providerName string) []*Correction {
dc.pendingCorrectionsMutex.Lock()
defer dc.pendingCorrectionsMutex.Unlock()
if dc.pendingCorrections == nil {
// First time storing anything.
return nil
}
if c, ok := dc.pendingCorrections[providerName]; ok {
return c
}
return nil
}

View file

@ -14,24 +14,26 @@ import (
// 1. All explicitly defined NAMESERVER records will be used.
// 2. Each DSP declares how many nameservers to use. Default is all. 0 indicates to use none.
func DetermineNameservers(dc *models.DomainConfig) ([]*models.Nameserver, error) {
return DetermineNameserversForProviders(dc, dc.DNSProviderInstances)
return DetermineNameserversForProviders(dc, dc.DNSProviderInstances, false)
}
// DetermineNameserversForProviders is like DetermineNameservers, for a subset of providers.
func DetermineNameserversForProviders(dc *models.DomainConfig, providers []*models.DNSProviderInstance) ([]*models.Nameserver, error) {
// always take explicit
func DetermineNameserversForProviders(dc *models.DomainConfig, providers []*models.DNSProviderInstance, silent bool) ([]*models.Nameserver, error) {
// start with the nameservers that have been explicitly added:
ns := dc.Nameservers
for _, dnsProvider := range providers {
n := dnsProvider.NumberOfNameservers
if n == 0 {
continue
}
if !printer.SkinnyReport {
if !silent && !printer.SkinnyReport {
fmt.Printf("----- Getting nameservers from: %s\n", dnsProvider.Name)
}
nss, err := dnsProvider.Driver.GetNameservers(dc.Name)
if err != nil {
return nil, err
return nil, fmt.Errorf("error while getting Nameservers for zone=%q with provider=%q: %w", dc.Name, dnsProvider.Name, err)
}
// Clean up the nameservers due to
// https://github.com/StackExchange/dnscontrol/issues/491

View file

@ -31,6 +31,7 @@ type Printer interface {
Println(lines ...string)
Warnf(fmt string, args ...interface{})
Errorf(fmt string, args ...interface{})
PrintfIf(print bool, fmt string, args ...interface{})
}
// Debugf is called to print/format debug information.
@ -58,6 +59,11 @@ func Warnf(fmt string, args ...interface{}) {
// DefaultPrinter.Errorf(fmt, args...)
// }
// PrintfIf is called to optionally print something.
func PrintfIf(print bool, fmt string, args ...interface{}) {
DefaultPrinter.PrintfIf(print, fmt, args...)
}
var (
// DefaultPrinter is the default Printer, used by Debugf, Printf, and Warnf.
DefaultPrinter = &ConsolePrinter{
@ -190,3 +196,10 @@ func (c ConsolePrinter) Warnf(format string, args ...interface{}) {
func (c ConsolePrinter) Errorf(format string, args ...interface{}) {
fmt.Fprintf(c.Writer, "ERROR: "+format, args...)
}
// Errorf is called to optionally print/format a message.
func (c ConsolePrinter) PrintfIf(print bool, format string, args ...interface{}) {
if print {
fmt.Fprintf(c.Writer, format, args...)
}
}

View file

@ -23,15 +23,13 @@ type azurednsProvider struct {
zones map[string]*adns.Zone
resourceGroup *string
subscriptionID *string
rawRecords map[string][]*adns.RecordSet
zoneName map[string]string
}
func newAzureDNSDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
return newAzureDNS(conf, metadata)
}
func newAzureDNS(m map[string]string, metadata json.RawMessage) (*azurednsProvider, error) {
func newAzureDNS(m map[string]string, _ json.RawMessage) (*azurednsProvider, error) {
subID, rg := m["SubscriptionID"], m["ResourceGroup"]
clientID, clientSecret, tenantID := m["ClientID"], m["ClientSecret"], m["TenantID"]
credential, authErr := aauth.NewClientSecretCredential(tenantID, clientID, clientSecret, nil)
@ -52,8 +50,6 @@ func newAzureDNS(m map[string]string, metadata json.RawMessage) (*azurednsProvid
recordsClient: recordsClient,
resourceGroup: to.StringPtr(rg),
subscriptionID: to.StringPtr(subID),
rawRecords: map[string][]*adns.RecordSet{},
zoneName: map[string]string{},
}
err := api.getZones()
if err != nil {
@ -66,7 +62,7 @@ var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.CanUseAlias: providers.Cannot("Azure DNS does not provide a generic ALIAS functionality. Use AZURE_ALIAS instead."),
providers.CanUseAzureAlias: providers.Can(),
providers.CanUseCAA: providers.Can(),
@ -187,9 +183,6 @@ func (a *azurednsProvider) getExistingRecords(domain string) (models.Records, []
existingRecords = append(existingRecords, nativeToRecords(set, zoneName)...)
}
a.rawRecords[domain] = rawRecords
a.zoneName[domain] = zoneName
return existingRecords, rawRecords, zoneName, nil
}

View file

@ -2,7 +2,9 @@
package providers
import "log"
import (
"log"
)
// Capability is a bitmasked set of "features" that a provider supports. Only use constants from this package.
type Capability uint32

View file

@ -8,6 +8,7 @@ import (
"os"
"strconv"
"strings"
"sync"
"golang.org/x/net/idna"
@ -43,7 +44,7 @@ var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.CanUseAlias: providers.Can("CF automatically flattens CNAME records into A records dynamically"),
providers.CanUseCAA: providers.Can(),
providers.CanUseDSForChildren: providers.Can(),
@ -79,6 +80,7 @@ type cloudflareProvider struct {
manageWorkers bool
accountID string
cfClient *cloudflare.API
sync.Mutex
}
// TODO(dlemenkov): remove this function after deleting all commented code referecing it
@ -94,27 +96,29 @@ type cloudflareProvider struct {
// GetNameservers returns the nameservers for a domain.
func (c *cloudflareProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
if c.domainIndex == nil {
if err := c.fetchDomainList(); err != nil {
return nil, err
}
if err := c.cacheDomainList(); err != nil {
return nil, err
}
c.Lock()
ns, ok := c.nameservers[domain]
c.Unlock()
if !ok {
return nil, fmt.Errorf("nameservers for %s not found in cloudflare account", domain)
return nil, fmt.Errorf("nameservers for %s not found in cloudflare cache(%q)", domain, c.accountID)
}
return models.ToNameservers(ns)
}
// ListZones returns a list of the DNS zones.
func (c *cloudflareProvider) ListZones() ([]string, error) {
if err := c.fetchDomainList(); err != nil {
if err := c.cacheDomainList(); err != nil {
return nil, err
}
c.Lock()
zones := make([]string, 0, len(c.domainIndex))
for d := range c.domainIndex {
zones = append(zones, d)
}
c.Unlock()
return zones, nil
}
@ -178,12 +182,12 @@ func (c *cloudflareProvider) GetZoneRecords(domain string, meta map[string]strin
}
func (c *cloudflareProvider) getDomainID(name string) (string, error) {
if c.domainIndex == nil {
if err := c.fetchDomainList(); err != nil {
return "", err
}
if err := c.cacheDomainList(); err != nil {
return "", err
}
c.Lock()
id, ok := c.domainIndex[name]
c.Unlock()
if !ok {
return "", fmt.Errorf("'%s' not a zone in cloudflare account", name)
}
@ -196,14 +200,6 @@ func (c *cloudflareProvider) GetZoneRecordsCorrections(dc *models.DomainConfig,
if err := c.preprocessConfig(dc); err != nil {
return nil, err
}
// for i := len(records) - 1; i >= 0; i-- {
// rec := records[i]
// // Delete ignore labels
// if labelMatches(dnsutil.TrimDomainName(rec.Original.(cloudflare.DNSRecord).Name, dc.Name), c.ignoredLabels) {
// printer.Debugf("ignored_label: %s\n", rec.Original.(cloudflare.DNSRecord).Name)
// records = append(records[:i], records[i+1:]...)
// }
// }
checkNSModifications(dc)
@ -222,9 +218,6 @@ func (c *cloudflareProvider) GetZoneRecordsCorrections(dc *models.DomainConfig,
if rec.Metadata[metaProxy] != "off" {
rec.TTL = 1
}
// if labelMatches(rec.GetLabel(), c.ignoredLabels) {
// log.Fatalf("FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n", rec.GetLabel(), c.ignoredLabels)
// }
}
checkNSModifications(dc)
@ -815,11 +808,14 @@ func getProxyMetadata(r *models.RecordConfig) map[string]string {
// EnsureZoneExists creates a zone if it does not exist
func (c *cloudflareProvider) EnsureZoneExists(domain string) error {
if c.domainIndex == nil {
if err := c.fetchDomainList(); err != nil {
return err
}
if err := c.cacheDomainList(); err != nil {
return err
}
// if c.domainIndex == nil {
// if err := c.fetchDomainList(); err != nil {
// return err
// }
// }
if _, ok := c.domainIndex[domain]; ok {
return nil
}

View file

@ -13,7 +13,10 @@ import (
)
// get list of domains for account. Cache so the ids can be looked up from domain name
func (c *cloudflareProvider) fetchDomainList() error {
func (c *cloudflareProvider) cacheDomainList() error {
c.Lock()
defer c.Unlock()
c.domainIndex = map[string]string{}
c.nameservers = map[string][]string{}
zones, err := c.cfClient.ListZones(context.Background())

View file

@ -28,7 +28,7 @@ var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.CanUseCAA: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.DocOfficiallySupported: providers.Can(),

View file

@ -26,7 +26,7 @@ var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.CanUseAlias: providers.Can(),
providers.CanUseCAA: providers.Can(),
providers.CanUseDSForChildren: providers.Can(),

View file

@ -171,10 +171,16 @@ func (n None) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correctio
return nil, nil
}
var featuresNone = DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
CanConcur: Can(),
}
func init() {
RegisterRegistrarType("NONE", func(map[string]string) (Registrar, error) {
return None{}, nil
})
}, featuresNone)
}
// CustomRType stores an rtype that is only valid for this DSP.

View file

@ -27,12 +27,11 @@ import (
)
type route53Provider struct {
client *r53.Client
registrar *r53d.Client
delegationSet *string
zonesByID map[string]r53Types.HostedZone
zonesByDomain map[string]r53Types.HostedZone
originalRecords []r53Types.ResourceRecordSet
client *r53.Client
registrar *r53d.Client
delegationSet *string
zonesByID map[string]r53Types.HostedZone
zonesByDomain map[string]r53Types.HostedZone
}
func newRoute53Reg(conf map[string]string) (providers.Registrar, error) {
@ -79,7 +78,7 @@ var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Cannot(),
providers.CanConcur: providers.Can(),
providers.CanUseAlias: providers.Cannot("R53 does not provide a generic ALIAS functionality. Use R53_ALIAS instead."),
providers.CanUseCAA: providers.Can(),
providers.CanUseLOC: providers.Cannot(),
@ -267,7 +266,6 @@ func (r *route53Provider) getZoneRecords(zone r53Types.HostedZone) (models.Recor
if err != nil {
return nil, err
}
r.originalRecords = records
var existingRecords = []*models.RecordConfig{}
for _, set := range records {