BUG: Create zones ahead of gathering data (#3337)

Co-authored-by: Tom Limoncelli <tlimoncelli@stackoverflow.com>
This commit is contained in:
Jakob Ackermann 2025-01-14 22:02:05 +00:00 committed by GitHub
parent a3d6c51c7f
commit 556926a2f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 101 additions and 25 deletions

View file

@ -84,6 +84,7 @@ type PPreviewArgs struct {
ConcurMode string
NoPopulate bool
DePopulate bool
PopulateOnPreview bool
Report string
Full bool
}
@ -132,6 +133,12 @@ func (args *PPreviewArgs) flags() []cli.Flag {
Destination: &args.NoPopulate,
Usage: `Delete unknown zones at provider (dangerous!)`,
})
flags = append(flags, &cli.BoolFlag{
Name: "populate-on-preview",
Destination: &args.PopulateOnPreview,
Value: true,
Usage: `Auto-create zones on preview`,
})
flags = append(flags, &cli.BoolFlag{
Name: "full",
Destination: &args.Full,
@ -258,7 +265,58 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor
// 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 totalCorrections int
var reportItems []*ReportItem
var anyErrors bool
// Populate the zones (if desired/needed/able):
if !args.NoPopulate {
out.PrintfIf(fullMode, "PHASE 1: CHECKING for missing zones\n")
var wg sync.WaitGroup
wg.Add(len(zonesConcurrent))
out.PrintfIf(fullMode, "CONCURRENTLY checking for %d zone(s)\n", len(zonesConcurrent))
for _, zone := range optimizeOrder(zonesConcurrent) {
out.PrintfIf(fullMode, "Concurrently checking for zone: %q\n", zone.Name)
go func(zone *models.DomainConfig) {
defer wg.Done()
oneZonePopulate(zone, args, zcache)
}(zone)
}
out.PrintfIf(fullMode, "SERIALLY checking for %d zone(s)\n", len(zonesSerial))
for _, zone := range zonesSerial {
out.PrintfIf(fullMode, "Serially checking for zone: %q\n", zone.Name)
oneZonePopulate(zone, args, zcache)
}
out.PrintfIf(fullMode && len(zonesConcurrent) > 0, "Waiting for concurrent checking(s) to complete...")
wg.Wait()
out.PrintfIf(fullMode && len(zonesConcurrent) > 0, "DONE\n")
for _, zone := range zonesToProcess {
started := false // Do not emit noise when no provider has corrections.
providersToProcess := whichProvidersToProcess(zone.DNSProviderInstances, args.Providers)
for _, provider := range zone.DNSProviderInstances {
corrections := zone.GetPopulateCorrections(provider.Name)
if len(corrections) == 0 {
continue // Do not emit noise when zone exists
}
if !started {
out.StartDomain(zone.GetUniqueName())
started = true
}
skip := skipProvider(provider.Name, providersToProcess)
out.StartDNSProvider(provider.Name, skip)
if !skip {
totalCorrections += len(corrections)
out.EndProvider2(provider.Name, len(corrections))
reportItems = append(reportItems, genReportItem(zone.Name, corrections, provider.Name))
anyErrors = cmp.Or(anyErrors, pprintOrRunCorrections(zone.Name, provider.Name, corrections, out, args.PopulateOnPreview, interactive, notifier, report))
}
}
}
}
out.PrintfIf(fullMode, "PHASE 2: GATHERING data\n")
var wg sync.WaitGroup
wg.Add(len(zonesConcurrent))
out.Printf("CONCURRENTLY gathering %d zone(s)\n", len(zonesConcurrent))
@ -266,23 +324,20 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor
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)
oneZone(zone, args)
}(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)
oneZone(zone, args)
}
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
out.PrintfIf(fullMode, "PHASE 3: CORRECTIONS\n")
for _, zone := range zonesToProcess {
out.StartDomain(zone.GetUniqueName())
@ -413,19 +468,21 @@ func optimizeOrder(zones []*models.DomainConfig) []*models.DomainConfig {
return zones
}
func oneZone(zone *models.DomainConfig, args PPreviewArgs, zc *zoneCache) {
func oneZonePopulate(zone *models.DomainConfig, args PPreviewArgs, zc *zoneCache) {
// Loop over all the providers configured for that zone:
for _, provider := range zone.DNSProviderInstances {
populateCorrections := generatePopulateCorrections(provider, zone.Name, zc)
zone.StorePopulateCorrections(provider.Name, populateCorrections)
}
}
func oneZone(zone *models.DomainConfig, args PPreviewArgs) {
// Fix the parent zone's delegation: (if able/needed)
delegationCorrections, dcCount := generateDelegationCorrections(zone, zone.DNSProviderInstances, zone.RegistrarInstance)
// 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, actualChangeCount := generateZoneCorrections(zone, provider)
zone.StoreCorrections(provider.Name, rep)

View file

@ -50,9 +50,10 @@ type DomainConfig struct {
// Pending work to do for each provider. Provider may be a registrar or DSP.
pendingCorrectionsMutex sync.Mutex // Protect pendingCorrections*
pendingCorrections map[string]([]*Correction) // Work to be done for each provider
pendingCorrections map[string][]*Correction // Work to be done for each provider
pendingCorrectionsOrder []string // Call the providers in this order
pendingActualChangeCount map[string](int) // Number of changes to report (cumulative)
pendingActualChangeCount map[string]int // Number of changes to report (cumulative)
pendingPopulateCorrections map[string][]*Correction // Corrections for zone creations at each provider
}
// GetSplitHorizonNames returns the domain's name, uniquename, and tag.
@ -201,3 +202,21 @@ func (dc *DomainConfig) GetChangeCount(providerName string) int {
return dc.pendingActualChangeCount[providerName]
}
// StorePopulateCorrections accumulates corrections in a thread-safe way.
func (dc *DomainConfig) StorePopulateCorrections(providerName string, corrections []*Correction) {
dc.pendingCorrectionsMutex.Lock()
defer dc.pendingCorrectionsMutex.Unlock()
if dc.pendingPopulateCorrections == nil {
dc.pendingPopulateCorrections = make(map[string][]*Correction, 1)
}
dc.pendingPopulateCorrections[providerName] = append(dc.pendingPopulateCorrections[providerName], corrections...)
}
// GetPopulateCorrections returns zone corrections in a thread-safe way.
func (dc *DomainConfig) GetPopulateCorrections(providerName string) []*Correction {
dc.pendingCorrectionsMutex.Lock()
defer dc.pendingCorrectionsMutex.Unlock()
return dc.pendingPopulateCorrections[providerName]
}