Skip to content

Commit

Permalink
Activate PowerDNS
Browse files Browse the repository at this point in the history
  • Loading branch information
robertvolkmann committed Aug 7, 2024
1 parent 509e139 commit a8aca6c
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/compound/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
_ "github.com/gardener/external-dns-management/pkg/controller/provider/infoblox"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/netlify"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/openstack"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/powerdns"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/remote"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/rfc2136"
_ "github.com/gardener/external-dns-management/pkg/controller/remoteaccesscertificates"
Expand Down
1 change: 1 addition & 0 deletions cmd/dedicated/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
_ "github.com/gardener/external-dns-management/pkg/controller/provider/infoblox/controller"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/netlify/controller"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/openstack/controller"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/powerdns/controller"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/remote/controller"
_ "github.com/gardener/external-dns-management/pkg/controller/provider/rfc2136/controller"
_ "github.com/gardener/external-dns-management/pkg/controller/remoteaccesscertificates"
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/gophercloud/gophercloud v0.24.0
github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16
github.com/infobloxopen/infoblox-go-client/v2 v2.1.0
github.com/joeig/go-powerdns/v3 v3.10.0
github.com/miekg/dns v1.1.51
github.com/netlify/open-api v1.1.0
github.com/onsi/ginkgo/v2 v2.19.0
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,16 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ironcore-dev/vgopath v0.1.4 h1:hBMuv7+wnZp5JHkVfdg4mtP8hsIGvuv42+l+F2wmQxk=
github.com/ironcore-dev/vgopath v0.1.4/go.mod h1:PTGnX8xW/QDytFR7oU4kcXr1RPDLCgAJ0ZUa5Rp8vyI=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joeig/go-powerdns/v3 v3.10.0 h1:pUhC/8kdDJW4Y7/J/QawPXWlp0WcNrynABk0yX0WmRk=
github.com/joeig/go-powerdns/v3 v3.10.0/go.mod h1:SA9nmMT7kJr4vgSFTlYLMbomSwPxydacVWTPqSUoPFA=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
Expand Down Expand Up @@ -415,6 +419,7 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
Expand Down
16 changes: 16 additions & 0 deletions pkg/controller/provider/powerdns/controller/controller.go
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)
}
100 changes: 100 additions & 0 deletions pkg/controller/provider/powerdns/execution.go
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
}
25 changes: 25 additions & 0 deletions pkg/controller/provider/powerdns/factory.go
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)
}
201 changes: 201 additions & 0 deletions pkg/controller/provider/powerdns/handler.go
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
}

0 comments on commit a8aca6c

Please sign in to comment.