forked from gardener/external-dns-management
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
509e139
commit a8aca6c
Showing
8 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package controller | ||
|
||
import ( | ||
"github.com/gardener/external-dns-management/pkg/controller/provider/powerdns" | ||
"github.com/gardener/external-dns-management/pkg/dns/provider" | ||
) | ||
|
||
func init() { | ||
provider.DNSController("", powerdns.Factory). | ||
FinalizerDomain("dns.gardener.cloud"). | ||
MustRegister(provider.CONTROLLER_GROUP_DNS_CONTROLLERS) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package powerdns | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/gardener/controller-manager-library/pkg/logger" | ||
"github.com/joeig/go-powerdns/v3" | ||
|
||
"github.com/gardener/external-dns-management/pkg/dns" | ||
"github.com/gardener/external-dns-management/pkg/dns/provider" | ||
) | ||
|
||
type RecordSet struct { | ||
Name string | ||
RecordType powerdns.RRType | ||
TTL uint32 | ||
Content []string | ||
} | ||
|
||
type Execution struct { | ||
logger.LogContext | ||
handler *Handler | ||
zone provider.DNSHostedZone | ||
} | ||
|
||
func NewExecution(logger logger.LogContext, h *Handler, zone provider.DNSHostedZone) *Execution { | ||
return &Execution{LogContext: logger, handler: h, zone: zone} | ||
} | ||
|
||
func (exec *Execution) buildRecordSet(req *provider.ChangeRequest) (*RecordSet, error) { | ||
var dnsset *dns.DNSSet | ||
|
||
switch req.Action { | ||
case provider.R_CREATE, provider.R_UPDATE: | ||
dnsset = req.Addition | ||
case provider.R_DELETE: | ||
dnsset = req.Deletion | ||
} | ||
|
||
name, rset := dns.MapToProvider(req.Type, dnsset, exec.zone.Domain()) | ||
|
||
if name.SetIdentifier != "" || dnsset.RoutingPolicy != nil { | ||
return nil, fmt.Errorf("routing policies not supported for " + TYPE_CODE) | ||
} | ||
|
||
if name.DNSName == "" || len(rset.Records) == 0 { | ||
return nil, nil | ||
} | ||
|
||
exec.Infof("Desired %s: %s record set %s[%s] with TTL %d: %s", req.Action, rset.Type, name.DNSName, exec.zone.Id(), rset.TTL, rset.RecordString()) | ||
|
||
recordSet := RecordSet{ | ||
Name: name.DNSName, | ||
RecordType: powerdns.RRType(rset.Type), | ||
} | ||
|
||
switch req.Action { | ||
case provider.R_CREATE, provider.R_UPDATE: | ||
var content []string | ||
for _, record := range rset.Records { | ||
content = append(content, record.Value) | ||
} | ||
|
||
recordSet.Content = content | ||
recordSet.TTL = uint32(rset.TTL) | ||
} | ||
|
||
return &recordSet, nil | ||
} | ||
|
||
func (exec *Execution) apply(action string, rset *RecordSet, metrics provider.Metrics) error { | ||
var err error | ||
switch action { | ||
case provider.R_CREATE, provider.R_UPDATE: | ||
err = exec.update(rset, metrics) | ||
case provider.R_DELETE: | ||
err = exec.delete(rset, metrics) | ||
} | ||
return err | ||
} | ||
|
||
func (exec *Execution) update(rset *RecordSet, metrics provider.Metrics) error { | ||
exec.handler.config.RateLimiter.Accept() | ||
zoneID := exec.zone.Id().ID | ||
err := exec.handler.powerdns.Records.Change(exec.handler.ctx, zoneID, rset.Name, rset.RecordType, rset.TTL, rset.Content) | ||
metrics.AddZoneRequests(zoneID, provider.M_UPDATERECORDS, 1) | ||
return err | ||
} | ||
|
||
func (exec *Execution) delete(rset *RecordSet, metrics provider.Metrics) error { | ||
exec.handler.config.RateLimiter.Accept() | ||
zoneID := exec.zone.Id().ID | ||
err := exec.handler.powerdns.Records.Delete(exec.handler.ctx, zoneID, rset.Name, rset.RecordType) | ||
metrics.AddZoneRequests(zoneID, provider.M_DELETERECORDS, 1) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package powerdns | ||
|
||
import ( | ||
"github.com/gardener/external-dns-management/pkg/controller/provider/compound" | ||
"github.com/gardener/external-dns-management/pkg/dns/provider" | ||
) | ||
|
||
const TYPE_CODE = "powerdns" | ||
|
||
var rateLimiterDefaults = provider.RateLimiterOptions{ | ||
Enabled: true, | ||
QPS: 50, | ||
Burst: 10, | ||
} | ||
|
||
var Factory = provider.NewDNSHandlerFactory(TYPE_CODE, NewHandler). | ||
SetGenericFactoryOptionDefaults(provider.GenericFactoryOptionDefaults.SetRateLimiterOptions(rateLimiterDefaults)) | ||
|
||
func init() { | ||
compound.MustRegister(Factory) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package powerdns | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/gardener/controller-manager-library/pkg/logger" | ||
miekgdns "github.com/miekg/dns" | ||
|
||
"github.com/joeig/go-powerdns/v3" | ||
|
||
"github.com/gardener/external-dns-management/pkg/dns" | ||
"github.com/gardener/external-dns-management/pkg/dns/provider" | ||
) | ||
|
||
type Handler struct { | ||
provider.DefaultDNSHandler | ||
config provider.DNSHandlerConfig | ||
cache provider.ZoneCache | ||
ctx context.Context | ||
|
||
powerdns *powerdns.Client | ||
} | ||
|
||
var _ provider.DNSHandler = &Handler{} | ||
|
||
// tsigAlgs are the supported TSIG algorithms | ||
var tsigAlgs = []string{miekgdns.HmacSHA1, miekgdns.HmacSHA224, miekgdns.HmacSHA256, miekgdns.HmacSHA384, miekgdns.HmacSHA512} | ||
|
||
func NewHandler(c *provider.DNSHandlerConfig) (provider.DNSHandler, error) { | ||
h := &Handler{ | ||
DefaultDNSHandler: provider.NewDefaultDNSHandler(TYPE_CODE), | ||
config: *c, | ||
} | ||
|
||
h.ctx = c.Context | ||
|
||
server, err := c.GetRequiredProperty("Server", "server") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
apiKey, err := c.GetRequiredProperty("ApiKey", "apiKey") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
virtualHost := c.GetProperty("VirtualHost", "virtualHost") | ||
|
||
insecureSkipVerify, err := c.GetDefaultedBoolProperty("InsecureSkipVerify", false, "insecureSkipVerify") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
trustedCaCert := c.GetProperty("TrustedCaCert", "trustedCaCert") | ||
|
||
headers := map[string]string{"X-API-Key": apiKey} | ||
httpClient := newHttpClient(insecureSkipVerify, trustedCaCert) | ||
|
||
h.powerdns = powerdns.NewClient(server, virtualHost, headers, httpClient) | ||
|
||
h.cache, err = c.ZoneCacheFactory.CreateZoneCache(provider.CacheZoneState, c.Metrics, h.getZones, h.getZoneState) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return h, nil | ||
} | ||
|
||
func newHttpClient(insecureSkipVerify bool, trustedCaCert string) *http.Client { | ||
httpClient := http.DefaultClient | ||
|
||
if insecureSkipVerify { | ||
httpClient.Transport = &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | ||
} | ||
} | ||
|
||
if trustedCaCert != "" { | ||
caCertPool := x509.NewCertPool() | ||
caCertPool.AppendCertsFromPEM([]byte(trustedCaCert)) | ||
httpClient.Transport = &http.Transport{ | ||
TLSClientConfig: &tls.Config{ | ||
RootCAs: caCertPool, | ||
InsecureSkipVerify: false, | ||
MinVersion: tls.VersionTLS12, | ||
}, | ||
} | ||
} | ||
|
||
return httpClient | ||
} | ||
|
||
func (h *Handler) Release() { | ||
h.cache.Release() | ||
} | ||
|
||
func (h *Handler) GetZones() (provider.DNSHostedZones, error) { | ||
return h.cache.GetZones() | ||
} | ||
|
||
func (h *Handler) getZones(_ provider.ZoneCache) (provider.DNSHostedZones, error) { | ||
hostedZones := provider.DNSHostedZones{} | ||
zones, err := h.powerdns.Zones.List(h.ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for _, z := range zones { | ||
id := powerdns.StringValue(z.ID) | ||
domain := dns.NormalizeHostname(powerdns.StringValue(z.Name)) | ||
hostedZone := provider.NewDNSHostedZone(h.ProviderType(), id, domain, id, []string{}, false) | ||
hostedZones = append(hostedZones, hostedZone) | ||
} | ||
h.config.Metrics.AddGenericRequests(provider.M_LISTZONES, 1) | ||
return hostedZones, nil | ||
} | ||
|
||
func (h *Handler) GetZoneState(zone provider.DNSHostedZone) (provider.DNSZoneState, error) { | ||
return h.cache.GetZoneState(zone) | ||
} | ||
|
||
func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache) (provider.DNSZoneState, error) { | ||
dnssets := dns.DNSSets{} | ||
|
||
h.config.RateLimiter.Accept() | ||
|
||
state, err := h.powerdns.Zones.Get(h.ctx, zone.Id().ID) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, rrset := range state.RRsets { | ||
h.config.Metrics.AddZoneRequests(zone.Id().ID, provider.M_LISTRECORDS, 1) | ||
if rrset.Type == nil { | ||
h.config.Logger.Warnf("Missing type for RRSet %s from Zone %s", powerdns.StringValue(rrset.Name), zone.Id().ID) | ||
continue | ||
} | ||
rs := dns.NewRecordSet(powerdns.StringValue((*string)(rrset.Type)), int64(powerdns.Uint32Value(rrset.TTL)), nil) | ||
for _, rr := range rrset.Records { | ||
rs.Add(&dns.Record{Value: powerdns.StringValue(rr.Content)}) | ||
} | ||
dnssets.AddRecordSetFromProvider(powerdns.StringValue(rrset.Name), rs) | ||
} | ||
return provider.NewDNSZoneState(dnssets), nil | ||
} | ||
|
||
func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool { | ||
return h.cache.ReportZoneStateConflict(zone, err) | ||
} | ||
|
||
func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error { | ||
err := h.executeRequests(logger, zone, state, reqs) | ||
h.cache.ApplyRequests(logger, err, zone, reqs) | ||
return err | ||
} | ||
|
||
func (h *Handler) executeRequests(logger logger.LogContext, zone provider.DNSHostedZone, _ provider.DNSZoneState, reqs []*provider.ChangeRequest) error { | ||
exec := NewExecution(logger, h, zone) | ||
|
||
var succeeded, failed int | ||
for _, req := range reqs { | ||
rset, err := exec.buildRecordSet(req) | ||
if err != nil { | ||
if req.Done != nil { | ||
req.Done.SetInvalid(err) | ||
} | ||
continue | ||
} | ||
|
||
err = exec.apply(req.Action, rset, h.config.Metrics) | ||
if err != nil { | ||
failed++ | ||
logger.Infof("Apply failed with %s", err.Error()) | ||
if req.Done != nil { | ||
req.Done.Failed(err) | ||
} | ||
} else { | ||
succeeded++ | ||
if req.Done != nil { | ||
req.Done.Succeeded() | ||
} | ||
} | ||
} | ||
|
||
if succeeded > 0 { | ||
logger.Infof("Succeeded updates for records in apiKey %s: %d", zone.Id(), succeeded) | ||
} | ||
if failed > 0 { | ||
logger.Infof("Failed updates for records in apiKey %s: %d", zone.Id(), failed) | ||
return fmt.Errorf("%d changes failed", failed) | ||
} | ||
|
||
return nil | ||
} |