Merge branch 'kubernetes-sigs:master' into master

This commit is contained in:
kvendingoldo 2022-03-29 00:06:37 +04:00 committed by GitHub
commit e4cb7fbf09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 71 additions and 780 deletions

View File

@ -15,10 +15,19 @@ jobs:
with:
fetch-depth: 0
- name: Run Artifact Hub lint
shell: bash
run: |
set -euo pipefail
curl -Lo ah_linux_amd64.tar.gz https://github.com/artifacthub/hub/releases/download/v1.6.0/ah_1.6.0_linux_amd64.tar.gz
tar -xzvf ah_linux_amd64.tar.gz ah
./ah lint --kind helm || exit 1
rm -f ./ah ./ah_linux_amd64.tar.gz
- name: Set up Helm
uses: azure/setup-helm@v1
with:
version: v3.6.3
version: 3.*
- name: Set up Python
uses: actions/setup-python@v2
@ -26,7 +35,7 @@ jobs:
python-version: 3.7
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.1.0
uses: helm/chart-testing-action@v2.2.0
- name: Run chart-testing (list-changed)
id: list-changed

View File

@ -17,6 +17,27 @@ jobs:
with:
fetch-depth: 0
- name: Get chart version
id: chart_version
shell: bash
run: |
set -euo pipefail
chart_version="$(grep -Po "(?<=^version: ).+" charts/external-dns/Chart.yaml)"
echo "::set-output name=version::${chart_version}"
- name: Get changelog entry
id: changelog_reader
uses: mindsers/changelog-reader-action@v2
with:
path: charts/external-dns/CHANGELOG.md
version: "v${{ steps.chart_version.outputs.version }}"
- name: Create release notes
shell: bash
run: |
set -euo pipefail
printf '${{ steps.changelog_reader.outputs.changes }}' > charts/external-dns/_release-notes.md
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
@ -28,7 +49,8 @@ jobs:
version: v3.6.3
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.2.1
uses: helm/chart-releaser-action@v1.3.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CR_RELEASE_NAME_TEMPLATE: "external-dns-helm-chart-{{ .Version }}"
CR_RELEASE_NOTES_FILE: _release-notes.md

View File

@ -32,7 +32,6 @@ ExternalDNS' allows you to keep selected zones (via `--domain-filter`) synchroni
* [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/)
@ -89,7 +88,6 @@ 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 | |
@ -147,7 +145,6 @@ The following tutorials are provided:
* [BlueCat](docs/tutorials/bluecat.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)

View File

@ -0,0 +1,24 @@
# ExternalDNS Helm Chart Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
<!-- ## [UNRELEASED]
### Added
### Changed
### Deprecated
### Removed -->
## [v1.8.0] - UNRELEASED
### Added
- Add annotations to Deployment. [#2477](https://github.com/kubernetes-sigs/external-dns/pull/2477) from @beastob
### Changed
- Fix RBAC for `istio-virtualservice` source when `istio-gateway` isn't also added. [#2564](https://github.com/kubernetes-sigs/external-dns/pull/2564) from @mcwarman

View File

@ -2,7 +2,7 @@ apiVersion: v2
name: external-dns
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
type: application
version: 1.7.1
version: 1.8.0
appVersion: 0.10.2
keywords:
- kubernetes
@ -18,8 +18,6 @@ maintainers:
annotations:
artifacthub.io/changes: |
- kind: added
description: "Allow custom ClusterRole rules to be specified for sources without defaults."
description: "Add annotations to Deployment."
- kind: changed
description: "Update ExternalDNS version to v0.10.2."
- kind: changed
description: "Set ClusterRole rules based more enabled sources."
description: "Fix RBAC for istio-virtualservice source when istio-gateway isn't also added."

View File

@ -3,3 +3,5 @@
## Chart Changes
When contributing chart changes please follow the same process as when contributing other content but also please **DON'T** modify _Chart.yaml_ in the PR as this would result in a chart release when merged and will mean that your PR will need modifying before it can be accepted. The chart version will be updated as part of the PR to release the chart.
Please **DO** add your changes to the _CHANGELOG.md_ file in the chart directory under the `## [UNRELEASED]` section, if there isn't an uncommented `## [UNRELEASED]` section please copy the commented out template and use that.

View File

@ -1,191 +0,0 @@
# 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.6** 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: k8s.gcr.io/external-dns/external-dns:v0.7.6
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"
```
### Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
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: k8s.gcr.io/external-dns/external-dns:v0.7.6
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
```

5
go.mod
View File

@ -4,7 +4,6 @@ go 1.17
require (
cloud.google.com/go/compute v1.2.0
git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d
github.com/Azure/azure-sdk-for-go v61.5.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.21
github.com/Azure/go-autorest/autorest/adal v0.9.16
@ -46,7 +45,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/transip/gotransip/v6 v6.14.0
github.com/ukfast/sdk-go v1.4.23
github.com/ukfast/sdk-go v1.4.34
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.14.1
@ -132,7 +131,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.3.0 // indirect
github.com/terra-farm/udnssdk v1.3.5 // indirect
github.com/ukfast/go-durationstring v1.0.0 // indirect
github.com/ukfast/go-durationstring v1.1.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.7.0 // indirect

10
go.sum
View File

@ -53,8 +53,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
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-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=
@ -1200,10 +1198,10 @@ github.com/tsaarni/x500dn v0.0.0-20210331182804-14283c7f5a16/go.mod h1:RquKZ5rER
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
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/ukfast/go-durationstring v1.1.0 h1:Ki0ubc5jqSt7XuAs+gkPNpHYolIwbcsRW4LS239tIHA=
github.com/ukfast/go-durationstring v1.1.0/go.mod h1:Ci81n51kfxlKUIaLY9cINIKRO94VTqV+iCGbOMTb0V8=
github.com/ukfast/sdk-go v1.4.34 h1:xuNbJ+WxsUqBfrm6eEdnTi6GcKL3R1cLxSN0jMk+4Rc=
github.com/ukfast/sdk-go v1.4.34/go.mod h1:vxlI1IHy2pp04AYqRMm0MHWSWOF0lwTkPJXHxTDLPok=
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=

View File

@ -3,7 +3,7 @@ kind: Kustomization
images:
- name: k8s.gcr.io/external-dns/external-dns
newTag: v0.10.2
newTag: v0.11.0
resources:
- ./external-dns-deployment.yaml

View File

@ -51,7 +51,6 @@ import (
"sigs.k8s.io/external-dns/provider/gandi"
"sigs.k8s.io/external-dns/provider/godaddy"
"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"
@ -227,8 +226,6 @@ func main() {
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
case "digitalocean":
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
case "hetzner":
p, err = hetzner.NewHetznerProvider(ctx, domainFilter, cfg.DryRun)
case "ovh":
p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.DryRun)
case "linode":

View File

@ -397,7 +397,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("default-targets", "Set globally default IP address that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets)
// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns)").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", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns)").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", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns")
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("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default(defaultConfig.RegexDomainFilter.String()).RegexpVar(&cfg.RegexDomainFilter)

View File

@ -1,259 +0,0 @@
/*
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 (
"context"
"errors"
"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 {
provider.BaseProvider
Client hclouddns.HCloudClientAdapter
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, errors.New("no environment variable HETZNER_TOKEN provided")
}
client := hclouddns.New(token)
provider := &HetznerProvider{
Client: client,
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 {
// Prepare record name
recordName := strings.TrimSuffix(change.ResourceRecordSet.Name, "."+change.ZoneName)
if recordName == change.ZoneName {
recordName = "@"
}
if change.ResourceRecordSet.RecordType == hclouddns.CNAME && !strings.HasSuffix(change.ResourceRecordSet.Value, ".") {
change.ResourceRecordSet.Value += "."
}
change.ResourceRecordSet.Name = recordName
// Get ID of record if not create operation
if change.Action != hetznerCreate {
allRecords, err := p.Client.GetRecords(hclouddns.HCloudGetRecordsParams{ZoneID: change.ZoneID})
if err != nil {
return err
}
for _, record := range allRecords.Records {
if record.Name == change.ResourceRecordSet.Name && record.RecordType == change.ResourceRecordSet.RecordType {
change.ResourceRecordSet.ID = record.ID
break
}
}
}
logMessage := "Changing record"
if p.DryRun {
logMessage = "Would change record"
}
log.WithFields(log.Fields{
"id": change.ResourceRecordSet.ID,
"record": change.ResourceRecordSet.Name,
"type": change.ResourceRecordSet.RecordType,
"value": change.ResourceRecordSet.Value,
"ttl": change.ResourceRecordSet.TTL,
"action": change.Action,
"zone": change.ZoneName,
"zone_id": change.ZoneID,
}).Info(logMessage)
if p.DryRun {
continue
}
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,
}
answer, err := p.Client.CreateRecord(record)
if err != nil {
log.WithFields(log.Fields{
"Code": answer.Error.Code,
"Message": answer.Error.Message,
"Record name": answer.Record.Name,
"Record type": answer.Record.RecordType,
"Record value": answer.Record.Value,
}).Warning("Create problem")
return err
}
case hetznerDelete:
answer, err := p.Client.DeleteRecord(change.ResourceRecordSet.ID)
if err != nil {
log.WithFields(log.Fields{
"Code": answer.Error.Code,
"Message": answer.Error.Message,
}).Warning("Delete problem")
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,
ID: change.ResourceRecordSet.ID,
}
answer, err := p.Client.UpdateRecord(record)
if err != nil {
log.WithFields(log.Fields{
"Code": answer.Error.Code,
"Message": answer.Error.Message,
"Record name": answer.Record.Name,
"Record type": answer.Record.RecordType,
"Record value": answer.Record.Value,
}).Warning("Update problem")
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
}

View File

@ -1,305 +0,0 @@
/*
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 (
"context"
"fmt"
"github.com/maxatome/go-testdeep/td"
"os"
"reflect"
"testing"
hclouddns "git.blindage.org/21h/hcloud-dns"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
)
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)
GetRecord(ID string) (hclouddns.HCloudAnswerGetRecord, 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 MockAction struct {
Name string
RecordData hclouddns.HCloudRecord
}
type mockHCloudClient struct {
Token string `yaml:"token"`
Actions []MockAction
}
// New instance
func mockHCloudNew(t string) *mockHCloudClient {
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{
{
ID: "HetznerZoneID",
Name: "blindage.org",
TTL: 666,
RecordsCount: 1,
},
},
}, nil
}
// 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) GetRecord(ID string) (hclouddns.HCloudAnswerGetRecord, error) {
return hclouddns.HCloudAnswerGetRecord{}, nil
}
func (m *mockHCloudClient) GetRecords(params hclouddns.HCloudGetRecordsParams) (hclouddns.HCloudAnswerGetRecords, error) {
return hclouddns.HCloudAnswerGetRecords{
Records: []hclouddns.HCloudRecord{
{
RecordType: hclouddns.RecordType("A"),
ID: "ATypeRecordID",
ZoneID: "HetznerZoneID",
Name: "@",
Value: "127.0.0.1",
TTL: 666,
},
},
}, nil
}
func (m *mockHCloudClient) UpdateRecord(record hclouddns.HCloudRecord) (hclouddns.HCloudAnswerGetRecord, error) {
m.Actions = append(m.Actions, MockAction{
Name: "UpdateRecord",
RecordData: record,
})
return hclouddns.HCloudAnswerGetRecord{}, nil
}
func (m *mockHCloudClient) DeleteRecord(ID string) (hclouddns.HCloudAnswerDeleteRecord, error) {
m.Actions = append(m.Actions, MockAction{
Name: "DeleteRecord",
RecordData: hclouddns.HCloudRecord{
ID: ID,
},
})
return hclouddns.HCloudAnswerDeleteRecord{}, nil
}
func (m *mockHCloudClient) CreateRecord(record hclouddns.HCloudRecord) (hclouddns.HCloudAnswerGetRecord, error) {
m.Actions = append(m.Actions, MockAction{
Name: "CreateRecord",
RecordData: record,
})
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")
_, 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_TestData(t *testing.T) {
mockedClient := mockHCloudNew("myHetznerToken")
// 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.Errorf("should not fail, %s", err)
}
if !reflect.DeepEqual(expectedZonesAnswer, testingZonesAnswer) {
t.Errorf("should be equal, %s", 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.Errorf("should not fail, %s", err)
}
if !reflect.DeepEqual(expectedRecordsAnswer, testingRecordsAnswer) {
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.Errorf("should not fail, %s", err)
}
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: "blindage.org", Targets: endpoint.Targets{"target"}},
{DNSName: "test.blindage.org", Targets: endpoint.Targets{"target"}, RecordTTL: 666},
}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test.blindage.org", Targets: endpoint.Targets{"target-new"}, RecordType: "A", RecordTTL: 777}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "test.blindage.org", Targets: endpoint.Targets{"target"}, RecordType: "A"}}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
if len(mockedClient.Actions) != 4 {
t.Errorf("should be 4 changes not %d", len(mockedClient.Actions))
}
}
func TestHetznerProvider_ApplyChangesCreateUpdateCname(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockHCloudNew("myHetznerToken")
mockedProvider := &HetznerProvider{
Client: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "test-cname.blindage.org", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "CNAME"},
}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test-cname2.blindage.org", Targets: endpoint.Targets{"target-new"}, RecordType: "CNAME", RecordTTL: 777}}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "CreateRecord",
RecordData: hclouddns.HCloudRecord{
RecordType: "CNAME",
ID: "",
Created: "",
Modified: "",
ZoneID: "HetznerZoneID",
Name: "test-cname",
Value: "target.",
TTL: 666,
},
},
{
Name: "UpdateRecord",
RecordData: hclouddns.HCloudRecord{
RecordType: "CNAME",
ID: "",
Created: "",
Modified: "",
ZoneID: "HetznerZoneID",
Name: "test-cname2",
Value: "target-new.",
TTL: 777,
},
},
})
}