Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 57 additions & 30 deletions provider/pdns/pdns.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,16 @@ func stringifyHTTPResponseBody(r *http.Response) string {
// well as mock APIClients used in testing
type PDNSAPIProvider interface {
ListZones() ([]pgo.Zone, *http.Response, error)
PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone)
ListZone(zoneID string) (pgo.Zone, *http.Response, error)
PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error)
}

// PDNSAPIClient : Struct that encapsulates all the PowerDNS specific implementation details
type PDNSAPIClient struct {
dryRun bool
serverID string
authCtx context.Context
client *pgo.APIClient
domainFilter *endpoint.DomainFilter
dryRun bool
serverID string
authCtx context.Context
client *pgo.APIClient
}

// ListZones : Method returns all enabled zones from PowerDNS
Expand All @@ -171,23 +169,22 @@ func (c *PDNSAPIClient) ListZones() ([]pgo.Zone, *http.Response, error) {
return zones, resp, provider.NewSoftErrorf("unable to list zones: %v", err)
}

// PartitionZones : Method returns a slice of zones that adhere to the domain filter and a slice of ones that does not adhere to the filter
func (c *PDNSAPIClient) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) {
var filteredZones []pgo.Zone
var residualZones []pgo.Zone
// partitionZones returns a slice of zones that adhere to the domain filter and a slice of ones that do not adhere to the filter.
func partitionZones(zones []pgo.Zone, domainFilter *endpoint.DomainFilter) ([]pgo.Zone, []pgo.Zone) {
if domainFilter == nil || !domainFilter.IsConfigured() {
return zones, nil
}

if c.domainFilter.IsConfigured() {
for _, zone := range zones {
if c.domainFilter.Match(zone.Name) {
filteredZones = append(filteredZones, zone)
} else {
residualZones = append(residualZones, zone)
}
var filtered, residual []pgo.Zone
for _, zone := range zones {
if domainFilter.Match(zone.Name) {
filtered = append(filtered, zone)
} else {
residual = append(residual, zone)
}
} else {
filteredZones = zones
}
return filteredZones, residualZones

return filtered, residual
}

// ListZone : Method returns the details of a specific zone from PowerDNS
Expand Down Expand Up @@ -229,7 +226,8 @@ func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Res
// PDNSProvider is an implementation of the Provider interface for PowerDNS
type PDNSProvider struct {
provider.BaseProvider
client PDNSAPIProvider
client PDNSAPIProvider
domainFilter *endpoint.DomainFilter
}

// NewPDNSProvider initializes a new PowerDNS based Provider.
Expand Down Expand Up @@ -258,16 +256,47 @@ func NewPDNSProvider(ctx context.Context, config PDNSConfig) (*PDNSProvider, err

provider := &PDNSProvider{
client: &PDNSAPIClient{
dryRun: config.DryRun,
serverID: config.ServerID,
authCtx: context.WithValue(ctx, pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}),
client: pgo.NewAPIClient(pdnsClientConfig),
domainFilter: config.DomainFilter,
dryRun: config.DryRun,
serverID: config.ServerID,
authCtx: context.WithValue(ctx, pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}),
client: pgo.NewAPIClient(pdnsClientConfig),
},
domainFilter: config.DomainFilter,
}
return provider, nil
}

// filteredZones fetches all zones from the PowerDNS API and partitions them
// using the provider's domain filter. It returns the matching zones, the
// non-matching (residual) zones, and any error from the API call.
func (p *PDNSProvider) filteredZones() ([]pgo.Zone, []pgo.Zone, error) {
zones, _, err := p.client.ListZones()
if err != nil {
return nil, nil, err
}
filtered, residual := partitionZones(zones, p.domainFilter)
return filtered, residual, nil
}

func (p *PDNSProvider) GetDomainFilter() endpoint.DomainFilterInterface {
// Return all zones the provider manages so the controller can intersect
// with --domain-filter on its own. Do NOT apply p.domainFilter here;
// double-filtering would produce an empty filter when no zones match,
// silently failing open instead of letting the controller see the
// mismatch and produce a safe empty plan.
zones, _, err := p.client.ListZones()
if err != nil {
log.Errorf("Unable to fetch zones from PowerDNS API: %v", err)
return &endpoint.DomainFilter{}
}

zoneNames := make([]string, 0, 2*len(zones))
for _, zone := range zones {
zoneNames = append(zoneNames, zone.Name, "."+zone.Name)
}
return endpoint.NewDomainFilter(zoneNames)
}

// hasAliasAnnotation checks if the endpoint has the alias annotation set to true
func (p *PDNSProvider) hasAliasAnnotation(ep *endpoint.Endpoint) bool {
value, exists := ep.GetProviderSpecificProperty("alias")
Expand Down Expand Up @@ -308,11 +337,10 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
return endpoints[i].DNSName < endpoints[j].DNSName
})

zones, _, err := p.client.ListZones()
filteredZones, residualZones, err := p.filteredZones()
if err != nil {
return nil, err
}
filteredZones, residualZones := p.client.PartitionZones(zones)

// Sort the zone by length of the name in descending order, we use this
// property later to ensure we add a record to the longest matching zone
Expand Down Expand Up @@ -437,11 +465,10 @@ func (p *PDNSProvider) mutateRecords(endpoints []*endpoint.Endpoint, changetype

// Records returns all DNS records controlled by the configured PDNS server (for all zones)
func (p *PDNSProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error) {
zones, _, err := p.client.ListZones()
filteredZones, _, err := p.filteredZones()
if err != nil {
return nil, err
}
filteredZones, _ := p.client.PartitionZones(zones)

var endpoints []*endpoint.Endpoint

Expand Down
Loading
Loading