From a106313f5590b47dd67a668f65800a7e7a22224b Mon Sep 17 00:00:00 2001 From: Rick Henry Date: Thu, 7 Oct 2021 17:47:16 +0100 Subject: [PATCH] First-pass implementation of SafeDNS provider --- go.mod | 1 + go.sum | 15 ++++++ main.go | 2 +- provider/safedns/safedns.go | 102 ++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c41477dbb..9b8a3adae 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/terra-farm/udnssdk v1.3.5 // indirect github.com/transip/gotransip/v6 v6.6.2 + github.com/ukfast/sdk-go v1.4.23 // indirect github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 github.com/vultr/govultr/v2 v2.9.0 diff --git a/go.sum b/go.sum index e1c8538f9..64d568cf8 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d h1:d6sdozgfqtgaOhjUn++lbo5siX3HELjcOUnbtrvVQi4= git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= git.lukeshu.com/go/libsystemd v0.5.3/go.mod h1:FfDoP0i92r4p5Vn4NCLxvjkd7rCOe6otPa4L6hZg9WM= +github.com/0x4c6565/genie v1.0.0/go.mod h1:fDOjW0hFamMWOIkh4irf2D/TZpXXWMFtpP8MfgK0N3c= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v46.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v46.4.0+incompatible h1:fCN6Pi+tEiEwFa8RSmtVlFHRXEZ+DJm9gfx/MKqYWw4= @@ -265,6 +266,7 @@ github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2 github.com/datawire/ambassador v1.6.0 h1:4KduhY/wqtv0jK8sMVQNtENHy9fmoXugsuFp/UrM0Ts= github.com/datawire/ambassador v1.6.0/go.mod h1:mV5EhoG/NnHBsffmLnjrq+x4ZNkYDWFZXW9R+AueUiE= github.com/datawire/pf v0.0.0-20180510150411-31a823f9495a/go.mod h1:H8uUmE8qqo7z9u30MYB9riLyRckPHOPBk9ZdCuH+dQQ= +github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -426,6 +428,10 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -738,6 +744,8 @@ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4F github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -1076,6 +1084,10 @@ github.com/transip/gotransip/v6 v6.6.2 h1:+d3QO5Cyfh9n/J5OZxz8roer4JQIdmYvHVHExO github.com/transip/gotransip/v6 v6.6.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ukfast/go-durationstring v1.0.0 h1:kgPuA7XjLjgLDfkG8j0MpolxcZh/eMdiVoOIFD/uc5I= +github.com/ukfast/go-durationstring v1.0.0/go.mod h1:Ci81n51kfxlKUIaLY9cINIKRO94VTqV+iCGbOMTb0V8= +github.com/ukfast/sdk-go v1.4.23 h1:dLZmHW2jgV0QQ2TGGdbL2tYVdtQPcuUub7Rzh+6Cqic= +github.com/ukfast/sdk-go v1.4.23/go.mod h1:tspweEP77MHhVEYgEEieKAKGITFgwkYl1q5fLh4HZAo= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 h1:n7unetnX8WWTc0U85h/0+dJoLWLqoaJwowXB9RkBdxU= github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60/go.mod h1:43vmy6GEvRuVMpGEWfJ/JoEM6RIqUQI1/tb8JqZR1zI= @@ -1686,6 +1698,9 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.27.0 h1:wCg/0hk9RzcB0CYw8pYV6FiBYug1on0cpco9YZF8jqA= +gopkg.in/go-playground/validator.v9 v9.27.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= diff --git a/main.go b/main.go index 861b66513..4d76d0b4e 100644 --- a/main.go +++ b/main.go @@ -325,7 +325,7 @@ func main() { case "gandi": p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun) case "safedns": - p, err = safedns.NewSafeDNSProvider(ctx, domainFilter, cfg.DryRun) + p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun) default: log.Fatalf("unknown dns provider: %s", cfg.Provider) } diff --git a/provider/safedns/safedns.go b/provider/safedns/safedns.go index 06c1ecec1..3339b2019 100644 --- a/provider/safedns/safedns.go +++ b/provider/safedns/safedns.go @@ -17,4 +17,106 @@ limitations under the License. package safedns import ( + "context" + "fmt" + "os" + + ukf_client "github.com/ukfast/sdk-go/pkg/client" + ukf_connection "github.com/ukfast/sdk-go/pkg/connection" + "github.com/ukfast/sdk-go/pkg/service/safedns" + + "sigs.k8s.io/external-dns/provider" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" ) + +// The SafeDNS interface is a subset of the SafeDNS service API that are actually used. +// Signatures must match exactly. +type SafeDNS interface { + CreateZoneRecord(zoneName string, req safedns.CreateRecordRequest) (int, error) + DeleteZoneRecord(zoneName string, recordID int) error + GetZone(zoneName string) (safedns.Zone, error) + GetZoneRecord(zoneName string, recordID int) (safedns.Record, error) + GetZoneRecords(zoneName string, parameters ukf_connection.APIRequestParameters) ([]safedns.Record, error) + GetZones(parameters ukf_connection.APIRequestParameters) ([]safedns.Zone, error) + PatchZoneRecord(zoneName string, recordID int, patch safedns.PatchRecordRequest) (int, error) + UpdateZoneRecord(zoneName string, record safedns.Record) (int, error) +} + +type SafeDNSProvider struct { + provider.BaseProvider + Client SafeDNS + // Only consider hosted zones managing domains ending in this suffix + domainFilter endpoint.DomainFilter + DryRun bool + APIRequestParams ukf_connection.APIRequestParameters +} + +func NewSafeDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*SafeDNSProvider, error) { + token, ok := os.LookupEnv("SAFEDNS_TOKEN") + if !ok { + return nil, fmt.Errorf("No SAFEDNS_TOKEN found in environment") + } + + ukfAPIConnection := ukf_connection.NewAPIKeyCredentialsAPIConnection(token) + ukfClient := ukf_client.NewClient(ukfAPIConnection) + safeDNS := ukfClient.SafeDNSService() + + provider := &SafeDNSProvider{ + Client: safeDNS, + domainFilter: domainFilter, + DryRun: dryRun, + APIRequestParams: *ukf_connection.NewAPIRequestParameters(), + } + return provider, nil +} + +// Zones returns the list of hosted zones in the SafeDNS account +func (p *SafeDNSProvider) Zones(ctx context.Context) ([]safedns.Zone, error) { + var zones []safedns.Zone + + allZones, err := p.Client.GetZones(p.APIRequestParams) + if err != nil { + return nil, err + } + + // Check each found zone to see whether they match the domain filter provided. If they do, append it to the array of + // zones defined above. If not, continue to the next item in the loop. + for _, zone := range allZones { + if p.domainFilter.Match(zone.Name) { + zones = append(zones, zone) + } else { + continue + } + } + return zones, nil +} + +// Records returns a list of Endpoint resources created from all records in supported zones. +func (p *SafeDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { + zones, err := p.Zones(ctx) + if err != nil { + return nil, err + } + + endpoints := []*endpoint.Endpoint{} + for _, zone := range zones { + // For each zone in the zonelist, get all records of an ExternalDNS supported type. + records, err := p.Client.GetZoneRecords(zone.Name, p.APIRequestParams) + if err != nil { + return nil, err + } + for _, r := range records { + if provider.SupportedRecordType(string(r.Type)) { + endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, string(r.Type), endpoint.TTL(r.TTL), r.Content)) + } + } + } + return endpoints, nil +} + +// ApplyChanges applies a given set of changes in a given zone. +func (p *SafeDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + // TODO: Implement this + return nil +}