diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 420ee6499..00ca59f63 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -96,7 +96,7 @@ jobs: if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main' runs-on: ubuntu-latest container: - image: golang:1.21 + image: golang:1.22 needs: - integration-test-providers env: diff --git a/.github/workflows/release_draft.yml b/.github/workflows/release_draft.yml index 30f5d5007..cc918ce3a 100644 --- a/.github/workflows/release_draft.yml +++ b/.github/workflows/release_draft.yml @@ -42,7 +42,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ^1.21 + go-version: ^1.22 # Stringer is needed because .goreleaser includes "go generate ./..." - name: Install stringer diff --git a/.goreleaser.yml b/.goreleaser.yml index b0d183176..abec1e093 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -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 diff --git a/README.md b/README.md index ca8c92c00..4992f9e9a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/commands/ppreviewPush.go b/commands/ppreviewPush.go new file mode 100644 index 000000000..f04bee6d1 --- /dev/null +++ b/commands/ppreviewPush.go @@ -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 + } + +} diff --git a/commands/previewPush.go b/commands/previewPush.go index 594383224..8872c1eb1 100644 --- a/commands/previewPush.go +++ b/commands/previewPush.go @@ -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 diff --git a/commands/zonecache.go b/commands/zonecache.go new file mode 100644 index 000000000..32aa4a8e1 --- /dev/null +++ b/commands/zonecache.go @@ -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 +} diff --git a/documentation/SUMMARY.md b/documentation/SUMMARY.md index 5fc70355c..8202c7b66 100644 --- a/documentation/SUMMARY.md +++ b/documentation/SUMMARY.md @@ -8,7 +8,6 @@ * [Examples](examples.md) * [Migrating zones to DNSControl](migrating.md) * [TypeScript autocomplete and type checking](typescript.md) -* [Disabling Colors](colors.md) ## Language Reference @@ -150,10 +149,14 @@ ## Commands -* [creds.json](creds-json.md) +* [preview/push](preview-push.md) * [check-creds](check-creds.md) -* [get-certs](get-certs.md) * [get-zones](get-zones.md) +* [get-certs](get-certs.md) +* [fmt](fmt.md) +* [creds.json](creds-json.md) +* [Global Flag](globalflags.md) +* [Disabling Colors](colors.md) ## Advanced features diff --git a/documentation/preview-push.md b/documentation/preview-push.md new file mode 100644 index 000000000..2390f3531 --- /dev/null +++ b/documentation/preview-push.md @@ -0,0 +1,100 @@ +# preview/push + +`preview` reads the dnsconfig.js file (or equivalent), determines what changes are to be made, and +prints them. `push` is the same but executes the changes. + +```shell +NAME: + dnscontrol preview - read live configuration and identify changes to be made, without applying them + +USAGE: + dnscontrol preview [command options] [arguments...] + +CATEGORY: + main + +OPTIONS: + --config value File containing dns config in javascript DSL (default: "dnsconfig.js") + --dev Use helpers.js from disk instead of embedded copy (default: false) + --variable value, -v value [ --variable value, -v value ] Add variable that is passed to JS + --ir value Read IR (json) directly from this file. Do not process DSL at all + --creds value Provider credentials JSON file (or !program to execute program that outputs json) (default: "creds.json") + --providers value Providers to enable (comma separated list); default is all. Can exclude individual providers from default by adding '"_exclude_from_defaults": "true"' to the credentials file for a provider + --domains value Comma separated list of domain names to include + --notify set to true to send notifications to configured destinations (default: false) + --expect-no-changes set to true for non-zero return code if there are changes (default: false) + --no-populate Use this flag to not auto-create non-existing zones at the provider (default: false) + --full Add headings, providers names, notifications of no changes, etc (default: false) + --bindserial value Force BIND serial numbers to this value (for reproducibility) (default: 0) + --report value (push) Generate a JSON-formatted report of the number of changes made. + --help, -h show help +``` + +`--config name` -- Specifies the name of the main configuration file, normally +`dnsconfig.js`. + +`--dev` -- Developer mode. Normally `helpers.js` is embedded in the dnscontrol +executable. With this flag, the local file `helpers.js` is read instead. + +`--v foo=bar` -- Sets the variable `foo` to the value `bar` prior to +interpreting the configuration file. Multiple `-v` options can be used. + +`--creds name` -- Specifies the name of the credentials file, normally +`creds.json`. Typically the file is read. If the executable bit is set, the +file is executed and the output is used as the configuration. (That feature may +or may not work on Windows.) If the filename begins with a `|` (for example: +`|runme.sh`) the `|` is removed and the remaining string is used as the name of +the program. + +`--providers name,name2` -- Specifies a comma-separated list of providers to +enable. The default is all providers. A provider can opt out of being in the +default list by `"_exclude_from_defaults": "true"` to the credentials entry for +that provider. In that case, the provider will only be activated if it is +included in `--providers`. + +`--domains value` -- Specifies a comma-separated list of domains to include. +Typically all domains are included in `preview`/`push`. Wildcards are not +permitted except `*` at the start of the entry. For example, `--domains +example.com,*.in-addr.arpa` would include `example.com` plus all reverse lookup +domains. + +`--notify` -- Enables sending notifications to the destinations configured in +`creds.json`. + +`--expect-no-changes` -- If set, a non-zero exit code is returned if there are +changes. Normally DNSControl sets the exit code based on whether or not there +were protocol errors or other reasons the program can not continue. With this +flag set, the exit code indicates if any changes were required. This is +typically used with `preview` to allow scripts to determine if changes would +happen if `push` was used. For example, one might want to run `dnscontrol +preview --expect-no-changes` daily to determine if changes have been made to +a domain outside of DNSControl. + +`--no-populate` -- Do not auto-create non-existing zones at the provider. +Normally non-existent zones are automatically created at a provider (unless the +provider does not implement zone creation). This flag disables that feature. + +`--full` -- Add headings, providers names, notifications of no changes, etc. to +the output. Normally the output of `preview`/`push` is extremely brief. This +makes the output more verbose. Useful for debugging. + +`--bindserial value` -- Force BIND serial numbers to this value. Normally the +BIND provider generates SOA serial numbers automatically. This flag forces the +serial number generator to output the value specified for all domains. This is +generally used for reproducibility in testing pipelines. + +`--report name` -- (`push` only!) Generate a machine-parseable report of +performed corrections in the file named `name`. If no name is specified, no +report is generated. + +## Experimental + +The `ppreview`/`ppush` subcommands are a preview of a future feature where zone +data is gathered concurrently. The commands will go away when +they replace the existing `preview`/`push` commands. + +`--cmode value` -- Concurrency mode. Specifies what kind of providers should be run concurrently. This flag onl + +* `default` -- Providers are run sequentially or concurrently depending on whether the provider is marked as having been tested to run concurrently. +* `none` -- All providers are run sequentially. This is the safest mode. It can be used if a concurrency bug is discovered. +* `all` -- This is unsafe. It runs all providers concurrently, even the ones that have not be validated to run concurrently. It is generally only used for demonstrating bugs. diff --git a/documentation/providers.md b/documentation/providers.md index 5626be138..f1adf61b0 100644 --- a/documentation/providers.md +++ b/documentation/providers.md @@ -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) | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❔ | ❔ | ✅ | ✅ | ✅ | ❌ | ❔ | ❔ | ❌ | ✅ | diff --git a/go.mod b/go.mod index 5148af07f..581caa294 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/StackExchange/dnscontrol/v4 -go 1.21 - -toolchain go1.21.1 +go 1.22.1 retract v4.8.0 @@ -20,16 +18,16 @@ require ( github.com/TomOnTime/utfutil v0.0.0-20230223141146-125e65197b36 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 - github.com/aws/aws-sdk-go-v2 v1.25.3 - github.com/aws/aws-sdk-go-v2/config v1.27.7 - github.com/aws/aws-sdk-go-v2/credentials v1.17.7 - github.com/aws/aws-sdk-go-v2/service/route53 v1.40.2 - github.com/aws/aws-sdk-go-v2/service/route53domains v1.23.2 + github.com/aws/aws-sdk-go-v2 v1.26.0 + github.com/aws/aws-sdk-go-v2/config v1.27.9 + github.com/aws/aws-sdk-go-v2/credentials v1.17.9 + github.com/aws/aws-sdk-go-v2/service/route53 v1.40.3 + github.com/aws/aws-sdk-go-v2/service/route53domains v1.23.3 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 github.com/billputer/go-namecheap v0.0.0-20210108011502-994a912fb7f9 github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v3 v3.5.6 - github.com/cloudflare/cloudflare-go v0.89.0 - github.com/digitalocean/godo v1.109.0 + github.com/cloudflare/cloudflare-go v0.91.0 + github.com/digitalocean/godo v1.110.0 github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c github.com/dnsimple/dnsimple-go v1.5.1 github.com/exoscale/egoscale v0.90.2 @@ -37,7 +35,7 @@ require ( github.com/go-gandi/go-gandi v0.7.0 github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe github.com/gopherjs/jquery v0.0.0-20191017083323-73f4c7416038 - github.com/hashicorp/vault/api v1.12.0 + github.com/hashicorp/vault/api v1.12.2 github.com/jinzhu/copier v0.4.0 github.com/miekg/dns v1.1.58 github.com/mittwald/go-powerdns v0.6.2 @@ -58,7 +56,7 @@ require ( golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.18.0 - google.golang.org/api v0.169.0 + google.golang.org/api v0.171.0 gopkg.in/ns1/ns1-go.v2 v2.7.11 ) @@ -73,7 +71,7 @@ require ( github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-isatty v0.0.20 github.com/vultr/govultr/v2 v2.17.2 - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -85,15 +83,15 @@ require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect github.com/aws/smithy-go v1.20.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect @@ -115,7 +113,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -128,6 +126,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -140,6 +139,7 @@ require ( github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect + github.com/smartystreets/assertions v1.2.0 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect @@ -148,14 +148,14 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/mod v0.15.0 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78 // indirect - google.golang.org/grpc v1.62.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/grpc v1.62.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/go.sum b/go.sum index eb4cf77d0..8ea0d45c5 100644 --- a/go.sum +++ b/go.sum @@ -39,34 +39,34 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.25.3 h1:xYiLpZTQs1mzvz5PaI6uR0Wh57ippuEthxS4iK5v0n0= -github.com/aws/aws-sdk-go-v2 v1.25.3/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= -github.com/aws/aws-sdk-go-v2/config v1.27.7 h1:JSfb5nOQF01iOgxFI5OIKWwDiEXWTyTgg1Mm1mHi0A4= -github.com/aws/aws-sdk-go-v2/config v1.27.7/go.mod h1:PH0/cNpoMO+B04qET699o5W92Ca79fVtbUnvMIZro4I= -github.com/aws/aws-sdk-go-v2/credentials v1.17.7 h1:WJd+ubWKoBeRh7A5iNMnxEOs982SyVKOJD+K8HIezu4= -github.com/aws/aws-sdk-go-v2/credentials v1.17.7/go.mod h1:UQi7LMR0Vhvs+44w5ec8Q+VS+cd10cjwgHwiVkE0YGU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 h1:p+y7FvkK2dxS+FEwRIDHDe//ZX+jDhP8HHE50ppj4iI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3/go.mod h1:/fYB+FZbDlwlAiynK9KDXlzZl3ANI9JkD0Uhz5FjNT4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 h1:ifbIbHZyGl1alsAhPIYsHOg5MuApgqOvVeI8wIugXfs= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3/go.mod h1:oQZXg3c6SNeY6OZrDY+xHcF4VGIEoNotX2B4PrDeoJI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 h1:Qvodo9gHG9F3E8SfYOspPeBt0bjSbsevK8WhRAUHcoY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3/go.mod h1:vCKrdLXtybdf/uQd/YfVR2r5pcbNuEYKzMQpcxmeSJw= +github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= +github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= +github.com/aws/aws-sdk-go-v2/config v1.27.9 h1:gRx/NwpNEFSk+yQlgmk1bmxxvQ5TyJ76CWXs9XScTqg= +github.com/aws/aws-sdk-go-v2/config v1.27.9/go.mod h1:dK1FQfpwpql83kbD873E9vz4FyAxuJtR22wzoXn3qq0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.9 h1:N8s0/7yW+h8qR8WaRlPQeJ6czVMNQVNtNdUqf6cItao= +github.com/aws/aws-sdk-go-v2/credentials v1.17.9/go.mod h1:446YhIdmSV0Jf/SLafGZalQo+xr2iw7/fzXGDPTU1yQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0 h1:af5YzcLf80tv4Em4jWVD75lpnOHSBkPUZxZfGkrI3HI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.0/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 h1:K/NXvIftOlX+oGgWGIa3jDyYLDNsdVhsjHmsBH2GLAQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5/go.mod h1:cl9HGLV66EnCmMNzq4sYOti+/xo8w34CsgzVtm2GgsY= -github.com/aws/aws-sdk-go-v2/service/route53 v1.40.2 h1:YXQQJm3KnxabBHGNU8iC0GSvKRLtUSNUfP2R7L+Z/Tg= -github.com/aws/aws-sdk-go-v2/service/route53 v1.40.2/go.mod h1:ORinaAeDvAI7L7zPyE2RmG0RpwHKZDaQ7ALO8/dXFtY= -github.com/aws/aws-sdk-go-v2/service/route53domains v1.23.2 h1:TypWfzHKes0zADvTyJEwGsb0BAscUE8Ty0xs/jQM4Dk= -github.com/aws/aws-sdk-go-v2/service/route53domains v1.23.2/go.mod h1:jVbk98+jKo/JiX+qt028oGR6yApccx4ij5FaFUjiIAg= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 h1:XOPfar83RIRPEzfihnp+U6udOveKZJvPQ76SKWrLRHc= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.2/go.mod h1:Vv9Xyk1KMHXrR3vNQe8W5LMFdTjSeWk0gBZBzvf3Qa0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 h1:pi0Skl6mNl2w8qWZXcdOyg197Zsf4G97U7Sso9JXGZE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2/go.mod h1:JYzLoEVeLXk+L4tn1+rrkfhkxl6mLDEVaDSvGq9og90= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 h1:Ppup1nVNAOWbBOrcoOxaxPeEnSFB2RnnQdguhXpmeQk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.4/go.mod h1:+K1rNPVyGxkRuv9NNiaZ4YhBFuyw2MMA9SlIJ1Zlpz8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.40.3 h1:wr5gulbwbb8PSRMWjCROoP0TIMccpF8x5A7hEk2SjpA= +github.com/aws/aws-sdk-go-v2/service/route53 v1.40.3/go.mod h1:/Gyl9xjGcjIVe80ar75YlmA8m6oFh0A4XfLciBmdS8s= +github.com/aws/aws-sdk-go-v2/service/route53domains v1.23.3 h1:iuBYHWv0+2pF2qbM+UmnSwvcdOknXRi4yE9KBHhAItQ= +github.com/aws/aws-sdk-go-v2/service/route53domains v1.23.3/go.mod h1:Ijqatnvuyx5IE/FmFJWJyWTcUY5XrlXgc6G7z1Fqk6o= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0= @@ -87,8 +87,8 @@ github.com/centralnicgroup-opensource/rtldev-middleware-go-sdk/v3 v3.5.6/go.mod github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.89.0 h1:3zoVntC8xmUR/weFEcNE1RizdW4LRZdQnJ/AN8DDa1U= -github.com/cloudflare/cloudflare-go v0.89.0/go.mod h1:eyuehb1i6BNRc+ZwaTZAiRHeE+4jbKvHAns19oGeakg= +github.com/cloudflare/cloudflare-go v0.91.0 h1:L7IR+86qrZuEMSjGFg4cwRwtHqC8uCPmMUkP7BD4CPw= +github.com/cloudflare/cloudflare-go v0.91.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -101,8 +101,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/deepmap/oapi-codegen v1.9.1 h1:yHmEnA7jSTUMQgV+uN02WpZtwHnz2CBW3mZRIxr1vtI= github.com/deepmap/oapi-codegen v1.9.1/go.mod h1:PLqNAhdedP8ttRpBBkzLKU3bp+Fpy+tTgeAMlztR2cw= -github.com/digitalocean/godo v1.109.0 h1:4W97RJLJSUQ3veRZDNbp1Ol3Rbn6Lmt9bKGvfqYI5SU= -github.com/digitalocean/godo v1.109.0/go.mod h1:R6EmmWI8CT1+fCtjWY9UCB+L5uufuZH13wk3YhxycCs= +github.com/digitalocean/godo v1.110.0 h1:EY+rewWCYrUNOPbk9wI2Ytf0TBSRTJcZ6BINCb5dfmQ= +github.com/digitalocean/godo v1.110.0/go.mod h1:R6EmmWI8CT1+fCtjWY9UCB+L5uufuZH13wk3YhxycCs= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= @@ -206,8 +206,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= -github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= @@ -240,8 +240,8 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= -github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= +github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= @@ -261,8 +261,9 @@ github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -344,8 +345,8 @@ github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oq github.com/robertkrimen/otto v0.3.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -358,8 +359,9 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= @@ -426,8 +428,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -435,8 +437,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -526,14 +528,14 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= -google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -541,19 +543,18 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78 h1:Xs9lu+tLXxLIfuci70nG4cpwaRC+mRQPUL7LoIeDJC4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/models/domain.go b/models/domain.go index 65118e00a..8666be924 100644 --- a/models/domain.go +++ b/models/domain.go @@ -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 +} diff --git a/pkg/nameservers/nameservers.go b/pkg/nameservers/nameservers.go index 6df91f8e6..56faabd72 100644 --- a/pkg/nameservers/nameservers.go +++ b/pkg/nameservers/nameservers.go @@ -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 diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 9cc6d26ce..e5717145d 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -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...) + } +} diff --git a/providers/azuredns/azureDnsProvider.go b/providers/azuredns/azureDnsProvider.go index 849f8f5b0..916cc56b6 100644 --- a/providers/azuredns/azureDnsProvider.go +++ b/providers/azuredns/azureDnsProvider.go @@ -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 } diff --git a/providers/capabilities.go b/providers/capabilities.go index f0baa2d3e..6691c5699 100644 --- a/providers/capabilities.go +++ b/providers/capabilities.go @@ -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 diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index 0a4b75931..13cc08a05 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -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 } diff --git a/providers/cloudflare/rest.go b/providers/cloudflare/rest.go index 67876e6b3..a223f1e30 100644 --- a/providers/cloudflare/rest.go +++ b/providers/cloudflare/rest.go @@ -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()) diff --git a/providers/cscglobal/cscglobalProvider.go b/providers/cscglobal/cscglobalProvider.go index 2cfb86635..e52c2a526 100644 --- a/providers/cscglobal/cscglobalProvider.go +++ b/providers/cscglobal/cscglobalProvider.go @@ -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(), diff --git a/providers/gcloud/gcloudProvider.go b/providers/gcloud/gcloudProvider.go index c62ea9e4a..b9500a6af 100644 --- a/providers/gcloud/gcloudProvider.go +++ b/providers/gcloud/gcloudProvider.go @@ -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(), diff --git a/providers/providers.go b/providers/providers.go index e04ab35c4..79f86523b 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -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. diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go index 03a7de4e1..d88658f17 100644 --- a/providers/route53/route53Provider.go +++ b/providers/route53/route53Provider.go @@ -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 {