From 7aecb284e0ed4ef3affb315f3cd5f9fedf768a57 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Fri, 8 May 2020 05:46:23 +0700 Subject: [PATCH 01/19] Add hetzner support (#1) To run: provide token HETZNER_TOKEN in ENV set command line parameter --provider=hetzner --- go.mod | 1 + go.sum | 3 + main.go | 3 + pkg/apis/externaldns/types.go | 2 +- provider/hetzner/hetzner.go | 207 ++++++++++++++++++++++++++++++++++ 5 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 provider/hetzner/hetzner.go diff --git a/go.mod b/go.mod index 61aa118cf..1e08eff9a 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( cloud.google.com/go v0.44.3 + git.blindage.org/21h/hcloud-dns v0.0.0-20200507195107-b8e668eab385 github.com/Azure/azure-sdk-for-go v36.0.0+incompatible github.com/Azure/go-autorest/autorest v0.9.0 github.com/Azure/go-autorest/autorest/adal v0.6.0 diff --git a/go.sum b/go.sum index fb15c0a7b..5d8b9382d 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZ contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw= contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= fortio.org/fortio v1.3.0/go.mod h1:Go0fRqoPJ1xy5JOWcS23jyF58byVZxFyEePYsGmCR0k= +git.blindage.org/21h/hcloud-dns v0.0.0-20200507195107-b8e668eab385 h1:OgpuaGZsfYkxZG/L1hRFHCfGcIsCrAY7/7uCciOvkJU= +git.blindage.org/21h/hcloud-dns v0.0.0-20200507195107-b8e668eab385/go.mod h1:xjcUpOv7TY3agUKC3jdPHR3e77ojEwdNMu4dvPmsCYo= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible h1:XIaBmA4pgKqQ7jInQPaNJQ4pOHrdJjw9gYXhbyiChaU= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -520,6 +522,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/prom2json v1.1.0/go.mod h1:v7OY1795b9fEUZgq4UU2+15YjRv0LfpxKejIQCy3L7o= github.com/prometheus/prom2json v1.2.1/go.mod h1:yIcXOj/TLPdtZ12qRyhswPnu+02sfDoqatDjj0WGSvo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= diff --git a/main.go b/main.go index 871ec1c97..d101ba976 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,7 @@ import ( "sigs.k8s.io/external-dns/provider/dyn" "sigs.k8s.io/external-dns/provider/exoscale" "sigs.k8s.io/external-dns/provider/google" + "sigs.k8s.io/external-dns/provider/hetzner" "sigs.k8s.io/external-dns/provider/infoblox" "sigs.k8s.io/external-dns/provider/inmemory" "sigs.k8s.io/external-dns/provider/linode" @@ -201,6 +202,8 @@ func main() { p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun) case "digitalocean": p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun) + case "hetzner": + p, err = hetzner.NewHetznerProvider(ctx, domainFilter, cfg.DryRun) case "ovh": p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun) case "linode": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 385427d9d..b59dc8e84 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -312,7 +312,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter) // Flags related to providers - app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, vultr)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "vultr") + app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, vultr)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "vultr") app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter) app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains) app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter) diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go new file mode 100644 index 000000000..4c70c2194 --- /dev/null +++ b/provider/hetzner/hetzner.go @@ -0,0 +1,207 @@ +/* + by Vladimir Smagin, 2020 + vlad@blindage.org +*/ + +package hetzner + +import ( + "context" + "fmt" + "os" + "strings" + + hclouddns "git.blindage.org/21h/hcloud-dns" + log "github.com/sirupsen/logrus" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" +) + +const ( + hetznerCreate = "CREATE" + hetznerDelete = "DELETE" + hetznerUpdate = "UPDATE" + hetznerTTL = 600 +) + +type HetznerChanges struct { + Action string + ZoneID string + ZoneName string + ResourceRecordSet hclouddns.HCloudRecord +} + +type HetznerProvider struct { + Client hclouddns.HCloudDNS + domainFilter endpoint.DomainFilter + DryRun bool +} + +func NewHetznerProvider(ctx context.Context, domainFilter endpoint.DomainFilter, dryRun bool) (*HetznerProvider, error) { + token, ok := os.LookupEnv("HETZNER_TOKEN") + if !ok { + return nil, fmt.Errorf("No token found") + } + + provider := &HetznerProvider{ + Client: *hclouddns.New(token), + domainFilter: domainFilter, + DryRun: dryRun, + } + return provider, nil +} + +func (p *HetznerProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { + zones, err := p.Client.GetZones(hclouddns.HCloudGetZonesParams{}) + if err != nil { + return nil, err + } + endpoints := []*endpoint.Endpoint{} + for _, zone := range zones.Zones { + records, err := p.Client.GetRecords(hclouddns.HCloudGetRecordsParams{ZoneID: zone.ID}) + if err != nil { + return nil, err + } + + for _, r := range records.Records { + if provider.SupportedRecordType(string(r.RecordType)) { + name := r.Name + "." + zone.Name + + if r.Name == "@" { + name = zone.Name + } + + endpoints = append(endpoints, endpoint.NewEndpoint(name, string(r.RecordType), r.Value)) + } + } + } + + return endpoints, nil +} + +func (p *HetznerProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + combinedChanges := make([]*HetznerChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete)) + + combinedChanges = append(combinedChanges, p.newHetznerChanges(hetznerCreate, changes.Create)...) + combinedChanges = append(combinedChanges, p.newHetznerChanges(hetznerUpdate, changes.UpdateNew)...) + combinedChanges = append(combinedChanges, p.newHetznerChanges(hetznerDelete, changes.Delete)...) + + return p.submitChanges(ctx, combinedChanges) +} + +func (p *HetznerProvider) submitChanges(ctx context.Context, changes []*HetznerChanges) error { + if len(changes) == 0 { + log.Infof("All records are already up to date") + return nil + } + + zones, err := p.Client.GetZones(hclouddns.HCloudGetZonesParams{}) + if err != nil { + return err + } + + zoneChanges := p.seperateChangesByZone(zones.Zones, changes) + + for _, changes := range zoneChanges { + for _, change := range changes { + + log.WithFields(log.Fields{ + "record": change.ResourceRecordSet.Name, + "type": change.ResourceRecordSet.RecordType, + "ttl": change.ResourceRecordSet.TTL, + "action": change.Action, + "zone": change.ZoneName, + "zone_id": change.ZoneID, + }).Info("Changing record.") + + change.ResourceRecordSet.Name = strings.TrimSuffix(change.ResourceRecordSet.Name, "."+change.ZoneName) + if change.ResourceRecordSet.Name == change.ZoneName { + change.ResourceRecordSet.Name = "@" + } + if change.ResourceRecordSet.RecordType == endpoint.RecordTypeCNAME { + change.ResourceRecordSet.Value += "." + } + + switch change.Action { + case hetznerCreate: + record := hclouddns.HCloudRecord{ + RecordType: change.ResourceRecordSet.RecordType, + ZoneID: change.ZoneID, + Name: change.ResourceRecordSet.Name, + Value: change.ResourceRecordSet.Value, + TTL: change.ResourceRecordSet.TTL, + } + _, err := p.Client.CreateRecord(record) + if err != nil { + return err + } + case hetznerDelete: + _, err := p.Client.DeleteRecord(change.ResourceRecordSet.ID) + if err != nil { + return err + } + case hetznerUpdate: + record := hclouddns.HCloudRecord{ + RecordType: change.ResourceRecordSet.RecordType, + ZoneID: change.ZoneID, + Name: change.ResourceRecordSet.Name, + Value: change.ResourceRecordSet.Value, + TTL: change.ResourceRecordSet.TTL, + } + _, err := p.Client.UpdateRecord(record) + if err != nil { + return err + } + } + } + } + + return nil +} + +func (p *HetznerProvider) newHetznerChanges(action string, endpoints []*endpoint.Endpoint) []*HetznerChanges { + changes := make([]*HetznerChanges, 0, len(endpoints)) + ttl := hetznerTTL + for _, e := range endpoints { + + if e.RecordTTL.IsConfigured() { + ttl = int(e.RecordTTL) + } + + change := &HetznerChanges{ + Action: action, + ResourceRecordSet: hclouddns.HCloudRecord{ + RecordType: hclouddns.RecordType(e.RecordType), + Name: e.DNSName, + Value: e.Targets[0], + TTL: ttl, + }, + } + changes = append(changes, change) + } + return changes +} + +func (p *HetznerProvider) seperateChangesByZone(zones []hclouddns.HCloudZone, changes []*HetznerChanges) map[string][]*HetznerChanges { + change := make(map[string][]*HetznerChanges) + zoneNameID := provider.ZoneIDName{} + + for _, z := range zones { + zoneNameID.Add(z.ID, z.Name) + change[z.ID] = []*HetznerChanges{} + } + + for _, c := range changes { + zoneID, zoneName := zoneNameID.FindZone(c.ResourceRecordSet.Name) + if zoneName == "" { + log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSet.Name) + continue + } + c.ZoneName = zoneName + c.ZoneID = zoneID + change[zoneID] = append(change[zoneID], c) + + } + return change +} From 371b219d576a734f7ca68452324f9a9da6bb0254 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Fri, 8 May 2020 12:37:06 +0700 Subject: [PATCH 02/19] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5f1431445..949ac4c53 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ ExternalDNS' current release is `v0.7`. This version allows you to keep selected * [CloudFlare](https://www.cloudflare.com/dns) * [RcodeZero](https://www.rcodezero.at/) * [DigitalOcean](https://www.digitalocean.com/products/networking) +* [Hetzner](https://hetzner.com/) * [DNSimple](https://dnsimple.com/) * [Infoblox](https://www.infoblox.com/products/dns/) * [Dyn](https://dyn.com/dns/) @@ -79,6 +80,7 @@ The following table clarifies the current status of the providers according to t | CloudFlare | Beta | | | RcodeZero | Alpha | | | DigitalOcean | Alpha | | +| Hetzner | Alpha | @21h | | DNSimple | Alpha | | | Infoblox | Alpha | @saileshgiri | | Dyn | Alpha | | From f3c8694670c72ab61d23dce2a8da122ff1678c4b Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Thu, 14 May 2020 08:08:26 +0700 Subject: [PATCH 03/19] cleanup go mods --- go.mod | 2 +- go.sum | 20 ++------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 1e08eff9a..3cb17aabc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( cloud.google.com/go v0.44.3 - git.blindage.org/21h/hcloud-dns v0.0.0-20200507195107-b8e668eab385 + git.blindage.org/21h/hcloud-dns v0.0.0-20200514010343-e2e98dfc3e36 github.com/Azure/azure-sdk-for-go v36.0.0+incompatible github.com/Azure/go-autorest/autorest v0.9.0 github.com/Azure/go-autorest/autorest/adal v0.6.0 diff --git a/go.sum b/go.sum index 5d8b9382d..8b66f9942 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZ contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw= contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= fortio.org/fortio v1.3.0/go.mod h1:Go0fRqoPJ1xy5JOWcS23jyF58byVZxFyEePYsGmCR0k= -git.blindage.org/21h/hcloud-dns v0.0.0-20200507195107-b8e668eab385 h1:OgpuaGZsfYkxZG/L1hRFHCfGcIsCrAY7/7uCciOvkJU= -git.blindage.org/21h/hcloud-dns v0.0.0-20200507195107-b8e668eab385/go.mod h1:xjcUpOv7TY3agUKC3jdPHR3e77ojEwdNMu4dvPmsCYo= +git.blindage.org/21h/hcloud-dns v0.0.0-20200514010343-e2e98dfc3e36 h1:wma3Ga6OIP9x63tzv/HpS6vAmGx5nN0KwWSkQoxdXws= +git.blindage.org/21h/hcloud-dns v0.0.0-20200514010343-e2e98dfc3e36/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible h1:XIaBmA4pgKqQ7jInQPaNJQ4pOHrdJjw9gYXhbyiChaU= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -294,13 +294,11 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -361,7 +359,6 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -416,7 +413,6 @@ github.com/maxatome/go-testdeep v1.4.0 h1:vKQh3/lHKAMsxggya/fXB6fLbf70c7k6wlLveu github.com/maxatome/go-testdeep v1.4.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= github.com/mdempsky/unconvert v0.0.0-20190325185700-2f5dc3378ed3/go.mod h1:9+3Wp2ccIz73BJqVfc7n2+1A+mzvnEwtDTqEjeRngBQ= github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= -github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -490,7 +486,6 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.2.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -508,7 +503,6 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -522,7 +516,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/prom2json v1.1.0/go.mod h1:v7OY1795b9fEUZgq4UU2+15YjRv0LfpxKejIQCy3L7o= github.com/prometheus/prom2json v1.2.1/go.mod h1:yIcXOj/TLPdtZ12qRyhswPnu+02sfDoqatDjj0WGSvo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -546,14 +539,12 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -634,7 +625,6 @@ golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= @@ -667,7 +657,6 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -707,7 +696,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -775,13 +763,11 @@ google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -812,9 +798,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 41fa4d02636ceb71a6c550a05d6bf0c99ceb4d97 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Thu, 14 May 2020 08:10:40 +0700 Subject: [PATCH 04/19] fix for error report --- provider/hetzner/hetzner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go index 4c70c2194..660b3550c 100644 --- a/provider/hetzner/hetzner.go +++ b/provider/hetzner/hetzner.go @@ -7,7 +7,7 @@ package hetzner import ( "context" - "fmt" + "errors" "os" "strings" @@ -41,7 +41,7 @@ type HetznerProvider struct { func NewHetznerProvider(ctx context.Context, domainFilter endpoint.DomainFilter, dryRun bool) (*HetznerProvider, error) { token, ok := os.LookupEnv("HETZNER_TOKEN") if !ok { - return nil, fmt.Errorf("No token found") + return nil, errors.New("no environment variable HETZNER_TOKEN provided") } provider := &HetznerProvider{ From ae60b0cebe77d70ea0c14cb9a8713c114deb90d7 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Mon, 18 May 2020 12:25:21 +0700 Subject: [PATCH 05/19] Add doc for Hetzner --- README.md | 1 + docs/tutorials/hetzner.md | 191 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 docs/tutorials/hetzner.md diff --git a/README.md b/README.md index 949ac4c53..72722d0a4 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ The following tutorials are provided: * [Cloudflare](docs/tutorials/cloudflare.md) * [CoreDNS](docs/tutorials/coredns.md) * [DigitalOcean](docs/tutorials/digitalocean.md) +* [Hetzner](docs/tutorials/hetzner.md) * [DNSimple](docs/tutorials/dnsimple.md) * [Dyn](docs/tutorials/dyn.md) * [Exoscale](docs/tutorials/exoscale.md) diff --git a/docs/tutorials/hetzner.md b/docs/tutorials/hetzner.md new file mode 100644 index 000000000..e40f1af83 --- /dev/null +++ b/docs/tutorials/hetzner.md @@ -0,0 +1,191 @@ +# Setting up ExternalDNS for Services on Hetzner DNS + +This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Hetzner DNS. + +Make sure to use **>=0.7.2** version of ExternalDNS for this tutorial. + +## Creating a Hetzner DNS zone + +If you want to learn about how to use Hetzner's DNS service read the following tutorial series: + +[An Introduction to Managing DNS](https://wiki.hetzner.de/index.php/DNS_Overview), and [Add a new DNS zone](https://wiki.hetzner.de/index.php/Getting_started). + +Create a new DNS zone where you want to create your records in. Let's use `example.com` as an example here. + +## Creating Hetzner Credentials + +Generate a new personal token by going to [the API settings](https://dns.hetzner.com/settings/api-token) or follow [Generating an API access token](https://wiki.hetzner.de/index.php/API_access_token) if you need more information. Give the token a name and choose read and write access. The token needs to be passed to ExternalDNS so make a note of it for later use. + +The environment variable `HETZNER_TOKEN` will be needed to run ExternalDNS with Hetzner. + +## Deploy ExternalDNS + +Connect your `kubectl` client to the cluster you want to test ExternalDNS with. +Then apply one of the following manifests file to deploy ExternalDNS. + +### Manifest (for clusters without RBAC enabled) +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + replicas: 1 + selector: + matchLabels: + app: external-dns + strategy: + type: Recreate + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + image: registry.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service # ingress is also possible + - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. + - --provider=hetznerYOUR_HETZNER_DNS_API_KEY + env: + - name: HETZNER_TOKEN + value: "YOUR_HETZNER_DNS_API_KEY" +``` + +### Manifest (for clusters with RBAC enabled) +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [""] + resources: ["services","endpoints","pods"] + verbs: ["get","watch","list"] +- apiGroups: ["extensions"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + replicas: 1 + selector: + matchLabels: + app: external-dns + strategy: + type: Recreate + template: + metadata: + labels: + app: external-dns + spec: + serviceAccountName: external-dns + containers: + - name: external-dns + image: registry.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service # ingress is also possible + - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. + - --provider=hetzner + env: + - name: HETZNER_TOKEN + value: "YOUR_HETZNER_DNS_API_KEY" +``` + + +## Deploying an Nginx Service + +Create a service file called 'nginx.yaml' with the following contents: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + annotations: + external-dns.alpha.kubernetes.io/hostname: my-app.example.com +spec: + selector: + app: nginx + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 +``` + +Note the annotation on the service; use the same hostname as the Hetzner DNS zone created above. + +ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation will cause ExternalDNS to remove the corresponding DNS records. + +Create the deployment and service: + +```console +$ kubectl create -f nginx.yaml +``` + +Depending where you run your service it can take a little while for your cloud provider to create an external IP for the service. + +Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize the Hetzner DNS records. + +## Verifying Hetzner DNS records + +Check your [Hetzner DNS UI](https://dns.hetzner.com/) to view the records for your Hetzner DNS zone. + +Click on the zone for the one created above if a different domain was used. + +This should show the external IP address of the service as the A record for your domain. + +## Cleanup + +Now that we have verified that ExternalDNS will automatically manage Hetzner DNS records, we can delete the tutorial's example: + +``` +$ kubectl delete service -f nginx.yaml +$ kubectl delete service -f externaldns.yaml +``` From f2b838cdda86fb056822f993074fd34ef539761b Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Sat, 23 May 2020 22:16:30 +0700 Subject: [PATCH 06/19] + --- provider/hetzner/hetzner_test.go | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 provider/hetzner/hetzner_test.go diff --git a/provider/hetzner/hetzner_test.go b/provider/hetzner/hetzner_test.go new file mode 100644 index 000000000..c546f19cc --- /dev/null +++ b/provider/hetzner/hetzner_test.go @@ -0,0 +1,86 @@ +package hetzner + +import ( + "context" + "fmt" + "os" + "testing" + + hclouddns "git.blindage.org/21h/hcloud-dns" + + "sigs.k8s.io/external-dns/endpoint" +) + +type mockHetznerProvider struct { + HetznerProvider + Client mockHetznerClient +} + +type mockHetznerClient struct { + hclouddns.HCloudDNS +} + +func (m *mockHetznerClient) GetZones(params hclouddns.HCloudGetZonesParams) (hclouddns.HCloudAnswerGetZones, error) { + return hclouddns.HCloudAnswerGetZones{ + Zones: []hclouddns.HCloudZone{ + { + ID: "HetznerZoneID", + Name: "blindage.org", + TTL: 666, + RecordsCount: 1, + }, + }, + }, nil +} + +func (m *mockHetznerClient) GetRecords(params hclouddns.HCloudGetRecordsParams) (hclouddns.HCloudAnswerGetRecords, error) { + return hclouddns.HCloudAnswerGetRecords{ + Records: []hclouddns.HCloudRecord{ + { + ID: "HetznerRecordID", + RecordType: "A", + Name: "local", + Value: "127.0.0.1", + TTL: 666, + ZoneID: "HetznerZoneImocked.Client.D", + }, + }, + }, nil +} + +func TestNewHetznerProvider(t *testing.T) { + _ = os.Setenv("HETZNER_TOKEN", "myHetznerToken") + _, err := NewHetznerProvider(context.Background(), endpoint.NewDomainFilter([]string{"blindage.org"}), true) + if err != nil { + t.Errorf("failed : %s", err) + } + + _ = os.Unsetenv("HETZNER_TOKEN") + _, err = NewHetznerProvider(context.Background(), endpoint.NewDomainFilter([]string{"blindage.org"}), true) + if err == nil { + t.Errorf("expected to fail") + } +} + +func TestHetznerProvider_Records(t *testing.T) { + mockedClient := mockHetznerClient{} + mockedProvider := mockHetznerProvider{ + Client: mockedClient, + } + + expectedZonesAnswer, err := mockedClient.GetZones(hclouddns.HCloudGetZonesParams{}) + if err != nil { + t.Fatal(err) + } + fmt.Println("expectedZonesAnswer:", expectedZonesAnswer) + + endpoints, err := mockedProvider.Records(context.Background()) + if err != nil { + t.Fatal(err) + } + fmt.Println("endpoints:", endpoints) + + // if !reflect.DeepEqual(expectedZonesAnswer.Zones, endpoints) { + // t.Fatal(err) + // } +} From a7ee80f2e1a739fd2cd5a60dabc2d4452e104734 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 26 May 2020 00:25:36 +0700 Subject: [PATCH 07/19] test of Records --- go.mod | 2 +- go.sum | 21 +---- provider/hetzner/hetzner.go | 6 +- provider/hetzner/hetzner_test.go | 141 ++++++++++++++++++++++++++----- 4 files changed, 129 insertions(+), 41 deletions(-) diff --git a/go.mod b/go.mod index 2014c2f16..38ba2e7c6 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module sigs.k8s.io/external-dns go 1.14 require ( - git.blindage.org/21h/hcloud-dns v0.0.0-20200514010343-e2e98dfc3e36 cloud.google.com/go v0.50.0 + git.blindage.org/21h/hcloud-dns v0.0.0-20200525163427-28c94ccdc833 github.com/Azure/azure-sdk-for-go v36.0.0+incompatible github.com/Azure/go-autorest/autorest v0.9.4 github.com/Azure/go-autorest/autorest/adal v0.8.3 diff --git a/go.sum b/go.sum index 0bfc00acb..0e4142c9b 100644 --- a/go.sum +++ b/go.sum @@ -13,14 +13,9 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= -contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= -contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= -contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw= -contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= -fortio.org/fortio v1.3.0/go.mod h1:Go0fRqoPJ1xy5JOWcS23jyF58byVZxFyEePYsGmCR0k= -git.blindage.org/21h/hcloud-dns v0.0.0-20200514010343-e2e98dfc3e36 h1:wma3Ga6OIP9x63tzv/HpS6vAmGx5nN0KwWSkQoxdXws= -git.blindage.org/21h/hcloud-dns v0.0.0-20200514010343-e2e98dfc3e36/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.blindage.org/21h/hcloud-dns v0.0.0-20200525163427-28c94ccdc833 h1:08uFTZYleGVTfcYnTb/XHClK0ih/SmRafKD+HYk7gE4= +git.blindage.org/21h/hcloud-dns v0.0.0-20200525163427-28c94ccdc833/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible h1:XIaBmA4pgKqQ7jInQPaNJQ4pOHrdJjw9gYXhbyiChaU= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -284,7 +279,6 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -292,7 +286,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -376,9 +369,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxatome/go-testdeep v1.4.0 h1:vKQh3/lHKAMsxggya/fXB6fLbf70c7k6wlLveuS9sKE= github.com/maxatome/go-testdeep v1.4.0/go.mod h1:011SgQ6efzZYAen6fDn4BqQ+lUR72ysdyKe7Dyogw70= -github.com/mdempsky/unconvert v0.0.0-20190325185700-2f5dc3378ed3/go.mod h1:9+3Wp2ccIz73BJqVfc7n2+1A+mzvnEwtDTqEjeRngBQ= -github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b h1:5f5B1kp+QerGOF91q1qVJcUWWvXsVEN3OKiyEzAAjIM= @@ -458,8 +448,6 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= @@ -489,11 +477,11 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.3.4 h1:iHc8Rfhb/uCOc9a3KGuD3ut22L+hLIVaqR1o5fS6zC4= @@ -668,9 +656,6 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190825160603-fb81701db80f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go index 660b3550c..b71b650c2 100644 --- a/provider/hetzner/hetzner.go +++ b/provider/hetzner/hetzner.go @@ -33,7 +33,7 @@ type HetznerChanges struct { } type HetznerProvider struct { - Client hclouddns.HCloudDNS + Client hclouddns.HCloudClientAdapter domainFilter endpoint.DomainFilter DryRun bool } @@ -44,8 +44,10 @@ func NewHetznerProvider(ctx context.Context, domainFilter endpoint.DomainFilter, return nil, errors.New("no environment variable HETZNER_TOKEN provided") } + client := hclouddns.New(token) + provider := &HetznerProvider{ - Client: *hclouddns.New(token), + Client: client, domainFilter: domainFilter, DryRun: dryRun, } diff --git a/provider/hetzner/hetzner_test.go b/provider/hetzner/hetzner_test.go index c546f19cc..006e9b21b 100644 --- a/provider/hetzner/hetzner_test.go +++ b/provider/hetzner/hetzner_test.go @@ -4,23 +4,50 @@ import ( "context" "fmt" "os" + "reflect" "testing" hclouddns "git.blindage.org/21h/hcloud-dns" + "github.com/stretchr/testify/assert" "sigs.k8s.io/external-dns/endpoint" ) -type mockHetznerProvider struct { - HetznerProvider - Client mockHetznerClient +type mockHCloudClientAdapter interface { + GetZone(ID string) (hclouddns.HCloudAnswerGetZone, error) + GetZones(params hclouddns.HCloudGetZonesParams) (hclouddns.HCloudAnswerGetZones, error) + UpdateZone(zone hclouddns.HCloudZone) (hclouddns.HCloudAnswerGetZone, error) + DeleteZone(ID string) (hclouddns.HCloudAnswerDeleteZone, error) + CreateZone(zone hclouddns.HCloudZone) (hclouddns.HCloudAnswerGetZone, error) + ImportZoneString(zoneID string, zonePlainText string) (hclouddns.HCloudAnswerGetZone, error) + ExportZoneToString(zoneID string) (hclouddns.HCloudAnswerGetZonePlainText, error) + ValidateZoneString(zonePlainText string) (hclouddns.HCloudAnswerZoneValidate, error) + GetRecords(params hclouddns.HCloudGetRecordsParams) (hclouddns.HCloudAnswerGetRecords, error) + UpdateRecord(record hclouddns.HCloudRecord) (hclouddns.HCloudAnswerGetRecord, error) + DeleteRecord(ID string) (hclouddns.HCloudAnswerDeleteRecord, error) + CreateRecord(record hclouddns.HCloudRecord) (hclouddns.HCloudAnswerGetRecord, error) + CreateRecordBulk(record []hclouddns.HCloudRecord) (hclouddns.HCloudAnswerCreateRecords, error) + UpdateRecordBulk(record []hclouddns.HCloudRecord) (hclouddns.HCloudAnswerUpdateRecords, error) } -type mockHetznerClient struct { - hclouddns.HCloudDNS +type mockHCloudClient struct { + Token string `yaml:"token"` } -func (m *mockHetznerClient) GetZones(params hclouddns.HCloudGetZonesParams) (hclouddns.HCloudAnswerGetZones, error) { +// New instance +func mockHCloudNew(t string) mockHCloudClientAdapter { + return &mockHCloudClient{ + Token: t, + } +} + +// Mock all methods + +func (m *mockHCloudClient) GetZone(ID string) (hclouddns.HCloudAnswerGetZone, error) { + return hclouddns.HCloudAnswerGetZone{}, nil +} + +func (m *mockHCloudClient) GetZones(params hclouddns.HCloudGetZonesParams) (hclouddns.HCloudAnswerGetZones, error) { return hclouddns.HCloudAnswerGetZones{ Zones: []hclouddns.HCloudZone{ { @@ -33,20 +60,56 @@ func (m *mockHetznerClient) GetZones(params hclouddns.HCloudGetZonesParams) (hcl }, nil } -func (m *mockHetznerClient) GetRecords(params hclouddns.HCloudGetRecordsParams) (hclouddns.HCloudAnswerGetRecords, error) { +// zones +func (m *mockHCloudClient) UpdateZone(zone hclouddns.HCloudZone) (hclouddns.HCloudAnswerGetZone, error) { + return hclouddns.HCloudAnswerGetZone{}, nil +} +func (m *mockHCloudClient) DeleteZone(ID string) (hclouddns.HCloudAnswerDeleteZone, error) { + return hclouddns.HCloudAnswerDeleteZone{}, nil +} +func (m *mockHCloudClient) CreateZone(zone hclouddns.HCloudZone) (hclouddns.HCloudAnswerGetZone, error) { + return hclouddns.HCloudAnswerGetZone{}, nil +} +func (m *mockHCloudClient) ImportZoneString(zoneID string, zonePlainText string) (hclouddns.HCloudAnswerGetZone, error) { + return hclouddns.HCloudAnswerGetZone{}, nil +} +func (m *mockHCloudClient) ExportZoneToString(zoneID string) (hclouddns.HCloudAnswerGetZonePlainText, error) { + return hclouddns.HCloudAnswerGetZonePlainText{}, nil +} +func (m *mockHCloudClient) ValidateZoneString(zonePlainText string) (hclouddns.HCloudAnswerZoneValidate, error) { + return hclouddns.HCloudAnswerZoneValidate{}, nil +} + +// records +func (m *mockHCloudClient) GetRecords(params hclouddns.HCloudGetRecordsParams) (hclouddns.HCloudAnswerGetRecords, error) { return hclouddns.HCloudAnswerGetRecords{ Records: []hclouddns.HCloudRecord{ { - ID: "HetznerRecordID", - RecordType: "A", - Name: "local", + RecordType: hclouddns.RecordType("A"), + ID: "ATypeRecordID", + ZoneID: "HetznerZoneID", + Name: "@", Value: "127.0.0.1", TTL: 666, - ZoneID: "HetznerZoneImocked.Client.D", }, }, }, nil } +func (m *mockHCloudClient) UpdateRecord(record hclouddns.HCloudRecord) (hclouddns.HCloudAnswerGetRecord, error) { + return hclouddns.HCloudAnswerGetRecord{}, nil +} +func (m *mockHCloudClient) DeleteRecord(ID string) (hclouddns.HCloudAnswerDeleteRecord, error) { + return hclouddns.HCloudAnswerDeleteRecord{}, nil +} +func (m *mockHCloudClient) CreateRecord(record hclouddns.HCloudRecord) (hclouddns.HCloudAnswerGetRecord, error) { + return hclouddns.HCloudAnswerGetRecord{}, nil +} +func (m *mockHCloudClient) CreateRecordBulk(record []hclouddns.HCloudRecord) (hclouddns.HCloudAnswerCreateRecords, error) { + return hclouddns.HCloudAnswerCreateRecords{}, nil +} +func (m *mockHCloudClient) UpdateRecordBulk(record []hclouddns.HCloudRecord) (hclouddns.HCloudAnswerUpdateRecords, error) { + return hclouddns.HCloudAnswerUpdateRecords{}, nil +} func TestNewHetznerProvider(t *testing.T) { _ = os.Setenv("HETZNER_TOKEN", "myHetznerToken") @@ -63,24 +126,62 @@ func TestNewHetznerProvider(t *testing.T) { } func TestHetznerProvider_Records(t *testing.T) { - mockedClient := mockHetznerClient{} - mockedProvider := mockHetznerProvider{ + + mockedClient := mockHCloudNew("myHetznerToken") + + mockedProvider := &HetznerProvider{ Client: mockedClient, } - expectedZonesAnswer, err := mockedClient.GetZones(hclouddns.HCloudGetZonesParams{}) + // Check test zone data is ok + expectedZonesAnswer := hclouddns.HCloudAnswerGetZones{ + Zones: []hclouddns.HCloudZone{ + { + ID: "HetznerZoneID", + Name: "blindage.org", + TTL: 666, + RecordsCount: 1, + }, + }, + } + + testingZonesAnswer, err := mockedClient.GetZones(hclouddns.HCloudGetZonesParams{}) if err != nil { t.Fatal(err) } - fmt.Println("expectedZonesAnswer:", expectedZonesAnswer) + if !reflect.DeepEqual(expectedZonesAnswer, testingZonesAnswer) { + t.Fatal(err) + } + + // Check test record data is ok + expectedRecordsAnswer := hclouddns.HCloudAnswerGetRecords{ + Records: []hclouddns.HCloudRecord{ + { + RecordType: hclouddns.RecordType("A"), + ID: "ATypeRecordID", + ZoneID: "HetznerZoneID", + Name: "@", + Value: "127.0.0.1", + TTL: 666, + }, + }, + } + + testingRecordsAnswer, err := mockedClient.GetRecords(hclouddns.HCloudGetRecordsParams{}) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expectedRecordsAnswer, testingRecordsAnswer) { + t.Fatal(err) + } + + // Now check Records function of provider, if ZoneID equal "blindage.org" must be returned endpoints, err := mockedProvider.Records(context.Background()) if err != nil { t.Fatal(err) } - fmt.Println("endpoints:", endpoints) - - // if !reflect.DeepEqual(expectedZonesAnswer.Zones, endpoints) { - // t.Fatal(err) - // } + fmt.Printf("%+v\n", endpoints[0].DNSName) + assert.Equal(t, "blindage.org", endpoints[0].DNSName) } From f6961ef01b8e1f98ac1cad5572dbee79a7430947 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 26 May 2020 00:40:46 +0700 Subject: [PATCH 08/19] testing for ApplyChanges --- provider/hetzner/hetzner_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/provider/hetzner/hetzner_test.go b/provider/hetzner/hetzner_test.go index 006e9b21b..8663c32c7 100644 --- a/provider/hetzner/hetzner_test.go +++ b/provider/hetzner/hetzner_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" ) type mockHCloudClientAdapter interface { @@ -185,3 +186,23 @@ func TestHetznerProvider_Records(t *testing.T) { fmt.Printf("%+v\n", endpoints[0].DNSName) assert.Equal(t, "blindage.org", endpoints[0].DNSName) } + +func TestHetznerProvider_ApplyChanges(t *testing.T) { + changes := &plan.Changes{} + mockedClient := mockHCloudNew("myHetznerToken") + mockedProvider := &HetznerProvider{ + Client: mockedClient, + } + + changes.Create = []*endpoint.Endpoint{ + {DNSName: "test.org", Targets: endpoint.Targets{"target"}}, + {DNSName: "test.test.org", Targets: endpoint.Targets{"target"}, RecordTTL: 666}, + } + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test.test.org", Targets: endpoint.Targets{"target-new"}, RecordType: "A", RecordTTL: 777}} + changes.Delete = []*endpoint.Endpoint{{DNSName: "test.test.org", Targets: endpoint.Targets{"target"}, RecordType: "A"}} + + err := mockedProvider.ApplyChanges(context.Background(), changes) + if err != nil { + t.Errorf("should not fail, %s", err) + } +} From 940112684cd0793ee1e12babc0112483c6e7a141 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 26 May 2020 00:50:55 +0700 Subject: [PATCH 09/19] separate test data consistency check --- provider/hetzner/hetzner_test.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/provider/hetzner/hetzner_test.go b/provider/hetzner/hetzner_test.go index 8663c32c7..57f49a135 100644 --- a/provider/hetzner/hetzner_test.go +++ b/provider/hetzner/hetzner_test.go @@ -126,14 +126,10 @@ func TestNewHetznerProvider(t *testing.T) { } } -func TestHetznerProvider_Records(t *testing.T) { +func TestHetznerProvider_TestData(t *testing.T) { mockedClient := mockHCloudNew("myHetznerToken") - mockedProvider := &HetznerProvider{ - Client: mockedClient, - } - // Check test zone data is ok expectedZonesAnswer := hclouddns.HCloudAnswerGetZones{ Zones: []hclouddns.HCloudZone{ @@ -148,11 +144,11 @@ func TestHetznerProvider_Records(t *testing.T) { testingZonesAnswer, err := mockedClient.GetZones(hclouddns.HCloudGetZonesParams{}) if err != nil { - t.Fatal(err) + t.Errorf("should not fail, %s", err) } if !reflect.DeepEqual(expectedZonesAnswer, testingZonesAnswer) { - t.Fatal(err) + t.Errorf("should be equal, %s", err) } // Check test record data is ok @@ -171,17 +167,27 @@ func TestHetznerProvider_Records(t *testing.T) { testingRecordsAnswer, err := mockedClient.GetRecords(hclouddns.HCloudGetRecordsParams{}) if err != nil { - t.Fatal(err) + t.Errorf("should not fail, %s", err) } if !reflect.DeepEqual(expectedRecordsAnswer, testingRecordsAnswer) { - t.Fatal(err) + t.Errorf("should be equal, %s", err) + } + +} + +func TestHetznerProvider_Records(t *testing.T) { + + mockedClient := mockHCloudNew("myHetznerToken") + + mockedProvider := &HetznerProvider{ + Client: mockedClient, } // Now check Records function of provider, if ZoneID equal "blindage.org" must be returned endpoints, err := mockedProvider.Records(context.Background()) if err != nil { - t.Fatal(err) + t.Errorf("should not fail, %s", err) } fmt.Printf("%+v\n", endpoints[0].DNSName) assert.Equal(t, "blindage.org", endpoints[0].DNSName) From 97fa464403ef2fcce058a95828ea167dfd5ecc9f Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 26 May 2020 00:54:19 +0700 Subject: [PATCH 10/19] update modules --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 38ba2e7c6..5dbb1a816 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( cloud.google.com/go v0.50.0 - git.blindage.org/21h/hcloud-dns v0.0.0-20200525163427-28c94ccdc833 + git.blindage.org/21h/hcloud-dns v0.0.0-20200525170043-def10a4a28e0 github.com/Azure/azure-sdk-for-go v36.0.0+incompatible github.com/Azure/go-autorest/autorest v0.9.4 github.com/Azure/go-autorest/autorest/adal v0.8.3 diff --git a/go.sum b/go.sum index 0e4142c9b..806255dda 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.blindage.org/21h/hcloud-dns v0.0.0-20200525163427-28c94ccdc833 h1:08uFTZYleGVTfcYnTb/XHClK0ih/SmRafKD+HYk7gE4= -git.blindage.org/21h/hcloud-dns v0.0.0-20200525163427-28c94ccdc833/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= +git.blindage.org/21h/hcloud-dns v0.0.0-20200525170043-def10a4a28e0 h1:kdxglEveTcqIG5zEPdQ0Y5KctnIGR7zXsQCQakoTNxU= +git.blindage.org/21h/hcloud-dns v0.0.0-20200525170043-def10a4a28e0/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible h1:XIaBmA4pgKqQ7jInQPaNJQ4pOHrdJjw9gYXhbyiChaU= github.com/Azure/azure-sdk-for-go v36.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= From 76f391d8f81f0d297c675b356662a37aab183295 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 26 May 2020 03:30:57 +0700 Subject: [PATCH 11/19] fix typo --- docs/tutorials/hetzner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/hetzner.md b/docs/tutorials/hetzner.md index e40f1af83..319ec91de 100644 --- a/docs/tutorials/hetzner.md +++ b/docs/tutorials/hetzner.md @@ -47,7 +47,7 @@ spec: args: - --source=service # ingress is also possible - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - - --provider=hetznerYOUR_HETZNER_DNS_API_KEY + - --provider=hetzner env: - name: HETZNER_TOKEN value: "YOUR_HETZNER_DNS_API_KEY" From 3b070a6fbf27bb11122ad5464b2f0ba57969e006 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Wed, 27 May 2020 02:49:41 +0700 Subject: [PATCH 12/19] add apache license --- provider/hetzner/hetzner.go | 12 ++++++++++-- provider/hetzner/hetzner_test.go | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go index b71b650c2..eb53e8983 100644 --- a/provider/hetzner/hetzner.go +++ b/provider/hetzner/hetzner.go @@ -1,6 +1,14 @@ /* - by Vladimir Smagin, 2020 - vlad@blindage.org +Copyright 2020 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package hetzner diff --git a/provider/hetzner/hetzner_test.go b/provider/hetzner/hetzner_test.go index 57f49a135..2faa63816 100644 --- a/provider/hetzner/hetzner_test.go +++ b/provider/hetzner/hetzner_test.go @@ -1,3 +1,16 @@ +/* +Copyright 2020 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package hetzner import ( From 2494672f12944a7e875dab62ed0af8fa211de7e8 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 9 Jun 2020 07:54:53 +0700 Subject: [PATCH 13/19] readme --- docs/tutorials/hetzner.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/hetzner.md b/docs/tutorials/hetzner.md index 319ec91de..25895f43d 100644 --- a/docs/tutorials/hetzner.md +++ b/docs/tutorials/hetzner.md @@ -2,7 +2,7 @@ This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Hetzner DNS. -Make sure to use **>=0.7.2** version of ExternalDNS for this tutorial. +Make sure to use **>=0.7.3** version of ExternalDNS for this tutorial. ## Creating a Hetzner DNS zone @@ -43,7 +43,7 @@ spec: spec: containers: - name: external-dns - image: registry.opensource.zalan.do/teapot/external-dns:latest + image: eu.gcr.io/k8s-artifacts-prod/external-dns/external-dns:v0.7.3 args: - --source=service # ingress is also possible - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. From ba5afe9518dd6c9d653cc1a512ca41b82c8587f8 Mon Sep 17 00:00:00 2001 From: Hugome Date: Sun, 7 Jun 2020 05:00:36 +0200 Subject: [PATCH 14/19] Add OVH API rate limiting option --- go.mod | 1 + go.sum | 2 ++ main.go | 2 +- pkg/apis/externaldns/types.go | 3 +++ pkg/apis/externaldns/types_test.go | 4 ++++ provider/ovh/ovh.go | 22 ++++++++++++++++++---- provider/ovh/ovh_test.go | 16 +++++++++------- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 59025b32c..f867c11fe 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92 github.com/vultr/govultr v0.3.2 go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875 + go.uber.org/ratelimit v0.1.0 golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 google.golang.org/api v0.15.0 diff --git a/go.sum b/go.sum index a4af0ab59..4747dabaf 100644 --- a/go.sum +++ b/go.sum @@ -550,6 +550,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw= +go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= diff --git a/main.go b/main.go index 0c98bf053..80d3dcf23 100644 --- a/main.go +++ b/main.go @@ -200,7 +200,7 @@ func main() { case "digitalocean": p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize) case "ovh": - p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun) + p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.DryRun) case "linode": p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version) case "dnsimple": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index c2c6c06c3..3017a35bc 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -99,6 +99,7 @@ type Config struct { OCIConfigFile string InMemoryZones []string OVHEndpoint string + OVHApiRateLimit int PDNSServer string PDNSAPIKey string `secure:"yes"` PDNSTLSEnabled bool @@ -197,6 +198,7 @@ var defaultConfig = &Config{ OCIConfigFile: "/etc/kubernetes/oci.yaml", InMemoryZones: []string{}, OVHEndpoint: "ovh-eu", + OVHApiRateLimit: 20, PDNSServer: "http://localhost:8081", PDNSAPIKey: "", PDNSTLSEnabled: false, @@ -360,6 +362,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("rcodezero-txt-encrypt", "When using the Rcodezero provider with txt registry option, set if TXT rrs are encrypted (default: false)").Default(strconv.FormatBool(defaultConfig.RcodezeroTXTEncrypt)).BoolVar(&cfg.RcodezeroTXTEncrypt) app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones) app.Flag("ovh-endpoint", "When using the OVH provider, specify the endpoint (default: ovh-eu)").Default(defaultConfig.OVHEndpoint).StringVar(&cfg.OVHEndpoint) + app.Flag("ovh-api-rate-limit", "When using the OVH provider, specify the API request rate limit, X operations by seconds (default: 20)").Default(strconv.Itoa(defaultConfig.OVHApiRateLimit)).IntVar(&cfg.OVHApiRateLimit) app.Flag("pdns-server", "When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns)").Default(defaultConfig.PDNSServer).StringVar(&cfg.PDNSServer) app.Flag("pdns-api-key", "When using the PowerDNS/PDNS provider, specify the API key to use to authorize requests (required when --provider=pdns)").Default(defaultConfig.PDNSAPIKey).StringVar(&cfg.PDNSAPIKey) app.Flag("pdns-tls-enabled", "When using the PowerDNS/PDNS provider, specify whether to use TLS (default: false, requires --tls-ca, optionally specify --tls-client-cert and --tls-client-cert-key)").Default(strconv.FormatBool(defaultConfig.PDNSTLSEnabled)).BoolVar(&cfg.PDNSTLSEnabled) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 929f5f1cc..506ea2f43 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -75,6 +75,7 @@ var ( OCIConfigFile: "/etc/kubernetes/oci.yaml", InMemoryZones: []string{""}, OVHEndpoint: "ovh-eu", + OVHApiRateLimit: 20, PDNSServer: "http://localhost:8081", PDNSAPIKey: "", Policy: "sync", @@ -149,6 +150,7 @@ var ( OCIConfigFile: "oci.yaml", InMemoryZones: []string{"example.org", "company.com"}, OVHEndpoint: "ovh-ca", + OVHApiRateLimit: 42, PDNSServer: "http://ns.example.com:8081", PDNSAPIKey: "some-secret-key", PDNSTLSEnabled: true, @@ -237,6 +239,7 @@ func TestParseFlags(t *testing.T) { "--inmemory-zone=example.org", "--inmemory-zone=company.com", "--ovh-endpoint=ovh-ca", + "--ovh-api-rate-limit=42", "--pdns-server=http://ns.example.com:8081", "--pdns-api-key=some-secret-key", "--pdns-tls-enabled", @@ -326,6 +329,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_OCI_CONFIG_FILE": "oci.yaml", "EXTERNAL_DNS_INMEMORY_ZONE": "example.org\ncompany.com", "EXTERNAL_DNS_OVH_ENDPOINT": "ovh-ca", + "EXTERNAL_DNS_OVH_API_RATE_LIMIT": "42", "EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com", "EXTERNAL_DNS_EXCLUDE_DOMAINS": "xapi.example.org\nxapi.company.com", "EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081", diff --git a/provider/ovh/ovh.go b/provider/ovh/ovh.go index a32260e1b..fdb324bf7 100644 --- a/provider/ovh/ovh.go +++ b/provider/ovh/ovh.go @@ -29,6 +29,8 @@ import ( "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" + + "go.uber.org/ratelimit" ) const ( @@ -50,6 +52,8 @@ type OVHProvider struct { client ovhClient + apiRateLimiter ratelimit.Limiter + domainFilter endpoint.DomainFilter DryRun bool } @@ -79,7 +83,7 @@ type ovhChange struct { } // NewOVHProvider initializes a new OVH DNS based Provider. -func NewOVHProvider(ctx context.Context, domainFilter endpoint.DomainFilter, endpoint string, dryRun bool) (*OVHProvider, error) { +func NewOVHProvider(ctx context.Context, domainFilter endpoint.DomainFilter, endpoint string, apiRateLimit int, dryRun bool) (*OVHProvider, error) { client, err := ovh.NewEndpointClient(endpoint) if err != nil { return nil, err @@ -89,9 +93,10 @@ func NewOVHProvider(ctx context.Context, domainFilter endpoint.DomainFilter, end return nil, ErrNoDryRun } return &OVHProvider{ - client: client, - domainFilter: domainFilter, - DryRun: dryRun, + client: client, + domainFilter: domainFilter, + apiRateLimiter: ratelimit.New(apiRateLimit), + DryRun: dryRun, }, nil } @@ -149,10 +154,14 @@ func (p *OVHProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) e func (p *OVHProvider) refresh(zone string) error { log.Debugf("OVH: Refresh %s zone", zone) + + p.apiRateLimiter.Take() return p.client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) } func (p *OVHProvider) change(change ovhChange) error { + p.apiRateLimiter.Take() + switch change.Action { case ovhCreate: log.Debugf("OVH: Add an entry to %s", change.String()) @@ -194,6 +203,7 @@ func (p *OVHProvider) zones() ([]string, error) { zones := []string{} filteredZones := []string{} + p.apiRateLimiter.Take() if err := p.client.Get("/domain/zone", &zones); err != nil { return nil, err } @@ -213,6 +223,8 @@ func (p *OVHProvider) records(ctx *context.Context, zone *string, records chan<- eg, _ := errgroup.WithContext(*ctx) log.Debugf("OVH: Getting records for %s", *zone) + + p.apiRateLimiter.Take() if err := p.client.Get(fmt.Sprintf("/domain/zone/%s/record", *zone), &recordsIds); err != nil { return err } @@ -236,6 +248,8 @@ func (p *OVHProvider) record(zone *string, id uint64, records chan<- ovhRecord) record := ovhRecord{} log.Debugf("OVH: Getting record %d for %s", id, *zone) + + p.apiRateLimiter.Take() if err := p.client.Get(fmt.Sprintf("/domain/zone/%s/record/%d", *zone, id), &record); err != nil { return err } diff --git a/provider/ovh/ovh_test.go b/provider/ovh/ovh_test.go index 59b336941..b7d5dd78d 100644 --- a/provider/ovh/ovh_test.go +++ b/provider/ovh/ovh_test.go @@ -25,6 +25,7 @@ import ( "github.com/ovh/go-ovh/ovh" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "go.uber.org/ratelimit" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" ) @@ -58,8 +59,9 @@ func TestOvhZones(t *testing.T) { assert := assert.New(t) client := new(mockOvhClient) provider := &OVHProvider{ - client: client, - domainFilter: endpoint.NewDomainFilter([]string{"com"}), + client: client, + apiRateLimiter: ratelimit.New(10), + domainFilter: endpoint.NewDomainFilter([]string{"com"}), } // Basic zones @@ -81,7 +83,7 @@ func TestOvhZones(t *testing.T) { func TestOvhZoneRecords(t *testing.T) { assert := assert.New(t) client := new(mockOvhClient) - provider := &OVHProvider{client: client} + provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10)} // Basic zones records client.On("Get", "/domain/zone").Return([]string{"example.org"}, nil).Once() @@ -125,7 +127,7 @@ func TestOvhZoneRecords(t *testing.T) { func TestOvhRecords(t *testing.T) { assert := assert.New(t) client := new(mockOvhClient) - provider := &OVHProvider{client: client} + provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10)} // Basic zones records client.On("Get", "/domain/zone").Return([]string{"example.org", "example.net"}, nil).Once() @@ -158,7 +160,7 @@ func TestOvhRecords(t *testing.T) { func TestOvhRefresh(t *testing.T) { client := new(mockOvhClient) - provider := &OVHProvider{client: client} + provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10)} // Basic zone refresh client.On("Post", "/domain/zone/example.net/refresh", nil).Return(nil, nil).Once() @@ -199,7 +201,7 @@ func TestOvhNewChange(t *testing.T) { func TestOvhApplyChanges(t *testing.T) { assert := assert.New(t) client := new(mockOvhClient) - provider := &OVHProvider{client: client} + provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10)} changes := plan.Changes{ Create: []*endpoint.Endpoint{ {DNSName: ".example.net", RecordType: "A", RecordTTL: 10, Targets: []string{"203.0.113.42"}}, @@ -252,7 +254,7 @@ func TestOvhApplyChanges(t *testing.T) { func TestOvhChange(t *testing.T) { assert := assert.New(t) client := new(mockOvhClient) - provider := &OVHProvider{client: client} + provider := &OVHProvider{client: client, apiRateLimiter: ratelimit.New(10)} // Record creation client.On("Post", "/domain/zone/example.net/record", ovhRecordFields{SubDomain: "ovh"}).Return(nil, nil).Once() From f0d811a198d069bb364777007332bca8bdf7bd1f Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Tue, 9 Jun 2020 23:47:37 +0700 Subject: [PATCH 15/19] watch for nodes --- docs/tutorials/hetzner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/hetzner.md b/docs/tutorials/hetzner.md index 25895f43d..4a45b84c1 100644 --- a/docs/tutorials/hetzner.md +++ b/docs/tutorials/hetzner.md @@ -73,7 +73,7 @@ rules: verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] - verbs: ["list"] + verbs: ["list","watch"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding From ebb8ee6e53f2eaedcb7ef73b601a03b62bf2f690 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Wed, 10 Jun 2020 00:23:18 +0700 Subject: [PATCH 16/19] fix --- go.mod | 1 - go.sum | 2 -- provider/hetzner/hetzner.go | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8dd10a254..d12cbbb97 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,6 @@ require ( k8s.io/api v0.17.5 k8s.io/apimachinery v0.17.5 k8s.io/client-go v0.17.5 - k8s.io/klog v1.0.0 ) replace ( diff --git a/go.sum b/go.sum index a53ba0b73..6d418b6b9 100644 --- a/go.sum +++ b/go.sum @@ -312,8 +312,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65 h1:FP5rOFP4ifbtFIjFHJmwhFrsbDyONILK/FNntl/Pou8= github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go index eb53e8983..ac610b941 100644 --- a/provider/hetzner/hetzner.go +++ b/provider/hetzner/hetzner.go @@ -41,6 +41,7 @@ type HetznerChanges struct { } type HetznerProvider struct { + provider.BaseProvider Client hclouddns.HCloudClientAdapter domainFilter endpoint.DomainFilter DryRun bool From 32e09cb109f3437700e08e6952be10c680f06886 Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Wed, 10 Jun 2020 00:35:40 +0700 Subject: [PATCH 17/19] fckn linter --- provider/hetzner/hetzner.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go index ac610b941..4d177aef2 100644 --- a/provider/hetzner/hetzner.go +++ b/provider/hetzner/hetzner.go @@ -175,11 +175,9 @@ func (p *HetznerProvider) newHetznerChanges(action string, endpoints []*endpoint changes := make([]*HetznerChanges, 0, len(endpoints)) ttl := hetznerTTL for _, e := range endpoints { - if e.RecordTTL.IsConfigured() { ttl = int(e.RecordTTL) } - change := &HetznerChanges{ Action: action, ResourceRecordSet: hclouddns.HCloudRecord{ @@ -212,7 +210,6 @@ func (p *HetznerProvider) seperateChangesByZone(zones []hclouddns.HCloudZone, ch c.ZoneName = zoneName c.ZoneID = zoneID change[zoneID] = append(change[zoneID], c) - } return change } From f1b0e924022e8aa5301d76643d9dc5ab82c609ed Mon Sep 17 00:00:00 2001 From: Vladimir Smagin <21h@blindage.org> Date: Wed, 10 Jun 2020 00:49:50 +0700 Subject: [PATCH 18/19] fffff --- provider/hetzner/hetzner.go | 1 - 1 file changed, 1 deletion(-) diff --git a/provider/hetzner/hetzner.go b/provider/hetzner/hetzner.go index 4d177aef2..c8d9c7c87 100644 --- a/provider/hetzner/hetzner.go +++ b/provider/hetzner/hetzner.go @@ -116,7 +116,6 @@ func (p *HetznerProvider) submitChanges(ctx context.Context, changes []*HetznerC for _, changes := range zoneChanges { for _, change := range changes { - log.WithFields(log.Fields{ "record": change.ResourceRecordSet.Name, "type": change.ResourceRecordSet.RecordType, From 240d57636d68e2cf8a964599ed8ca1f537da08a6 Mon Sep 17 00:00:00 2001 From: Raffaele Di Fazio Date: Sun, 14 Jun 2020 12:17:41 +0200 Subject: [PATCH 19/19] adds kustomize base Signed-off-by: Raffaele Di Fazio --- kustomize/external-dns-clusterrole.yaml | 17 ++++++++++++++ .../external-dns-clusterrolebinding.yaml | 12 ++++++++++ kustomize/external-dns-deployment.yaml | 22 +++++++++++++++++++ kustomize/external-dns-serviceaccount.yaml | 4 ++++ kustomize/kustomization.yaml | 5 +++++ 5 files changed, 60 insertions(+) create mode 100644 kustomize/external-dns-clusterrole.yaml create mode 100644 kustomize/external-dns-clusterrolebinding.yaml create mode 100644 kustomize/external-dns-deployment.yaml create mode 100644 kustomize/external-dns-serviceaccount.yaml create mode 100644 kustomize/kustomization.yaml diff --git a/kustomize/external-dns-clusterrole.yaml b/kustomize/external-dns-clusterrole.yaml new file mode 100644 index 000000000..0470770ef --- /dev/null +++ b/kustomize/external-dns-clusterrole.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [""] + resources: ["services"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["pods"] + verbs: ["get","watch","list"] +- apiGroups: ["extensions"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] diff --git a/kustomize/external-dns-clusterrolebinding.yaml b/kustomize/external-dns-clusterrolebinding.yaml new file mode 100644 index 000000000..6630f84a2 --- /dev/null +++ b/kustomize/external-dns-clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: default diff --git a/kustomize/external-dns-deployment.yaml b/kustomize/external-dns-deployment.yaml new file mode 100644 index 000000000..75f2c760f --- /dev/null +++ b/kustomize/external-dns-deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + image: us.gcr.io/k8s-artifacts-prod/external-dns/external-dns:v0.7.2 + args: + - --source=service + - --source=ingress + - --registry=txt diff --git a/kustomize/external-dns-serviceaccount.yaml b/kustomize/external-dns-serviceaccount.yaml new file mode 100644 index 000000000..5b022409b --- /dev/null +++ b/kustomize/external-dns-serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns diff --git a/kustomize/kustomization.yaml b/kustomize/kustomization.yaml new file mode 100644 index 000000000..46ba1c12a --- /dev/null +++ b/kustomize/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- ./external-dns-deployment.yaml +- ./external-dns-serviceaccount.yaml +- ./external-dns-clusterrole.yaml +- ./external-dns-clusterrolebinding.yaml