Skip to content

Commit

Permalink
Merge branch 'main' into branch_gorec_rebasing
Browse files Browse the repository at this point in the history
  • Loading branch information
tlimoncelli committed Jan 15, 2025
2 parents 79d02ee + 556926a commit eb0df5e
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 199 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES
# - name: Enforce Go Formatted Code
# run: "[ `go fmt ./... | wc -l` -eq 0 ]"
- uses: actions/upload-artifact@v4.5.0
- uses: actions/upload-artifact@v4.6.0
with:
name: unit-tests
path: ${{ env.TEST_RESULTS }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr_integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ jobs:
echo "Skip test for ${{ matrix.provider }} provider"
fi
working-directory: integrationTest
- uses: actions/upload-artifact@v4.5.0
- uses: actions/upload-artifact@v4.6.0
with:
name: integration-tests-${{ matrix.provider }}
path: ${{ env.TEST_RESULTS }}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Currently supported Domain Registrars:

- AWS Route 53
- CSC Global
- CentralNic Reseller (formerly RRPProxy)
- CentralNic Reseller (CNR) - formerly RRPProxy
- DNSOVERHTTPS
- Dynadot
- easyname
Expand Down
101 changes: 79 additions & 22 deletions commands/ppreviewPush.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ type PPreviewArgs struct {
GetDNSConfigArgs
GetCredentialsArgs
FilterArgs
Notify bool
WarnChanges bool
ConcurMode string
NoPopulate bool
DePopulate bool
Report string
Full bool
Notify bool
WarnChanges bool
ConcurMode string
NoPopulate bool
DePopulate bool
PopulateOnPreview bool
Report string
Full bool
}

// ReportItem is a record of corrections for a particular domain/provider/registrar.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -258,31 +265,79 @@ 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))
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)
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())

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -770,7 +827,7 @@ func prefineProviderType(credEntryName string, t string, credFields map[string]s
// - 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 INFO "working but unneeded: clean up as follows..."
// GANDI NAMEDOT ERROR "error mismatched: please fix as follows..."

// ERROR: Invalid.
Expand Down
2 changes: 1 addition & 1 deletion commands/previewPush.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ func refineProviderType(credEntryName string, t string, credFields map[string]st
// - 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 INFO "working but unneeded: clean up as follows..."
// GANDI NAMEDOT ERROR "error mismatched: please fix as follows..."

// ERROR: Invalid.
Expand Down
2 changes: 1 addition & 1 deletion documentation/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
* [Azure Private DNS](provider/azure_private_dns.md)
* [BIND](provider/bind.md)
* [Bunny DNS](provider/bunny\_dns.md)
* [CentralNic Reseller (fka RRPproxy)](provider/cnr.md)
* [CentralNic Reseller (CNR) - formerly RRPProxy](provider/cnr.md)
* [Cloudflare](provider/cloudflareapi.md)
* [ClouDNS](provider/cloudns.md)
* [CSC Global](provider/cscglobal.md)
Expand Down
2 changes: 1 addition & 1 deletion documentation/provider/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ If a feature is definitively not supported for whatever reason, we would also li
| [`BUNNY_DNS`](bunny_dns.md) ||||||||||||||||||||||||
| [`CLOUDFLAREAPI`](cloudflareapi.md) ||||||||||||||||||||||||
| [`CLOUDNS`](cloudns.md) ||||||||||||||||||||||||
| [`CNR`](cnr.md) ||||| |||||||||||||||||||
| [`CNR`](cnr.md) ||||| |||||||||||||||||||
| [`CSCGLOBAL`](cscglobal.md) ||||||||||||||||||||||||
| [`DESEC`](desec.md) ||||||||||||||||||||||||
| [`DIGITALOCEAN`](digitalocean.md) ||||||||||||||||||||||||
Expand Down
1 change: 0 additions & 1 deletion integrationTest/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,6 @@ func manyA(namePattern, target string, n int) []*models.RecordConfig {
recs := []*models.RecordConfig{}
for i := range n {
recs = append(recs, a(fmt.Sprintf(namePattern, i), target))
//recs = append(recs, makeRec(fmt.Sprintf(namePattern, i), target, "A"))
}
return recs
}
Expand Down
1 change: 0 additions & 1 deletion models/cloudflare_from.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
)

// cfSingleRedirecttargetFromRaw create the display text used for a normal Redirect.
func cfSingleRedirecttargetFromRaw(name string, code uint16, when, then string) string {
return fmt.Sprintf("%s code=(%03d) when=(%s) then=(%s)",
name,
Expand Down
27 changes: 23 additions & 4 deletions models/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ type DomainConfig struct {
DefaultTTL uint32 `json:"defaultTTL,omitempty"`

// 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
pendingCorrectionsOrder []string // Call the providers in this order
pendingActualChangeCount map[string](int) // Number of changes to report (cumulative)
pendingCorrectionsMutex sync.Mutex // Protect pendingCorrections*
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)
pendingPopulateCorrections map[string][]*Correction // Corrections for zone creations at each provider
}

// GetSplitHorizonNames returns the domain's name, uniquename, and tag.
Expand Down Expand Up @@ -202,3 +203,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]
}
2 changes: 1 addition & 1 deletion providers/cnr/cnrProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var features = providers.DocumentationNotes{
providers.DocOfficiallySupported: providers.Cannot("Actively maintained provider module."),
// --- Supported record types ---
// providers.CanUseAKAMAICDN: providers.Cannot(), // can only be supported by Akamai EdgeDns provider
providers.CanUseAlias: providers.Cannot("Not supported. You may use CNAME records instead. An Alternative solution is planned."),
providers.CanUseAlias: providers.Can(),
// providers.CanUseAzureAlias: providers.Cannot(), // can only be supported by Azure provider
providers.CanUseCAA: providers.Can(),
providers.CanUseDHCID: providers.Cannot("Ask for this feature."),
Expand Down
8 changes: 4 additions & 4 deletions providers/cnr/records.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type CNRRecord struct {
Host string
// FQDN is the Fully Qualified Domain Name. It is the combination of the host and the domain name. It always ends in a ".". FQDN is ignored in CreateRecord, specify via the Host field instead.
Fqdn string
// Type is one of the following: A, AAAA, ANAME, CNAME, MX, NS, SRV, or TXT.
// Type is one of the following: A, AAAA, ANAME, ALIAS, CNAME, MX, NS, SRV, or TXT.
Type string
// Answer is either the IP address for A or AAAA records; the target for ANAME, CNAME, MX, or NS records; the text for TXT records.
// For SRV records, answer has the following format: "{weight} {port} {target}" e.g. "1 5061 sip.example.org".
Expand Down Expand Up @@ -150,7 +150,7 @@ func toRecord(r *CNRRecord, origin string) *models.RecordConfig {
panic(fmt.Errorf("unparsable SRV record received from centralnic reseller API: %w", err))
}
}
default: // "A", "AAAA", "ANAME", "CNAME", "NS", "TXT", "CAA", "TLSA", "PTR"
default: // "A", "AAAA", "ANAME", "ALIAS", "CNAME", "NS", "TXT", "CAA", "TLSA", "PTR"
if err := rc.PopulateFromStringFunc(r.Type, r.Answer, r.Fqdn, txtutil.ParseQuoted); err != nil {
panic(fmt.Errorf("unparsable record received from centralnic reseller API: %w", err))
}
Expand Down Expand Up @@ -250,7 +250,7 @@ func (n *CNRClient) getRecords(domain string) ([]*CNRRecord, error) {
priority, _ := strconv.ParseUint(data["PRIO"], 10, 32)

// Add dot to Answer if supported by the record type
pattern := `^CNAME|MX|NS|SRV|PTR$`
pattern := `^ALIAS|CNAME|MX|NS|SRV|PTR$`
re, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("error compiling regex in getRecords: %w", err)
Expand Down Expand Up @@ -291,7 +291,7 @@ func (n *CNRClient) createRecordString(rc *models.RecordConfig, domain string) (
answer := ""

switch rc.Type { // #rtype_variations
case "A", "AAAA", "ANAME", "CNAME", "MX", "NS", "PTR":
case "A", "AAAA", "ANAME", "ALIAS", "CNAME", "MX", "NS", "PTR":
answer = rc.GetTargetField()
if domain == host {
host = host + "."
Expand Down
6 changes: 5 additions & 1 deletion providers/powerdns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ func (dsp *powerdnsProvider) GetNameservers(string) ([]*models.Nameserver, error

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (dsp *powerdnsProvider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
curRecords := models.Records{}
zone, err := dsp.client.Zones().GetZone(context.Background(), dsp.ServerName, canonical(domain))
if err != nil {
if _, ok := err.(pdnshttp.ErrNotFound); ok {
// Zone is not found, but everything else is okay so return no records
return curRecords, nil
}
return nil, err
}

curRecords := models.Records{}
// loop over grouped records by type, called RRSet
for _, rrset := range zone.ResourceRecordSets {
if rrset.Type == "SOA" {
Expand Down
5 changes: 5 additions & 0 deletions providers/powerdns/dnssec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import (

"github.com/StackExchange/dnscontrol/v4/models"
"github.com/mittwald/go-powerdns/apis/cryptokeys"
"github.com/mittwald/go-powerdns/pdnshttp"
)

// getDNSSECCorrections returns corrections that update a domain's DNSSEC state.
func (dsp *powerdnsProvider) getDNSSECCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
zoneCryptokeys, getErr := dsp.client.Cryptokeys().ListCryptokeys(context.Background(), dsp.ServerName, dc.Name)
if getErr != nil {
if _, ok := getErr.(pdnshttp.ErrNotFound); ok {
// Zone doesn't exist, this is okay as no corrections are needed
return nil, nil
}
return nil, getErr
}

Expand Down
Loading

0 comments on commit eb0df5e

Please sign in to comment.