mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-05-05 22:56:09 +02:00
commit
490ff5dd4e
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
@ -61,3 +61,6 @@ provider/oci: provider/oci*
|
||||
|
||||
# Add 'provider/vinyldns' in file which starts with vinyldns
|
||||
provider/vinyldns: provider/vinyldns*
|
||||
|
||||
# Add 'provider/vultr' in file which starts with vultr
|
||||
provider/vultr: provider/vultr*
|
||||
|
||||
@ -95,6 +95,7 @@ The following table clarifies the current status of the providers according to t
|
||||
| RancherDNS | Alpha | |
|
||||
| Akamai FastDNS | Alpha | |
|
||||
| OVH | Alpha | |
|
||||
| Vultr | Alpha | |
|
||||
|
||||
## Running ExternalDNS:
|
||||
|
||||
@ -142,6 +143,7 @@ The following tutorials are provided:
|
||||
* [TransIP](docs/tutorials/transip.md)
|
||||
* [VinylDNS](docs/tutorials/vinyldns.md)
|
||||
* [OVH](docs/tutorials/ovh.md)
|
||||
* [Vultr](docs/tutorials/vultr.md)
|
||||
|
||||
### Running Locally
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ Providers
|
||||
- [x] Linode
|
||||
- [x] TransIP
|
||||
- [x] RFC2136
|
||||
- [x] Vultr
|
||||
|
||||
PRs welcome!
|
||||
|
||||
@ -72,3 +73,6 @@ The Linode Provider default TTL is used when the TTL is 0. The default is 24 hou
|
||||
|
||||
### TransIP Provider
|
||||
The TransIP Provider minimal TTL is used when the TTL is 0. The minimal TTL is 60s.
|
||||
|
||||
### Vultr Provider
|
||||
The Vultr provider minimal TTL is used when the TTL is 0. The default is 1 hour.
|
||||
|
||||
188
docs/tutorials/vultr.md
Normal file
188
docs/tutorials/vultr.md
Normal file
@ -0,0 +1,188 @@
|
||||
# Setting up ExternalDNS for Services on Vultr
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Vultr DNS.
|
||||
|
||||
Make sure to use **>=0.6** version of ExternalDNS for this tutorial.
|
||||
|
||||
## Managing DNS with Vultr
|
||||
|
||||
If you want to read up on vultr DNS service you can read the following tutorial:
|
||||
[Introduction to Vultr DNS](https://www.vultr.com/docs/introduction-to-vultr-dns)
|
||||
|
||||
Create a new DNS Zone where you want to create your records in. For the examples we will be using `example.com`
|
||||
|
||||
## Creating Vultr Credentials
|
||||
|
||||
You will need to create a new API Key which can be found on the [Vultr Dashboard](https://my.vultr.com/settings/#settingsapi).
|
||||
|
||||
The environment variable `VULTR_API_KEY` will be needed to run ExternalDNS with Vultr.
|
||||
|
||||
## 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:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
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=vultr
|
||||
env:
|
||||
- name: VULTR_API_KEY
|
||||
value: "YOU_VULTR_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:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
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=vultr
|
||||
env:
|
||||
- name: VULTR_API_KEY
|
||||
value: "YOU_VULTR_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:
|
||||
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 Vultr 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 Vultr DNS records.
|
||||
|
||||
## Verifying Vultr DNS records
|
||||
|
||||
Check your [Vultr UI](https://my.vultr.com/dns/) to view the records for your Vultr 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 Vultr DNS records, we can delete the tutorial's example:
|
||||
|
||||
```
|
||||
$ kubectl delete service -f nginx.yaml
|
||||
$ kubectl delete service -f externaldns.yaml
|
||||
```
|
||||
1
go.mod
1
go.mod
@ -50,6 +50,7 @@ require (
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/transip/gotransip v5.8.2+incompatible
|
||||
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
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
|
||||
8
go.sum
8
go.sum
@ -320,8 +320,12 @@ github.com/hashicorp/consul v1.3.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.9.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-memdb v1.0.1/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
@ -330,6 +334,8 @@ github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uP
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
@ -604,6 +610,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
|
||||
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92 h1:Q76MzqJu++vAfhj0mVf7t0F4xHUbg+V/d/Uk5PBQjRU=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92/go.mod h1:AZuEfReFWdvtU0LatbLpo70t3lqdLvph2D5mqFP0bkA=
|
||||
github.com/vultr/govultr v0.3.2 h1:1tV/88jkm+4Y345qAXBe3peNbnmvCY/VAIZApklbKkI=
|
||||
github.com/vultr/govultr v0.3.2/go.mod h1:81RwK1wAmb08alkFDJiZmu9gdv+IO+UamzaF0+PIieE=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
|
||||
2
main.go
2
main.go
@ -165,6 +165,8 @@ func main() {
|
||||
p, err = provider.NewAzurePrivateDNSProvider(domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureSubscriptionID, cfg.DryRun)
|
||||
case "vinyldns":
|
||||
p, err = provider.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "vultr":
|
||||
p, err = provider.NewVultrProvider(domainFilter, cfg.DryRun)
|
||||
case "cloudflare":
|
||||
p, err = provider.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
|
||||
case "rcodezero":
|
||||
|
||||
@ -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)").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")
|
||||
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("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)
|
||||
|
||||
276
provider/vultr.go
Normal file
276
provider/vultr.go
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vultr/govultr"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
const (
|
||||
vultrCreate = "CREATE"
|
||||
vultrDelete = "DELETE"
|
||||
vultrUpdate = "UPDATE"
|
||||
vultrTTL = 3600
|
||||
)
|
||||
|
||||
type VultrProvider struct {
|
||||
client govultr.Client
|
||||
|
||||
domainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
type VultrChanges struct {
|
||||
Action string
|
||||
|
||||
ResourceRecordSet govultr.DNSRecord
|
||||
}
|
||||
|
||||
// NewVultrProvider initializes a new Vultr BNS based provider
|
||||
func NewVultrProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*VultrProvider, error) {
|
||||
apiKey, ok := os.LookupEnv("VULTR_API_KEY")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no token found")
|
||||
}
|
||||
|
||||
client := govultr.NewClient(nil, apiKey)
|
||||
client.SetUserAgent(fmt.Sprintf("ExternalDNS/%s", client.UserAgent))
|
||||
|
||||
provider := &VultrProvider{
|
||||
client: *client,
|
||||
domainFilter: domainFilter,
|
||||
DryRun: dryRun,
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// Zones returns list of hosted zones
|
||||
func (p *VultrProvider) Zones(ctx context.Context) ([]govultr.DNSDomain, error) {
|
||||
zones, err := p.fetchZones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (p *VultrProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
for _, zone := range zones {
|
||||
records, err := p.fetchRecords(ctx, zone.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(r.Type) {
|
||||
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
||||
|
||||
// root name is identified by the empty string and should be
|
||||
// translated to zone name for the endpoint entry.
|
||||
if r.Name == "" {
|
||||
name = zone.Domain
|
||||
}
|
||||
|
||||
endPointTTL := endpoint.NewEndpointWithTTL(name, r.Type, endpoint.TTL(r.TTL), r.Data)
|
||||
endpoints = append(endpoints, endPointTTL)
|
||||
}
|
||||
}
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (p *VultrProvider) fetchRecords(ctx context.Context, domain string) ([]govultr.DNSRecord, error) {
|
||||
records, err := p.client.DNSRecord.List(ctx, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (p *VultrProvider) fetchZones(ctx context.Context) ([]govultr.DNSDomain, error) {
|
||||
var zones []govultr.DNSDomain
|
||||
|
||||
allZones, err := p.client.DNSDomain.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, zone := range allZones {
|
||||
if p.domainFilter.Match(zone.Domain) {
|
||||
zones = append(zones, zone)
|
||||
}
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (p *VultrProvider) submitChanges(ctx context.Context, changes []*VultrChanges) error {
|
||||
if len(changes) == 0 {
|
||||
log.Infof("All records are already up to date")
|
||||
return nil
|
||||
}
|
||||
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zoneChanges := seperateChangesByZone(zones, changes)
|
||||
|
||||
for zoneName, changes := range zoneChanges {
|
||||
for _, change := range changes {
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"record": change.ResourceRecordSet.Name,
|
||||
"type": change.ResourceRecordSet.Type,
|
||||
"ttl": change.ResourceRecordSet.TTL,
|
||||
"action": change.Action,
|
||||
"zone": zoneName,
|
||||
}).Info("Changing record.")
|
||||
|
||||
switch change.Action {
|
||||
case vultrCreate:
|
||||
err = p.client.DNSRecord.Create(ctx, zoneName, change.ResourceRecordSet.Type, change.ResourceRecordSet.Name, change.ResourceRecordSet.Data, change.ResourceRecordSet.TTL, change.ResourceRecordSet.Priority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case vultrDelete:
|
||||
id, err := p.getRecordID(ctx, zoneName, change.ResourceRecordSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.client.DNSRecord.Delete(ctx, zoneName, strconv.Itoa(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case vultrUpdate:
|
||||
id, err := p.getRecordID(ctx, zoneName, change.ResourceRecordSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := &govultr.DNSRecord{
|
||||
RecordID: id,
|
||||
Type: change.ResourceRecordSet.Type,
|
||||
Name: change.ResourceRecordSet.Name,
|
||||
Data: change.ResourceRecordSet.Data,
|
||||
TTL: change.ResourceRecordSet.TTL,
|
||||
}
|
||||
|
||||
err = p.client.DNSRecord.Update(ctx, zoneName, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *VultrProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
combinedChanges := make([]*VultrChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||
|
||||
combinedChanges = append(combinedChanges, newVultrChanges(vultrCreate, changes.Create)...)
|
||||
combinedChanges = append(combinedChanges, newVultrChanges(vultrUpdate, changes.UpdateNew)...)
|
||||
combinedChanges = append(combinedChanges, newVultrChanges(vultrDelete, changes.Delete)...)
|
||||
|
||||
return p.submitChanges(ctx, combinedChanges)
|
||||
}
|
||||
|
||||
func newVultrChanges(action string, endpoints []*endpoint.Endpoint) []*VultrChanges {
|
||||
changes := make([]*VultrChanges, 0, len(endpoints))
|
||||
ttl := vultrTTL
|
||||
for _, e := range endpoints {
|
||||
|
||||
if e.RecordTTL.IsConfigured() {
|
||||
ttl = int(e.RecordTTL)
|
||||
}
|
||||
|
||||
change := &VultrChanges{
|
||||
Action: action,
|
||||
ResourceRecordSet: govultr.DNSRecord{
|
||||
Type: e.RecordType,
|
||||
Name: e.DNSName,
|
||||
Data: e.Targets[0],
|
||||
TTL: ttl,
|
||||
},
|
||||
}
|
||||
changes = append(changes, change)
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
func seperateChangesByZone(zones []govultr.DNSDomain, changes []*VultrChanges) map[string][]*VultrChanges {
|
||||
change := make(map[string][]*VultrChanges)
|
||||
zoneNameID := zoneIDName{}
|
||||
|
||||
for _, z := range zones {
|
||||
zoneNameID.Add(z.Domain, z.Domain)
|
||||
change[z.Domain] = []*VultrChanges{}
|
||||
}
|
||||
|
||||
for _, c := range changes {
|
||||
zone, _ := zoneNameID.FindZone(c.ResourceRecordSet.Name)
|
||||
if zone == "" {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSet.Name)
|
||||
continue
|
||||
}
|
||||
change[zone] = append(change[zone], c)
|
||||
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
func (p *VultrProvider) getRecordID(ctx context.Context, zone string, record govultr.DNSRecord) (recordID int, err error) {
|
||||
records, err := p.client.DNSRecord.List(ctx, zone)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
strippedName := strings.TrimSuffix(record.Name, "."+zone)
|
||||
if record.Name == zone {
|
||||
strippedName = ""
|
||||
}
|
||||
|
||||
if r.Name == strippedName && r.Type == record.Type {
|
||||
return r.RecordID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("no record was found")
|
||||
}
|
||||
191
provider/vultr_test.go
Normal file
191
provider/vultr_test.go
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vultr/govultr"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
type mockVultrDomain struct {
|
||||
client *govultr.Client
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) Create(ctx context.Context, domain, InstanceIP string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) Delete(ctx context.Context, domain string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) ToggleDNSSec(ctx context.Context, domain string, enabled bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) DNSSecInfo(ctx context.Context, domain string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) List(ctx context.Context) ([]govultr.DNSDomain, error) {
|
||||
return []govultr.DNSDomain{{Domain: "test.com"}}, nil
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) GetSoa(ctx context.Context, domain string) (*govultr.Soa, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockVultrDomain) UpdateSoa(ctx context.Context, domain, nsPrimary, email string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockVultrRecord struct {
|
||||
client *govultr.Client
|
||||
}
|
||||
|
||||
func (m *mockVultrRecord) Create(ctx context.Context, domain, recordType, name, data string, ttl, priority int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockVultrRecord) Delete(ctx context.Context, domain, recordID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockVultrRecord) List(ctx context.Context, domain string) ([]govultr.DNSRecord, error) {
|
||||
return []govultr.DNSRecord{{RecordID: 123, Type: "A", Name: "test", Data: "192.168.1.1", TTL: 300}}, nil
|
||||
}
|
||||
|
||||
func (m *mockVultrRecord) Update(ctx context.Context, domain string, dnsRecord *govultr.DNSRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNewVultrProvider(t *testing.T) {
|
||||
_ = os.Setenv("VULTR_API_KEY", "")
|
||||
_, err := NewVultrProvider(endpoint.NewDomainFilter([]string{"test.vultr.com"}), true)
|
||||
if err != nil {
|
||||
t.Errorf("failed : %s", err)
|
||||
}
|
||||
|
||||
_ = os.Unsetenv("VULTR_API_KEY")
|
||||
_, err = NewVultrProvider(endpoint.NewDomainFilter([]string{"test.vultr.com"}), true)
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVultrProvider_Zones(t *testing.T) {
|
||||
mocked := mockVultrDomain{nil}
|
||||
provider := &VultrProvider{
|
||||
client: govultr.Client{
|
||||
DNSDomain: &mocked,
|
||||
},
|
||||
}
|
||||
|
||||
expected, err := provider.client.DNSDomain.List(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
zones, err := provider.Zones(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, zones) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVultrProvider_Records(t *testing.T) {
|
||||
mocked := mockVultrRecord{nil}
|
||||
mockedDomain := mockVultrDomain{nil}
|
||||
|
||||
provider := &VultrProvider{
|
||||
client: govultr.Client{
|
||||
DNSRecord: &mocked,
|
||||
DNSDomain: &mockedDomain,
|
||||
},
|
||||
}
|
||||
|
||||
expected, _ := provider.client.DNSRecord.List(context.Background(), "test.com")
|
||||
records, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, v := range records {
|
||||
assert.Equal(t, strings.TrimSuffix(v.DNSName, ".test.com"), expected[0].Name)
|
||||
assert.Equal(t, v.RecordType, expected[0].Type)
|
||||
assert.Equal(t, int(v.RecordTTL), expected[0].TTL)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestVultrProvider_ApplyChanges(t *testing.T) {
|
||||
changes := &plan.Changes{}
|
||||
mocked := mockVultrRecord{nil}
|
||||
mockedDomain := mockVultrDomain{nil}
|
||||
|
||||
provider := &VultrProvider{
|
||||
client: govultr.Client{
|
||||
DNSRecord: &mocked,
|
||||
DNSDomain: &mockedDomain,
|
||||
},
|
||||
}
|
||||
|
||||
changes.Create = []*endpoint.Endpoint{
|
||||
{DNSName: "test.com", Targets: endpoint.Targets{"target"}},
|
||||
{DNSName: "ttl.test.com", Targets: endpoint.Targets{"target"}, RecordTTL: 100},
|
||||
}
|
||||
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test.test.com", Targets: endpoint.Targets{"target-new"}, RecordType: "A", RecordTTL: 100}}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "test.test.com", Targets: endpoint.Targets{"target"}, RecordType: "A"}}
|
||||
err := provider.ApplyChanges(context.Background(), changes)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVultrProvider_getRecordID(t *testing.T) {
|
||||
mocked := mockVultrRecord{nil}
|
||||
mockedDomain := mockVultrDomain{nil}
|
||||
|
||||
provider := &VultrProvider{
|
||||
client: govultr.Client{
|
||||
DNSRecord: &mocked,
|
||||
DNSDomain: &mockedDomain,
|
||||
},
|
||||
}
|
||||
|
||||
record := govultr.DNSRecord{
|
||||
RecordID: 123,
|
||||
Type: "A",
|
||||
Name: "test.test.com",
|
||||
}
|
||||
id, err := provider.getRecordID(context.Background(), "test.com", record)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, id, record.RecordID)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user