Merge pull request #1509 from ddymko/vultr-dns

Vultr Provider
This commit is contained in:
Kubernetes Prow Robot 2020-04-23 10:37:11 -07:00 committed by GitHub
commit 490ff5dd4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 676 additions and 1 deletions

3
.github/labeler.yml vendored
View File

@ -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*

View File

@ -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

View File

@ -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
View 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
View File

@ -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
View File

@ -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=

View File

@ -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":

View File

@ -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
View 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
View 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)
}