mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 01:56:57 +02:00
removes deprecated rdns provider
Signed-off-by: Raffaele Di Fazio <difazio.raffaele@gmail.com>
This commit is contained in:
parent
25b44e5c4e
commit
59fe374d79
@ -126,7 +126,6 @@ The following table clarifies the current status of the providers according to t
|
|||||||
| RFC2136 | Alpha | |
|
| RFC2136 | Alpha | |
|
||||||
| NS1 | Alpha | |
|
| NS1 | Alpha | |
|
||||||
| TransIP | Alpha | |
|
| TransIP | Alpha | |
|
||||||
| RancherDNS | Alpha | |
|
|
||||||
| OVH | Alpha | |
|
| OVH | Alpha | |
|
||||||
| Scaleway DNS | Alpha | @Sh4d1 |
|
| Scaleway DNS | Alpha | @Sh4d1 |
|
||||||
| UltraDNS | Alpha | |
|
| UltraDNS | Alpha | |
|
||||||
@ -188,7 +187,6 @@ The following tutorials are provided:
|
|||||||
* [OpenStack Designate](docs/tutorials/designate.md)
|
* [OpenStack Designate](docs/tutorials/designate.md)
|
||||||
* [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
|
* [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
|
||||||
* [PowerDNS](docs/tutorials/pdns.md)
|
* [PowerDNS](docs/tutorials/pdns.md)
|
||||||
* [RancherDNS (RDNS)](docs/tutorials/rdns.md)
|
|
||||||
* [RFC2136](docs/tutorials/rfc2136.md)
|
* [RFC2136](docs/tutorials/rfc2136.md)
|
||||||
* [TransIP](docs/tutorials/transip.md)
|
* [TransIP](docs/tutorials/transip.md)
|
||||||
* [OVH](docs/tutorials/ovh.md)
|
* [OVH](docs/tutorials/ovh.md)
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
# RancherDNS
|
|
||||||
|
|
||||||
This tutorial describes how to setup ExternalDNS for usage within a kubernetes cluster that makes use of [RDNS](https://github.com/rancher/rdns-server) and [nginx ingress controller](https://github.com/kubernetes/ingress-nginx).
|
|
||||||
|
|
||||||
You need to:
|
|
||||||
|
|
||||||
* install RDNS with [etcd](https://github.com/etcd-io/etcd) enabled
|
|
||||||
* install external-dns with rdns as a provider
|
|
||||||
|
|
||||||
## Installing RDNS with etcdv3 backend
|
|
||||||
|
|
||||||
### Clone RDNS
|
|
||||||
```
|
|
||||||
git clone https://github.com/rancher/rdns-server.git
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installing ETCD
|
|
||||||
```
|
|
||||||
cd rdns-server
|
|
||||||
docker-compose -f deploy/etcdv3/etcd-compose.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
> ETCD was successfully deployed on `http://172.31.35.77:2379`
|
|
||||||
|
|
||||||
### Installing RDNS
|
|
||||||
```
|
|
||||||
export ETCD_ENDPOINTS="http://172.31.35.77:2379"
|
|
||||||
export DOMAIN="lb.rancher.cloud"
|
|
||||||
./scripts/start etcdv3
|
|
||||||
```
|
|
||||||
|
|
||||||
> RDNS was successfully deployed on `172.31.35.77`
|
|
||||||
|
|
||||||
## Installing ExternalDNS
|
|
||||||
### Install external ExternalDNS
|
|
||||||
ETCD_URLS is configured to etcd client service address.
|
|
||||||
RDNS_ROOT_DOMAIN is configured to the same with RDNS DOMAIN environment. e.g. lb.rancher.cloud.
|
|
||||||
|
|
||||||
#### Manifest (for clusters without RBAC enabled)
|
|
||||||
```yaml
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: external-dns
|
|
||||||
namespace: kube-system
|
|
||||||
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.k8s.io/external-dns/external-dns:v0.15.0
|
|
||||||
args:
|
|
||||||
- --source=ingress
|
|
||||||
- --provider=rdns
|
|
||||||
- --log-level=debug # debug only
|
|
||||||
env:
|
|
||||||
- name: ETCD_URLS
|
|
||||||
value: http://172.31.35.77:2379
|
|
||||||
- name: RDNS_ROOT_DOMAIN
|
|
||||||
value: lb.rancher.cloud
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Manifest (for clusters with RBAC enabled)
|
|
||||||
```yaml
|
|
||||||
|
|
||||||
---
|
|
||||||
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"]
|
|
||||||
---
|
|
||||||
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: kube-system
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: external-dns
|
|
||||||
namespace: kube-system
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: external-dns
|
|
||||||
namespace: kube-system
|
|
||||||
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.k8s.io/external-dns/external-dns:v0.15.0
|
|
||||||
args:
|
|
||||||
- --source=ingress
|
|
||||||
- --provider=rdns
|
|
||||||
- --log-level=debug # debug only
|
|
||||||
env:
|
|
||||||
- name: ETCD_URLS
|
|
||||||
value: http://172.31.35.77:2379
|
|
||||||
- name: RDNS_ROOT_DOMAIN
|
|
||||||
value: lb.rancher.cloud
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing ingress example
|
|
||||||
```
|
|
||||||
$ cat ingress.yaml
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: nginx
|
|
||||||
spec:
|
|
||||||
ingressClassName: nginx
|
|
||||||
rules:
|
|
||||||
- host: nginx.lb.rancher.cloud
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- backend:
|
|
||||||
serviceName: nginx
|
|
||||||
servicePort: 80
|
|
||||||
|
|
||||||
$ kubectl apply -f ingress.yaml
|
|
||||||
ingress.extensions "nginx" created
|
|
||||||
```
|
|
||||||
|
|
||||||
Wait a moment until DNS has the ingress IP. The RDNS IP in this example is "172.31.35.77".
|
|
||||||
```
|
|
||||||
$ kubectl get ingress
|
|
||||||
NAME HOSTS ADDRESS PORTS AGE
|
|
||||||
nginx nginx.lb.rancher.cloud 172.31.42.211 80 2m
|
|
||||||
|
|
||||||
$ kubectl run -it --rm --restart=Never --image=infoblox/dnstools:latest dnstools
|
|
||||||
If you don't see a command prompt, try pressing enter.
|
|
||||||
dnstools# dig @172.31.35.77 nginx.lb.rancher.cloud +short
|
|
||||||
172.31.42.211
|
|
||||||
dnstools#
|
|
||||||
```
|
|
8
main.go
8
main.go
@ -65,7 +65,6 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/provider/pdns"
|
"sigs.k8s.io/external-dns/provider/pdns"
|
||||||
"sigs.k8s.io/external-dns/provider/pihole"
|
"sigs.k8s.io/external-dns/provider/pihole"
|
||||||
"sigs.k8s.io/external-dns/provider/plural"
|
"sigs.k8s.io/external-dns/provider/plural"
|
||||||
"sigs.k8s.io/external-dns/provider/rdns"
|
|
||||||
"sigs.k8s.io/external-dns/provider/rfc2136"
|
"sigs.k8s.io/external-dns/provider/rfc2136"
|
||||||
"sigs.k8s.io/external-dns/provider/scaleway"
|
"sigs.k8s.io/external-dns/provider/scaleway"
|
||||||
"sigs.k8s.io/external-dns/provider/tencentcloud"
|
"sigs.k8s.io/external-dns/provider/tencentcloud"
|
||||||
@ -258,13 +257,6 @@ func main() {
|
|||||||
p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||||
case "coredns", "skydns":
|
case "coredns", "skydns":
|
||||||
p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
||||||
case "rdns":
|
|
||||||
p, err = rdns.NewRDNSProvider(
|
|
||||||
rdns.RDNSConfig{
|
|
||||||
DomainFilter: domainFilter,
|
|
||||||
DryRun: cfg.DryRun,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
case "exoscale":
|
case "exoscale":
|
||||||
p, err = exoscale.NewExoscaleProvider(
|
p, err = exoscale.NewExoscaleProvider(
|
||||||
cfg.ExoscaleAPIEnvironment,
|
cfg.ExoscaleAPIEnvironment,
|
||||||
|
@ -445,7 +445,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
|||||||
app.Flag("nat64-networks", "Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.NAT64Networks)
|
app.Flag("nat64-networks", "Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.NAT64Networks)
|
||||||
|
|
||||||
// Flags related to providers
|
// Flags related to providers
|
||||||
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rdns", "rfc2136", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "webhook"}
|
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rfc2136", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "webhook"}
|
||||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: "+strings.Join(providers, ", ")+")").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, providers...)
|
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: "+strings.Join(providers, ", ")+")").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, providers...)
|
||||||
app.Flag("provider-cache-time", "The time to cache the DNS provider record list requests.").Default(defaultConfig.ProviderCacheTime.String()).DurationVar(&cfg.ProviderCacheTime)
|
app.Flag("provider-cache-time", "The time to cache the DNS provider record list requests.").Default(defaultConfig.ProviderCacheTime.String()).DurationVar(&cfg.ProviderCacheTime)
|
||||||
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("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
|
||||||
|
@ -1,551 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2019 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 rdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
|
||||||
"sigs.k8s.io/external-dns/plan"
|
|
||||||
"sigs.k8s.io/external-dns/provider"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
etcdTimeout = 5 * time.Second
|
|
||||||
rdnsMaxHosts = 10
|
|
||||||
rdnsOriginalLabel = "originalText"
|
|
||||||
rdnsPrefix = "/rdnsv3"
|
|
||||||
rdnsTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RDNSClient is an interface to work with Rancher DNS(RDNS) records in etcdv3 backend.
|
|
||||||
type RDNSClient interface {
|
|
||||||
Get(key string) ([]RDNSRecord, error)
|
|
||||||
List(rootDomain string) ([]RDNSRecord, error)
|
|
||||||
Set(value RDNSRecord) error
|
|
||||||
Delete(key string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// RDNSConfig contains configuration to create a new Rancher DNS(RDNS) provider.
|
|
||||||
type RDNSConfig struct {
|
|
||||||
DryRun bool
|
|
||||||
DomainFilter endpoint.DomainFilter
|
|
||||||
RootDomain string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
|
||||||
type RDNSProvider struct {
|
|
||||||
provider.BaseProvider
|
|
||||||
client RDNSClient
|
|
||||||
dryRun bool
|
|
||||||
domainFilter endpoint.DomainFilter
|
|
||||||
rootDomain string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RDNSRecord represents Rancher DNS(RDNS) etcdv3 record.
|
|
||||||
type RDNSRecord struct {
|
|
||||||
AggregationHosts []string `json:"aggregation_hosts,omitempty"`
|
|
||||||
Host string `json:"host,omitempty"`
|
|
||||||
Text string `json:"text,omitempty"`
|
|
||||||
TTL uint32 `json:"ttl,omitempty"`
|
|
||||||
Key string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RDNSRecordType represents Rancher DNS(RDNS) etcdv3 record type.
|
|
||||||
type RDNSRecordType struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
Domain string `json:"domain,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type etcdv3Client struct {
|
|
||||||
client *clientv3.Client
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RDNSClient = etcdv3Client{}
|
|
||||||
|
|
||||||
// NewRDNSProvider initializes a new Rancher DNS(RDNS) based Provider.
|
|
||||||
func NewRDNSProvider(config RDNSConfig) (*RDNSProvider, error) {
|
|
||||||
client, err := newEtcdv3Client()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
domain := os.Getenv("RDNS_ROOT_DOMAIN")
|
|
||||||
if domain == "" {
|
|
||||||
return nil, errors.New("needed root domain environment")
|
|
||||||
}
|
|
||||||
return &RDNSProvider{
|
|
||||||
client: client,
|
|
||||||
dryRun: config.DryRun,
|
|
||||||
domainFilter: config.DomainFilter,
|
|
||||||
rootDomain: domain,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records returns all DNS records found in Rancher DNS(RDNS) etcdv3 backend. Depending on the record fields
|
|
||||||
// it may be mapped to one or two records of type A, TXT, A+TXT.
|
|
||||||
func (p RDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
|
||||||
var result []*endpoint.Endpoint
|
|
||||||
|
|
||||||
rs, err := p.client.List(p.rootDomain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range rs {
|
|
||||||
domains := strings.Split(strings.TrimPrefix(r.Key, rdnsPrefix+"/"), "/")
|
|
||||||
keyToDNSNameSplits(domains)
|
|
||||||
dnsName := strings.Join(domains, ".")
|
|
||||||
if !p.domainFilter.Match(dnsName) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// only return rdnsMaxHosts at most
|
|
||||||
if len(r.AggregationHosts) > 0 {
|
|
||||||
if len(r.AggregationHosts) > rdnsMaxHosts {
|
|
||||||
r.AggregationHosts = r.AggregationHosts[:rdnsMaxHosts]
|
|
||||||
}
|
|
||||||
ep := endpoint.NewEndpointWithTTL(
|
|
||||||
dnsName,
|
|
||||||
endpoint.RecordTypeA,
|
|
||||||
endpoint.TTL(r.TTL),
|
|
||||||
r.AggregationHosts...,
|
|
||||||
)
|
|
||||||
ep.Labels[rdnsOriginalLabel] = r.Text
|
|
||||||
result = append(result, ep)
|
|
||||||
}
|
|
||||||
if r.Text != "" {
|
|
||||||
ep := endpoint.NewEndpoint(
|
|
||||||
dnsName,
|
|
||||||
endpoint.RecordTypeTXT,
|
|
||||||
r.Text,
|
|
||||||
)
|
|
||||||
result = append(result, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyChanges stores changes back to etcdv3 converting them to Rancher DNS(RDNS) format and aggregating A and TXT records.
|
|
||||||
func (p RDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
|
||||||
grouped := map[string][]*endpoint.Endpoint{}
|
|
||||||
|
|
||||||
for _, ep := range changes.Create {
|
|
||||||
grouped[ep.DNSName] = append(grouped[ep.DNSName], ep)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ep := range changes.UpdateNew {
|
|
||||||
if ep.RecordType == endpoint.RecordTypeA {
|
|
||||||
// append useless domain records to the changes.Delete
|
|
||||||
if err := p.filterAndRemoveUseless(ep, changes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
grouped[ep.DNSName] = append(grouped[ep.DNSName], ep)
|
|
||||||
}
|
|
||||||
|
|
||||||
for dnsName, group := range grouped {
|
|
||||||
if !p.domainFilter.Match(dnsName) {
|
|
||||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", dnsName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var rs []RDNSRecord
|
|
||||||
|
|
||||||
for _, ep := range group {
|
|
||||||
if ep.RecordType == endpoint.RecordTypeTXT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, target := range ep.Targets {
|
|
||||||
rs = append(rs, RDNSRecord{
|
|
||||||
Host: target,
|
|
||||||
Text: ep.Labels[rdnsOriginalLabel],
|
|
||||||
Key: keyFor(ep.DNSName) + "/" + formatKey(target),
|
|
||||||
TTL: uint32(ep.RecordTTL),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the TXT attribute to the existing A record
|
|
||||||
for _, ep := range group {
|
|
||||||
if ep.RecordType != endpoint.RecordTypeTXT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i, r := range rs {
|
|
||||||
if strings.Contains(r.Key, keyFor(ep.DNSName)) {
|
|
||||||
r.Text = ep.Targets[0]
|
|
||||||
rs[i] = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range rs {
|
|
||||||
log.Infof("Add/set key %s to Host=%s, Text=%s, TTL=%d", r.Key, r.Host, r.Text, r.TTL)
|
|
||||||
if !p.dryRun {
|
|
||||||
err := p.client.Set(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ep := range changes.Delete {
|
|
||||||
key := keyFor(ep.DNSName)
|
|
||||||
log.Infof("Delete key %s", key)
|
|
||||||
if !p.dryRun {
|
|
||||||
err := p.client.Delete(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterAndRemoveUseless filter and remove useless records.
|
|
||||||
func (p *RDNSProvider) filterAndRemoveUseless(ep *endpoint.Endpoint, changes *plan.Changes) error {
|
|
||||||
rs, err := p.client.Get(keyFor(ep.DNSName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, r := range rs {
|
|
||||||
exist := false
|
|
||||||
for _, target := range ep.Targets {
|
|
||||||
if strings.Contains(r.Key, formatKey(target)) {
|
|
||||||
exist = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
ds := strings.Split(strings.TrimPrefix(r.Key, rdnsPrefix+"/"), "/")
|
|
||||||
keyToDNSNameSplits(ds)
|
|
||||||
changes.Delete = append(changes.Delete, &endpoint.Endpoint{
|
|
||||||
DNSName: strings.Join(ds, "."),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newEtcdv3Client is an etcdv3 client constructor.
|
|
||||||
func newEtcdv3Client() (RDNSClient, error) {
|
|
||||||
cfg := &clientv3.Config{}
|
|
||||||
|
|
||||||
endpoints := os.Getenv("ETCD_URLS")
|
|
||||||
ca := os.Getenv("ETCD_CA_FILE")
|
|
||||||
cert := os.Getenv("ETCD_CERT_FILE")
|
|
||||||
key := os.Getenv("ETCD_KEY_FILE")
|
|
||||||
name := os.Getenv("ETCD_TLS_SERVER_NAME")
|
|
||||||
insecure := os.Getenv("ETCD_TLS_INSECURE")
|
|
||||||
|
|
||||||
if endpoints == "" {
|
|
||||||
endpoints = "http://localhost:2379"
|
|
||||||
}
|
|
||||||
|
|
||||||
urls := strings.Split(endpoints, ",")
|
|
||||||
scheme := strings.ToLower(urls[0])[0:strings.Index(strings.ToLower(urls[0]), "://")]
|
|
||||||
|
|
||||||
switch scheme {
|
|
||||||
case "http":
|
|
||||||
cfg.Endpoints = urls
|
|
||||||
case "https":
|
|
||||||
var certificates []tls.Certificate
|
|
||||||
|
|
||||||
insecure = strings.ToLower(insecure)
|
|
||||||
isInsecure := insecure == "true" || insecure == "yes" || insecure == "1"
|
|
||||||
|
|
||||||
if ca != "" && key == "" || cert == "" && key != "" {
|
|
||||||
return nil, errors.New("either both cert and key or none must be provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert != "" {
|
|
||||||
cert, err := tls.LoadX509KeyPair(cert, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not load TLS cert: %w", err)
|
|
||||||
}
|
|
||||||
certificates = append(certificates, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &tls.Config{
|
|
||||||
Certificates: certificates,
|
|
||||||
InsecureSkipVerify: isInsecure,
|
|
||||||
ServerName: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ca != "" {
|
|
||||||
roots := x509.NewCertPool()
|
|
||||||
pem, err := os.ReadFile(ca)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading %s: %w", ca, err)
|
|
||||||
}
|
|
||||||
ok := roots.AppendCertsFromPEM(pem)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("could not read root certs: %w", err)
|
|
||||||
}
|
|
||||||
config.RootCAs = roots
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.Endpoints = urls
|
|
||||||
cfg.TLS = config
|
|
||||||
default:
|
|
||||||
return nil, errors.New("etcdv3 URLs must start with either http:// or https://")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := clientv3.New(*cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return etcdv3Client{c, context.Background()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get return A records stored in etcdv3 stored anywhere under the given key (recursively).
|
|
||||||
func (c etcdv3Client) Get(key string) ([]RDNSRecord, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(c.ctx, rdnsTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
result, err := c.client.Get(ctx, key, clientv3.WithPrefix())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rs := make([]RDNSRecord, 0)
|
|
||||||
for _, v := range result.Kvs {
|
|
||||||
r := new(RDNSRecord)
|
|
||||||
if err := json.Unmarshal(v.Value, r); err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: %w", v.Key, err)
|
|
||||||
}
|
|
||||||
r.Key = string(v.Key)
|
|
||||||
rs = append(rs, *r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List return all records stored in etcdv3 stored anywhere under the given rootDomain (recursively).
|
|
||||||
func (c etcdv3Client) List(rootDomain string) ([]RDNSRecord, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(c.ctx, rdnsTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
path := keyFor(rootDomain)
|
|
||||||
|
|
||||||
result, err := c.client.Get(ctx, path, clientv3.WithPrefix())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.aggregationRecords(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set persists records data into etcdv3.
|
|
||||||
func (c etcdv3Client) Set(r RDNSRecord) error {
|
|
||||||
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
v, err := json.Marshal(&r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Text == "" && r.Host == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.client.Put(ctx, r.Key, string(v))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes record from etcdv3.
|
|
||||||
func (c etcdv3Client) Delete(key string) error {
|
|
||||||
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
_, err := c.client.Delete(ctx, key, clientv3.WithPrefix())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// aggregationRecords will aggregation multi A records under the given path.
|
|
||||||
// e.g. A: 1_1_1_1.xxx.lb.rancher.cloud & 2_2_2_2.sample.lb.rancher.cloud => sample.lb.rancher.cloud {"aggregation_hosts": ["1.1.1.1", "2.2.2.2"]}
|
|
||||||
// e.g. TXT: sample.lb.rancher.cloud => sample.lb.rancher.cloud => {"text": "xxx"}
|
|
||||||
func (c etcdv3Client) aggregationRecords(result *clientv3.GetResponse) ([]RDNSRecord, error) {
|
|
||||||
var rs []RDNSRecord
|
|
||||||
bx := make(map[RDNSRecordType]RDNSRecord)
|
|
||||||
|
|
||||||
for _, n := range result.Kvs {
|
|
||||||
r := new(RDNSRecord)
|
|
||||||
if err := json.Unmarshal(n.Value, r); err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: %w", n.Key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Key = string(n.Key)
|
|
||||||
|
|
||||||
if r.Host == "" && r.Text == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Host != "" {
|
|
||||||
c := RDNSRecord{
|
|
||||||
AggregationHosts: r.AggregationHosts,
|
|
||||||
Host: r.Host,
|
|
||||||
Text: r.Text,
|
|
||||||
TTL: r.TTL,
|
|
||||||
Key: r.Key,
|
|
||||||
}
|
|
||||||
n, isContinue := appendRecords(c, endpoint.RecordTypeA, bx, rs)
|
|
||||||
if isContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rs = n
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Text != "" && r.Host == "" {
|
|
||||||
c := RDNSRecord{
|
|
||||||
AggregationHosts: []string{},
|
|
||||||
Host: r.Host,
|
|
||||||
Text: r.Text,
|
|
||||||
TTL: r.TTL,
|
|
||||||
Key: r.Key,
|
|
||||||
}
|
|
||||||
n, isContinue := appendRecords(c, endpoint.RecordTypeTXT, bx, rs)
|
|
||||||
if isContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rs = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendRecords append record to an array
|
|
||||||
func appendRecords(r RDNSRecord, dnsType string, bx map[RDNSRecordType]RDNSRecord, rs []RDNSRecord) ([]RDNSRecord, bool) {
|
|
||||||
dnsName := keyToParentDNSName(r.Key)
|
|
||||||
bt := RDNSRecordType{Domain: dnsName, Type: dnsType}
|
|
||||||
if v, ok := bx[bt]; ok {
|
|
||||||
// skip the TXT records if already added to record list.
|
|
||||||
// append A record if dnsName already added to record list but not found the value.
|
|
||||||
// the same record might be found in multiple etcdv3 nodes.
|
|
||||||
if bt.Type == endpoint.RecordTypeA {
|
|
||||||
exist := false
|
|
||||||
for _, h := range v.AggregationHosts {
|
|
||||||
if h == r.Host {
|
|
||||||
exist = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
for i, t := range rs {
|
|
||||||
if !strings.HasPrefix(r.Key, t.Key) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Host = ""
|
|
||||||
t.AggregationHosts = append(t.AggregationHosts, r.Host)
|
|
||||||
bx[bt] = t
|
|
||||||
rs[i] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rs, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if bt.Type == endpoint.RecordTypeA {
|
|
||||||
r.AggregationHosts = append(r.AggregationHosts, r.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Key = rdnsPrefix + dnsNameToKey(dnsName)
|
|
||||||
r.Host = ""
|
|
||||||
bx[bt] = r
|
|
||||||
rs = append(rs, r)
|
|
||||||
return rs, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyFor used to get a path as etcdv3 preferred.
|
|
||||||
// e.g. sample.lb.rancher.cloud => /rdnsv3/cloud/rancher/lb/sample
|
|
||||||
func keyFor(fqdn string) string {
|
|
||||||
return rdnsPrefix + dnsNameToKey(fqdn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyToParentDNSName used to get dnsName.
|
|
||||||
// e.g. /rdnsv3/cloud/rancher/lb/sample/xxx => xxx.sample.lb.rancher.cloud
|
|
||||||
// e.g. /rdnsv3/cloud/rancher/lb/sample/xxx/1_1_1_1 => xxx.sample.lb.rancher.cloud
|
|
||||||
func keyToParentDNSName(key string) string {
|
|
||||||
ds := strings.Split(strings.TrimPrefix(key, rdnsPrefix+"/"), "/")
|
|
||||||
keyToDNSNameSplits(ds)
|
|
||||||
|
|
||||||
dns := strings.Join(ds, ".")
|
|
||||||
prefix := strings.Split(dns, ".")[0]
|
|
||||||
|
|
||||||
p := `^\d{1,3}_\d{1,3}_\d{1,3}_\d{1,3}$`
|
|
||||||
m, _ := regexp.MatchString(p, prefix)
|
|
||||||
if prefix != "" && strings.Contains(prefix, "_") && m {
|
|
||||||
// 1_1_1_1.xxx.sample.lb.rancher.cloud => xxx.sample.lb.rancher.cloud
|
|
||||||
return strings.Join(strings.Split(dns, ".")[1:], ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
return dns
|
|
||||||
}
|
|
||||||
|
|
||||||
// dnsNameToKey used to convert domain to a path as etcdv3 preferred.
|
|
||||||
// e.g. sample.lb.rancher.cloud => /cloud/rancher/lb/sample
|
|
||||||
func dnsNameToKey(domain string) string {
|
|
||||||
ss := strings.Split(domain, ".")
|
|
||||||
last := len(ss) - 1
|
|
||||||
for i := 0; i < len(ss)/2; i++ {
|
|
||||||
ss[i], ss[last-i] = ss[last-i], ss[i]
|
|
||||||
}
|
|
||||||
return "/" + strings.Join(ss, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyToDNSNameSplits used to reverse etcdv3 path to domain splits.
|
|
||||||
// e.g. /cloud/rancher/lb/sample => [sample lb rancher cloud]
|
|
||||||
func keyToDNSNameSplits(ss []string) {
|
|
||||||
for i := 0; i < len(ss)/2; i++ {
|
|
||||||
j := len(ss) - i - 1
|
|
||||||
ss[i], ss[j] = ss[j], ss[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatKey used to format a key as etcdv3 preferred
|
|
||||||
// e.g. 1.1.1.1 => 1_1_1_1
|
|
||||||
// e.g. sample.lb.rancher.cloud => sample_lb_rancher_cloud
|
|
||||||
func formatKey(key string) string {
|
|
||||||
return strings.Replace(key, ".", "_", -1)
|
|
||||||
}
|
|
@ -1,355 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2019 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 rdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
|
||||||
"sigs.k8s.io/external-dns/plan"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeEtcdv3Client struct {
|
|
||||||
rs map[string]RDNSRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c fakeEtcdv3Client) Get(key string) ([]RDNSRecord, error) {
|
|
||||||
rs := make([]RDNSRecord, 0)
|
|
||||||
for k, v := range c.rs {
|
|
||||||
if strings.Contains(k, key) {
|
|
||||||
rs = append(rs, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c fakeEtcdv3Client) List(rootDomain string) ([]RDNSRecord, error) {
|
|
||||||
var result []RDNSRecord
|
|
||||||
for key, value := range c.rs {
|
|
||||||
rootPath := rdnsPrefix + dnsNameToKey(rootDomain)
|
|
||||||
if strings.HasPrefix(key, rootPath) {
|
|
||||||
value.Key = key
|
|
||||||
result = append(result, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r := &clientv3.GetResponse{}
|
|
||||||
|
|
||||||
for _, v := range result {
|
|
||||||
b, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k := &mvccpb.KeyValue{
|
|
||||||
Key: []byte(v.Key),
|
|
||||||
Value: b,
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Kvs = append(r.Kvs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.aggregationRecords(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c fakeEtcdv3Client) Set(r RDNSRecord) error {
|
|
||||||
c.rs[r.Key] = r
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c fakeEtcdv3Client) Delete(key string) error {
|
|
||||||
ks := make([]string, 0)
|
|
||||||
for k := range c.rs {
|
|
||||||
if strings.Contains(k, key) {
|
|
||||||
ks = append(ks, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range ks {
|
|
||||||
delete(c.rs, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestARecordTranslation(t *testing.T) {
|
|
||||||
expectedTarget1 := "1.2.3.4"
|
|
||||||
expectedTarget2 := "2.3.4.5"
|
|
||||||
expectedTargets := []string{expectedTarget1, expectedTarget2}
|
|
||||||
expectedDNSName := "p1xaf1.lb.rancher.cloud"
|
|
||||||
expectedRecordType := endpoint.RecordTypeA
|
|
||||||
|
|
||||||
client := fakeEtcdv3Client{
|
|
||||||
map[string]RDNSRecord{
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/1_2_3_4": {Host: expectedTarget1},
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/2_3_4_5": {Host: expectedTarget2},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := RDNSProvider{
|
|
||||||
client: client,
|
|
||||||
rootDomain: "lb.rancher.cloud",
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints, err := provider.Records(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(endpoints) != 1 {
|
|
||||||
t.Fatalf("got unexpected number of endpoints: %d", len(endpoints))
|
|
||||||
}
|
|
||||||
|
|
||||||
ep := endpoints[0]
|
|
||||||
if ep.DNSName != expectedDNSName {
|
|
||||||
t.Errorf("got unexpected DNS name: %s != %s", ep.DNSName, expectedDNSName)
|
|
||||||
}
|
|
||||||
assert.Contains(t, expectedTargets, ep.Targets[0])
|
|
||||||
assert.Contains(t, expectedTargets, ep.Targets[1])
|
|
||||||
if ep.RecordType != expectedRecordType {
|
|
||||||
t.Errorf("got unexpected DNS record type: %s != %s", ep.RecordType, expectedRecordType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTXTRecordTranslation(t *testing.T) {
|
|
||||||
expectedTarget := "string"
|
|
||||||
expectedDNSName := "p1xaf1.lb.rancher.cloud"
|
|
||||||
expectedRecordType := endpoint.RecordTypeTXT
|
|
||||||
|
|
||||||
client := fakeEtcdv3Client{
|
|
||||||
map[string]RDNSRecord{
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1": {Text: expectedTarget},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := RDNSProvider{
|
|
||||||
client: client,
|
|
||||||
rootDomain: "lb.rancher.cloud",
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints, err := provider.Records(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(endpoints) != 1 {
|
|
||||||
t.Fatalf("got unexpected number of endpoints: %d", len(endpoints))
|
|
||||||
}
|
|
||||||
if endpoints[0].DNSName != expectedDNSName {
|
|
||||||
t.Errorf("got unexpected DNS name: %s != %s", endpoints[0].DNSName, expectedDNSName)
|
|
||||||
}
|
|
||||||
if endpoints[0].Targets[0] != expectedTarget {
|
|
||||||
t.Errorf("got unexpected DNS target: %s != %s", endpoints[0].Targets[0], expectedTarget)
|
|
||||||
}
|
|
||||||
if endpoints[0].RecordType != expectedRecordType {
|
|
||||||
t.Errorf("got unexpected DNS record type: %s != %s", endpoints[0].RecordType, expectedRecordType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAWithTXTRecordTranslation(t *testing.T) {
|
|
||||||
expectedTargets := map[string]string{
|
|
||||||
endpoint.RecordTypeA: "1.2.3.4",
|
|
||||||
endpoint.RecordTypeTXT: "string",
|
|
||||||
}
|
|
||||||
expectedDNSName := "p1xaf1.lb.rancher.cloud"
|
|
||||||
|
|
||||||
client := fakeEtcdv3Client{
|
|
||||||
map[string]RDNSRecord{
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1": {Host: "1.2.3.4", Text: "string"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := RDNSProvider{
|
|
||||||
client: client,
|
|
||||||
rootDomain: "lb.rancher.cloud",
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints, err := provider.Records(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(endpoints) != len(expectedTargets) {
|
|
||||||
t.Fatalf("got unexpected number of endpoints: %d", len(endpoints))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ep := range endpoints {
|
|
||||||
expectedTarget := expectedTargets[ep.RecordType]
|
|
||||||
if expectedTarget == "" {
|
|
||||||
t.Errorf("got unexpected DNS record type: %s", ep.RecordType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(expectedTargets, ep.RecordType)
|
|
||||||
|
|
||||||
if ep.DNSName != expectedDNSName {
|
|
||||||
t.Errorf("got unexpected DNS name: %s != %s", ep.DNSName, expectedDNSName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ep.Targets[0] != expectedTarget {
|
|
||||||
t.Errorf("got unexpected DNS target: %s != %s", ep.Targets[0], expectedTarget)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRDNSApplyChanges(t *testing.T) {
|
|
||||||
client := fakeEtcdv3Client{
|
|
||||||
map[string]RDNSRecord{},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := RDNSProvider{
|
|
||||||
client: client,
|
|
||||||
rootDomain: "lb.rancher.cloud",
|
|
||||||
}
|
|
||||||
|
|
||||||
changes1 := &plan.Changes{
|
|
||||||
Create: []*endpoint.Endpoint{
|
|
||||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeA, "5.5.5.5", "6.6.6.6"),
|
|
||||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeTXT, "string1"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := provider.ApplyChanges(context.Background(), changes1); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedRecords1 := map[string]RDNSRecord{
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/5_5_5_5": {Host: "5.5.5.5", Text: "string1"},
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/6_6_6_6": {Host: "6.6.6.6", Text: "string1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
client.validateRecords(client.rs, expectedRecords1, t)
|
|
||||||
|
|
||||||
changes2 := &plan.Changes{
|
|
||||||
Create: []*endpoint.Endpoint{
|
|
||||||
endpoint.NewEndpoint("abx1v1.lb.rancher.cloud", endpoint.RecordTypeA, "7.7.7.7"),
|
|
||||||
},
|
|
||||||
UpdateNew: []*endpoint.Endpoint{
|
|
||||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeA, "8.8.8.8", "9.9.9.9"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
records, _ := provider.Records(context.Background())
|
|
||||||
for _, ep := range records {
|
|
||||||
if ep.DNSName == "p1xaf1.lb.rancher.cloud" {
|
|
||||||
changes2.UpdateOld = append(changes2.UpdateOld, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := provider.ApplyChanges(context.Background(), changes2); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedRecords2 := map[string]RDNSRecord{
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/8_8_8_8": {Host: "8.8.8.8"},
|
|
||||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/9_9_9_9": {Host: "9.9.9.9"},
|
|
||||||
"/rdnsv3/cloud/rancher/lb/abx1v1/7_7_7_7": {Host: "7.7.7.7"},
|
|
||||||
}
|
|
||||||
|
|
||||||
client.validateRecords(client.rs, expectedRecords2, t)
|
|
||||||
|
|
||||||
changes3 := &plan.Changes{
|
|
||||||
Delete: []*endpoint.Endpoint{
|
|
||||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeA, "8.8.8.8", "9.9.9.9"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := provider.ApplyChanges(context.Background(), changes3); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedRecords3 := map[string]RDNSRecord{
|
|
||||||
"/rdnsv3/cloud/rancher/lb/abx1v1/7_7_7_7": {Host: "7.7.7.7"},
|
|
||||||
}
|
|
||||||
|
|
||||||
client.validateRecords(client.rs, expectedRecords3, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c fakeEtcdv3Client) aggregationRecords(result *clientv3.GetResponse) ([]RDNSRecord, error) {
|
|
||||||
var rs []RDNSRecord
|
|
||||||
bx := make(map[RDNSRecordType]RDNSRecord)
|
|
||||||
|
|
||||||
for _, n := range result.Kvs {
|
|
||||||
r := new(RDNSRecord)
|
|
||||||
if err := json.Unmarshal(n.Value, r); err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Key = string(n.Key)
|
|
||||||
|
|
||||||
if r.Host == "" && r.Text == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Host != "" {
|
|
||||||
c := RDNSRecord{
|
|
||||||
AggregationHosts: r.AggregationHosts,
|
|
||||||
Host: r.Host,
|
|
||||||
Text: r.Text,
|
|
||||||
TTL: r.TTL,
|
|
||||||
Key: r.Key,
|
|
||||||
}
|
|
||||||
n, isContinue := appendRecords(c, endpoint.RecordTypeA, bx, rs)
|
|
||||||
if isContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rs = n
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Text != "" && r.Host == "" {
|
|
||||||
c := RDNSRecord{
|
|
||||||
AggregationHosts: []string{},
|
|
||||||
Host: r.Host,
|
|
||||||
Text: r.Text,
|
|
||||||
TTL: r.TTL,
|
|
||||||
Key: r.Key,
|
|
||||||
}
|
|
||||||
n, isContinue := appendRecords(c, endpoint.RecordTypeTXT, bx, rs)
|
|
||||||
if isContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rs = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c fakeEtcdv3Client) validateRecords(rs, expectedRs map[string]RDNSRecord, t *testing.T) {
|
|
||||||
if len(rs) != len(expectedRs) {
|
|
||||||
t.Errorf("wrong number of records: %d != %d", len(rs), len(expectedRs))
|
|
||||||
}
|
|
||||||
for key, value := range rs {
|
|
||||||
if _, ok := expectedRs[key]; !ok {
|
|
||||||
t.Errorf("unexpected record %s", key)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expected := expectedRs[key]
|
|
||||||
delete(expectedRs, key)
|
|
||||||
if value.Host != expected.Host {
|
|
||||||
t.Errorf("wrong host for record %s: %s != %s", key, value.Host, expected.Host)
|
|
||||||
}
|
|
||||||
if value.Text != expected.Text {
|
|
||||||
t.Errorf("wrong text for record %s: %s != %s", key, value.Text, expected.Text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user