mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-10-14 03:11:20 +02:00
Merge remote-tracking branch 'upstream/master' into coredns
This commit is contained in:
commit
e938d71bc3
3
.gitignore
vendored
3
.gitignore
vendored
@ -43,3 +43,6 @@ cscope.*
|
||||
cover.out
|
||||
*.coverprofile
|
||||
external-dns
|
||||
|
||||
# vendor dir
|
||||
vendor/
|
||||
|
@ -6,7 +6,7 @@ os:
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.9
|
||||
- 1.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
@ -14,13 +14,18 @@ matrix:
|
||||
- go: tip
|
||||
|
||||
before_install:
|
||||
- make dep
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/lawrencewoodman/roveralls
|
||||
- go get github.com/alecthomas/gometalinter
|
||||
|
||||
install:
|
||||
- gometalinter --install
|
||||
- sed -i 's/--deadline=50s/--deadline=120s/g'
|
||||
./vendor/github.com/kubernetes/repo-infra/verify/go-tools/verify-gometalinter.sh
|
||||
|
||||
script:
|
||||
- vendor/github.com/kubernetes/repo-infra/verify/verify-boilerplate.sh --rootdir=$(pwd)
|
||||
- vendor/github.com/kubernetes/repo-infra/verify/verify-go-src.sh -v --rootdir $(pwd)
|
||||
- travis_wait 20 goveralls -service=travis-ci
|
||||
- travis_wait 20 roveralls
|
||||
- goveralls -coverprofile=roveralls.coverprofile -service=travis-ci
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,3 +1,29 @@
|
||||
## v0.5.1 - 2018-05-16
|
||||
|
||||
- Refactor implementation of sync loop to use `time.Ticker` (#553) @r0fls
|
||||
- Document how ExternalDNS gets permission to change AWS Route53 entries (#557) @hjacobs
|
||||
- Fix CNAME support for the PowerDNS provider (#547) @kciredor
|
||||
- Add support for hostname annotation in Ingress resource (#545) @rajatjindal
|
||||
- Fix for TTLs being ignored on headless Services (#546) @danbondd
|
||||
- Fix failing tests by giving linters more time to do their work (#548) @linki
|
||||
- Fix misspelled flag for the OpenStack Designate provider (#542) @zentale
|
||||
- Document additional RBAC rules needed to read Pods (#538) @danbondd
|
||||
|
||||
## v0.5.0 - 2018-04-23
|
||||
|
||||
- Google: Correctly filter records that don't match all filters (#533) @prydie @linki
|
||||
- AWS: add support for AWS Network Load Balancers (#531) @linki
|
||||
- Add a flag that allows FQDN template and annotations to combine (#513) @helgi
|
||||
- Fix: Use PodIP instead of HostIP for headless Services (#498) @nrobert13
|
||||
- Support a comma separated list for the FQDN template (#512) @helgi
|
||||
- Google Provider: Add auto-detection of Google Project when running on GCP (#492) @drzero42
|
||||
- Add custom TTL support for DNSimple (#477) @jbowes
|
||||
- Fix docker build and delete vendor files which were not deleted (#473) @njuettner
|
||||
- DigitalOcean: DigitalOcean creates entries with host in them twice (#459) @njuettner
|
||||
- Bugfix: Retrive all DNSimple response pages (#468) @jbowes
|
||||
- external-dns does now provide support for multiple targets for A records. This is currently only supported by the Google Cloud DNS provider (#418) @dereulenspiegel
|
||||
- Graceful handling of misconfigure password for dyn provider (#470) @jvassev
|
||||
- Don't log sensitive data on start (#463) @jvassev
|
||||
- Google: Improve logging to help trace misconfigurations (#388) @stealthybox
|
||||
- AWS: In addition to the one best public hosted zone, records will be added to all matching private hosted zones (#356) @coreypobrien
|
||||
- Every record managed by External DNS is now mapped to a kubernetes resource (service/ingress) @ideahitme
|
||||
|
@ -13,10 +13,11 @@
|
||||
# limitations under the License.
|
||||
|
||||
# builder image
|
||||
FROM golang:1.9 as builder
|
||||
FROM golang as builder
|
||||
|
||||
WORKDIR /go/src/github.com/kubernetes-incubator/external-dns
|
||||
COPY . .
|
||||
RUN make dep
|
||||
RUN make test
|
||||
RUN make build
|
||||
|
||||
|
24
Gopkg.lock
generated
24
Gopkg.lock
generated
@ -184,6 +184,12 @@
|
||||
]
|
||||
revision = "09691a3b6378b740595c1002f40c34dd5f218a22"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/ffledgling/pdns-go"
|
||||
packages = ["."]
|
||||
revision = "524e7daccd99651cdb56426eb15b7d61f9597a5c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
@ -244,6 +250,22 @@
|
||||
packages = ["."]
|
||||
revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gophercloud/gophercloud"
|
||||
packages = [
|
||||
".",
|
||||
"openstack",
|
||||
"openstack/dns/v2/recordsets",
|
||||
"openstack/dns/v2/zones",
|
||||
"openstack/identity/v2/tenants",
|
||||
"openstack/identity/v2/tokens",
|
||||
"openstack/identity/v3/tokens",
|
||||
"openstack/utils",
|
||||
"pagination"
|
||||
]
|
||||
revision = "bfc4756e1a693a850d7d459f4b28b21f35a24b5a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/howeyc/gopass"
|
||||
packages = ["."]
|
||||
@ -640,6 +662,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "4e118ef87cbafb30cee5759beb6d5fea3acbf8906b41148b3bb116cd1e69f5e9"
|
||||
inputs-digest = "371e8260c0580f391a14a867114e0b92e9b5b1b1b9fd2945410b6b8f7d1db498"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
3
Makefile
3
Makefile
@ -27,6 +27,9 @@ cover:
|
||||
cover-html: cover
|
||||
go tool cover -html cover.out
|
||||
|
||||
dep:
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure -vendor-only
|
||||
|
||||
# The verify target runs tasks similar to the CI tasks, but without code coverage
|
||||
.PHONY: verify test
|
||||
|
32
README.md
32
README.md
@ -21,9 +21,9 @@ The [FAQ](docs/faq.md) contains additional information and addresses several que
|
||||
|
||||
To see ExternalDNS in action, have a look at this [video](https://www.youtube.com/watch?v=9HQ2XgL9YVI).
|
||||
|
||||
## The Latest Release: v0.4
|
||||
## The Latest Release: v0.5
|
||||
|
||||
ExternalDNS' current release is `v0.4`. This version allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` in various cloud providers:
|
||||
ExternalDNS' current release is `v0.5`. This version allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` in various cloud providers:
|
||||
* [Google CloudDNS](https://cloud.google.com/dns/docs/)
|
||||
* [AWS Route 53](https://aws.amazon.com/route53/)
|
||||
* [AzureDNS](https://azure.microsoft.com/en-us/services/dns)
|
||||
@ -32,8 +32,10 @@ ExternalDNS' current release is `v0.4`. This version allows you to keep selected
|
||||
* [DNSimple](https://dnsimple.com/)
|
||||
* [Infoblox](https://www.infoblox.com/products/dns/)
|
||||
* [Dyn](https://dyn.com/dns/)
|
||||
* [OpenStack Designate](https://docs.openstack.org/designate/latest/)
|
||||
* [PowerDNS](https://www.powerdns.com/)
|
||||
|
||||
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.4` with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
|
||||
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
|
||||
|
||||
Note that all flags can be replaced with environment variables; for instance,
|
||||
`--dry-run` could be replaced with `EXTERNAL_DNS_DRY_RUN=1`, or
|
||||
@ -66,10 +68,16 @@ Make sure you have the following prerequisites:
|
||||
|
||||
First, get ExternalDNS:
|
||||
|
||||
**To install all dependencies, make sure to install [dep](https://github.com/golang/dep) first.**
|
||||
|
||||
```console
|
||||
$ go get -u github.com/kubernetes-incubator/external-dns
|
||||
$ git clone https://github.com/kubernetes-incubator/external-dns.git && cd external-dns
|
||||
$ dep ensure -vendor-only
|
||||
$ make
|
||||
```
|
||||
|
||||
This will create external-dns in the build directory directly from master.
|
||||
|
||||
Next, run an application and expose it via a Kubernetes Service:
|
||||
|
||||
```console
|
||||
@ -146,13 +154,23 @@ Here's a rough outline on what is to come (subject to change):
|
||||
- [x] Support for multiple zones
|
||||
- [x] Ownership System
|
||||
|
||||
### v0.4 - _current version_
|
||||
### v0.4
|
||||
|
||||
- [x] Support for AzureDNS
|
||||
- [x] Support for CloudFlare
|
||||
- [x] Support for DigitalOcean
|
||||
- [x] Multiple DNS names per Service
|
||||
|
||||
### v0.5 - _current version_
|
||||
|
||||
- [x] Support for creating DNS records to multiple targets (for Google and AWS)
|
||||
- [x] Support for OpenStack Designate
|
||||
- [x] Support for PowerDNS
|
||||
|
||||
### v0.6
|
||||
|
||||
- [ ] Ability to replace Kops' [DNS Controller](https://github.com/kubernetes/kops/tree/master/dns-controller) (This could also directly become `v1.0`)
|
||||
|
||||
### v1.0
|
||||
|
||||
- [ ] Ability to replace Kops' [DNS Controller](https://github.com/kubernetes/kops/tree/master/dns-controller)
|
||||
@ -161,11 +179,11 @@ Here's a rough outline on what is to come (subject to change):
|
||||
|
||||
### Yet to be defined
|
||||
|
||||
* Support for CoreDNS and Azure DNS
|
||||
* Support for CoreDNS
|
||||
* Support for record weights
|
||||
* Support for different behavioral policies
|
||||
* Support for Services with `type=NodePort`
|
||||
* Support for TPRs
|
||||
* Support for CRDs
|
||||
* Support for more advanced DNS record configurations
|
||||
|
||||
Have a look at [the milestones](https://github.com/kubernetes-incubator/external-dns/milestones) to get an idea of where we currently stand.
|
||||
|
@ -66,14 +66,15 @@ func (c *Controller) RunOnce() error {
|
||||
|
||||
// Run runs RunOnce in a loop with a delay until stopChan receives a value.
|
||||
func (c *Controller) Run(stopChan <-chan struct{}) {
|
||||
ticker := time.NewTicker(c.Interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
err := c.RunOnce()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(c.Interval):
|
||||
case <-ticker.C:
|
||||
case <-stopChan:
|
||||
log.Info("Terminating main controller loop")
|
||||
return
|
||||
|
@ -48,25 +48,25 @@ func (p *mockProvider) ApplyChanges(changes *plan.Changes) error {
|
||||
}
|
||||
|
||||
for i := range changes.Create {
|
||||
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || changes.Create[i].Target != p.ExpectChanges.Create[i].Target {
|
||||
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) {
|
||||
return errors.New("created record is wrong")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range changes.UpdateNew {
|
||||
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || changes.UpdateNew[i].Target != p.ExpectChanges.UpdateNew[i].Target {
|
||||
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) {
|
||||
return errors.New("delete record is wrong")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range changes.UpdateOld {
|
||||
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || changes.UpdateOld[i].Target != p.ExpectChanges.UpdateOld[i].Target {
|
||||
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) {
|
||||
return errors.New("delete record is wrong")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range changes.Delete {
|
||||
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || changes.Delete[i].Target != p.ExpectChanges.Delete[i].Target {
|
||||
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || !changes.Delete[i].Targets.Same(p.ExpectChanges.Delete[i].Targets) {
|
||||
return errors.New("delete record is wrong")
|
||||
}
|
||||
}
|
||||
@ -91,11 +91,11 @@ func TestRunOnce(t *testing.T) {
|
||||
source.On("Endpoints").Return([]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "create-record",
|
||||
Target: "1.2.3.4",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
},
|
||||
{
|
||||
DNSName: "update-record",
|
||||
Target: "8.8.4.4",
|
||||
Targets: endpoint.Targets{"8.8.4.4"},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
@ -104,25 +104,25 @@ func TestRunOnce(t *testing.T) {
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "update-record",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
{
|
||||
DNSName: "delete-record",
|
||||
Target: "4.3.2.1",
|
||||
Targets: endpoint.Targets{"4.3.2.1"},
|
||||
},
|
||||
},
|
||||
&plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{DNSName: "create-record", Target: "1.2.3.4"},
|
||||
{DNSName: "create-record", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{DNSName: "update-record", Target: "8.8.4.4"},
|
||||
{DNSName: "update-record", Targets: endpoint.Targets{"8.8.4.4"}},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{DNSName: "update-record", Target: "8.8.8.8"},
|
||||
{DNSName: "update-record", Targets: endpoint.Targets{"8.8.8.8"}},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{DNSName: "delete-record", Target: "4.3.2.1"},
|
||||
{DNSName: "delete-record", Targets: endpoint.Targets{"4.3.2.1"}},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -22,6 +22,7 @@ All sources live in package `source`.
|
||||
* `ServiceSource`: collects all Services that have an external IP and returns them as Endpoint objects. The desired DNS name corresponds to an annotation set on the Service or is compiled from the Service attributes via the FQDN Go template string.
|
||||
* `IngressSource`: collects all Ingresses that have an external IP and returns them as Endpoint objects. The desired DNS name corresponds to the host rules defined in the Ingress object.
|
||||
* `FakeSource`: returns a random list of Endpoints for the purpose of testing providers without having access to a Kubernetes cluster.
|
||||
* `ConnectorSource`: returns a list of Endpoint objects which are served by a tcp server configured through `connector-source-server` flag.
|
||||
|
||||
### Providers
|
||||
|
||||
|
39
docs/faq.md
39
docs/faq.md
@ -1,6 +1,6 @@
|
||||
# Frequently asked questions
|
||||
|
||||
### When would ExternalDNS become useful to me?
|
||||
### How is ExternalDNS useful to me?
|
||||
|
||||
You've probably created many deployments. Typically, you expose your deployment to the Internet by creating a Service with `type=LoadBalancer`. Depending on your environment, this usually assigns a random publicly available endpoint to your service that you can access from anywhere in the world. On Google Container Engine, this is a public IP address:
|
||||
|
||||
@ -28,17 +28,26 @@ ExternalDNS can solve this for you as well.
|
||||
|
||||
### Which DNS providers are supported?
|
||||
|
||||
So far, Google CloudDNS and AWS Route 53 with ALIAS records. There's interest in supporting CoreDNS and Azure DNS. We're open to discussing/adding other providers if the community believes it would be valuable.
|
||||
Currently, the following providers are supported:
|
||||
|
||||
Initial support for Google CloudDNS is available since the `v0.1` release. Initial support for AWS Route 53 is available in the `v0.2` release (CNAME based) and ALIAS is targeted for the `v0.3` release.
|
||||
- Google CloudDNS
|
||||
- AWS Route 53
|
||||
- AzureDNS
|
||||
- CloudFlare
|
||||
- DigitalOcean
|
||||
- DNSimple
|
||||
- Infoblox
|
||||
- Dyn
|
||||
- OpenStack Designate
|
||||
- PowerDNS
|
||||
|
||||
There are no plans regarding other providers at the moment.
|
||||
As stated in the README, we are currently looking for stable maintainers for those providers, to ensure that bugfixes and new features will be available for all of those.
|
||||
|
||||
### Which Kubernetes objects are supported?
|
||||
|
||||
Services exposed via `type=LoadBalancer` and for the hostnames defined in Ingress objects. It also seems useful to expose Services with `type=NodePort` to point to your cluster's nodes directly, but there's no commitment to doing this yet.
|
||||
Services exposed via `type=LoadBalancer` and for the hostnames defined in Ingress objects as well as headless hostPort services. An initial effort to support type `NodePort` was started as of May 2018 and it is in progress at the time of writing.
|
||||
|
||||
### How do I specify DNS name for my Kubernetes objects?
|
||||
### How do I specify a DNS name for my Kubernetes objects?
|
||||
|
||||
There are three sources of information for ExternalDNS to decide on DNS name. ExternalDNS will pick one in order as listed below:
|
||||
|
||||
@ -48,6 +57,10 @@ There are three sources of information for ExternalDNS to decide on DNS name. Ex
|
||||
|
||||
3. If `--fqdn-template` flag is specified, e.g. `--fqdn-template={{.Name}}.my-org.com`, ExternalDNS will use service/ingress specifications for the provided template to generate DNS name.
|
||||
|
||||
### Can I specify multiple global FQDN templates?
|
||||
|
||||
Yes, you can. Pass in a comma separated list to `--fqdn-template`. Beaware this will double (triple, etc) the amount of DNS entries based on how many services, ingresses and so on you have and will get you faster towards the API request limit of your DNS provider.
|
||||
|
||||
### Which Service and Ingress controllers are supported?
|
||||
|
||||
Regarding Services, we'll support the OSI Layer 4 load balancers that Kubernetes creates on AWS and Google Container Engine, and possibly other clusters running on Google Compute Engine.
|
||||
@ -63,9 +76,9 @@ For Ingress objects, ExternalDNS will attempt to discover the target hostname of
|
||||
|
||||
Another reason you may want to override the ingress hostname or IP address is if you have an external mechanism for handling failover across ingress endpoints. Possible scenarios for this would include using [keepalived-vip](https://github.com/kubernetes/contrib/tree/master/keepalived-vip) to manage failover faster than DNS TTLs might expire.
|
||||
|
||||
Note that if you set the target to a hostname, then a CNAME record will be created. In this case, the hostname specified in the Ingress object's annotation must already exist. (i.e.: You have a Service resource for your Ingress Controller with the `external-dns.alpha.kubernetes.io/hostname` annotation set to the same value.)
|
||||
Note that if you set the target to a hostname, then a CNAME record will be created. In this case, the hostname specified in the Ingress object's annotation must already exist. (i.e. you have a Service resource for your Ingress Controller with the `external-dns.alpha.kubernetes.io/hostname` annotation set to the same value.)
|
||||
|
||||
### What about those other projects?
|
||||
### What about other projects similar to ExternalDNS?
|
||||
|
||||
ExternalDNS is a joint effort to unify different projects accomplishing the same goals, namely:
|
||||
|
||||
@ -87,11 +100,11 @@ For now ExternalDNS uses TXT records to label owned records, and there might be
|
||||
|
||||
### Does anyone use ExternalDNS in production?
|
||||
|
||||
Yes — Zalando replaced [Mate](https://github.com/linki/mate) with ExternalDNS since its v0.3 release, which now runs in production-level clusters. We are planning to document a step-by-step tutorial on how the switch from Mate to ExternalDNS has occurred.
|
||||
Yes, multiple companies are using ExternalDNS in production. Zalando, as an example, has been using it in production since its v0.3 release, mostly using the AWS provider.
|
||||
|
||||
### How can we start using ExternalDNS?
|
||||
|
||||
Check out the following descriptive tutorials on how to run ExternalDNS in [GKE](tutorials/gke.md) and [AWS](tutorials/aws.md).
|
||||
Check out the following descriptive tutorials on how to run ExternalDNS in [GKE](tutorials/gke.md) and [AWS](tutorials/aws.md) or any other supported provider.
|
||||
|
||||
### Why is ExternalDNS only adding a single IP address in Route 53 on AWS when using the `nginx-ingress-controller`? How do I get it to use the FQDN of the ELB assigned to my `nginx-ingress-controller` Service instead?
|
||||
|
||||
@ -131,11 +144,11 @@ spec:
|
||||
|
||||
### I have a Service/Ingress but it's ignored by ExternalDNS. Why?
|
||||
|
||||
TODO (https://github.com/kubernetes-incubator/external-dns/issues/267)
|
||||
ExternalDNS can be configured to only use Services or Ingresses as source. In case Services or Ingresses seem to be ignored in your setup, consider checking how the flag `--source` was configured when deployed. For reference, see the issue https://github.com/kubernetes-incubator/external-dns/issues/267.
|
||||
|
||||
### I'm using an ELB with TXT registry but the CNAME record clashes with the TXT record. How to avoid this?
|
||||
|
||||
TODO (https://github.com/kubernetes-incubator/external-dns/issues/262)
|
||||
CNAMEs cannot co-exist with other records, therefore you can use the `--txt-prefix` flag which makes sure to create a TXT record with a name following the pattern `prefix.<CNAME record>`. For reference, see the issue https://github.com/kubernetes-incubator/external-dns/issues/262.
|
||||
|
||||
### Which permissions do I need when running ExternalDNS on a GCE or GKE node.
|
||||
|
||||
@ -156,7 +169,7 @@ $ docker run \
|
||||
-e EXTERNAL_DNS_SOURCE=$'service\ningress' \
|
||||
-e EXTERNAL_DNS_PROVIDER=google \
|
||||
-e EXTERNAL_DNS_DOMAIN_FILTER=$'foo.com\nbar.com' \
|
||||
registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
time="2017-08-08T14:10:26Z" level=info msg="config: &{Master: KubeConfig: Sources:[service ingress] Namespace: ...
|
||||
```
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
[Initial discussion](https://docs.google.com/document/d/1ML_q3OppUtQKXan6Q42xIq2jelSoIivuXI8zExbc6ec/edit#heading=h.1pgkuagjhm4p)
|
||||
|
||||
This document describes the initial design proposal
|
||||
This document describes the initial design proposal.
|
||||
|
||||
External DNS is purposed to fill the existing gap of creating DNS records for Kubernetes resources. While there exist alternative solutions, this project is meant to be a standard way of managing DNS records for Kubernetes. The current project is a fusion of the following projects and driven by its maintainers:
|
||||
|
||||
@ -41,8 +41,6 @@ DNS records will be automatically created in multiple situations:
|
||||
|
||||
### Annotations
|
||||
|
||||
TODO:*This should probably be placed in a separate file*.
|
||||
|
||||
Record configuration should occur via resource annotations. Supported annotations:
|
||||
|
||||
| Annotations | |
|
||||
@ -63,8 +61,6 @@ Record configuration should occur via resource annotations. Supported annotation
|
||||
|
||||
External DNS should be compatible with annotations used by three above mentioned projects. The idea is that resources created and tagged with annotations for other projects should continue to be valid and now managed by External DNS.
|
||||
|
||||
TODO:*Add complete list here*
|
||||
|
||||
**Mate**
|
||||
|
||||
Mate does not require services/ingress to be tagged. Therefore, it is not safe to run both Mate and External-DNS simultaneously. The idea is that initial release (?) of External DNS will support Mate annotations, which indicates the hostname to be created. Therefore the switch should be simple.
|
||||
|
@ -31,6 +31,8 @@ This tutorial describes how to setup ExternalDNS for usage within a Kubernetes c
|
||||
}
|
||||
```
|
||||
|
||||
When running on AWS, you need to make sure that your nodes (on which External DNS runs) have the IAM instance profile with the above IAM role assigned (either directly or via something like [kube2iam](https://github.com/jtblin/kube2iam)).
|
||||
|
||||
## Set up a hosted zone
|
||||
|
||||
*If you prefer to try-out ExternalDNS in one of the existing hosted-zones you can skip this step*
|
||||
@ -63,8 +65,9 @@ In this case it's the ones shown above but your's will differ.
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
|
||||
Then apply the following manifest file to deploy ExternalDNS.
|
||||
Then apply one of the following manifests file to deploy ExternalDNS.
|
||||
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -80,7 +83,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
@ -92,6 +95,71 @@ spec:
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
||||
|
||||
### 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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
|
||||
- --provider=aws
|
||||
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
|
||||
- --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
|
||||
- --registry=txt
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Arguments
|
||||
|
||||
This list is not the full list, but a few arguments that where chosen.
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
# Setting up ExternalDNS for Services on Azure
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Azure.
|
||||
@ -48,58 +49,85 @@ To create the secret:
|
||||
|
||||
```
|
||||
$ kubectl create secret generic azure-config-file --from-file=/etc/kubernetes/azure.json
|
||||
```
|
||||
|
||||
### Azure Kubernetes Services (aka AKS)
|
||||
When your cluster is created, unlike ACS there are no Azure credentials stored and you must create an azure.json object manually like with other hosting providers. In order to create the azure.json you must first create an Azure AD service principal in the Azure AD tenant linked to your Azure subscription that is hosting your DNS zone.
|
||||
|
||||
#### Create service principal
|
||||
A Service Principal with a minimum access level of contribute to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. This is an Azure CLI example on how to query the Azure API for the information required for the Resource Group and DNS zone you would have already created in previous steps.
|
||||
|
||||
```
|
||||
>az login
|
||||
...
|
||||
# find the relevant subscription and set the az context. id = subscriptionId value in the azure.json.
|
||||
>az account list
|
||||
{
|
||||
"cloudName": "AzureCloud",
|
||||
"id": "<subscriptionId GUID>",
|
||||
"isDefault": false,
|
||||
"name": "My Subscription",
|
||||
"state": "Enabled",
|
||||
"tenantId": "AzureAD tenant ID",
|
||||
"user": {
|
||||
"name": "name",
|
||||
"type": "user"
|
||||
}
|
||||
>az account set -s id
|
||||
...
|
||||
>az group show --name externaldns
|
||||
{
|
||||
"id": "/subscriptions/id/resourceGroups/externaldns",
|
||||
...
|
||||
}
|
||||
|
||||
# use the id from the previous step in the scopes argument
|
||||
>az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/id/resourceGroups/externaldns" -n ExternalDnsServicePrincipal
|
||||
{
|
||||
"appId": "appId GUID", <-- aadClientId value
|
||||
...
|
||||
"password": "password", <-- aadClientSecret value
|
||||
"tenant": "AzureAD Tenant Id" <-- tenantId value
|
||||
}
|
||||
...
|
||||
|
||||
```
|
||||
### Other hosting providers
|
||||
If the Kubernetes cluster is not hosted by Azure Container Services and you still want to use Azure DNS, you need to create the secret manually. The secret should contain an object named azure.json with content similar to this:
|
||||
```
|
||||
{
|
||||
"tenantId": "837b898d-7dd5-4967-b718-7dfd25878104",
|
||||
"subscriptionId": "670d2139-c4ef-4a98-8f38-b7052d5a06b2",
|
||||
"aadClientId": "a0b083bd-c0fc-473d-be48-e2a4df3ec908",
|
||||
"aadClientSecret": "11c78103-8109-40af-a6d4-3db265fed095",
|
||||
"tenantId": "AzureAD tenant Id",
|
||||
"subscriptionId": "Id",
|
||||
"aadClientId": "Service Principal AppId",
|
||||
"aadClientSecret": "Service Principal Password",
|
||||
"resourceGroup": "MyDnsResourceGroup",
|
||||
}
|
||||
```
|
||||
If you have all the information necessary: create a file called azure.json containing the json structure above and substitute the values. Otherwise create a service principal as shown below before creating the Kubernetes secret.
|
||||
If you have all the information necessary: create a file called azure.json containing the json structure above and substitute the values. Otherwise create a service principal as previously shown before creating the Kubernetes secret.
|
||||
|
||||
Then add the secret to the Kubernetes cluster before continuing:
|
||||
```
|
||||
kubectl create secret generic azure-config-file --from-file=azure.json
|
||||
```
|
||||
|
||||
#### (Optional) Create service principal
|
||||
A Service Principal with a minimum access level of contribute to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. This is an Azure CLI example of how you can create a resource group, service principal and dns resource pointing out key information you need to put in the azure.json file.
|
||||
|
||||
```
|
||||
>az login
|
||||
...
|
||||
# find the relevant subscription and set the az context. This is the "subscriptionId" value.
|
||||
>az account set --subscription "670d2139-c4ef-4a98-8f38-b7052d5a06b2"
|
||||
...
|
||||
>az group create --name MyDnsResourceGroup --location "West Europe"
|
||||
{
|
||||
"id": "/subscriptions/670d2139-c4ef-4a98-8f38-b7052d5a06b2/resourceGroups/MyDnsResourceGroup",
|
||||
...
|
||||
}
|
||||
|
||||
# use the id from the previous step in the scopes argument
|
||||
>az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/670d2139-c4ef-4a98-8f38-b7052d5a06b2/resourceGroups/MyDnsResourceGroup" -n ExternalDnsServicePrincipal
|
||||
{
|
||||
"appId": "a0b083bd-c0fc-473d-be48-e2a4df3ec908", <-- aadClientId value
|
||||
...
|
||||
"password": "11c78103-8109-40af-a6d4-3db265fed095", <-- aadClientSecret value
|
||||
"tenant": "837b898d-7dd5-4967-b718-7dfd25878104" <-- tenantId value
|
||||
}
|
||||
|
||||
>az network dns zone create -g MyDnsResourceGroup -n example.com
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Create a deployment file called `externaldns.yaml` with the following contents:
|
||||
This deployment assumes that you will be using nginx-ingress. When using nginx-ingress do not deploy it as a Daemon Set. This causes nginx-ingress to write the Cluster IP of the backend pods in the ingress status.loadbalancer.ip property which then has external-dns write the Cluster IP(s) in DNS vs. the nginx-ingress service external IP.
|
||||
|
||||
Ensure that your nginx-ingress deployment has the following arg: added to it:
|
||||
|
||||
```
|
||||
- --publish-service=namespace/nginx-ingress-controller-svcname
|
||||
```
|
||||
|
||||
For more details see here: [nginx-ingress external-dns](https://github.com/kubernetes-incubator/external-dns/blob/master/docs/faq.md#why-is-externaldns-only-adding-a-single-ip-address-in-route-53-on-aws-when-using-the-nginx-ingress-controller-how-do-i-get-it-to-use-the-fqdn-of-the-elb-assigned-to-my-nginx-ingress-controller-service-instead)
|
||||
|
||||
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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -115,9 +143,77 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
|
||||
- --provider=azure
|
||||
- --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
|
||||
volumeMounts:
|
||||
- name: azure-config-file
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: azure-config-file
|
||||
secret:
|
||||
secretName: azure-config-file
|
||||
```
|
||||
|
||||
### 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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
|
||||
- --provider=azure
|
||||
- --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
|
||||
@ -161,36 +257,43 @@ spec:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: example.com
|
||||
name: nginx-svc
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: tcp
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: nginx
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
spec:
|
||||
rules:
|
||||
- host: server.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: nginx-svc
|
||||
servicePort: 80
|
||||
path: /
|
||||
```
|
||||
|
||||
Note the annotation on the service; use the same hostname as the Azure DNS zone created above. The annotation may also be a subdomain
|
||||
of the DNS zone (e.g. 'www.example.com').
|
||||
When using external-dns with ingress objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the external-dns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
|
||||
|
||||
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:
|
||||
Create the deployment, service and ingress object:
|
||||
|
||||
```
|
||||
$ kubectl create -f nginx.yaml
|
||||
```
|
||||
|
||||
It takes a little while for the Azure cloud provider to create an external IP for the service. Check the status by running
|
||||
`kubectl get services nginx`. If the `EXTERNAL-IP` field shows an address, the service is ready to be accessed externally.
|
||||
|
||||
Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize
|
||||
the Azure DNS records.
|
||||
Since your external IP would have already been assigned to the nginx-ingress service, the DNS records pointing to the IP of the nginx-ingress service should be created within a minute.
|
||||
|
||||
## Verifying Azure DNS records
|
||||
|
||||
|
@ -22,7 +22,10 @@ The environment vars `CF_API_KEY` and `CF_API_EMAIL` will be needed to run Exter
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Create a deployment file called `externaldns.yaml` with the following contents:
|
||||
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: extensions/v1beta1
|
||||
@ -39,7 +42,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
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.
|
||||
@ -52,10 +55,68 @@ spec:
|
||||
value: "YOUR_CLOUDFLARE_EMAIL"
|
||||
```
|
||||
|
||||
Create the deployment for ExternalDNS:
|
||||
### Manifest (for clusters with RBAC enabled)
|
||||
|
||||
```
|
||||
$ kubectl create -f externaldns.yaml
|
||||
```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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
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=cloudflare
|
||||
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
|
||||
env:
|
||||
- name: CF_API_KEY
|
||||
value: "YOUR_CLOUDFLARE_API_KEY"
|
||||
- name: CF_API_EMAIL
|
||||
value: "YOUR_CLOUDFLARE_EMAIL"
|
||||
```
|
||||
|
||||
## Deploying an Nginx Service
|
||||
|
155
docs/tutorials/designate.md
Normal file
155
docs/tutorials/designate.md
Normal file
@ -0,0 +1,155 @@
|
||||
# Setting up ExternalDNS for Services on OpenStack Designate
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using OpenStack Designate DNS.
|
||||
|
||||
## Authenticating with OpenStack
|
||||
|
||||
We are going to use OpenStack CLI - `openstack` utility, which is an umbrella application for most of OpenStack clients including `designate`.
|
||||
|
||||
All OpenStack CLIs require authentication parameters to be provided. These parameters include:
|
||||
* URL of the OpenStack identity service (`keystone`) which is responsible for user authentication and also served as a registry for other
|
||||
OpenStack services. Designate endpoints must be registered in `keystone` in order to ExternalDNS and OpenStack CLI be able to find them.
|
||||
* OpenStack region name
|
||||
* User login name.
|
||||
* User project (tenant) name.
|
||||
* User domain (only when using keystone API v3)
|
||||
|
||||
Although these parameters can be passed explicitly through the CLI flags, traditionally it is done by sourcing `openrc` file (`source ~/openrc`) that is a
|
||||
shell snippet that sets environment variables that all OpenStack CLI understand by convention.
|
||||
|
||||
Recent versions of OpenStack Dashboard have a nice UI to download `openrc` file for both v2 and v3 auth protocols. Both protocols can be used with ExternalDNS.
|
||||
v3 is generally preferred over v2, but might not be available in some OpenStack installations.
|
||||
|
||||
## Installing OpenStack Designate
|
||||
|
||||
Please refer to the Designate deployment [tutorial](https://docs.openstack.org/project-install-guide/dns/ocata/install.html) for instructions on how
|
||||
to install and test Designate with BIND backend. You will be required to have admin rights in existing OpenStack installation to do this. One convenient
|
||||
way to get yourself an OpenStack installation to play with is to use [DevStack](https://docs.openstack.org/devstack/latest/).
|
||||
|
||||
## Creating DNS zones
|
||||
|
||||
All domain names that are ExternalDNS is going to create must belong to one of DNS zones created in advance. Here is an example of how to create `example.com` DNS zone:
|
||||
```console
|
||||
$ openstack zone create --email dnsmaster@example.com example.com.
|
||||
```
|
||||
|
||||
It is important to manually create all the zones that are going to be used for kubernetes entities (ExternalDNS sources) before starting ExternalDNS.
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Create a deployment file called `externaldns.yaml` with the following contents:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns
|
||||
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=designate
|
||||
env: # values from openrc file
|
||||
- name: OS_AUTH_URL
|
||||
value: http://controller/identity/v3
|
||||
- name: OS_REGION_NAME
|
||||
value: RegionOne
|
||||
- name: OS_USERNAME
|
||||
value: admin
|
||||
- name: OS_PASSWORD
|
||||
value: p@ssw0rd
|
||||
- name: OS_PROJECT_NAME
|
||||
value: demo
|
||||
- name: OS_USER_DOMAIN_NAME
|
||||
value: Default
|
||||
```
|
||||
|
||||
Create the deployment for ExternalDNS:
|
||||
|
||||
```console
|
||||
$ kubectl create -f externaldns.yaml
|
||||
```
|
||||
|
||||
## Deploying an Nginx Service
|
||||
|
||||
Create a service file called 'nginx.yaml' with the following contents:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
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 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
|
||||
```
|
||||
|
||||
|
||||
Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and notify Designate,
|
||||
which in turn synchronize DNS records with underlying DNS server backend.
|
||||
|
||||
## Verifying DNS records
|
||||
|
||||
To verify that DNS record was indeed created, you can use the following command:
|
||||
|
||||
```console
|
||||
$ openstack recordset list example.com.
|
||||
```
|
||||
|
||||
There should be a record for my-app.example.com having `ACTIVE` status. And of course, the ultimate method to verify is to issue a DNS query:
|
||||
|
||||
```console
|
||||
$ dig my-app.example.com @controller
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
Now that we have verified that ExternalDNS created all DNS records, we can delete the tutorial's example:
|
||||
|
||||
```console
|
||||
$ kubectl delete service -f nginx.yaml
|
||||
$ kubectl delete service -f externaldns.yaml
|
||||
```
|
@ -20,8 +20,10 @@ The environment variable `DO_TOKEN` will be needed to run ExternalDNS with Digit
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Create a deployment file called `externaldns.yaml` with the following contents:
|
||||
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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -37,7 +39,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
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.
|
||||
@ -47,12 +49,67 @@ spec:
|
||||
value: "YOUR_DIGITALOCEAN_API_KEY"
|
||||
```
|
||||
|
||||
Create the deployment for ExternalDNS:
|
||||
|
||||
```console
|
||||
$ kubectl create -f externaldns.yaml
|
||||
### 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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
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=digitalocean
|
||||
env:
|
||||
- name: DO_TOKEN
|
||||
value: "YOUR_DIGITALOCEAN_API_KEY"
|
||||
```
|
||||
|
||||
|
||||
## Deploying an Nginx Service
|
||||
|
||||
Create a service file called 'nginx.yaml' with the following contents:
|
||||
|
@ -41,7 +41,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=ingress
|
||||
- --txt-prefix=_d
|
||||
|
@ -54,14 +54,25 @@ $ gcloud dns record-sets transaction execute --zone "gcp-zalan-do"
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
[RBAC]("https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control") is enabled by default on all Container clusters which are running Kubernetes version 1.6 or higher.
|
||||
|
||||
Because of the way Container Engine checks permissions when you create a Role or ClusterRole, you must first create a RoleBinding that grants you all of the permissions included in the role you want to create.
|
||||
|
||||
```console
|
||||
kubectl create clusterrolebinding your-user-cluster-admin-binding --clusterrole=cluster-admin --user=your.google.cloud.email@example.org
|
||||
```
|
||||
|
||||
Connect your `kubectl` client to the cluster you just created.
|
||||
|
||||
```console
|
||||
gcloud container clusters get-credentials "external-dns"
|
||||
```
|
||||
|
||||
Apply the following manifest file to deploy ExternalDNS.
|
||||
Then apply one of the following manifests file to deploy ExternalDNS.
|
||||
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -77,13 +88,75 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --domain-filter=external-dns-test.gcp.zalan.do # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
|
||||
- --provider=google
|
||||
- --google-project=zalando-external-dns-test
|
||||
# - --google-project=zalando-external-dns-test # Use this to specify a project different from the one external-dns is running inside
|
||||
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
|
||||
- --registry=txt
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
||||
|
||||
### 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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --domain-filter=external-dns-test.gcp.zalan.do # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
|
||||
- --provider=google
|
||||
# - --google-project=zalando-external-dns-test # Use this to specify a project different from the one external-dns is running inside
|
||||
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
|
||||
- --registry=txt
|
||||
- --txt-owner-id=my-identifier
|
||||
|
@ -12,6 +12,7 @@ We will go through a small example of deploying a simple Kafka with use of a hea
|
||||
### Exernal DNS
|
||||
|
||||
A simple deploy could look like this:
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
@ -24,7 +25,66 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --debug
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --namespace=dev
|
||||
- --domain-filter=example.org.
|
||||
- --provider=aws
|
||||
- --registry=txt
|
||||
- --txt-owner-id=dev.example.org
|
||||
```
|
||||
|
||||
### 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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: exeternal-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --debug
|
||||
- --source=service
|
||||
@ -42,7 +102,6 @@ spec:
|
||||
First lets deploy a Kafka Stateful set, a simple example(a lot of stuff is missing) with a headless service called `kafka-hsvc`
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
@ -96,7 +155,6 @@ Now we need to define a headless service to use to expose the Kafka pods. There
|
||||
If you go with #1, you just need to define the headless service, here is an example of the case #2:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
|
@ -47,10 +47,11 @@ $ kubectl create secret generic external-dns \
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Create a deployment file called `externaldns.yaml` with the following contents:
|
||||
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
|
||||
Then apply one of the following manifests file to deploy ExternalDNS.
|
||||
|
||||
```
|
||||
$ cat > externaldns.yaml <<EOF
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@ -65,7 +66,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --domain-filter=example.com # (optional) limit to only example.com domains.
|
||||
@ -89,14 +90,85 @@ spec:
|
||||
secretKeyRef:
|
||||
name: external-dns
|
||||
key: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD
|
||||
EOF
|
||||
```
|
||||
|
||||
Create the deployment for ExternalDNS:
|
||||
### 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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service
|
||||
- --domain-filter=example.com # (optional) limit to only example.com domains.
|
||||
- --provider=infoblox
|
||||
- --infoblox-grid-host=${GRID_HOST} # (required) IP of the Infoblox Grid host.
|
||||
- --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443".
|
||||
- --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1"
|
||||
- --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification.
|
||||
env:
|
||||
- name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS
|
||||
value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10".
|
||||
- name: EXTERNAL_DNS_INFOBLOX_HTTP_REQUEST_TIMEOUT
|
||||
value: "60" # (optional) Infoblox WAPI request timeout in seconds. The default is "60".
|
||||
- name: EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: external-dns
|
||||
key: EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME
|
||||
- name: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: external-dns
|
||||
key: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD
|
||||
```
|
||||
$ kubectl create -f externaldns.yaml
|
||||
```
|
||||
|
||||
|
||||
## Deploying an Nginx Service
|
||||
|
||||
|
@ -203,6 +203,39 @@ spec:
|
||||
Apply the following manifest file to deploy ExternalDNS.
|
||||
|
||||
```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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","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: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@ -215,9 +248,10 @@ spec:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=ingress
|
||||
- --domain-filter=external-dns-test.gcp.zalan.do
|
||||
|
154
docs/tutorials/pdns.md
Normal file
154
docs/tutorials/pdns.md
Normal file
@ -0,0 +1,154 @@
|
||||
# Setting up ExternalDNS for PowerDNS
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The provider has been written for and tested against [PowerDNS](https://github.com/PowerDNS/pdns) v4.1.x and thus requires **PowerDNS Auth Server >= 4.1.x**
|
||||
|
||||
PowerDNS provider support was added via [this PR](https://github.com/kubernetes-incubator/external-dns/pull/373), thus you need to use external-dns version >= v0.5
|
||||
|
||||
The PDNS provider expects that your PowerDNS instance is already setup and
|
||||
functional. It expects that zones, you wish to add records to, already exist
|
||||
and are configured correctly. It does not add, remove or configure new zones in
|
||||
anyway.
|
||||
|
||||
## Feature Support
|
||||
|
||||
The PDNS provider currently does not support:
|
||||
|
||||
1. Dry running a configuration is not supported.
|
||||
2. The `--domain-filter` flag is not supported.
|
||||
|
||||
## Deployment
|
||||
|
||||
Deploying external DNS for PowerDNS is actually nearly identical to deploying
|
||||
it for other providers. This is what a sample `deployment.yaml` looks like:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy-external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
# Only use if you're also using RBAC
|
||||
# serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
args:
|
||||
- --source=service # or ingress or both
|
||||
- --provider=pdns
|
||||
- --pdns-server={{ pdns-api-url }}
|
||||
- --pdns-api-key={{ pdns-http-api-key }}
|
||||
- --txt-owner-id={{ owner-id-for-this-external-dns }}
|
||||
- --log-level=debug
|
||||
- --interval=30s
|
||||
```
|
||||
|
||||
## RBAC
|
||||
|
||||
If your cluster is RBAC enabled, you also need to setup the following, before you can run external-dns:
|
||||
```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"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","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
|
||||
```
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
**Important!**: Remember to change `example.com` with your own domain throughout the following text.
|
||||
|
||||
Spin up a simple "Hello World" HTTP server with the following spec (`kubectl apply -f`):
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: echo
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: echo
|
||||
spec:
|
||||
containers:
|
||||
- image: hashicorp/http-echo
|
||||
name: echo
|
||||
ports:
|
||||
- containerPort: 5678
|
||||
args:
|
||||
- -text="Hello World"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: echo
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: echo.example.com
|
||||
spec:
|
||||
selector:
|
||||
app: echo
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 5678
|
||||
```
|
||||
**Important!**: Don't run dig, nslookup or similar immediately (until you've
|
||||
confirmed the record exists). You'll get hit by [negative DNS caching](https://tools.ietf.org/html/rfc2308), which is hard to flush.
|
||||
|
||||
Run the following to make sure everything is in order:
|
||||
|
||||
```bash
|
||||
$ kubectl get services echo
|
||||
$ kubectl get endpoints echo
|
||||
```
|
||||
|
||||
Make sure everything looks correct, i.e the service is defined and recieves a
|
||||
public IP, and that the endpoint also has a pod IP.
|
||||
|
||||
Once that's done, wait about 30s-1m (interval for external-dns to kick in), then do:
|
||||
```bash
|
||||
$ curl -H "X-API-Key: ${PDNS_API_KEY}" ${PDNS_API_URL}/api/v1/servers/localhost/zones/example.com. | jq '.rrsets[] | select(.name | contains("echo"))'
|
||||
```
|
||||
|
||||
Once the API shows the record correctly, you can double check your record using:
|
||||
```bash
|
||||
$ dig @${PDNS_FQDN} echo.example.com.
|
||||
```
|
||||
|
386
docs/tutorials/public-private-route53.md
Normal file
386
docs/tutorials/public-private-route53.md
Normal file
@ -0,0 +1,386 @@
|
||||
# Setting up ExternalDNS using the same domain for public and private Route53 zones
|
||||
|
||||
This tutorial describes how to setup ExternalDNS using the same domain for public and private Route53 zones and [nginx-ingress-controller](https://github.com/kubernetes/ingress-nginx). It also outlines how to use [cert-manager](https://github.com/jetstack/cert-manager) to automatically issue SSL certificates from [Let's Encrypt](https://letsencrypt.org/) for both public and private records.
|
||||
|
||||
## Deploy public nginx-ingress-controller
|
||||
|
||||
Consult [External DNS nginx ingress docs](nginx-ingress.md) for installation guidelines.
|
||||
|
||||
Specify `ingress-class` in nginx-ingress-controller container args:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: external-ingress
|
||||
name: external-ingress-controller
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-ingress
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-ingress
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
|
||||
- --configmap=$(POD_NAMESPACE)/external-ingress-configuration
|
||||
- --tcp-services-configmap=$(POD_NAMESPACE)/external-tcp-services
|
||||
- --udp-services-configmap=$(POD_NAMESPACE)/external-udp-services
|
||||
- --annotations-prefix=nginx.ingress.kubernetes.io
|
||||
- --ingress-class=external-ingress
|
||||
- --publish-service=$(POD_NAMESPACE)/external-ingress
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.11.0
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10254
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
name: external-ingress-controller
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
name: https
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10254
|
||||
scheme: HTTP
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
```
|
||||
|
||||
Set `type: LoadBalancer` in your public nginx-ingress-controller Service definition.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "3600"
|
||||
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
|
||||
labels:
|
||||
app: external-ingress
|
||||
name: external-ingress
|
||||
spec:
|
||||
externalTrafficPolicy: Cluster
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: http
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
selector:
|
||||
app: external-ingress
|
||||
sessionAffinity: None
|
||||
type: LoadBalancer
|
||||
```
|
||||
|
||||
## Deploy private nginx-ingress-controller
|
||||
|
||||
Consult [External DNS nginx ingress docs](nginx-ingress.md) for installation guidelines.
|
||||
|
||||
Make sure to specify `ingress-class` in nginx-ingress-controller container args:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: internal-ingress
|
||||
name: internal-ingress-controller
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: internal-ingress
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: internal-ingress
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- /nginx-ingress-controller
|
||||
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
|
||||
- --configmap=$(POD_NAMESPACE)/internal-ingress-configuration
|
||||
- --tcp-services-configmap=$(POD_NAMESPACE)/internal-tcp-services
|
||||
- --udp-services-configmap=$(POD_NAMESPACE)/internal-udp-services
|
||||
- --annotations-prefix=nginx.ingress.kubernetes.io
|
||||
- --ingress-class=internal-ingress
|
||||
- --publish-service=$(POD_NAMESPACE)/internal-ingress
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.11.0
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10254
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
name: internal-ingress-controller
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
name: https
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 10254
|
||||
scheme: HTTP
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
```
|
||||
|
||||
Set additional annotations in your private nginx-ingress-controller Service definition to create an internal load balancer.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "3600"
|
||||
service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
|
||||
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
|
||||
labels:
|
||||
app: internal-ingress
|
||||
name: internal-ingress
|
||||
spec:
|
||||
externalTrafficPolicy: Cluster
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: http
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
selector:
|
||||
app: internal-ingress
|
||||
sessionAffinity: None
|
||||
type: LoadBalancer
|
||||
```
|
||||
|
||||
## Deploy the public zone ExternalDNS
|
||||
|
||||
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines.
|
||||
|
||||
In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns-public
|
||||
name: external-dns-public
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns-public
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns-public
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --source=ingress
|
||||
- --provider=aws
|
||||
- --registry=txt
|
||||
- --txt-owner-id=external-dns
|
||||
- --annotation-filter=kubernetes.io/ingress.class=external-ingress
|
||||
- --aws-zone-type=public
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
name: external-dns-public
|
||||
```
|
||||
|
||||
## Deploy the private zone ExternalDNS
|
||||
|
||||
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines.
|
||||
|
||||
In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns-private
|
||||
name: external-dns-private
|
||||
namespace: kube-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns-private
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns-private
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --source=ingress
|
||||
- --provider=aws
|
||||
- --registry=txt
|
||||
- --txt-owner-id=dev.k8s.nexus
|
||||
- --annotation-filter=kubernetes.io/ingress.class=internal-ingress
|
||||
- --aws-zone-type=private
|
||||
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.1
|
||||
name: external-dns-private
|
||||
```
|
||||
|
||||
## Create application Service definitions
|
||||
|
||||
For this setup to work, you've to create two Service definitions for your application.
|
||||
|
||||
At first, create public Service definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "external-ingress"
|
||||
labels:
|
||||
app: app
|
||||
name: app-public
|
||||
spec:
|
||||
rules:
|
||||
- host: app.domain.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: app
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
Then create private Service definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "internal-ingress"
|
||||
labels:
|
||||
app: app
|
||||
name: app-private
|
||||
spec:
|
||||
rules:
|
||||
- host: app.domain.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: app
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
Additionally, you may leverage [cert-manager](https://github.com/jetstack/cert-manager) to automatically issue SSL certificates from [Let's Encrypt](https://letsencrypt.org/). To do that, request a certificate in public service definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
certmanager.k8s.io/acme-challenge-type: "dns01"
|
||||
certmanager.k8s.io/acme-dns01-provider: "route53"
|
||||
certmanager.k8s.io/cluster-issuer: "letsencrypt-production"
|
||||
kubernetes.io/ingress.class: "external-ingress"
|
||||
kubernetes.io/tls-acme: "true"
|
||||
labels:
|
||||
app: app
|
||||
name: app-public
|
||||
spec:
|
||||
rules:
|
||||
- host: app.domain.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: app
|
||||
servicePort: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- app.domain.com
|
||||
secretName: app-tls
|
||||
```
|
||||
|
||||
And reuse the requested certificate in private Service definition:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "internal-ingress"
|
||||
labels:
|
||||
app: app
|
||||
name: app-private
|
||||
spec:
|
||||
rules:
|
||||
- host: app.domain.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: app
|
||||
servicePort: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- app.domain.com
|
||||
secretName: app-tls
|
||||
```
|
@ -18,6 +18,7 @@ package endpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -38,12 +39,78 @@ func (ttl TTL) IsConfigured() bool {
|
||||
return ttl > 0
|
||||
}
|
||||
|
||||
// Targets is a representation of a list of targets for an endpoint.
|
||||
type Targets []string
|
||||
|
||||
// NewTargets is a convenience method to create a new Targets object from a vararg of strings
|
||||
func NewTargets(target ...string) Targets {
|
||||
t := make(Targets, 0, len(target))
|
||||
t = append(t, target...)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t Targets) String() string {
|
||||
return strings.Join(t, ";")
|
||||
}
|
||||
|
||||
func (t Targets) Len() int {
|
||||
return len(t)
|
||||
}
|
||||
|
||||
func (t Targets) Less(i, j int) bool {
|
||||
return t[i] < t[j]
|
||||
}
|
||||
|
||||
func (t Targets) Swap(i, j int) {
|
||||
t[i], t[j] = t[j], t[i]
|
||||
}
|
||||
|
||||
// Same compares to Targets and returns true if they are completely identical
|
||||
func (t Targets) Same(o Targets) bool {
|
||||
if len(t) != len(o) {
|
||||
return false
|
||||
}
|
||||
sort.Stable(t)
|
||||
sort.Stable(o)
|
||||
|
||||
for i, e := range t {
|
||||
if e != o[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsLess should fulfill the requirement to compare two targets and chosse the 'lesser' one.
|
||||
// In the past target was a simple string so simple string comparison could be used. Now we define 'less'
|
||||
// as either being the shorter list of targets or where the first entry is less.
|
||||
// FIXME We really need to define under which circumstances a list Targets is considered 'less'
|
||||
// than another.
|
||||
func (t Targets) IsLess(o Targets) bool {
|
||||
if len(t) < len(o) {
|
||||
return true
|
||||
}
|
||||
if len(t) > len(o) {
|
||||
return false
|
||||
}
|
||||
|
||||
sort.Sort(t)
|
||||
sort.Sort(o)
|
||||
|
||||
for i, e := range t {
|
||||
if e != o[i] {
|
||||
return e < o[i]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Endpoint is a high-level way of a connection between a service and an IP
|
||||
type Endpoint struct {
|
||||
// The hostname of the DNS record
|
||||
DNSName string
|
||||
// The target the DNS record points to
|
||||
Target string
|
||||
// The targets the DNS record points to
|
||||
Targets Targets
|
||||
// RecordType type of record, e.g. CNAME, A, TXT etc
|
||||
RecordType string
|
||||
// TTL for the record
|
||||
@ -53,15 +120,20 @@ type Endpoint struct {
|
||||
}
|
||||
|
||||
// NewEndpoint initialization method to be used to create an endpoint
|
||||
func NewEndpoint(dnsName, target, recordType string) *Endpoint {
|
||||
return NewEndpointWithTTL(dnsName, target, recordType, TTL(0))
|
||||
func NewEndpoint(dnsName, recordType string, targets ...string) *Endpoint {
|
||||
return NewEndpointWithTTL(dnsName, recordType, TTL(0), targets...)
|
||||
}
|
||||
|
||||
// NewEndpointWithTTL initialization method to be used to create an endpoint with a TTL struct
|
||||
func NewEndpointWithTTL(dnsName, target, recordType string, ttl TTL) *Endpoint {
|
||||
func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string) *Endpoint {
|
||||
cleanTargets := make([]string, len(targets))
|
||||
for idx, target := range targets {
|
||||
cleanTargets[idx] = strings.TrimSuffix(target, ".")
|
||||
}
|
||||
|
||||
return &Endpoint{
|
||||
DNSName: strings.TrimSuffix(dnsName, "."),
|
||||
Target: strings.TrimSuffix(target, "."),
|
||||
Targets: cleanTargets,
|
||||
RecordType: recordType,
|
||||
Labels: NewLabels(),
|
||||
RecordTTL: ttl,
|
||||
@ -69,5 +141,5 @@ func NewEndpointWithTTL(dnsName, target, recordType string, ttl TTL) *Endpoint {
|
||||
}
|
||||
|
||||
func (e *Endpoint) String() string {
|
||||
return fmt.Sprintf("%s %d IN %s %s", e.DNSName, e.RecordTTL, e.RecordType, e.Target)
|
||||
return fmt.Sprintf("%s %d IN %s %s", e.DNSName, e.RecordTTL, e.RecordType, e.Targets)
|
||||
}
|
||||
|
@ -21,16 +21,57 @@ import (
|
||||
)
|
||||
|
||||
func TestNewEndpoint(t *testing.T) {
|
||||
e := NewEndpoint("example.org", "foo.com", "CNAME")
|
||||
if e.DNSName != "example.org" || e.Target != "foo.com" || e.RecordType != "CNAME" {
|
||||
e := NewEndpoint("example.org", "CNAME", "foo.com")
|
||||
if e.DNSName != "example.org" || e.Targets[0] != "foo.com" || e.RecordType != "CNAME" {
|
||||
t.Error("endpoint is not initialized correctly")
|
||||
}
|
||||
if e.Labels == nil {
|
||||
t.Error("Labels is not initialized")
|
||||
}
|
||||
|
||||
w := NewEndpoint("example.org.", "load-balancer.com.", "")
|
||||
if w.DNSName != "example.org" || w.Target != "load-balancer.com" || w.RecordType != "" {
|
||||
w := NewEndpoint("example.org.", "", "load-balancer.com.")
|
||||
if w.DNSName != "example.org" || w.Targets[0] != "load-balancer.com" || w.RecordType != "" {
|
||||
t.Error("endpoint is not initialized correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTargetsSame(t *testing.T) {
|
||||
tests := []Targets{
|
||||
{""},
|
||||
{"1.2.3.4"},
|
||||
{"8.8.8.8", "8.8.4.4"},
|
||||
}
|
||||
|
||||
for _, d := range tests {
|
||||
if d.Same(d) != true {
|
||||
t.Errorf("%#v should equal %#v", d, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameFailures(t *testing.T) {
|
||||
tests := []struct {
|
||||
a Targets
|
||||
b Targets
|
||||
}{
|
||||
{
|
||||
[]string{"1.2.3.4"},
|
||||
[]string{"4.3.2.1"},
|
||||
}, {
|
||||
[]string{"1.2.3.4"},
|
||||
[]string{"1.2.3.4", "4.3.2.1"},
|
||||
}, {
|
||||
[]string{"1.2.3.4", "4.3.2.1"},
|
||||
[]string{"1.2.3.4"},
|
||||
}, {
|
||||
[]string{"1.2.3.4", "4.3.2.1"},
|
||||
[]string{"8.8.8.8", "8.8.4.4"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, d := range tests {
|
||||
if d.a.Same(d.b) == true {
|
||||
t.Errorf("%#v should not equal %#v", d.a, d.b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +33,12 @@ func (b byAllFields) Less(i, j int) bool {
|
||||
return true
|
||||
}
|
||||
if b[i].DNSName == b[j].DNSName {
|
||||
if b[i].Target < b[j].Target {
|
||||
return true
|
||||
}
|
||||
if b[i].Target == b[j].Target {
|
||||
// This rather bad, we need a more complex comparison for Targets, which considers all elements
|
||||
if b[i].Targets.Same(b[j].Targets) {
|
||||
return b[i].RecordType <= b[j].RecordType
|
||||
}
|
||||
return false
|
||||
return b[i].Targets.String() <= b[j].Targets.String()
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -47,7 +46,7 @@ func (b byAllFields) Less(i, j int) bool {
|
||||
// SameEndpoint returns true if two endpoints are same
|
||||
// considers example.org. and example.org DNSName/Target as different endpoints
|
||||
func SameEndpoint(a, b *endpoint.Endpoint) bool {
|
||||
return a.DNSName == b.DNSName && a.Target == b.Target && a.RecordType == b.RecordType &&
|
||||
return a.DNSName == b.DNSName && a.Targets.Same(b.Targets) && a.RecordType == b.RecordType &&
|
||||
a.Labels[endpoint.OwnerLabelKey] == b.Labels[endpoint.OwnerLabelKey] && a.RecordTTL == b.RecordTTL &&
|
||||
a.Labels[endpoint.ResourceLabelKey] == b.Labels[endpoint.ResourceLabelKey]
|
||||
}
|
||||
|
@ -27,31 +27,31 @@ func ExampleSameEndpoints() {
|
||||
eps := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "load-balancer.org",
|
||||
Targets: endpoint.Targets{"load-balancer.org"},
|
||||
},
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "load-balancer.org",
|
||||
Targets: endpoint.Targets{"load-balancer.org"},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
},
|
||||
{
|
||||
DNSName: "abc.com",
|
||||
Target: "something",
|
||||
Targets: endpoint.Targets{"something"},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
},
|
||||
{
|
||||
DNSName: "abc.com",
|
||||
Target: "1.2.3.4",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "bbc.com",
|
||||
Target: "foo.com",
|
||||
Targets: endpoint.Targets{"foo.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "cbc.com",
|
||||
Target: "foo.com",
|
||||
Targets: endpoint.Targets{"foo.com"},
|
||||
RecordType: "CNAME",
|
||||
RecordTTL: endpoint.TTL(60),
|
||||
},
|
||||
|
11
main.go
11
main.go
@ -41,7 +41,7 @@ func main() {
|
||||
if err := cfg.ParseFlags(os.Args[1:]); err != nil {
|
||||
log.Fatalf("flag parsing error: %v", err)
|
||||
}
|
||||
log.Infof("config: %+v", cfg)
|
||||
log.Infof("config: %s", cfg)
|
||||
|
||||
if err := validation.ValidateConfig(cfg); err != nil {
|
||||
log.Fatalf("config validation failed: %v", err)
|
||||
@ -70,8 +70,10 @@ func main() {
|
||||
Namespace: cfg.Namespace,
|
||||
AnnotationFilter: cfg.AnnotationFilter,
|
||||
FQDNTemplate: cfg.FQDNTemplate,
|
||||
CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation,
|
||||
Compatibility: cfg.Compatibility,
|
||||
PublishInternal: cfg.PublishInternal,
|
||||
ConnectorServer: cfg.ConnectorSourceServer,
|
||||
}
|
||||
|
||||
// Lookup all the selected sources by names and pass them the desired configuration.
|
||||
@ -93,7 +95,7 @@ func main() {
|
||||
var p provider.Provider
|
||||
switch cfg.Provider {
|
||||
case "aws":
|
||||
p, err = provider.NewAWSProvider(domainFilter, zoneIDFilter, zoneTypeFilter, cfg.DryRun)
|
||||
p, err = provider.NewAWSProvider(domainFilter, zoneIDFilter, zoneTypeFilter, cfg.AWSAssumeRole, cfg.DryRun)
|
||||
case "azure":
|
||||
p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.DryRun)
|
||||
case "cloudflare":
|
||||
@ -127,6 +129,7 @@ func main() {
|
||||
CustomerName: cfg.DynCustomerName,
|
||||
Username: cfg.DynUsername,
|
||||
Password: cfg.DynPassword,
|
||||
MinTTLSeconds: cfg.DynMinTTLSeconds,
|
||||
AppVersion: externaldns.Version,
|
||||
},
|
||||
)
|
||||
@ -134,6 +137,10 @@ func main() {
|
||||
p, err = provider.NewCoreDNSProvider(domainFilter, cfg.DryRun)
|
||||
case "inmemory":
|
||||
p, err = provider.NewInMemoryProvider(provider.InMemoryInitZones(cfg.InMemoryZones), provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
|
||||
case "designate":
|
||||
p, err = provider.NewDesignateProvider(domainFilter, cfg.DryRun)
|
||||
case "pdns":
|
||||
p, err = provider.NewPDNSProvider(cfg.PDNSServer, cfg.PDNSAPIKey, domainFilter, cfg.DryRun)
|
||||
default:
|
||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package externaldns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@ -24,6 +25,10 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
passwordMask = "******"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version is the current version of the app, generated at build time
|
||||
Version = "unknown"
|
||||
@ -37,13 +42,16 @@ type Config struct {
|
||||
Namespace string
|
||||
AnnotationFilter string
|
||||
FQDNTemplate string
|
||||
CombineFQDNAndAnnotation bool
|
||||
Compatibility string
|
||||
PublishInternal bool
|
||||
ConnectorSourceServer string
|
||||
Provider string
|
||||
GoogleProject string
|
||||
DomainFilter []string
|
||||
ZoneIDFilter []string
|
||||
AWSZoneType string
|
||||
AWSAssumeRole string
|
||||
AzureConfigFile string
|
||||
AzureResourceGroup string
|
||||
CloudflareProxied bool
|
||||
@ -56,7 +64,10 @@ type Config struct {
|
||||
DynCustomerName string
|
||||
DynUsername string
|
||||
DynPassword string
|
||||
DynMinTTLSeconds int
|
||||
InMemoryZones []string
|
||||
PDNSServer string
|
||||
PDNSAPIKey string
|
||||
Policy string
|
||||
Registry string
|
||||
TXTOwnerID string
|
||||
@ -76,12 +87,15 @@ var defaultConfig = &Config{
|
||||
Namespace: "",
|
||||
AnnotationFilter: "",
|
||||
FQDNTemplate: "",
|
||||
CombineFQDNAndAnnotation: false,
|
||||
Compatibility: "",
|
||||
PublishInternal: false,
|
||||
ConnectorSourceServer: "localhost:8080",
|
||||
Provider: "",
|
||||
GoogleProject: "",
|
||||
DomainFilter: []string{},
|
||||
AWSZoneType: "",
|
||||
AWSAssumeRole: "",
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
CloudflareProxied: false,
|
||||
@ -92,6 +106,8 @@ var defaultConfig = &Config{
|
||||
InfobloxWapiVersion: "2.3.1",
|
||||
InfobloxSSLVerify: true,
|
||||
InMemoryZones: []string{},
|
||||
PDNSServer: "http://localhost:8081",
|
||||
PDNSAPIKey: "",
|
||||
Policy: "sync",
|
||||
Registry: "txt",
|
||||
TXTOwnerID: "default",
|
||||
@ -109,6 +125,19 @@ func NewConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
func (cfg *Config) String() string {
|
||||
// prevent logging of sensitive information
|
||||
temp := *cfg
|
||||
if temp.DynPassword != "" {
|
||||
temp.DynPassword = passwordMask
|
||||
}
|
||||
if temp.InfobloxWapiPassword != "" {
|
||||
temp.InfobloxWapiPassword = passwordMask
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%+v", temp)
|
||||
}
|
||||
|
||||
// allLogLevelsAsStrings returns all logrus levels as a list of strings
|
||||
func allLogLevelsAsStrings() []string {
|
||||
var levels []string
|
||||
@ -129,19 +158,22 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("kubeconfig", "Retrieve target cluster configuration from a Kubernetes configuration file (default: auto-detect)").Default(defaultConfig.KubeConfig).StringVar(&cfg.KubeConfig)
|
||||
|
||||
// Flags related to processing sources
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, fake)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "fake")
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, fake, connector)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "fake", "connector")
|
||||
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
|
||||
app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
|
||||
app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional)").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
|
||||
app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
|
||||
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
|
||||
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule")
|
||||
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
|
||||
app.Flag("connector-source-server", "The server to connect for connector source, valid only when using connector source").Default(defaultConfig.ConnectorSourceServer).StringVar(&cfg.ConnectorSourceServer)
|
||||
|
||||
// Flags related to providers
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, dnsimple, infoblox, dyn, coredns, skydns, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "dyn", "coredns", "skydns", "inmemory")
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, dnsimple, infoblox, dyn, designate, coredns, skydns, inmemory, pdns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "pdns")
|
||||
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("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
|
||||
app.Flag("google-project", "When using the Google provider, specify the Google project (required when --provider=google)").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
|
||||
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
|
||||
app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private")
|
||||
app.Flag("aws-assume-role", "When using the AWS provider, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional)").Default(defaultConfig.AWSAssumeRole).StringVar(&cfg.AWSAssumeRole)
|
||||
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)
|
||||
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
|
||||
app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied)
|
||||
@ -154,7 +186,11 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("dyn-customer-name", "When using the Dyn provider, specify the Customer Name").Default("").StringVar(&cfg.DynCustomerName)
|
||||
app.Flag("dyn-username", "When using the Dyn provider, specify the Username").Default("").StringVar(&cfg.DynUsername)
|
||||
app.Flag("dyn-password", "When using the Dyn provider, specify the pasword").Default("").StringVar(&cfg.DynPassword)
|
||||
app.Flag("dyn-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.DynMinTTLSeconds)
|
||||
|
||||
app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones)
|
||||
app.Flag("pdns-server", "When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns)").Default(defaultConfig.PDNSServer).StringVar(&cfg.PDNSServer)
|
||||
app.Flag("pdns-api-key", "When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns)").Default(defaultConfig.PDNSAPIKey).StringVar(&cfg.PDNSAPIKey)
|
||||
|
||||
// Flags related to policies
|
||||
app.Flag("policy", "Modify how DNS records are sychronized between sources and providers (default: sync, options: sync, upsert-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only")
|
||||
|
@ -18,6 +18,7 @@ package externaldns
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -39,6 +40,7 @@ var (
|
||||
DomainFilter: []string{""},
|
||||
ZoneIDFilter: []string{""},
|
||||
AWSZoneType: "",
|
||||
AWSAssumeRole: "",
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
CloudflareProxied: false,
|
||||
@ -49,6 +51,8 @@ var (
|
||||
InfobloxWapiVersion: "2.3.1",
|
||||
InfobloxSSLVerify: true,
|
||||
InMemoryZones: []string{""},
|
||||
PDNSServer: "http://localhost:8081",
|
||||
PDNSAPIKey: "",
|
||||
Policy: "sync",
|
||||
Registry: "txt",
|
||||
TXTOwnerID: "default",
|
||||
@ -59,12 +63,13 @@ var (
|
||||
LogFormat: "text",
|
||||
MetricsAddress: ":7979",
|
||||
LogLevel: logrus.InfoLevel.String(),
|
||||
ConnectorSourceServer: "localhost:8080",
|
||||
}
|
||||
|
||||
overriddenConfig = &Config{
|
||||
Master: "http://127.0.0.1:8080",
|
||||
KubeConfig: "/some/path",
|
||||
Sources: []string{"service", "ingress"},
|
||||
Sources: []string{"service", "ingress", "connector"},
|
||||
Namespace: "namespace",
|
||||
FQDNTemplate: "{{.Name}}.service.example.com",
|
||||
Compatibility: "mate",
|
||||
@ -73,6 +78,7 @@ var (
|
||||
DomainFilter: []string{"example.org", "company.com"},
|
||||
ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
|
||||
AWSZoneType: "private",
|
||||
AWSAssumeRole: "some-other-role",
|
||||
AzureConfigFile: "azure.json",
|
||||
AzureResourceGroup: "arg",
|
||||
CloudflareProxied: true,
|
||||
@ -83,6 +89,8 @@ var (
|
||||
InfobloxWapiVersion: "2.6.1",
|
||||
InfobloxSSLVerify: false,
|
||||
InMemoryZones: []string{"example.org", "company.com"},
|
||||
PDNSServer: "http://ns.example.com:8081",
|
||||
PDNSAPIKey: "some-secret-key",
|
||||
Policy: "upsert-only",
|
||||
Registry: "noop",
|
||||
TXTOwnerID: "owner-1",
|
||||
@ -93,6 +101,7 @@ var (
|
||||
LogFormat: "json",
|
||||
MetricsAddress: "127.0.0.1:9099",
|
||||
LogLevel: logrus.DebugLevel.String(),
|
||||
ConnectorSourceServer: "localhost:8081",
|
||||
}
|
||||
)
|
||||
|
||||
@ -119,6 +128,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"--kubeconfig=/some/path",
|
||||
"--source=service",
|
||||
"--source=ingress",
|
||||
"--source=connector",
|
||||
"--namespace=namespace",
|
||||
"--fqdn-template={{.Name}}.service.example.com",
|
||||
"--compatibility=mate",
|
||||
@ -134,12 +144,15 @@ func TestParseFlags(t *testing.T) {
|
||||
"--infoblox-wapi-version=2.6.1",
|
||||
"--inmemory-zone=example.org",
|
||||
"--inmemory-zone=company.com",
|
||||
"--pdns-server=http://ns.example.com:8081",
|
||||
"--pdns-api-key=some-secret-key",
|
||||
"--no-infoblox-ssl-verify",
|
||||
"--domain-filter=example.org",
|
||||
"--domain-filter=company.com",
|
||||
"--zone-id-filter=/hostedzone/ZTST1",
|
||||
"--zone-id-filter=/hostedzone/ZTST2",
|
||||
"--aws-zone-type=private",
|
||||
"--aws-assume-role=some-other-role",
|
||||
"--policy=upsert-only",
|
||||
"--registry=noop",
|
||||
"--txt-owner-id=owner-1",
|
||||
@ -150,6 +163,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"--log-format=json",
|
||||
"--metrics-address=127.0.0.1:9099",
|
||||
"--log-level=debug",
|
||||
"--connector-source-server=localhost:8081",
|
||||
},
|
||||
envVars: map[string]string{},
|
||||
expected: overriddenConfig,
|
||||
@ -160,7 +174,7 @@ func TestParseFlags(t *testing.T) {
|
||||
envVars: map[string]string{
|
||||
"EXTERNAL_DNS_MASTER": "http://127.0.0.1:8080",
|
||||
"EXTERNAL_DNS_KUBECONFIG": "/some/path",
|
||||
"EXTERNAL_DNS_SOURCE": "service\ningress",
|
||||
"EXTERNAL_DNS_SOURCE": "service\ningress\nconnector",
|
||||
"EXTERNAL_DNS_NAMESPACE": "namespace",
|
||||
"EXTERNAL_DNS_FQDN_TEMPLATE": "{{.Name}}.service.example.com",
|
||||
"EXTERNAL_DNS_COMPATIBILITY": "mate",
|
||||
@ -177,8 +191,11 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_INFOBLOX_SSL_VERIFY": "0",
|
||||
"EXTERNAL_DNS_INMEMORY_ZONE": "example.org\ncompany.com",
|
||||
"EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com",
|
||||
"EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081",
|
||||
"EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key",
|
||||
"EXTERNAL_DNS_ZONE_ID_FILTER": "/hostedzone/ZTST1\n/hostedzone/ZTST2",
|
||||
"EXTERNAL_DNS_AWS_ZONE_TYPE": "private",
|
||||
"EXTERNAL_DNS_AWS_ASSUME_ROLE": "some-other-role",
|
||||
"EXTERNAL_DNS_POLICY": "upsert-only",
|
||||
"EXTERNAL_DNS_REGISTRY": "noop",
|
||||
"EXTERNAL_DNS_TXT_OWNER_ID": "owner-1",
|
||||
@ -189,6 +206,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_LOG_FORMAT": "json",
|
||||
"EXTERNAL_DNS_METRICS_ADDRESS": "127.0.0.1:9099",
|
||||
"EXTERNAL_DNS_LOG_LEVEL": "debug",
|
||||
"EXTERNAL_DNS_CONNECTOR_SOURCE_SERVER": "localhost:8081",
|
||||
},
|
||||
expected: overriddenConfig,
|
||||
},
|
||||
@ -222,3 +240,15 @@ func restoreEnv(t *testing.T, originalEnv map[string]string) {
|
||||
require.NoError(t, os.Setenv(k, v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordsNotLogged(t *testing.T) {
|
||||
cfg := Config{
|
||||
DynPassword: "dyn-pass",
|
||||
InfobloxWapiPassword: "infoblox-pass",
|
||||
}
|
||||
|
||||
s := cfg.String()
|
||||
|
||||
assert.False(t, strings.Contains(s, "dyn-pass"))
|
||||
assert.False(t, strings.Contains(s, "infoblox-pass"))
|
||||
}
|
||||
|
@ -52,5 +52,18 @@ func ValidateConfig(cfg *externaldns.Config) error {
|
||||
return errors.New("no Infoblox WAPI password specified")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Provider == "dyn" {
|
||||
if cfg.DynUsername == "" {
|
||||
return errors.New("no Dyn username specified")
|
||||
}
|
||||
if cfg.DynCustomerName == "" {
|
||||
return errors.New("no Dyn customer name specified")
|
||||
}
|
||||
|
||||
if cfg.DynMinTTLSeconds < 0 {
|
||||
return errors.New("TTL specified for Dyn is negative")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -63,3 +63,56 @@ func newValidConfig(t *testing.T) *externaldns.Config {
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func addRequiredFieldsForDyn(cfg *externaldns.Config) {
|
||||
cfg.LogFormat = "json"
|
||||
cfg.Sources = []string{"ingress"}
|
||||
cfg.Provider = "dyn"
|
||||
}
|
||||
|
||||
func TestValidateBadDynConfig(t *testing.T) {
|
||||
badConfigs := []*externaldns.Config{
|
||||
{},
|
||||
{
|
||||
// only username
|
||||
DynUsername: "test",
|
||||
},
|
||||
{
|
||||
// only customer name
|
||||
DynCustomerName: "test",
|
||||
},
|
||||
{
|
||||
// negative timeout
|
||||
DynUsername: "test",
|
||||
DynCustomerName: "test",
|
||||
DynMinTTLSeconds: -1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cfg := range badConfigs {
|
||||
addRequiredFieldsForDyn(cfg)
|
||||
err := ValidateConfig(cfg)
|
||||
assert.NotNil(t, err, "Configuration %+v should NOT have passed validation", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGoodDynConfig(t *testing.T) {
|
||||
goodConfigs := []*externaldns.Config{
|
||||
{
|
||||
DynUsername: "test",
|
||||
DynCustomerName: "test",
|
||||
DynMinTTLSeconds: 600,
|
||||
},
|
||||
{
|
||||
DynUsername: "test",
|
||||
DynCustomerName: "test",
|
||||
DynMinTTLSeconds: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cfg := range goodConfigs {
|
||||
addRequiredFieldsForDyn(cfg)
|
||||
err := ValidateConfig(cfg)
|
||||
assert.Nil(t, err, "Configuration should be valid, got this error instead", err)
|
||||
}
|
||||
}
|
||||
|
85
pkg/tlsutils/tlsconfig.go
Normal file
85
pkg/tlsutils/tlsconfig.go
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2017 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 tlsutils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateTLSConfig creates tls.Config instance from TLS parameters passed in environment variables with the given prefix
|
||||
func CreateTLSConfig(prefix string) (*tls.Config, error) {
|
||||
caFile := os.Getenv(fmt.Sprintf("%s_CA_FILE", prefix))
|
||||
certFile := os.Getenv(fmt.Sprintf("%s_CERT_FILE", prefix))
|
||||
keyFile := os.Getenv(fmt.Sprintf("%s_KEY_FILE", prefix))
|
||||
serverName := os.Getenv(fmt.Sprintf("%s_TLS_SERVER_NAME", prefix))
|
||||
isInsecureStr := strings.ToLower(os.Getenv(fmt.Sprintf("%s_TLS_INSECURE", prefix)))
|
||||
isInsecure := isInsecureStr == "true" || isInsecureStr == "yes" || isInsecureStr == "1"
|
||||
tlsConfig, err := newTLSConfig(certFile, keyFile, caFile, serverName, isInsecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func newTLSConfig(certPath, keyPath, caPath, serverName string, insecure bool) (*tls.Config, error) {
|
||||
if certPath != "" && keyPath == "" || certPath == "" && keyPath != "" {
|
||||
return nil, errors.New("either both cert and key or none must be provided")
|
||||
}
|
||||
var certificates []tls.Certificate
|
||||
if certPath != "" {
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load TLS cert: %s", err)
|
||||
}
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
roots, err := loadRoots(caPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: certificates,
|
||||
RootCAs: roots,
|
||||
InsecureSkipVerify: insecure,
|
||||
ServerName: serverName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// loads CA cert
|
||||
func loadRoots(caPath string) (*x509.CertPool, error) {
|
||||
if caPath == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
pem, err := ioutil.ReadFile(caPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %s", caPath, err)
|
||||
}
|
||||
ok := roots.AppendCertsFromPEM(pem)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not read root certs: %s", err)
|
||||
}
|
||||
return roots, nil
|
||||
}
|
@ -64,7 +64,7 @@ func (s PerResource) ResolveUpdate(current *endpoint.Endpoint, candidates []*end
|
||||
|
||||
// less returns true if endpoint x is less than y
|
||||
func (s PerResource) less(x, y *endpoint.Endpoint) bool {
|
||||
return x.Target < y.Target
|
||||
return x.Targets.IsLess(y.Targets)
|
||||
}
|
||||
|
||||
// TODO: with cross-resource/cross-cluster setup alternative variations of ConflictResolver can be used
|
||||
|
@ -45,7 +45,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
// initialize endpoints used in tests
|
||||
suite.fooV1Cname = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "v1",
|
||||
Targets: endpoint.Targets{"v1"},
|
||||
RecordType: "CNAME",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
|
||||
@ -53,7 +53,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.fooV2Cname = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "v2",
|
||||
Targets: endpoint.Targets{"v2"},
|
||||
RecordType: "CNAME",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-v2",
|
||||
@ -61,7 +61,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.fooV2CnameDuplicate = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "v2",
|
||||
Targets: endpoint.Targets{"v2"},
|
||||
RecordType: "CNAME",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-v2-duplicate",
|
||||
@ -69,7 +69,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.fooA5 = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "5.5.5.5",
|
||||
Targets: endpoint.Targets{"5.5.5.5"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-5",
|
||||
@ -77,7 +77,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.bar127A = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Target: "127.0.0.1",
|
||||
Targets: endpoint.Targets{"127.0.0.1"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/bar-127",
|
||||
@ -85,7 +85,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.bar127AAnother = &endpoint.Endpoint{ //TODO: remove this once we move to multiple targets under same endpoint
|
||||
DNSName: "bar",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/bar-127",
|
||||
@ -93,7 +93,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.bar192A = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Target: "192.168.0.1",
|
||||
Targets: endpoint.Targets{"192.168.0.1"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/bar-192",
|
||||
@ -101,7 +101,7 @@ func (suite *ResolverSuite) SetupTest() {
|
||||
}
|
||||
suite.legacyBar192A = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Target: "192.168.0.1",
|
||||
Targets: endpoint.Targets{"192.168.0.1"},
|
||||
RecordType: "A",
|
||||
}
|
||||
}
|
||||
@ -120,7 +120,7 @@ func (suite *ResolverSuite) TestStrictResolver() {
|
||||
// should actually get the updated record (note ttl is different)
|
||||
newFooV1Cname := &endpoint.Endpoint{
|
||||
DNSName: suite.fooV1Cname.DNSName,
|
||||
Target: suite.fooV1Cname.Target,
|
||||
Targets: suite.fooV1Cname.Targets,
|
||||
Labels: suite.fooV1Cname.Labels,
|
||||
RecordType: suite.fooV1Cname.RecordType,
|
||||
RecordTTL: suite.fooV1Cname.RecordTTL + 1, // ttl is different
|
||||
|
@ -166,7 +166,7 @@ func inheritOwner(from, to *endpoint.Endpoint) {
|
||||
}
|
||||
|
||||
func targetChanged(desired, current *endpoint.Endpoint) bool {
|
||||
return desired.Target != current.Target
|
||||
return !desired.Targets.Same(current.Targets)
|
||||
}
|
||||
|
||||
func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {
|
||||
|
@ -39,7 +39,7 @@ type PlanTestSuite struct {
|
||||
func (suite *PlanTestSuite) SetupTest() {
|
||||
suite.fooV1Cname = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "v1",
|
||||
Targets: endpoint.Targets{"v1"},
|
||||
RecordType: "CNAME",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
|
||||
@ -49,7 +49,7 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
// same resource as fooV1Cname, but target is different. It will never be picked because its target lexicographically bigger than "v1"
|
||||
suite.fooV3CnameSameResource = &endpoint.Endpoint{ // TODO: remove this once endpoint can support multiple targets
|
||||
DNSName: "foo",
|
||||
Target: "v3",
|
||||
Targets: endpoint.Targets{"v3"},
|
||||
RecordType: "CNAME",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
|
||||
@ -58,7 +58,7 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
}
|
||||
suite.fooV2Cname = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "v2",
|
||||
Targets: endpoint.Targets{"v2"},
|
||||
RecordType: "CNAME",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-v2",
|
||||
@ -66,12 +66,12 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
}
|
||||
suite.fooV2CnameNoLabel = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "v2",
|
||||
Targets: endpoint.Targets{"v2"},
|
||||
RecordType: "CNAME",
|
||||
}
|
||||
suite.fooA5 = &endpoint.Endpoint{
|
||||
DNSName: "foo",
|
||||
Target: "5.5.5.5",
|
||||
Targets: endpoint.Targets{"5.5.5.5"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/foo-5",
|
||||
@ -79,7 +79,7 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
}
|
||||
suite.bar127A = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Target: "127.0.0.1",
|
||||
Targets: endpoint.Targets{"127.0.0.1"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/bar-127",
|
||||
@ -87,7 +87,7 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
}
|
||||
suite.bar127AWithTTL = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Target: "127.0.0.1",
|
||||
Targets: endpoint.Targets{"127.0.0.1"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 300,
|
||||
Labels: map[string]string{
|
||||
@ -96,7 +96,7 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
}
|
||||
suite.bar192A = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Target: "192.168.0.1",
|
||||
Targets: endpoint.Targets{"192.168.0.1"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/bar-192",
|
||||
@ -196,7 +196,7 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
|
||||
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname}
|
||||
expectedUpdateNew := []*endpoint.Endpoint{{
|
||||
DNSName: suite.fooV2Cname.DNSName,
|
||||
Target: suite.fooV2Cname.Target,
|
||||
Targets: suite.fooV2Cname.Targets,
|
||||
RecordType: suite.fooV2Cname.RecordType,
|
||||
RecordTTL: suite.fooV2Cname.RecordTTL,
|
||||
Labels: map[string]string{
|
||||
|
@ -28,12 +28,12 @@ func TestApply(t *testing.T) {
|
||||
// empty list of records
|
||||
empty := []*endpoint.Endpoint{}
|
||||
// a simple entry
|
||||
fooV1 := []*endpoint.Endpoint{{DNSName: "foo", Target: "v1"}}
|
||||
fooV1 := []*endpoint.Endpoint{{DNSName: "foo", Targets: endpoint.Targets{"v1"}}}
|
||||
// the same entry but with different target
|
||||
fooV2 := []*endpoint.Endpoint{{DNSName: "foo", Target: "v2"}}
|
||||
fooV2 := []*endpoint.Endpoint{{DNSName: "foo", Targets: endpoint.Targets{"v2"}}}
|
||||
// another two simple entries
|
||||
bar := []*endpoint.Endpoint{{DNSName: "bar", Target: "v1"}}
|
||||
baz := []*endpoint.Endpoint{{DNSName: "baz", Target: "v1"}}
|
||||
bar := []*endpoint.Endpoint{{DNSName: "bar", Targets: endpoint.Targets{"v1"}}}
|
||||
baz := []*endpoint.Endpoint{{DNSName: "baz", Targets: endpoint.Targets{"v1"}}}
|
||||
|
||||
for _, tc := range []struct {
|
||||
policy Policy
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
@ -30,30 +31,47 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
elbHostnameSuffix = ".elb.amazonaws.com"
|
||||
evaluateTargetHealth = true
|
||||
recordTTL = 300
|
||||
maxChangeCount = 4000
|
||||
)
|
||||
|
||||
var (
|
||||
// see: https://docs.aws.amazon.com/general/latest/gr/rande.html
|
||||
// see: https://docs.aws.amazon.com/general/latest/gr/rande.html#elb_region
|
||||
canonicalHostedZones = map[string]string{
|
||||
"us-east-1" + elbHostnameSuffix: "Z35SXDOTRQ7X7K",
|
||||
"us-east-2" + elbHostnameSuffix: "Z3AADJGX6KTTL2",
|
||||
"us-west-1" + elbHostnameSuffix: "Z368ELLRRE2KJ0",
|
||||
"us-west-2" + elbHostnameSuffix: "Z1H1FL5HABSF5",
|
||||
"ca-central-1" + elbHostnameSuffix: "ZQSVJUPU6J1EY",
|
||||
"ap-south-1" + elbHostnameSuffix: "ZP97RAFLXTNZK",
|
||||
"ap-northeast-2" + elbHostnameSuffix: "ZWKZPGTI48KDX",
|
||||
"ap-southeast-1" + elbHostnameSuffix: "Z1LMS91P8CMLE5",
|
||||
"ap-southeast-2" + elbHostnameSuffix: "Z1GM3OXH4ZPM65",
|
||||
"ap-northeast-1" + elbHostnameSuffix: "Z14GRHDCWA56QT",
|
||||
"eu-central-1" + elbHostnameSuffix: "Z215JYRZR1TBD5",
|
||||
"eu-west-1" + elbHostnameSuffix: "Z32O12XQLNTSW2",
|
||||
"eu-west-2" + elbHostnameSuffix: "ZHURV8PSTC4K8",
|
||||
"eu-west-3" + elbHostnameSuffix: "Z3Q77PNBQS71R4",
|
||||
"sa-east-1" + elbHostnameSuffix: "Z2P70J7HTTTPLU",
|
||||
// Application Load Balancers and Classic Load Balancers
|
||||
"us-east-2.elb.amazonaws.com": "Z3AADJGX6KTTL2",
|
||||
"us-east-1.elb.amazonaws.com": "Z35SXDOTRQ7X7K",
|
||||
"us-west-1.elb.amazonaws.com": "Z368ELLRRE2KJ0",
|
||||
"us-west-2.elb.amazonaws.com": "Z1H1FL5HABSF5",
|
||||
"ca-central-1.elb.amazonaws.com": "ZQSVJUPU6J1EY",
|
||||
"ap-south-1.elb.amazonaws.com": "ZP97RAFLXTNZK",
|
||||
"ap-northeast-2.elb.amazonaws.com": "ZWKZPGTI48KDX",
|
||||
"ap-northeast-3.elb.amazonaws.com": "Z5LXEXXYW11ES",
|
||||
"ap-southeast-1.elb.amazonaws.com": "Z1LMS91P8CMLE5",
|
||||
"ap-southeast-2.elb.amazonaws.com": "Z1GM3OXH4ZPM65",
|
||||
"ap-northeast-1.elb.amazonaws.com": "Z14GRHDCWA56QT",
|
||||
"eu-central-1.elb.amazonaws.com": "Z215JYRZR1TBD5",
|
||||
"eu-west-1.elb.amazonaws.com": "Z32O12XQLNTSW2",
|
||||
"eu-west-2.elb.amazonaws.com": "ZHURV8PSTC4K8",
|
||||
"eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4",
|
||||
"sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU",
|
||||
// Network Load Balancers
|
||||
"elb.us-east-2.amazonaws.com": "ZLMOA37VPKANP",
|
||||
"elb.us-east-1.amazonaws.com": "Z26RNL4JYFTOTI",
|
||||
"elb.us-west-1.amazonaws.com": "Z24FKFUX50B4VW",
|
||||
"elb.us-west-2.amazonaws.com": "Z18D5FSROUN65G",
|
||||
"elb.ca-central-1.amazonaws.com": "Z2EPGBW3API2WT",
|
||||
"elb.ap-south-1.amazonaws.com": "ZVDDRBQ08TROA",
|
||||
"elb.ap-northeast-2.amazonaws.com": "ZIBE1TIR4HY56",
|
||||
"elb.ap-southeast-1.amazonaws.com": "ZKVM4W9LS7TM",
|
||||
"elb.ap-southeast-2.amazonaws.com": "ZCT6FZBF4DROD",
|
||||
"elb.ap-northeast-1.amazonaws.com": "Z31USIVHYNEOWT",
|
||||
"elb.eu-central-1.amazonaws.com": "Z3F0SRJ5LGBH90",
|
||||
"elb.eu-west-1.amazonaws.com": "Z2IFOLAFXWLO4F",
|
||||
"elb.eu-west-2.amazonaws.com": "ZD4D7Y8KGAS4G",
|
||||
"elb.eu-west-3.amazonaws.com": "Z1CMS0P5QUZ6D5",
|
||||
"elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU",
|
||||
}
|
||||
)
|
||||
|
||||
@ -79,10 +97,10 @@ type AWSProvider struct {
|
||||
}
|
||||
|
||||
// NewAWSProvider initializes a new AWS Route53 based Provider.
|
||||
func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, dryRun bool) (*AWSProvider, error) {
|
||||
func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, assumeRole string, dryRun bool) (*AWSProvider, error) {
|
||||
config := aws.NewConfig()
|
||||
|
||||
config = config.WithHTTPClient(
|
||||
config.WithHTTPClient(
|
||||
instrumented_http.NewClient(config.HTTPClient, &instrumented_http.Callbacks{
|
||||
PathProcessor: func(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
@ -99,6 +117,11 @@ func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTy
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if assumeRole != "" {
|
||||
log.Infof("Assuming role: %s", assumeRole)
|
||||
session.Config.WithCredentials(stscreds.NewCredentials(session, assumeRole))
|
||||
}
|
||||
|
||||
provider := &AWSProvider{
|
||||
client: route53.New(session),
|
||||
domainFilter: domainFilter,
|
||||
@ -176,12 +199,17 @@ func (p *AWSProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
ttl = endpoint.TTL(*r.TTL)
|
||||
}
|
||||
|
||||
for _, rr := range r.ResourceRecords {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), aws.StringValue(rr.Value), aws.StringValue(r.Type), ttl))
|
||||
if len(r.ResourceRecords) > 0 {
|
||||
targets := make([]string, len(r.ResourceRecords))
|
||||
for idx, rr := range r.ResourceRecords {
|
||||
targets[idx] = aws.StringValue(rr.Value)
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), aws.StringValue(r.Type), ttl, targets...))
|
||||
}
|
||||
|
||||
if r.AliasTarget != nil {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), aws.StringValue(r.AliasTarget.DNSName), endpoint.RecordTypeCNAME, ttl))
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), endpoint.RecordTypeCNAME, ttl, aws.StringValue(r.AliasTarget.DNSName)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,8 +406,8 @@ func newChange(action string, endpoint *endpoint.Endpoint) *route53.Change {
|
||||
if isAWSLoadBalancer(endpoint) {
|
||||
change.ResourceRecordSet.Type = aws.String(route53.RRTypeA)
|
||||
change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{
|
||||
DNSName: aws.String(endpoint.Target),
|
||||
HostedZoneId: aws.String(canonicalHostedZone(endpoint.Target)),
|
||||
DNSName: aws.String(endpoint.Targets[0]),
|
||||
HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])),
|
||||
EvaluateTargetHealth: aws.Bool(evaluateTargetHealth),
|
||||
}
|
||||
} else {
|
||||
@ -389,10 +417,11 @@ func newChange(action string, endpoint *endpoint.Endpoint) *route53.Change {
|
||||
} else {
|
||||
change.ResourceRecordSet.TTL = aws.Int64(int64(endpoint.RecordTTL))
|
||||
}
|
||||
change.ResourceRecordSet.ResourceRecords = []*route53.ResourceRecord{
|
||||
{
|
||||
Value: aws.String(endpoint.Target),
|
||||
},
|
||||
change.ResourceRecordSet.ResourceRecords = make([]*route53.ResourceRecord, len(endpoint.Targets))
|
||||
for idx, val := range endpoint.Targets {
|
||||
change.ResourceRecordSet.ResourceRecords[idx] = &route53.ResourceRecord{
|
||||
Value: aws.String(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,7 +458,7 @@ func suitableZones(hostname string, zones map[string]*route53.HostedZone) []*rou
|
||||
// isAWSLoadBalancer determines if a given hostname belongs to an AWS load balancer.
|
||||
func isAWSLoadBalancer(ep *endpoint.Endpoint) bool {
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
return canonicalHostedZone(ep.Target) != ""
|
||||
return canonicalHostedZone(ep.Targets[0]) != ""
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -205,22 +205,24 @@ func TestAWSZones(t *testing.T) {
|
||||
|
||||
func TestAWSRecords(t *testing.T) {
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("*.wildcard-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("*.wildcard-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("list-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
})
|
||||
|
||||
records, err := provider.Records()
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("*.wildcard-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("*.wildcard-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("list-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
})
|
||||
}
|
||||
|
||||
@ -229,10 +231,11 @@ func TestAWSCreateRecords(t *testing.T) {
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-custom-ttl.zone-1.ext-dns-test-2.teapot.zalan.do", "172.17.0.1", endpoint.RecordTypeA, customTTL),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-custom-ttl.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, customTTL, "172.17.0.1"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
|
||||
require.NoError(t, provider.CreateRecords(records))
|
||||
@ -241,29 +244,33 @@ func TestAWSCreateRecords(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-custom-ttl.zone-1.ext-dns-test-2.teapot.zalan.do", "172.17.0.1", endpoint.RecordTypeA, customTTL),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-custom-ttl.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, customTTL, "172.17.0.1"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestAWSUpdateRecords(t *testing.T) {
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
})
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
require.NoError(t, provider.UpdateRecords(updatedRecords, currentRecords))
|
||||
@ -272,19 +279,21 @@ func TestAWSUpdateRecords(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "4.3.2.1", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestAWSDeleteRecords(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, originalEndpoints)
|
||||
@ -300,41 +309,47 @@ func TestAWSDeleteRecords(t *testing.T) {
|
||||
|
||||
func TestAWSApplyChanges(t *testing.T) {
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"),
|
||||
})
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
@ -350,56 +365,64 @@ func TestAWSApplyChanges(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "4.3.2.1", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestAWSApplyChangesDryRun(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), true, originalEndpoints)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
@ -524,7 +547,7 @@ func TestAWSsubmitChanges(t *testing.T) {
|
||||
for j := 1; j < (hosts + 1); j++ {
|
||||
hostname := fmt.Sprintf("subnet%dhost%d.zone-1.ext-dns-test-2.teapot.zalan.do", i, j)
|
||||
ip := fmt.Sprintf("1.1.%d.%d", i, j)
|
||||
ep := endpoint.NewEndpointWithTTL(hostname, ip, endpoint.RecordTypeA, endpoint.TTL(recordTTL))
|
||||
ep := endpoint.NewEndpointWithTTL(hostname, endpoint.RecordTypeA, endpoint.TTL(recordTTL), ip)
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
}
|
||||
@ -630,7 +653,7 @@ func TestAWSCreateRecordsWithCNAME(t *testing.T) {
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.example.org", RecordType: endpoint.RecordTypeCNAME},
|
||||
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.example.org"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
}
|
||||
|
||||
require.NoError(t, provider.CreateRecords(records))
|
||||
@ -655,7 +678,7 @@ func TestAWSCreateRecordsWithALIAS(t *testing.T) {
|
||||
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.eu-central-1.elb.amazonaws.com", RecordType: endpoint.RecordTypeCNAME},
|
||||
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.eu-central-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
}
|
||||
|
||||
require.NoError(t, provider.CreateRecords(records))
|
||||
@ -685,7 +708,7 @@ func TestAWSisLoadBalancer(t *testing.T) {
|
||||
{"foo.example.org", endpoint.RecordTypeCNAME, false},
|
||||
} {
|
||||
ep := &endpoint.Endpoint{
|
||||
Target: tc.target,
|
||||
Targets: endpoint.Targets{tc.target},
|
||||
RecordType: tc.recordType,
|
||||
}
|
||||
assert.Equal(t, tc.expected, isAWSLoadBalancer(ep))
|
||||
@ -697,13 +720,15 @@ func TestAWSCanonicalHostedZone(t *testing.T) {
|
||||
hostname string
|
||||
expected string
|
||||
}{
|
||||
{"foo.us-east-1.elb.amazonaws.com", "Z35SXDOTRQ7X7K"},
|
||||
// Application Load Balancers and Classic Load Balancers
|
||||
{"foo.us-east-2.elb.amazonaws.com", "Z3AADJGX6KTTL2"},
|
||||
{"foo.us-east-1.elb.amazonaws.com", "Z35SXDOTRQ7X7K"},
|
||||
{"foo.us-west-1.elb.amazonaws.com", "Z368ELLRRE2KJ0"},
|
||||
{"foo.us-west-2.elb.amazonaws.com", "Z1H1FL5HABSF5"},
|
||||
{"foo.ca-central-1.elb.amazonaws.com", "ZQSVJUPU6J1EY"},
|
||||
{"foo.ap-south-1.elb.amazonaws.com", "ZP97RAFLXTNZK"},
|
||||
{"foo.ap-northeast-2.elb.amazonaws.com", "ZWKZPGTI48KDX"},
|
||||
{"foo.ap-northeast-3.elb.amazonaws.com", "Z5LXEXXYW11ES"},
|
||||
{"foo.ap-southeast-1.elb.amazonaws.com", "Z1LMS91P8CMLE5"},
|
||||
{"foo.ap-southeast-2.elb.amazonaws.com", "Z1GM3OXH4ZPM65"},
|
||||
{"foo.ap-northeast-1.elb.amazonaws.com", "Z14GRHDCWA56QT"},
|
||||
@ -712,6 +737,23 @@ func TestAWSCanonicalHostedZone(t *testing.T) {
|
||||
{"foo.eu-west-2.elb.amazonaws.com", "ZHURV8PSTC4K8"},
|
||||
{"foo.eu-west-3.elb.amazonaws.com", "Z3Q77PNBQS71R4"},
|
||||
{"foo.sa-east-1.elb.amazonaws.com", "Z2P70J7HTTTPLU"},
|
||||
// Network Load Balancers
|
||||
{"foo.elb.us-east-2.amazonaws.com", "ZLMOA37VPKANP"},
|
||||
{"foo.elb.us-east-1.amazonaws.com", "Z26RNL4JYFTOTI"},
|
||||
{"foo.elb.us-west-1.amazonaws.com", "Z24FKFUX50B4VW"},
|
||||
{"foo.elb.us-west-2.amazonaws.com", "Z18D5FSROUN65G"},
|
||||
{"foo.elb.ca-central-1.amazonaws.com", "Z2EPGBW3API2WT"},
|
||||
{"foo.elb.ap-south-1.amazonaws.com", "ZVDDRBQ08TROA"},
|
||||
{"foo.elb.ap-northeast-2.amazonaws.com", "ZIBE1TIR4HY56"},
|
||||
{"foo.elb.ap-southeast-1.amazonaws.com", "ZKVM4W9LS7TM"},
|
||||
{"foo.elb.ap-southeast-2.amazonaws.com", "ZCT6FZBF4DROD"},
|
||||
{"foo.elb.ap-northeast-1.amazonaws.com", "Z31USIVHYNEOWT"},
|
||||
{"foo.elb.eu-central-1.amazonaws.com", "Z3F0SRJ5LGBH90"},
|
||||
{"foo.elb.eu-west-1.amazonaws.com", "Z2IFOLAFXWLO4F"},
|
||||
{"foo.elb.eu-west-2.amazonaws.com", "ZD4D7Y8KGAS4G"},
|
||||
{"foo.elb.eu-west-3.amazonaws.com", "Z1CMS0P5QUZ6D5"},
|
||||
{"foo.elb.sa-east-1.amazonaws.com", "ZTK26PT1VY4CU"},
|
||||
// No Load Balancer
|
||||
{"foo.example.org", ""},
|
||||
} {
|
||||
zone := canonicalHostedZone(tc.hostname)
|
||||
|
@ -112,9 +112,9 @@ func NewAzureProvider(configFile string, domainFilter DomainFilter, zoneIDFilter
|
||||
return nil, fmt.Errorf("failed to create service principal token: %v", err)
|
||||
}
|
||||
|
||||
zonesClient := dns.NewZonesClient(cfg.SubscriptionID)
|
||||
zonesClient := dns.NewZonesClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
zonesClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||
recordsClient := dns.NewRecordSetsClient(cfg.SubscriptionID)
|
||||
recordsClient := dns.NewRecordSetsClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
recordsClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||
|
||||
provider := &AzureProvider{
|
||||
@ -158,12 +158,12 @@ func (p *AzureProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
ttl = endpoint.TTL(*recordSet.TTL)
|
||||
}
|
||||
|
||||
ep := endpoint.NewEndpointWithTTL(name, target, recordType, endpoint.TTL(ttl))
|
||||
ep := endpoint.NewEndpointWithTTL(name, recordType, endpoint.TTL(ttl), target)
|
||||
log.Debugf(
|
||||
"Found %s record for '%s' with target '%s'.",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Target,
|
||||
ep.Targets,
|
||||
)
|
||||
endpoints = append(endpoints, ep)
|
||||
return true
|
||||
@ -323,7 +323,7 @@ func (p *AzureProvider) updateRecords(updated azureChangeMap) {
|
||||
"Would update %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
||||
endpoint.RecordType,
|
||||
name,
|
||||
endpoint.Target,
|
||||
endpoint.Targets,
|
||||
zone,
|
||||
)
|
||||
continue
|
||||
@ -333,7 +333,7 @@ func (p *AzureProvider) updateRecords(updated azureChangeMap) {
|
||||
"Updating %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
||||
endpoint.RecordType,
|
||||
name,
|
||||
endpoint.Target,
|
||||
endpoint.Targets,
|
||||
zone,
|
||||
)
|
||||
|
||||
@ -354,7 +354,7 @@ func (p *AzureProvider) updateRecords(updated azureChangeMap) {
|
||||
"Failed to update %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
endpoint.RecordType,
|
||||
name,
|
||||
endpoint.Target,
|
||||
endpoint.Targets,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
@ -388,7 +388,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
|
||||
TTL: to.Int64Ptr(ttl),
|
||||
ARecords: &[]dns.ARecord{
|
||||
{
|
||||
Ipv4Address: to.StringPtr(endpoint.Target),
|
||||
Ipv4Address: to.StringPtr(endpoint.Targets[0]),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -398,7 +398,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
|
||||
RecordSetProperties: &dns.RecordSetProperties{
|
||||
TTL: to.Int64Ptr(ttl),
|
||||
CnameRecord: &dns.CnameRecord{
|
||||
Cname: to.StringPtr(endpoint.Target),
|
||||
Cname: to.StringPtr(endpoint.Targets[0]),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
@ -409,7 +409,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
|
||||
TxtRecords: &[]dns.TxtRecord{
|
||||
{
|
||||
Value: &[]string{
|
||||
endpoint.Target,
|
||||
endpoint.Targets[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -129,8 +129,8 @@ func (client *mockRecordsClient) Delete(resourceGroupName string, zoneName strin
|
||||
client.deletedEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
formatAzureDNSName(relativeRecordSetName, zoneName),
|
||||
"",
|
||||
string(recordType),
|
||||
"",
|
||||
),
|
||||
)
|
||||
return autorest.Response{}, nil
|
||||
@ -145,9 +145,9 @@ func (client *mockRecordsClient) CreateOrUpdate(resourceGroupName string, zoneNa
|
||||
client.updatedEndpoints,
|
||||
endpoint.NewEndpointWithTTL(
|
||||
formatAzureDNSName(relativeRecordSetName, zoneName),
|
||||
extractAzureTarget(¶meters),
|
||||
string(recordType),
|
||||
ttl,
|
||||
extractAzureTarget(¶meters),
|
||||
),
|
||||
)
|
||||
return parameters, nil
|
||||
@ -197,11 +197,11 @@ func TestAzureRecord(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("example.com", "123.123.123.122", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("example.com", "heritage=external-dns,external-dns/owner=default", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpointWithTTL("nginx.example.com", "123.123.123.123", endpoint.RecordTypeA, 3600),
|
||||
endpoint.NewEndpointWithTTL("nginx.example.com", "heritage=external-dns,external-dns/owner=default", endpoint.RecordTypeTXT, recordTTL),
|
||||
endpoint.NewEndpointWithTTL("hack.example.com", "hack.azurewebsites.net", endpoint.RecordTypeCNAME, 10),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
|
||||
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
|
||||
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
|
||||
endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
|
||||
}
|
||||
|
||||
validateAzureEndpoints(t, actual, expected)
|
||||
@ -214,23 +214,23 @@ func TestAzureApplyChanges(t *testing.T) {
|
||||
testAzureApplyChangesInternal(t, false, &recordsClient)
|
||||
|
||||
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("old.example.com", "", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("oldcname.example.com", "", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("deleted.example.com", "", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", "", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""),
|
||||
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""),
|
||||
endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""),
|
||||
})
|
||||
|
||||
validateAzureEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("example.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("foo.example.com", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("foo.example.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("bar.example.com", "other.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("bar.example.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("other.com", "5.6.7.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("other.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)),
|
||||
endpoint.NewEndpointWithTTL("new.example.com", "111.222.111.222", endpoint.RecordTypeA, 3600),
|
||||
endpoint.NewEndpointWithTTL("newcname.example.com", "other.com", endpoint.RecordTypeCNAME, 10),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
|
||||
endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
|
||||
endpoint.NewEndpointWithTTL("bar.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "other.com"),
|
||||
endpoint.NewEndpointWithTTL("bar.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
|
||||
endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "5.6.7.8"),
|
||||
endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
|
||||
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
|
||||
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
|
||||
})
|
||||
}
|
||||
|
||||
@ -262,33 +262,33 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClie
|
||||
)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("example.com", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("foo.example.com", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("foo.example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("bar.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("bar.example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("other.com", "5.6.7.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("other.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("nope.com", "4.4.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("nope.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"),
|
||||
endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
|
||||
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("old.example.com", "121.212.121.212", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("oldcname.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("old.nope.com", "121.212.121.212", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
|
||||
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("new.example.com", "111.222.111.222", endpoint.RecordTypeA, 3600),
|
||||
endpoint.NewEndpointWithTTL("newcname.example.com", "other.com", endpoint.RecordTypeCNAME, 10),
|
||||
endpoint.NewEndpoint("new.nope.com", "222.111.222.111", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
|
||||
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
|
||||
endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("deleted.example.com", "111.222.111.222", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("deleted.nope.com", "222.111.222.111", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "111.222.111.222"),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
|
@ -161,7 +161,7 @@ func (p *CloudFlareProvider) Records() ([]*endpoint.Endpoint, error) {
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(r.Type) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(r.Name, r.Content, r.Type))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(r.Name, r.Type, r.Content))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,7 +290,7 @@ func newCloudFlareChange(action string, endpoint *endpoint.Endpoint, proxied boo
|
||||
TTL: 1,
|
||||
Proxied: proxied,
|
||||
Type: endpoint.RecordType,
|
||||
Content: endpoint.Target,
|
||||
Content: endpoint.Targets[0],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/kubernetes-incubator/external-dns/plan"
|
||||
|
||||
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -336,12 +336,12 @@ func (m *mockCloudFlareUpdateRecordsFail) ListZones(zoneID ...string) ([]cloudfl
|
||||
}
|
||||
|
||||
func TestNewCloudFlareChanges(t *testing.T) {
|
||||
endpoints := []*endpoint.Endpoint{{DNSName: "new", Target: "target"}}
|
||||
endpoints := []*endpoint.Endpoint{{DNSName: "new", Targets: endpoint.Targets{"target"}}}
|
||||
newCloudFlareChanges(cloudFlareCreate, endpoints, true)
|
||||
}
|
||||
|
||||
func TestNewCloudFlareChangeNoProxied(t *testing.T) {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Target: "target"}, false)
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}}, false)
|
||||
assert.False(t, change.ResourceRecordSet.Proxied)
|
||||
}
|
||||
|
||||
@ -361,7 +361,7 @@ func TestNewCloudFlareChangeProxiable(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, cloudFlareType := range cloudFlareTypes {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: cloudFlareType.recordType, Target: "target"}, true)
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: cloudFlareType.recordType, Targets: endpoint.Targets{"target"}}, true)
|
||||
|
||||
if cloudFlareType.proxiable {
|
||||
assert.True(t, change.ResourceRecordSet.Proxied)
|
||||
@ -370,7 +370,7 @@ func TestNewCloudFlareChangeProxiable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "*.foo", RecordType: "A", Target: "target"}, true)
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "*.foo", RecordType: "A", Targets: endpoint.Targets{"target"}}, true)
|
||||
assert.False(t, change.ResourceRecordSet.Proxied)
|
||||
}
|
||||
|
||||
@ -433,10 +433,10 @@ func TestApplyChanges(t *testing.T) {
|
||||
provider := &CloudFlareProvider{
|
||||
Client: &mockCloudFlareClient{},
|
||||
}
|
||||
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.zalando.to.", Target: "target"}, {DNSName: "new.ext-dns-test.unrelated.to.", Target: "target"}}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target"}}
|
||||
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target-old"}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target-new"}}
|
||||
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target"}}, {DNSName: "new.ext-dns-test.unrelated.to.", Targets: endpoint.Targets{"target"}}}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target"}}}
|
||||
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target-old"}}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target-new"}}}
|
||||
err := provider.ApplyChanges(changes)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
|
@ -274,8 +274,8 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
|
||||
if service.Host != "" {
|
||||
ep := endpoint.NewEndpoint(
|
||||
dnsName,
|
||||
service.Host,
|
||||
guessRecordType(service.Host),
|
||||
service.Host,
|
||||
)
|
||||
ep.Labels["originalText"] = service.Text
|
||||
ep.Labels["prefix"] = prefix
|
||||
@ -284,8 +284,8 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
|
||||
if service.Text != "" {
|
||||
ep := endpoint.NewEndpoint(
|
||||
dnsName,
|
||||
service.Text,
|
||||
endpoint.RecordTypeTXT,
|
||||
service.Text,
|
||||
)
|
||||
ep.Labels["prefix"] = prefix
|
||||
result = append(result, ep)
|
||||
@ -318,7 +318,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
|
||||
prefix = fmt.Sprintf("%08x", rand.Int31())
|
||||
}
|
||||
service := Service{
|
||||
Host: ep.Target,
|
||||
Host: ep.Targets[0],
|
||||
Text: ep.Labels["originalText"],
|
||||
Key: etcdKeyFor(prefix + "." + dnsName),
|
||||
TargetStrip: strings.Count(prefix, ".") + 1,
|
||||
@ -340,7 +340,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
|
||||
TargetStrip: strings.Count(prefix, ".") + 1,
|
||||
})
|
||||
}
|
||||
services[index].Text = ep.Target
|
||||
services[index].Text = ep.Targets[0]
|
||||
index++
|
||||
}
|
||||
|
||||
|
@ -70,8 +70,8 @@ func TestAServiceTranslation(t *testing.T) {
|
||||
if endpoints[0].DNSName != expectedDNSName {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", endpoints[0].DNSName, expectedDNSName)
|
||||
}
|
||||
if endpoints[0].Target != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", endpoints[0].Target, expectedTarget)
|
||||
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)
|
||||
@ -99,8 +99,8 @@ func TestCNAMEServiceTranslation(t *testing.T) {
|
||||
if endpoints[0].DNSName != expectedDNSName {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", endpoints[0].DNSName, expectedDNSName)
|
||||
}
|
||||
if endpoints[0].Target != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", endpoints[0].Target, expectedTarget)
|
||||
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)
|
||||
@ -128,8 +128,8 @@ func TestTXTServiceTranslation(t *testing.T) {
|
||||
if endpoints[0].DNSName != expectedDNSName {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", endpoints[0].DNSName, expectedDNSName)
|
||||
}
|
||||
if endpoints[0].Target != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", endpoints[0].Target, expectedTarget)
|
||||
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)
|
||||
@ -169,8 +169,8 @@ func TestAWithTXTServiceTranslation(t *testing.T) {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", ep.DNSName, expectedDNSName)
|
||||
}
|
||||
|
||||
if ep.Target != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", ep.Target, expectedTarget)
|
||||
if ep.Targets[0] != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", ep.Targets[0], expectedTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,8 +208,8 @@ func TestCNAMEWithTXTServiceTranslation(t *testing.T) {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", ep.DNSName, expectedDNSName)
|
||||
}
|
||||
|
||||
if ep.Target != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", ep.Target, expectedTarget)
|
||||
if ep.Targets[0] != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", ep.Targets[0], expectedTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -222,9 +222,9 @@ func TestCoreDNSApplyChanges(t *testing.T) {
|
||||
|
||||
changes1 := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("domain1.local", "5.5.5.5", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("domain1.local", "string1", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("domain2.local", "site.local", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "5.5.5.5"),
|
||||
endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeTXT, "string1"),
|
||||
endpoint.NewEndpoint("domain2.local", endpoint.RecordTypeCNAME, "site.local"),
|
||||
},
|
||||
}
|
||||
coredns.ApplyChanges(changes1)
|
||||
@ -235,14 +235,14 @@ func TestCoreDNSApplyChanges(t *testing.T) {
|
||||
}
|
||||
validateServices(client.services, expectedServices1, t, 1)
|
||||
|
||||
updatedEp := endpoint.NewEndpoint("domain1.local", "6.6.6.6", endpoint.RecordTypeA)
|
||||
updatedEp := endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "6.6.6.6")
|
||||
updatedEp.Labels["originalText"] = "string1"
|
||||
changes2 := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("domain3.local", "7.7.7.7", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "7.7.7.7"),
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("domain1.local", "6.6.6.6", "A"),
|
||||
endpoint.NewEndpoint("domain1.local", "A", "6.6.6.6"),
|
||||
},
|
||||
}
|
||||
applyServiceChanges(coredns, changes2)
|
||||
@ -256,9 +256,9 @@ func TestCoreDNSApplyChanges(t *testing.T) {
|
||||
|
||||
changes3 := &plan.Changes{
|
||||
Delete: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("domain1.local", "6.6.6.6", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("domain1.local", "string", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("domain3.local", "7.7.7.7", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "6.6.6.6"),
|
||||
endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeTXT, "string"),
|
||||
endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "7.7.7.7"),
|
||||
},
|
||||
}
|
||||
|
||||
|
454
provider/designate.go
Normal file
454
provider/designate.go
Normal file
@ -0,0 +1,454 @@
|
||||
/*
|
||||
Copyright 2017 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 (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/kubernetes-incubator/external-dns/pkg/tlsutils"
|
||||
"github.com/kubernetes-incubator/external-dns/plan"
|
||||
)
|
||||
|
||||
const (
|
||||
// ID of the RecordSet from which endpoint was created
|
||||
designateRecordSetID = "designate-recordset-id"
|
||||
// Zone ID of the RecordSet
|
||||
designateZoneID = "designate-record-id"
|
||||
|
||||
// Initial records values of the RecordSet. This label is required in order not to loose records that haven't
|
||||
// changed where there are several targets per domain and only some of them changed.
|
||||
// Values are joined by zero-byte to in order to get a single string
|
||||
designateOriginalRecords = "designate-original-records"
|
||||
)
|
||||
|
||||
// interface between provider and OpenStack DNS API
|
||||
type designateClientInterface interface {
|
||||
// ForEachZone calls handler for each zone managed by the Designate
|
||||
ForEachZone(handler func(zone *zones.Zone) error) error
|
||||
|
||||
// ForEachRecordSet calls handler for each recordset in the given DNS zone
|
||||
ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error
|
||||
|
||||
// CreateRecordSet creates recordset in the given DNS zone
|
||||
CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error)
|
||||
|
||||
// UpdateRecordSet updates recordset in the given DNS zone
|
||||
UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error
|
||||
|
||||
// DeleteRecordSet deletes recordset in the given DNS zone
|
||||
DeleteRecordSet(zoneID, recordSetID string) error
|
||||
}
|
||||
|
||||
// implementation of the designateClientInterface
|
||||
type designateClient struct {
|
||||
serviceClient *gophercloud.ServiceClient
|
||||
}
|
||||
|
||||
// factory function for the designateClientInterface
|
||||
func newDesignateClient() (designateClientInterface, error) {
|
||||
serviceClient, err := createDesignateServiceClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &designateClient{serviceClient}, nil
|
||||
}
|
||||
|
||||
// copies environment variables to new names without overwriting existing values
|
||||
func remapEnv(mapping map[string]string) {
|
||||
for k, v := range mapping {
|
||||
currentVal := os.Getenv(k)
|
||||
newVal := os.Getenv(v)
|
||||
if currentVal == "" && newVal != "" {
|
||||
os.Setenv(k, newVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns OpenStack Keystone authentication settings by obtaining values from standard environment variables.
|
||||
// also fixes incompatibilities between gophercloud implementation and *-stackrc files that can be downloaded
|
||||
// from OpenStack dashboard in latest versions
|
||||
func getAuthSettings() (gophercloud.AuthOptions, error) {
|
||||
remapEnv(map[string]string{
|
||||
"OS_TENANT_NAME": "OS_PROJECT_NAME",
|
||||
"OS_TENANT_ID": "OS_PROJECT_ID",
|
||||
"OS_DOMAIN_NAME": "OS_USER_DOMAIN_NAME",
|
||||
"OS_DOMAIN_ID": "OS_USER_DOMAIN_ID",
|
||||
})
|
||||
|
||||
opts, err := openstack.AuthOptionsFromEnv()
|
||||
if err != nil {
|
||||
return gophercloud.AuthOptions{}, err
|
||||
}
|
||||
opts.AllowReauth = true
|
||||
if !strings.HasSuffix(opts.IdentityEndpoint, "/") {
|
||||
opts.IdentityEndpoint += "/"
|
||||
}
|
||||
if !strings.HasSuffix(opts.IdentityEndpoint, "/v2.0/") && !strings.HasSuffix(opts.IdentityEndpoint, "/v3/") {
|
||||
opts.IdentityEndpoint += "v2.0/"
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// authenticate in OpenStack and obtain Designate service endpoint
|
||||
func createDesignateServiceClient() (*gophercloud.ServiceClient, error) {
|
||||
opts, err := getAuthSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Using OpenStack Keystone at %s", opts.IdentityEndpoint)
|
||||
authProvider, err := openstack.AuthenticatedClient(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eo := gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
}
|
||||
|
||||
client, err := openstack.NewDNSV2(authProvider, eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig, err := tlsutils.CreateTLSConfig("OPENSTACK")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
client.ProviderClient.HTTPClient.Transport = transport
|
||||
log.Infof("Found OpenStack Designate service at %s", client.Endpoint)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// ForEachZone calls handler for each zone managed by the Designate
|
||||
func (c designateClient) ForEachZone(handler func(zone *zones.Zone) error) error {
|
||||
pager := zones.List(c.serviceClient, zones.ListOpts{})
|
||||
return pager.EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
list, err := zones.ExtractZones(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, zone := range list {
|
||||
err := handler(&zone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ForEachRecordSet calls handler for each recordset in the given DNS zone
|
||||
func (c designateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error {
|
||||
pager := recordsets.ListByZone(c.serviceClient, zoneID, recordsets.ListOpts{})
|
||||
return pager.EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
list, err := recordsets.ExtractRecordSets(page)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, recordSet := range list {
|
||||
err := handler(&recordSet)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// CreateRecordSet creates recordset in the given DNS zone
|
||||
func (c designateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) {
|
||||
r, err := recordsets.Create(c.serviceClient, zoneID, opts).Extract()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return r.ID, nil
|
||||
}
|
||||
|
||||
// UpdateRecordSet updates recordset in the given DNS zone
|
||||
func (c designateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error {
|
||||
_, err := recordsets.Update(c.serviceClient, zoneID, recordSetID, opts).Extract()
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteRecordSet deletes recordset in the given DNS zone
|
||||
func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
||||
return recordsets.Delete(c.serviceClient, zoneID, recordSetID).ExtractErr()
|
||||
}
|
||||
|
||||
// designate provider type
|
||||
type designateProvider struct {
|
||||
client designateClientInterface
|
||||
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter DomainFilter
|
||||
dryRun bool
|
||||
}
|
||||
|
||||
// NewDesignateProvider is a factory function for OpenStack designate providers
|
||||
func NewDesignateProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
|
||||
client, err := newDesignateClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &designateProvider{
|
||||
client: client,
|
||||
domainFilter: domainFilter,
|
||||
dryRun: dryRun,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// converts domain names to FQDN
|
||||
func canonicalizeDomainNames(domains []string) []string {
|
||||
var cDomains []string
|
||||
for _, d := range domains {
|
||||
if !strings.HasSuffix(d, ".") {
|
||||
d += "."
|
||||
cDomains = append(cDomains, strings.ToLower(d))
|
||||
}
|
||||
}
|
||||
return cDomains
|
||||
}
|
||||
|
||||
// converts domain name to FQDN
|
||||
func canonicalizeDomainName(d string) string {
|
||||
if !strings.HasSuffix(d, ".") {
|
||||
d += "."
|
||||
}
|
||||
return strings.ToLower(d)
|
||||
}
|
||||
|
||||
// returns ZoneID -> ZoneName mapping for zones that are managed by the Designate and match domain filter
|
||||
func (p designateProvider) getZones() (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
|
||||
err := p.client.ForEachZone(
|
||||
func(zone *zones.Zone) error {
|
||||
if zone.Type != "" && strings.ToUpper(zone.Type) != "PRIMARY" || zone.Status != "ACTIVE" {
|
||||
return nil
|
||||
}
|
||||
|
||||
zoneName := canonicalizeDomainName(zone.Name)
|
||||
if !p.domainFilter.Match(zoneName) {
|
||||
return nil
|
||||
}
|
||||
result[zone.ID] = zoneName
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// finds best suitable DNS zone for the hostname
|
||||
func (p designateProvider) getHostZoneID(hostname string, managedZones map[string]string) (string, error) {
|
||||
longestZoneLength := 0
|
||||
resultID := ""
|
||||
|
||||
for zoneID, zoneName := range managedZones {
|
||||
if !strings.HasSuffix(hostname, zoneName) {
|
||||
continue
|
||||
}
|
||||
ln := len(zoneName)
|
||||
if ln > longestZoneLength {
|
||||
resultID = zoneID
|
||||
longestZoneLength = ln
|
||||
}
|
||||
}
|
||||
|
||||
return resultID, nil
|
||||
}
|
||||
|
||||
// Records returns the list of records.
|
||||
func (p designateProvider) Records() ([]*endpoint.Endpoint, error) {
|
||||
var result []*endpoint.Endpoint
|
||||
managedZones, err := p.getZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for zoneID := range managedZones {
|
||||
err = p.client.ForEachRecordSet(zoneID,
|
||||
func(recordSet *recordsets.RecordSet) error {
|
||||
if recordSet.Type != endpoint.RecordTypeA && recordSet.Type != endpoint.RecordTypeTXT && recordSet.Type != endpoint.RecordTypeCNAME {
|
||||
return nil
|
||||
}
|
||||
for _, record := range recordSet.Records {
|
||||
ep := endpoint.NewEndpoint(recordSet.Name, recordSet.Type, record)
|
||||
ep.Labels[designateRecordSetID] = recordSet.ID
|
||||
ep.Labels[designateZoneID] = recordSet.ZoneID
|
||||
ep.Labels[designateOriginalRecords] = strings.Join(recordSet.Records, "\000")
|
||||
result = append(result, ep)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// temporary structure to hold recordset parameters so that we could aggregate endpoints into recordsets
|
||||
type recordSet struct {
|
||||
dnsName string
|
||||
recordType string
|
||||
zoneID string
|
||||
recordSetID string
|
||||
names map[string]bool
|
||||
}
|
||||
|
||||
// adds endpoint into recordset aggregation, loading original values from endpoint labels first
|
||||
func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete bool) {
|
||||
key := fmt.Sprintf("%s/%s", ep.DNSName, ep.RecordType)
|
||||
rs := recordSets[key]
|
||||
if rs == nil {
|
||||
rs = &recordSet{
|
||||
dnsName: canonicalizeDomainName(ep.DNSName),
|
||||
recordType: ep.RecordType,
|
||||
names: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
if rs.zoneID == "" {
|
||||
rs.zoneID = ep.Labels[designateZoneID]
|
||||
}
|
||||
if rs.recordSetID == "" {
|
||||
rs.recordSetID = ep.Labels[designateRecordSetID]
|
||||
}
|
||||
for _, rec := range strings.Split(ep.Labels[designateOriginalRecords], "\000") {
|
||||
if _, ok := rs.names[rec]; !ok && rec != "" {
|
||||
rs.names[rec] = true
|
||||
}
|
||||
}
|
||||
targets := ep.Targets
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
targets = canonicalizeDomainNames(targets)
|
||||
}
|
||||
for _, t := range targets {
|
||||
rs.names[t] = !delete
|
||||
}
|
||||
recordSets[key] = rs
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p designateProvider) ApplyChanges(changes *plan.Changes) error {
|
||||
managedZones, err := p.getZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recordSets := map[string]*recordSet{}
|
||||
for _, ep := range changes.Create {
|
||||
addEndpoint(ep, recordSets, false)
|
||||
}
|
||||
for _, ep := range changes.UpdateNew {
|
||||
addEndpoint(ep, recordSets, false)
|
||||
}
|
||||
for _, ep := range changes.UpdateOld {
|
||||
addEndpoint(ep, recordSets, true)
|
||||
}
|
||||
for _, ep := range changes.Delete {
|
||||
addEndpoint(ep, recordSets, true)
|
||||
}
|
||||
for _, rs := range recordSets {
|
||||
if err2 := p.upsertRecordSet(rs, managedZones); err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// apply recordset changes by inserting/updating/deleting recordsets
|
||||
func (p designateProvider) upsertRecordSet(rs *recordSet, managedZones map[string]string) error {
|
||||
if rs.zoneID == "" {
|
||||
var err error
|
||||
rs.zoneID, err = p.getHostZoneID(rs.dnsName, managedZones)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rs.zoneID == "" {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected ", rs.dnsName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var records []string
|
||||
for rec, v := range rs.names {
|
||||
if v {
|
||||
records = append(records, rec)
|
||||
}
|
||||
}
|
||||
if rs.recordSetID == "" && records == nil {
|
||||
return nil
|
||||
}
|
||||
if rs.recordSetID == "" {
|
||||
opts := recordsets.CreateOpts{
|
||||
Name: rs.dnsName,
|
||||
Type: rs.recordType,
|
||||
Records: records,
|
||||
}
|
||||
log.Infof("Creating records: %s/%s: %s", rs.dnsName, rs.recordType, strings.Join(records, ","))
|
||||
if p.dryRun {
|
||||
return nil
|
||||
}
|
||||
_, err := p.client.CreateRecordSet(rs.zoneID, opts)
|
||||
return err
|
||||
} else if len(records) == 0 {
|
||||
log.Infof("Deleting records for %s/%s", rs.dnsName, rs.recordType)
|
||||
if p.dryRun {
|
||||
return nil
|
||||
}
|
||||
return p.client.DeleteRecordSet(rs.zoneID, rs.recordSetID)
|
||||
} else {
|
||||
opts := recordsets.UpdateOpts{
|
||||
Records: records,
|
||||
}
|
||||
log.Infof("Updating records: %s/%s: %s", rs.dnsName, rs.recordType, strings.Join(records, ","))
|
||||
if p.dryRun {
|
||||
return nil
|
||||
}
|
||||
return p.client.UpdateRecordSet(rs.zoneID, rs.recordSetID, opts)
|
||||
}
|
||||
}
|
519
provider/designate_test.go
Normal file
519
provider/designate_test.go
Normal file
@ -0,0 +1,519 @@
|
||||
/*
|
||||
Copyright 2017 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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/kubernetes-incubator/external-dns/plan"
|
||||
)
|
||||
|
||||
var lastGeneratedDesignateID int32
|
||||
|
||||
func generateDesignateID() string {
|
||||
return fmt.Sprintf("id-%d", atomic.AddInt32(&lastGeneratedDesignateID, 1))
|
||||
}
|
||||
|
||||
type fakeDesignateClient struct {
|
||||
managedZones map[string]*struct {
|
||||
zone *zones.Zone
|
||||
recordSets map[string]*recordsets.RecordSet
|
||||
}
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) AddZone(zone zones.Zone) string {
|
||||
if zone.ID == "" {
|
||||
zone.ID = zone.Name
|
||||
}
|
||||
c.managedZones[zone.ID] = &struct {
|
||||
zone *zones.Zone
|
||||
recordSets map[string]*recordsets.RecordSet
|
||||
}{
|
||||
zone: &zone,
|
||||
recordSets: make(map[string]*recordsets.RecordSet),
|
||||
}
|
||||
return zone.ID
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) ForEachZone(handler func(zone *zones.Zone) error) error {
|
||||
for _, zone := range c.managedZones {
|
||||
if err := handler(zone.zone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) ForEachRecordSet(zoneID string, handler func(recordSet *recordsets.RecordSet) error) error {
|
||||
zone := c.managedZones[zoneID]
|
||||
if zone == nil {
|
||||
return fmt.Errorf("unknown zone %s", zoneID)
|
||||
}
|
||||
for _, recordSet := range zone.recordSets {
|
||||
if err := handler(recordSet); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) CreateRecordSet(zoneID string, opts recordsets.CreateOpts) (string, error) {
|
||||
zone := c.managedZones[zoneID]
|
||||
if zone == nil {
|
||||
return "", fmt.Errorf("unknown zone %s", zoneID)
|
||||
}
|
||||
rs := &recordsets.RecordSet{
|
||||
ID: generateDesignateID(),
|
||||
ZoneID: zoneID,
|
||||
Name: opts.Name,
|
||||
Description: opts.Description,
|
||||
Records: opts.Records,
|
||||
TTL: opts.TTL,
|
||||
Type: opts.Type,
|
||||
}
|
||||
zone.recordSets[rs.ID] = rs
|
||||
return rs.ID, nil
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) UpdateRecordSet(zoneID, recordSetID string, opts recordsets.UpdateOpts) error {
|
||||
zone := c.managedZones[zoneID]
|
||||
if zone == nil {
|
||||
return fmt.Errorf("unknown zone %s", zoneID)
|
||||
}
|
||||
rs := zone.recordSets[recordSetID]
|
||||
if rs == nil {
|
||||
return fmt.Errorf("unknown record-set %s", recordSetID)
|
||||
}
|
||||
rs.Description = opts.Description
|
||||
rs.TTL = opts.TTL
|
||||
rs.Records = opts.Records
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
||||
zone := c.managedZones[zoneID]
|
||||
if zone == nil {
|
||||
return fmt.Errorf("unknown zone %s", zoneID)
|
||||
}
|
||||
delete(zone.recordSets, recordSetID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) ToProvider() Provider {
|
||||
return &designateProvider{client: c}
|
||||
}
|
||||
|
||||
func newFakeDesignateClient() *fakeDesignateClient {
|
||||
return &fakeDesignateClient{
|
||||
make(map[string]*struct {
|
||||
zone *zones.Zone
|
||||
recordSets map[string]*recordsets.RecordSet
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesignateRecords(t *testing.T) {
|
||||
client := newFakeDesignateClient()
|
||||
|
||||
zone1ID := client.AddZone(zones.Zone{
|
||||
Name: "example.com.",
|
||||
Type: "PRIMARY",
|
||||
Status: "ACTIVE",
|
||||
})
|
||||
rs11ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
|
||||
Name: "www.example.com.",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Records: []string{"10.1.1.1"},
|
||||
})
|
||||
rs12ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
|
||||
Name: "www.example.com.",
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Records: []string{"text1"},
|
||||
})
|
||||
client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
|
||||
Name: "xxx.example.com.",
|
||||
Type: "SRV",
|
||||
Records: []string{"http://test.com:1234"},
|
||||
})
|
||||
rs14ID, _ := client.CreateRecordSet(zone1ID, recordsets.CreateOpts{
|
||||
Name: "ftp.example.com.",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Records: []string{"10.1.1.2"},
|
||||
})
|
||||
|
||||
zone2ID := client.AddZone(zones.Zone{
|
||||
Name: "test.net.",
|
||||
Type: "PRIMARY",
|
||||
Status: "ACTIVE",
|
||||
})
|
||||
rs21ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{
|
||||
Name: "srv.test.net.",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Records: []string{"10.2.1.1", "10.2.1.2"},
|
||||
})
|
||||
rs22ID, _ := client.CreateRecordSet(zone2ID, recordsets.CreateOpts{
|
||||
Name: "db.test.net.",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Records: []string{"sql.test.net."},
|
||||
})
|
||||
expected := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "www.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.1.1.1"},
|
||||
Labels: map[string]string{
|
||||
designateRecordSetID: rs11ID,
|
||||
designateZoneID: zone1ID,
|
||||
designateOriginalRecords: "10.1.1.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "www.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"text1"},
|
||||
Labels: map[string]string{
|
||||
designateRecordSetID: rs12ID,
|
||||
designateZoneID: zone1ID,
|
||||
designateOriginalRecords: "text1",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "ftp.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.1.1.2"},
|
||||
Labels: map[string]string{
|
||||
designateRecordSetID: rs14ID,
|
||||
designateZoneID: zone1ID,
|
||||
designateOriginalRecords: "10.1.1.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.2.1.1"},
|
||||
Labels: map[string]string{
|
||||
designateRecordSetID: rs21ID,
|
||||
designateZoneID: zone2ID,
|
||||
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.2.1.2"},
|
||||
Labels: map[string]string{
|
||||
designateRecordSetID: rs21ID,
|
||||
designateZoneID: zone2ID,
|
||||
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "db.test.net",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"sql.test.net"},
|
||||
Labels: map[string]string{
|
||||
designateRecordSetID: rs22ID,
|
||||
designateZoneID: zone2ID,
|
||||
designateOriginalRecords: "sql.test.net.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
endpoints, err := client.ToProvider().Records()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out:
|
||||
for _, ep := range endpoints {
|
||||
for i, ex := range expected {
|
||||
if reflect.DeepEqual(ep, ex) {
|
||||
expected = append(expected[:i], expected[i+1:]...)
|
||||
continue out
|
||||
}
|
||||
}
|
||||
t.Errorf("unexpected endpoint %s/%s -> %s", ep.DNSName, ep.RecordType, ep.Targets)
|
||||
}
|
||||
if len(expected) != 0 {
|
||||
t.Errorf("not all expected endpoints were returned. Remained: %v", expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesignateCreateRecords(t *testing.T) {
|
||||
client := newFakeDesignateClient()
|
||||
testDesignateCreateRecords(t, client)
|
||||
}
|
||||
|
||||
func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet {
|
||||
|
||||
for i, zoneName := range []string{"example.com.", "test.net."} {
|
||||
client.AddZone(zones.Zone{
|
||||
ID: fmt.Sprintf("zone-%d", i+1),
|
||||
Name: zoneName,
|
||||
Type: "PRIMARY",
|
||||
Status: "ACTIVE",
|
||||
})
|
||||
}
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "www.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.1.1.1"},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
{
|
||||
DNSName: "www.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"text1"},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
{
|
||||
DNSName: "ftp.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.1.1.2"},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.2.1.1"},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.2.1.2"},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
{
|
||||
DNSName: "db.test.net",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"sql.test.net"},
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
}
|
||||
expected := []*recordsets.RecordSet{
|
||||
{
|
||||
Name: "www.example.com.",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Records: []string{"10.1.1.1"},
|
||||
ZoneID: "zone-1",
|
||||
},
|
||||
{
|
||||
Name: "www.example.com.",
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Records: []string{"text1"},
|
||||
ZoneID: "zone-1",
|
||||
},
|
||||
{
|
||||
Name: "ftp.example.com.",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Records: []string{"10.1.1.2"},
|
||||
ZoneID: "zone-1",
|
||||
},
|
||||
{
|
||||
Name: "srv.test.net.",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Records: []string{"10.2.1.1", "10.2.1.2"},
|
||||
ZoneID: "zone-2",
|
||||
},
|
||||
{
|
||||
Name: "db.test.net.",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Records: []string{"sql.test.net."},
|
||||
ZoneID: "zone-2",
|
||||
},
|
||||
}
|
||||
expectedCopy := make([]*recordsets.RecordSet, len(expected))
|
||||
copy(expectedCopy, expected)
|
||||
|
||||
err := client.ToProvider().ApplyChanges(&plan.Changes{Create: endpoints})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client.ForEachZone(func(zone *zones.Zone) error {
|
||||
client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
|
||||
id := recordSet.ID
|
||||
recordSet.ID = ""
|
||||
for i, ex := range expected {
|
||||
sort.Strings(recordSet.Records)
|
||||
if reflect.DeepEqual(ex, recordSet) {
|
||||
ex.ID = id
|
||||
recordSet.ID = id
|
||||
expected = append(expected[:i], expected[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
if len(expected) != 0 {
|
||||
t.Errorf("not all expected record-sets were created. Remained: %v", expected)
|
||||
}
|
||||
return expectedCopy
|
||||
}
|
||||
|
||||
func TestDesignateUpdateRecords(t *testing.T) {
|
||||
client := newFakeDesignateClient()
|
||||
testDesignateUpdateRecords(t, client)
|
||||
}
|
||||
|
||||
func testDesignateUpdateRecords(t *testing.T, client *fakeDesignateClient) []*recordsets.RecordSet {
|
||||
expected := testDesignateCreateRecords(t, client)
|
||||
|
||||
updatesOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "ftp.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.1.1.2"},
|
||||
Labels: map[string]string{
|
||||
designateZoneID: "zone-1",
|
||||
designateRecordSetID: expected[2].ID,
|
||||
designateOriginalRecords: "10.1.1.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net.",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.2.1.2"},
|
||||
Labels: map[string]string{
|
||||
designateZoneID: "zone-2",
|
||||
designateRecordSetID: expected[3].ID,
|
||||
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
updatesNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "ftp.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.3.3.1"},
|
||||
Labels: map[string]string{
|
||||
designateZoneID: "zone-1",
|
||||
designateRecordSetID: expected[2].ID,
|
||||
designateOriginalRecords: "10.1.1.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net.",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.3.3.2"},
|
||||
Labels: map[string]string{
|
||||
designateZoneID: "zone-2",
|
||||
designateRecordSetID: expected[3].ID,
|
||||
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedCopy := make([]*recordsets.RecordSet, len(expected))
|
||||
copy(expectedCopy, expected)
|
||||
|
||||
expected[2].Records = []string{"10.3.3.1"}
|
||||
expected[3].Records = []string{"10.2.1.1", "10.3.3.2"}
|
||||
|
||||
err := client.ToProvider().ApplyChanges(&plan.Changes{UpdateOld: updatesOld, UpdateNew: updatesNew})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client.ForEachZone(func(zone *zones.Zone) error {
|
||||
client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
|
||||
for i, ex := range expected {
|
||||
sort.Strings(recordSet.Records)
|
||||
if reflect.DeepEqual(ex, recordSet) {
|
||||
expected = append(expected[:i], expected[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
if len(expected) != 0 {
|
||||
t.Errorf("not all expected record-sets were updated. Remained: %v", expected)
|
||||
}
|
||||
return expectedCopy
|
||||
}
|
||||
|
||||
func TestDesignateDeleteRecords(t *testing.T) {
|
||||
client := newFakeDesignateClient()
|
||||
testDesignateDeleteRecords(t, client)
|
||||
}
|
||||
|
||||
func testDesignateDeleteRecords(t *testing.T, client *fakeDesignateClient) {
|
||||
expected := testDesignateUpdateRecords(t, client)
|
||||
deletes := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "www.example.com.",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.1.1.1"},
|
||||
Labels: map[string]string{
|
||||
designateZoneID: "zone-1",
|
||||
designateRecordSetID: expected[0].ID,
|
||||
designateOriginalRecords: "10.1.1.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "srv.test.net.",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"10.2.1.1"},
|
||||
Labels: map[string]string{
|
||||
designateZoneID: "zone-2",
|
||||
designateRecordSetID: expected[3].ID,
|
||||
designateOriginalRecords: "10.2.1.1\00010.3.3.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
expected[3].Records = []string{"10.3.3.2"}
|
||||
expected = expected[1:]
|
||||
|
||||
err := client.ToProvider().ApplyChanges(&plan.Changes{Delete: deletes})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client.ForEachZone(func(zone *zones.Zone) error {
|
||||
client.ForEachRecordSet(zone.ID, func(recordSet *recordsets.RecordSet) error {
|
||||
for i, ex := range expected {
|
||||
sort.Strings(recordSet.Records)
|
||||
if reflect.DeepEqual(ex, recordSet) {
|
||||
expected = append(expected[:i], expected[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
t.Errorf("unexpected record-set %s/%s -> %v", recordSet.Name, recordSet.Type, recordSet.Records)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
if len(expected) != 0 {
|
||||
t.Errorf("not all expected record-sets were deleted. Remained: %v", expected)
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ package provider
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
@ -105,7 +106,15 @@ func (p *DigitalOceanProvider) Records() ([]*endpoint.Endpoint, error) {
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(r.Type) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(r.Name, r.Data, r.Type))
|
||||
name := r.Name + "." + zone.Name
|
||||
|
||||
// root name is identified by @ and should be
|
||||
// translated to zone name for the endpoint entry.
|
||||
if r.Name == "@" {
|
||||
name = zone.Name
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(name, r.Type, r.Data))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -196,6 +205,15 @@ func (p *DigitalOceanProvider) submitChanges(changes []*DigitalOceanChange) erro
|
||||
if p.DryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
change.ResourceRecordSet.Name = strings.TrimSuffix(change.ResourceRecordSet.Name, "."+zoneName)
|
||||
|
||||
// record at the root should be defined as @ instead of
|
||||
// the full domain name
|
||||
if change.ResourceRecordSet.Name == zoneName {
|
||||
change.ResourceRecordSet.Name = "@"
|
||||
}
|
||||
|
||||
switch change.Action {
|
||||
case DigitalOceanCreate:
|
||||
_, _, err = p.Client.CreateRecord(context.TODO(), zoneName,
|
||||
@ -258,7 +276,7 @@ func newDigitalOceanChange(action string, endpoint *endpoint.Endpoint) *DigitalO
|
||||
ResourceRecordSet: godo.DomainRecord{
|
||||
Name: endpoint.DNSName,
|
||||
Type: endpoint.RecordType,
|
||||
Data: endpoint.Target,
|
||||
Data: endpoint.Targets[0],
|
||||
},
|
||||
}
|
||||
return change
|
||||
|
@ -41,7 +41,7 @@ type mockDigitalOceanClient struct{}
|
||||
|
||||
func (m *mockDigitalOceanClient) List(ctx context.Context, opt *godo.ListOptions) ([]godo.Domain, *godo.Response, error) {
|
||||
if opt == nil || opt.Page == 0 {
|
||||
return []godo.Domain{{Name: "foo.com"}}, &godo.Response{
|
||||
return []godo.Domain{{Name: "foo.com"}, {Name: "example.com"}}, &godo.Response{
|
||||
Links: &godo.Links{
|
||||
Pages: &godo.Pages{
|
||||
Next: "http://example.com/v2/domains/?page=2",
|
||||
@ -58,7 +58,7 @@ func (m *mockDigitalOceanClient) Create(context.Context, *godo.DomainCreateReque
|
||||
}
|
||||
|
||||
func (m *mockDigitalOceanClient) CreateRecord(context.Context, string, *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
||||
return &godo.DomainRecord{ID: 1}, nil, nil
|
||||
return &godo.DomainRecord{ID: 1, Name: "new", Type: "CNAME"}, nil, nil
|
||||
}
|
||||
|
||||
func (m *mockDigitalOceanClient) Delete(context.Context, string) (*godo.Response, error) {
|
||||
@ -80,9 +80,14 @@ func (m *mockDigitalOceanClient) Record(ctx context.Context, domain string, id i
|
||||
}
|
||||
|
||||
func (m *mockDigitalOceanClient) Records(ctx context.Context, domain string, opt *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) {
|
||||
if domain == "foo.com" {
|
||||
switch domain {
|
||||
case "foo.com":
|
||||
if opt == nil || opt.Page == 0 {
|
||||
return []godo.DomainRecord{{ID: 1, Name: "foo.ext-dns-test.foo.com.", Type: "CNAME"}, {ID: 2, Name: "bar.ext-dns-test.foo.com.", Type: "CNAME"}}, &godo.Response{
|
||||
return []godo.DomainRecord{
|
||||
{ID: 1, Name: "foo.ext-dns-test", Type: "CNAME"},
|
||||
{ID: 2, Name: "bar.ext-dns-test", Type: "CNAME"},
|
||||
{ID: 3, Name: "@", Type: endpoint.RecordTypeCNAME},
|
||||
}, &godo.Response{
|
||||
Links: &godo.Links{
|
||||
Pages: &godo.Pages{
|
||||
Next: "http://example.com/v2/domains/?page=2",
|
||||
@ -91,9 +96,22 @@ func (m *mockDigitalOceanClient) Records(ctx context.Context, domain string, opt
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return []godo.DomainRecord{{ID: 3, Name: "baz.ext-dns-test.foo.com.", Type: "A"}}, nil, nil
|
||||
return []godo.DomainRecord{{ID: 3, Name: "baz.ext-dns-test", Type: "A"}}, nil, nil
|
||||
case "example.com":
|
||||
if opt == nil || opt.Page == 0 {
|
||||
return []godo.DomainRecord{{ID: 1, Name: "new", Type: "CNAME"}}, &godo.Response{
|
||||
Links: &godo.Links{
|
||||
Pages: &godo.Pages{
|
||||
Next: "http://example.com/v2/domains/?page=2",
|
||||
Last: "1234",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil, nil
|
||||
default:
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
type mockDigitalOceanListFail struct{}
|
||||
@ -386,7 +404,7 @@ func (m *mockDigitalOceanCreateRecordsFail) Records(ctx context.Context, domain
|
||||
|
||||
func TestNewDigitalOceanChanges(t *testing.T) {
|
||||
action := DigitalOceanCreate
|
||||
endpoints := []*endpoint.Endpoint{{DNSName: "new", Target: "target"}}
|
||||
endpoints := []*endpoint.Endpoint{{DNSName: "new", Targets: endpoint.Targets{"target"}}}
|
||||
_ = newDigitalOceanChanges(action, endpoints)
|
||||
}
|
||||
|
||||
@ -402,38 +420,23 @@ func TestDigitalOceanZones(t *testing.T) {
|
||||
}
|
||||
|
||||
validateDigitalOceanZones(t, zones, []godo.Domain{
|
||||
{Name: "foo.com"}, {Name: "bar.com"},
|
||||
{Name: "foo.com"}, {Name: "example.com"}, {Name: "bar.com"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestDigitalOceanRecords(t *testing.T) {
|
||||
provider := &DigitalOceanProvider{
|
||||
Client: &mockDigitalOceanClient{},
|
||||
}
|
||||
|
||||
records, err := provider.Records()
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, len(records))
|
||||
|
||||
provider.Client = &mockDigitalOceanRecordsFail{}
|
||||
_, err = provider.Records()
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanApplyChanges(t *testing.T) {
|
||||
changes := &plan.Changes{}
|
||||
provider := &DigitalOceanProvider{
|
||||
Client: &mockDigitalOceanClient{},
|
||||
}
|
||||
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.bar.com", Target: "target"}, {DNSName: "new.ext-dns-test.unexpected.com", Target: "target"}}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.com", Target: "target"}}
|
||||
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.de", Target: "target-old"}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.foo.com", Target: "target-new"}}
|
||||
changes.Create = []*endpoint.Endpoint{
|
||||
{DNSName: "new.ext-dns-test.bar.com", Targets: endpoint.Targets{"target"}},
|
||||
{DNSName: "new.ext-dns-test.unexpected.com", Targets: endpoint.Targets{"target"}},
|
||||
{DNSName: "bar.com", Targets: endpoint.Targets{"target"}},
|
||||
}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.com", Targets: endpoint.Targets{"target"}}}
|
||||
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.de", Targets: endpoint.Targets{"target-old"}}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.foo.com", Targets: endpoint.Targets{"target-new"}}}
|
||||
err := provider.ApplyChanges(changes)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
@ -485,3 +488,37 @@ func validateDigitalOceanZones(t *testing.T, zones []godo.Domain, expected []god
|
||||
assert.Equal(t, expected[i].Name, zone.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanRecord(t *testing.T) {
|
||||
provider := &DigitalOceanProvider{
|
||||
Client: &mockDigitalOceanClient{},
|
||||
}
|
||||
|
||||
records, err := provider.fetchRecords("example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []godo.DomainRecord{{ID: 1, Name: "new", Type: "CNAME"}}
|
||||
require.Len(t, records, len(expected))
|
||||
for i, record := range records {
|
||||
assert.Equal(t, expected[i].Name, record.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanAllRecords(t *testing.T) {
|
||||
provider := &DigitalOceanProvider{
|
||||
Client: &mockDigitalOceanClient{},
|
||||
}
|
||||
|
||||
records, err := provider.Records()
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
require.Equal(t, 5, len(records))
|
||||
|
||||
provider.Client = &mockDigitalOceanRecordsFail{}
|
||||
_, err = provider.Records()
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail, %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const dnsimpleRecordTTL = 3600 // Default TTL of 1 hour if not set (DNSimple's default)
|
||||
|
||||
type identityService struct {
|
||||
service *dnsimple.IdentityService
|
||||
}
|
||||
@ -124,7 +126,11 @@ func NewDnsimpleProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, d
|
||||
// Returns a list of filtered Zones
|
||||
func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
||||
zones := make(map[string]dnsimple.Zone)
|
||||
zonesResponse, err := p.client.ListZones(p.accountID, &dnsimple.ZoneListOptions{})
|
||||
page := 1
|
||||
listOptions := &dnsimple.ZoneListOptions{}
|
||||
for {
|
||||
listOptions.Page = page
|
||||
zonesResponse, err := p.client.ListZones(p.accountID, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -139,6 +145,12 @@ func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
||||
|
||||
zones[strconv.Itoa(zone.ID)] = zone
|
||||
}
|
||||
|
||||
page++
|
||||
if page > zonesResponse.Pagination.TotalPages {
|
||||
break
|
||||
}
|
||||
}
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
@ -149,7 +161,11 @@ func (p *dnsimpleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
return nil, err
|
||||
}
|
||||
for _, zone := range zones {
|
||||
records, err := p.client.ListRecords(p.accountID, zone.Name, &dnsimple.ZoneRecordListOptions{})
|
||||
page := 1
|
||||
listOptions := &dnsimple.ZoneRecordListOptions{}
|
||||
for {
|
||||
listOptions.Page = page
|
||||
records, err := p.client.ListRecords(p.accountID, zone.Name, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -160,7 +176,12 @@ func (p *dnsimpleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
default:
|
||||
continue
|
||||
}
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(record.Name+"."+record.ZoneID, record.Content, record.Type))
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(record.Name+"."+record.ZoneID, record.Type, endpoint.TTL(record.TTL), record.Content))
|
||||
}
|
||||
page++
|
||||
if page > records.Pagination.TotalPages {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return endpoints, nil
|
||||
@ -168,12 +189,18 @@ func (p *dnsimpleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
|
||||
// newDnsimpleChange initializes a new change to dns records
|
||||
func newDnsimpleChange(action string, e *endpoint.Endpoint) *dnsimpleChange {
|
||||
ttl := dnsimpleRecordTTL
|
||||
if e.RecordTTL.IsConfigured() {
|
||||
ttl = int(e.RecordTTL)
|
||||
}
|
||||
|
||||
change := &dnsimpleChange{
|
||||
Action: action,
|
||||
ResourceRecordSet: dnsimple.ZoneRecord{
|
||||
Name: e.DNSName,
|
||||
Type: e.RecordType,
|
||||
Content: e.Target,
|
||||
Content: e.Targets[0],
|
||||
TTL: ttl,
|
||||
},
|
||||
}
|
||||
return change
|
||||
@ -239,15 +266,26 @@ func (p *dnsimpleProvider) submitChanges(changes []*dnsimpleChange) error {
|
||||
|
||||
// Returns the record ID for a given record name and zone
|
||||
func (p *dnsimpleProvider) GetRecordID(zone string, recordName string) (recordID int, err error) {
|
||||
records, err := p.client.ListRecords(p.accountID, zone, &dnsimple.ZoneRecordListOptions{})
|
||||
page := 1
|
||||
listOptions := &dnsimple.ZoneRecordListOptions{Name: recordName}
|
||||
for {
|
||||
listOptions.Page = page
|
||||
records, err := p.client.ListRecords(p.accountID, zone, listOptions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, record := range records.Data {
|
||||
if record.Name == recordName {
|
||||
return record.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
page++
|
||||
if page > records.Pagination.TotalPages {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("No record id found")
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ func TestDnsimpleServices(t *testing.T) {
|
||||
}
|
||||
zones := []dnsimple.Zone{firstZone, secondZone}
|
||||
dnsimpleListZonesResponse = dnsimple.ZonesResponse{
|
||||
Response: dnsimple.Response{},
|
||||
Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
|
||||
Data: zones,
|
||||
}
|
||||
firstRecord := dnsimple.ZoneRecord{
|
||||
@ -74,28 +74,48 @@ func TestDnsimpleServices(t *testing.T) {
|
||||
Priority: 0,
|
||||
Type: "A",
|
||||
}
|
||||
records := []dnsimple.ZoneRecord{firstRecord, secondRecord}
|
||||
thirdRecord := dnsimple.ZoneRecord{
|
||||
ID: 3,
|
||||
ZoneID: "example.com",
|
||||
ParentID: 0,
|
||||
Name: "custom-ttl",
|
||||
Content: "target",
|
||||
TTL: 60,
|
||||
Priority: 0,
|
||||
Type: "CNAME",
|
||||
}
|
||||
|
||||
records := []dnsimple.ZoneRecord{firstRecord, secondRecord, thirdRecord}
|
||||
dnsimpleListRecordsResponse = dnsimple.ZoneRecordsResponse{
|
||||
Response: dnsimple.Response{},
|
||||
Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
|
||||
Data: records,
|
||||
}
|
||||
|
||||
// Setup mock services
|
||||
mockDNS := &mockDnsimpleZoneServiceInterface{}
|
||||
mockDNS.On("ListZones", "1", &dnsimple.ZoneListOptions{}).Return(&dnsimpleListZonesResponse, nil)
|
||||
mockDNS.On("ListZones", "2", &dnsimple.ZoneListOptions{}).Return(nil, fmt.Errorf("Account ID not found"))
|
||||
mockDNS.On("ListRecords", "1", "example.com", &dnsimple.ZoneRecordListOptions{}).Return(&dnsimpleListRecordsResponse, nil)
|
||||
mockDNS.On("ListRecords", "1", "example-beta.com", &dnsimple.ZoneRecordListOptions{}).Return(&dnsimple.ZoneRecordsResponse{}, nil)
|
||||
mockDNS.On("ListZones", "1", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleListZonesResponse, nil)
|
||||
mockDNS.On("ListZones", "2", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(nil, fmt.Errorf("Account ID not found"))
|
||||
mockDNS.On("ListRecords", "1", "example.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleListRecordsResponse, nil)
|
||||
mockDNS.On("ListRecords", "1", "example-beta.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimple.ZoneRecordsResponse{Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}}}, nil)
|
||||
|
||||
for _, record := range records {
|
||||
simpleRecord := dnsimple.ZoneRecord{
|
||||
Name: record.Name,
|
||||
Type: record.Type,
|
||||
Content: record.Content,
|
||||
TTL: record.TTL,
|
||||
}
|
||||
|
||||
dnsimpleRecordResponse := dnsimple.ZoneRecordsResponse{
|
||||
Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}},
|
||||
Data: []dnsimple.ZoneRecord{record},
|
||||
}
|
||||
|
||||
mockDNS.On("ListRecords", "1", record.ZoneID, &dnsimple.ZoneRecordListOptions{Name: record.Name, ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleRecordResponse, nil)
|
||||
mockDNS.On("CreateRecord", "1", record.ZoneID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("DeleteRecord", "1", record.ZoneID, record.ID).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("UpdateRecord", "1", record.ZoneID, record.ID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("UpdateRecord", "1", record.ZoneID, record.ID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
}
|
||||
|
||||
mockProvider = dnsimpleProvider{client: mockDNS}
|
||||
@ -131,9 +151,12 @@ func testDnsimpleProviderRecords(t *testing.T) {
|
||||
}
|
||||
func testDnsimpleProviderApplyChanges(t *testing.T) {
|
||||
changes := &plan.Changes{}
|
||||
changes.Create = []*endpoint.Endpoint{{DNSName: "example.example.com", Target: "target", RecordType: endpoint.RecordTypeCNAME}}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "example-beta.example.com", Target: "127.0.0.1", RecordType: endpoint.RecordTypeA}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "example.example.com", Target: "target", RecordType: endpoint.RecordTypeCNAME}}
|
||||
changes.Create = []*endpoint.Endpoint{
|
||||
{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
{DNSName: "custom-ttl.example.com", RecordTTL: 60, Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "example-beta.example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME}}
|
||||
|
||||
mockProvider.accountID = "1"
|
||||
err := mockProvider.ApplyChanges(changes)
|
||||
|
@ -19,6 +19,7 @@ package provider
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -37,6 +38,12 @@ const (
|
||||
// may be made configurable in the future but 20K records seems like enough for a few zones
|
||||
cacheMaxSize = 20000
|
||||
|
||||
// two consecutive bad logins happen at least this many seconds appart
|
||||
// While it is easy to get the username right, misconfiguring the password
|
||||
// can get account blocked. Exit(1) is not a good solution
|
||||
// as k8s will restart the pod and another login attempt will be made
|
||||
badLoginMinIntervalSeconds = 30 * 60
|
||||
|
||||
// this prefix must be stripped from resource links before feeding them to dynect.Client.Do()
|
||||
restAPIPrefix = "/REST/"
|
||||
)
|
||||
@ -60,17 +67,21 @@ func (c *cache) Put(link string, ep *endpoint.Endpoint) {
|
||||
|
||||
c.contents[link] = &entry{
|
||||
ep: ep,
|
||||
expires: int64(time.Now().Unix()) + int64(ep.RecordTTL),
|
||||
expires: unixNow() + int64(ep.RecordTTL),
|
||||
}
|
||||
}
|
||||
|
||||
func unixNow() int64 {
|
||||
return int64(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (c *cache) Get(link string) *endpoint.Endpoint {
|
||||
result, ok := c.contents[link]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := int64(time.Now().Unix())
|
||||
now := unixNow()
|
||||
|
||||
if result.expires < now {
|
||||
delete(c.contents, link)
|
||||
@ -88,6 +99,7 @@ type DynConfig struct {
|
||||
CustomerName string
|
||||
Username string
|
||||
Password string
|
||||
MinTTLSeconds int
|
||||
AppVersion string
|
||||
DynVersion string
|
||||
}
|
||||
@ -96,6 +108,7 @@ type DynConfig struct {
|
||||
type dynProviderState struct {
|
||||
DynConfig
|
||||
Cache *cache
|
||||
LastLoginErrorTime int64
|
||||
}
|
||||
|
||||
// ZoneChange is missing from dynect: https://help.dyn.com/get-zone-changeset-api/
|
||||
@ -166,13 +179,17 @@ func filterAndFixLinks(links []string, filter DomainFilter) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func fixMissingTTL(ttl endpoint.TTL) string {
|
||||
func fixMissingTTL(ttl endpoint.TTL, minTTLSeconds int) string {
|
||||
i := dynDefaultTTL
|
||||
if ttl.IsConfigured() {
|
||||
if int(ttl) < minTTLSeconds {
|
||||
i = minTTLSeconds
|
||||
} else {
|
||||
i = int(ttl)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d", i)
|
||||
return strconv.Itoa(i)
|
||||
}
|
||||
|
||||
// merge produces a singe list of records that can be used as a replacement.
|
||||
@ -197,7 +214,7 @@ func merge(updateOld, updateNew []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
continue
|
||||
}
|
||||
|
||||
if matchingNew.Target != old.Target {
|
||||
if !matchingNew.Targets.Same(old.Targets) {
|
||||
// new target: always update, TTL will be overwritten too if necessary
|
||||
result = append(result, matchingNew)
|
||||
continue
|
||||
@ -258,7 +275,7 @@ func (d *dynProviderState) recordLinkToEndpoint(client *dynect.Client, recordLin
|
||||
DNSName: rec.Data.FQDN,
|
||||
RecordTTL: endpoint.TTL(rec.Data.TTL),
|
||||
RecordType: rec.Data.RecordType,
|
||||
Target: target,
|
||||
Targets: endpoint.Targets{target},
|
||||
}
|
||||
|
||||
log.Debugf("Fetched new endpoint for %s: %+v", recordLink, result)
|
||||
@ -280,11 +297,11 @@ func endpointToRecord(ep *endpoint.Endpoint) *dynect.DataBlock {
|
||||
result := dynect.DataBlock{}
|
||||
|
||||
if ep.RecordType == endpoint.RecordTypeA {
|
||||
result.Address = ep.Target
|
||||
result.Address = ep.Targets[0]
|
||||
} else if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
result.CName = ep.Target
|
||||
result.CName = ep.Targets[0]
|
||||
} else if ep.RecordType == endpoint.RecordTypeTXT {
|
||||
result.TxtData = ep.Target
|
||||
result.TxtData = ep.Targets[0]
|
||||
}
|
||||
|
||||
return &result
|
||||
@ -320,7 +337,6 @@ func (d *dynProviderState) buildLinkToRecord(ep *endpoint.Endpoint) string {
|
||||
}
|
||||
|
||||
if matchingZone == "" {
|
||||
fmt.Printf("no zone")
|
||||
// no matching zone, ignore
|
||||
return ""
|
||||
}
|
||||
@ -337,6 +353,12 @@ func (d *dynProviderState) buildLinkToRecord(ep *endpoint.Endpoint) string {
|
||||
// This method also stores the DynAPI version.
|
||||
// Don't user the dynect.Client.Login()
|
||||
func (d *dynProviderState) login() (*dynect.Client, error) {
|
||||
if d.LastLoginErrorTime != 0 {
|
||||
secondsSinceLastError := unixNow() - d.LastLoginErrorTime
|
||||
if secondsSinceLastError < badLoginMinIntervalSeconds {
|
||||
return nil, fmt.Errorf("will not attempt an API call as the last login failure occurred just %ds ago", secondsSinceLastError)
|
||||
}
|
||||
}
|
||||
client := dynect.NewClient(d.CustomerName)
|
||||
|
||||
var req = dynect.LoginBlock{
|
||||
@ -348,9 +370,11 @@ func (d *dynProviderState) login() (*dynect.Client, error) {
|
||||
|
||||
err := client.Do("POST", "Session", req, &resp)
|
||||
if err != nil {
|
||||
d.LastLoginErrorTime = unixNow()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.LastLoginErrorTime = 0
|
||||
client.Token = resp.Data.Token
|
||||
|
||||
// this is the only change from the original
|
||||
@ -371,7 +395,7 @@ func (d *dynProviderState) buildRecordRequest(ep *endpoint.Endpoint) (string, *d
|
||||
}
|
||||
|
||||
record := dynect.RecordRequest{
|
||||
TTL: fixMissingTTL(ep.RecordTTL),
|
||||
TTL: fixMissingTTL(ep.RecordTTL, d.MinTTLSeconds),
|
||||
RData: *endpointToRecord(ep),
|
||||
}
|
||||
return link, &record
|
||||
|
@ -32,13 +32,13 @@ func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
@ -47,13 +47,13 @@ func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -66,13 +66,13 @@ func TestDynMerge_UpdateOnTTLChanges(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -81,13 +81,13 @@ func TestDynMerge_UpdateOnTTLChanges(t *testing.T) {
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(77),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(10),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -102,13 +102,13 @@ func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -117,13 +117,13 @@ func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1-changed",
|
||||
Targets: endpoint.Targets{"target1-changed"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -131,20 +131,20 @@ func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
|
||||
|
||||
merged := merge(updateOld, updateNew)
|
||||
assert.Equal(t, 1, len(merged))
|
||||
assert.Equal(t, "target1-changed", merged[0].Target)
|
||||
assert.Equal(t, "target1-changed", merged[0].Targets[0])
|
||||
}
|
||||
|
||||
func TestDynMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -153,13 +153,13 @@ func TestDynMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Target: "target1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Target: "target2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
@ -190,9 +190,9 @@ func TestDyn_endpointToRecord(t *testing.T) {
|
||||
ep *endpoint.Endpoint
|
||||
extractor func(*dynect.DataBlock) string
|
||||
}{
|
||||
{endpoint.NewEndpoint("address", "the-target", "A"), func(b *dynect.DataBlock) string { return b.Address }},
|
||||
{endpoint.NewEndpoint("cname", "the-target", "CNAME"), func(b *dynect.DataBlock) string { return b.CName }},
|
||||
{endpoint.NewEndpoint("text", "the-target", "TXT"), func(b *dynect.DataBlock) string { return b.TxtData }},
|
||||
{endpoint.NewEndpoint("address", "A", "the-target"), func(b *dynect.DataBlock) string { return b.Address }},
|
||||
{endpoint.NewEndpoint("cname", "CNAME", "the-target"), func(b *dynect.DataBlock) string { return b.CName }},
|
||||
{endpoint.NewEndpoint("text", "TXT", "the-target"), func(b *dynect.DataBlock) string { return b.TxtData }},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
@ -213,11 +213,11 @@ func TestDyn_buildLinkToRecord(t *testing.T) {
|
||||
ep *endpoint.Endpoint
|
||||
link string
|
||||
}{
|
||||
{endpoint.NewEndpoint("sub.the-target.example.com", "address", "A"), "ARecord/example.com/sub.the-target.example.com/"},
|
||||
{endpoint.NewEndpoint("the-target.example.com", "cname", "CNAME"), "CNAMERecord/example.com/the-target.example.com/"},
|
||||
{endpoint.NewEndpoint("the-target.example.com", "text", "TXT"), "TXTRecord/example.com/the-target.example.com/"},
|
||||
{endpoint.NewEndpoint("the-target.google.com", "text", "TXT"), ""},
|
||||
{endpoint.NewEndpoint("mail.example.com", "text", "TXT"), ""},
|
||||
{endpoint.NewEndpoint("sub.the-target.example.com", "A", "address"), "ARecord/example.com/sub.the-target.example.com/"},
|
||||
{endpoint.NewEndpoint("the-target.example.com", "CNAME", "cname"), "CNAMERecord/example.com/the-target.example.com/"},
|
||||
{endpoint.NewEndpoint("the-target.example.com", "TXT", "text"), "TXTRecord/example.com/the-target.example.com/"},
|
||||
{endpoint.NewEndpoint("the-target.google.com", "TXT", "text"), ""},
|
||||
{endpoint.NewEndpoint("mail.example.com", "TXT", "text"), ""},
|
||||
{nil, ""},
|
||||
}
|
||||
|
||||
@ -255,10 +255,13 @@ func TestDyn_filterAndFixLinks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDyn_fixMissingTTL(t *testing.T) {
|
||||
assert.Equal(t, fmt.Sprintf("%v", dynDefaultTTL), fixMissingTTL(endpoint.TTL(0)))
|
||||
assert.Equal(t, fmt.Sprintf("%v", dynDefaultTTL), fixMissingTTL(endpoint.TTL(0), 0))
|
||||
|
||||
// nothing to fix
|
||||
assert.Equal(t, "111", fixMissingTTL(endpoint.TTL(111)))
|
||||
assert.Equal(t, "111", fixMissingTTL(endpoint.TTL(111), 25))
|
||||
|
||||
// apply min TTL
|
||||
assert.Equal(t, "1992", fixMissingTTL(endpoint.TTL(111), 1992))
|
||||
}
|
||||
|
||||
func TestDyn_cachePut(t *testing.T) {
|
||||
@ -268,7 +271,7 @@ func TestDyn_cachePut(t *testing.T) {
|
||||
|
||||
c.Put("link", &endpoint.Endpoint{
|
||||
DNSName: "name",
|
||||
Target: "target",
|
||||
Targets: endpoint.Targets{"target"},
|
||||
RecordTTL: endpoint.TTL(10000),
|
||||
RecordType: "A",
|
||||
})
|
||||
@ -284,7 +287,7 @@ func TestDyn_cachePutExpired(t *testing.T) {
|
||||
|
||||
c.Put("link", &endpoint.Endpoint{
|
||||
DNSName: "name",
|
||||
Target: "target",
|
||||
Targets: endpoint.Targets{"target"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: "A",
|
||||
})
|
||||
|
@ -18,8 +18,10 @@ package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/linki/instrumented_http"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@ -132,6 +134,14 @@ func NewGoogleProvider(project string, domainFilter DomainFilter, zoneIDFilter Z
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if project == "" {
|
||||
mProject, mErr := metadata.ProjectID()
|
||||
if mErr == nil {
|
||||
log.Infof("Google project auto-detected: %s", mProject)
|
||||
project = mProject
|
||||
}
|
||||
}
|
||||
|
||||
provider := &GoogleProvider{
|
||||
project: project,
|
||||
domainFilter: domainFilter,
|
||||
@ -151,7 +161,7 @@ func (p *GoogleProvider) Zones() (map[string]*dns.ManagedZone, error) {
|
||||
|
||||
f := func(resp *dns.ManagedZonesListResponse) error {
|
||||
for _, zone := range resp.ManagedZones {
|
||||
if p.domainFilter.Match(zone.DnsName) || p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) {
|
||||
if p.domainFilter.Match(zone.DnsName) && p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) {
|
||||
zones[zone.Name] = zone
|
||||
log.Debugf("Matched %s (zone: %s)", zone.DnsName, zone.Name)
|
||||
} else {
|
||||
@ -191,13 +201,20 @@ func (p *GoogleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
|
||||
f := func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||
for _, r := range resp.Rrsets {
|
||||
|
||||
if !supportedRecordType(r.Type) {
|
||||
continue
|
||||
}
|
||||
ep := &endpoint.Endpoint{
|
||||
DNSName: strings.TrimSuffix(r.Name, "."),
|
||||
RecordType: r.Type,
|
||||
Targets: make(endpoint.Targets, 0, len(r.Rrdatas)),
|
||||
}
|
||||
for _, rr := range r.Rrdatas {
|
||||
// each page is processed sequentially, no need for a mutex here.
|
||||
if supportedRecordType(r.Type) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(r.Name, rr, r.Type))
|
||||
}
|
||||
ep.Targets = append(ep.Targets, strings.TrimSuffix(rr, "."))
|
||||
}
|
||||
sort.Sort(ep.Targets)
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -347,9 +364,10 @@ func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
|
||||
// TODO(linki): works around appending a trailing dot to TXT records. I think
|
||||
// we should go back to storing DNS names with a trailing dot internally. This
|
||||
// way we can use it has is here and trim it off if it exists when necessary.
|
||||
target := ep.Target
|
||||
targets := make([]string, len(ep.Targets))
|
||||
copy(targets, []string(ep.Targets))
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
target = ensureTrailingDot(target)
|
||||
targets[0] = ensureTrailingDot(targets[0])
|
||||
}
|
||||
|
||||
// no annotation results in a Ttl of 0, default to 300 for backwards-compatability
|
||||
@ -360,7 +378,7 @@ func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
|
||||
|
||||
return &dns.ResourceRecordSet{
|
||||
Name: ensureTrailingDot(ep.DNSName),
|
||||
Rrdatas: []string{target},
|
||||
Rrdatas: targets,
|
||||
Ttl: ttl,
|
||||
Type: ep.RecordType,
|
||||
}
|
||||
|
@ -207,9 +207,9 @@ func TestGoogleZones(t *testing.T) {
|
||||
|
||||
func TestGoogleRecords(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("list-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("list-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("list-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("list-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
@ -222,12 +222,12 @@ func TestGoogleRecords(t *testing.T) {
|
||||
|
||||
func TestGoogleRecordsFilter(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(
|
||||
@ -247,12 +247,12 @@ func TestGoogleRecordsFilter(t *testing.T) {
|
||||
|
||||
// these records should be filtered out since they don't match a hosted zone or domain filter.
|
||||
ignoredEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("filter-create-test.zone-0.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-0.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("filter-delete-test.zone-0.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("filter-create-test.zone-3.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-3.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("filter-delete-test.zone-3.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("filter-create-test.zone-0.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-0.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("filter-delete-test.zone-0.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("filter-create-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("filter-delete-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
}
|
||||
|
||||
require.NoError(t, provider.CreateRecords(ignoredEndpoints))
|
||||
@ -268,9 +268,9 @@ func TestGoogleCreateRecords(t *testing.T) {
|
||||
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
require.NoError(t, provider.CreateRecords(records))
|
||||
@ -279,28 +279,28 @@ func TestGoogleCreateRecords(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleUpdateRecords(t *testing.T) {
|
||||
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
})
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
require.NoError(t, provider.UpdateRecords(updatedRecords, currentRecords))
|
||||
@ -309,17 +309,17 @@ func TestGoogleUpdateRecords(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleDeleteRecords(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
@ -346,43 +346,43 @@ func TestGoogleApplyChanges(t *testing.T) {
|
||||
NewZoneIDFilter([]string{""}),
|
||||
false,
|
||||
[]*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
},
|
||||
)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("filter-create-test.zone-3.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("nomatch-create-test.zone-0.ext-dns-test-2.gcp.zalan.do", "4.2.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("filter-create-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("nomatch-create-test.zone-0.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.1"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-3.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-3.ext-dns-test-2.gcp.zalan.do", "5.6.7.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("nomatch-update-test.zone-0.ext-dns-test-2.gcp.zalan.do", "8.7.6.5", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("filter-update-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "5.6.7.8"),
|
||||
endpoint.NewEndpoint("nomatch-update-test.zone-0.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.7.6.5"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("filter-delete-test.zone-3.ext-dns-test-2.gcp.zalan.do", "4.2.2.2", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("nomatch-delete-test.zone-0.ext-dns-test-2.gcp.zalan.do", "4.2.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("filter-delete-test.zone-3.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.2"),
|
||||
endpoint.NewEndpoint("nomatch-delete-test.zone-0.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.2.2.1"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
@ -398,48 +398,48 @@ func TestGoogleApplyChanges(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleApplyChangesDryRun(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), true, originalEndpoints)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
@ -466,13 +466,13 @@ func TestNewFilteredRecords(t *testing.T) {
|
||||
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := provider.newFilteredRecords([]*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA, 1),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA, 120),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", endpoint.RecordTypeCNAME, 4000),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, 1, "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, 120, "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, 4000, "bar.elb.amazonaws.com"),
|
||||
// test fallback to Ttl:300 when Ttl==0 :
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA, 0),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, 0, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, "qux.elb.amazonaws.com"),
|
||||
})
|
||||
|
||||
validateChangeRecords(t, records, []*dns.ResourceRecordSet{
|
||||
@ -593,6 +593,12 @@ func newGoogleProvider(t *testing.T, domainFilter DomainFilter, zoneIDFilter Zon
|
||||
DnsName: "zone-3.ext-dns-test-2.gcp.zalan.do.",
|
||||
})
|
||||
|
||||
// filtered out by domain filter
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
Name: "zone-4-ext-dns-test-3-gcp-zalan-do",
|
||||
DnsName: "zone-4.ext-dns-test-3.gcp.zalan.do.",
|
||||
})
|
||||
|
||||
setupGoogleRecords(t, provider, records)
|
||||
|
||||
provider.dryRun = dryRun
|
||||
|
@ -110,7 +110,7 @@ func (p *InfobloxProvider) Records() (endpoints []*endpoint.Endpoint, err error)
|
||||
return nil, err
|
||||
}
|
||||
for _, res := range resA {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, res.Ipv4Addr, endpoint.RecordTypeA))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, res.Ipv4Addr))
|
||||
}
|
||||
|
||||
// Include Host records since they should be treated synonymously with A records
|
||||
@ -126,7 +126,7 @@ func (p *InfobloxProvider) Records() (endpoints []*endpoint.Endpoint, err error)
|
||||
}
|
||||
for _, res := range resH {
|
||||
for _, ip := range res.Ipv4Addrs {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, ip.Ipv4Addr, endpoint.RecordTypeA))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr))
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ func (p *InfobloxProvider) Records() (endpoints []*endpoint.Endpoint, err error)
|
||||
return nil, err
|
||||
}
|
||||
for _, res := range resC {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, res.Canonical, endpoint.RecordTypeCNAME))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeCNAME, res.Canonical))
|
||||
}
|
||||
|
||||
var resT []ibclient.RecordTXT
|
||||
@ -160,7 +160,7 @@ func (p *InfobloxProvider) Records() (endpoints []*endpoint.Endpoint, err error)
|
||||
if _, err := strconv.Unquote(res.Text); err != nil {
|
||||
res.Text = strconv.Quote(res.Text)
|
||||
}
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, res.Text, endpoint.RecordTypeTXT))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeTXT, res.Text))
|
||||
}
|
||||
}
|
||||
return endpoints, nil
|
||||
@ -174,8 +174,8 @@ func (p *InfobloxProvider) ApplyChanges(changes *plan.Changes) error {
|
||||
}
|
||||
|
||||
created, deleted := p.mapChanges(zones, changes)
|
||||
p.createRecords(created)
|
||||
p.deleteRecords(deleted)
|
||||
p.createRecords(created)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (rec
|
||||
obj := ibclient.NewRecordA(
|
||||
ibclient.RecordA{
|
||||
Name: ep.DNSName,
|
||||
Ipv4Addr: ep.Target,
|
||||
Ipv4Addr: ep.Targets[0],
|
||||
},
|
||||
)
|
||||
if getObject {
|
||||
@ -275,7 +275,7 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (rec
|
||||
obj := ibclient.NewRecordCNAME(
|
||||
ibclient.RecordCNAME{
|
||||
Name: ep.DNSName,
|
||||
Canonical: ep.Target,
|
||||
Canonical: ep.Targets[0],
|
||||
},
|
||||
)
|
||||
if getObject {
|
||||
@ -292,13 +292,13 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (rec
|
||||
var res []ibclient.RecordTXT
|
||||
// The Infoblox API strips enclosing double quotes from TXT records lacking whitespace.
|
||||
// Here we reconcile that fact by making this state match that reality.
|
||||
if target, err2 := strconv.Unquote(ep.Target); err2 == nil && !strings.Contains(ep.Target, " ") {
|
||||
ep.Target = target
|
||||
if target, err2 := strconv.Unquote(ep.Targets[0]); err2 == nil && !strings.Contains(ep.Targets[0], " ") {
|
||||
ep.Targets = endpoint.Targets{target}
|
||||
}
|
||||
obj := ibclient.NewRecordTXT(
|
||||
ibclient.RecordTXT{
|
||||
Name: ep.DNSName,
|
||||
Text: ep.Target,
|
||||
Text: ep.Targets[0],
|
||||
},
|
||||
)
|
||||
if getObject {
|
||||
@ -323,7 +323,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
|
||||
"Would create %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Target,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
continue
|
||||
@ -333,7 +333,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
|
||||
"Creating %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Target,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
|
||||
@ -343,7 +343,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
|
||||
"Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Target,
|
||||
ep.Targets,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
@ -355,7 +355,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
|
||||
"Failed to create %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Target,
|
||||
ep.Targets,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
@ -378,7 +378,7 @@ func (p *InfobloxProvider) deleteRecords(deleted infobloxChangeMap) {
|
||||
"Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Target,
|
||||
ep.Targets,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
|
@ -44,8 +44,8 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string,
|
||||
client.createdEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
obj.(*ibclient.RecordA).Name,
|
||||
obj.(*ibclient.RecordA).Ipv4Addr,
|
||||
endpoint.RecordTypeA,
|
||||
obj.(*ibclient.RecordA).Ipv4Addr,
|
||||
),
|
||||
)
|
||||
ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordA).Name)), obj.(*ibclient.RecordA).Name)
|
||||
@ -55,8 +55,8 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string,
|
||||
client.createdEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
obj.(*ibclient.RecordCNAME).Name,
|
||||
obj.(*ibclient.RecordCNAME).Canonical,
|
||||
endpoint.RecordTypeCNAME,
|
||||
obj.(*ibclient.RecordCNAME).Canonical,
|
||||
),
|
||||
)
|
||||
ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordCNAME).Name)), obj.(*ibclient.RecordCNAME).Name)
|
||||
@ -67,8 +67,8 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string,
|
||||
client.createdEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
obj.(*ibclient.RecordHost).Name,
|
||||
i.Ipv4Addr,
|
||||
endpoint.RecordTypeA,
|
||||
i.Ipv4Addr,
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -79,8 +79,8 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string,
|
||||
client.createdEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
obj.(*ibclient.RecordTXT).Name,
|
||||
obj.(*ibclient.RecordTXT).Text,
|
||||
endpoint.RecordTypeTXT,
|
||||
obj.(*ibclient.RecordTXT).Text,
|
||||
),
|
||||
)
|
||||
obj.(*ibclient.RecordTXT).Ref = ref
|
||||
@ -183,8 +183,8 @@ func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err erro
|
||||
client.deletedEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
record.Name,
|
||||
"",
|
||||
endpoint.RecordTypeA,
|
||||
"",
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -201,8 +201,8 @@ func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err erro
|
||||
client.deletedEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
record.Name,
|
||||
"",
|
||||
endpoint.RecordTypeCNAME,
|
||||
"",
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -219,8 +219,8 @@ func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err erro
|
||||
client.deletedEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
record.Name,
|
||||
"",
|
||||
endpoint.RecordTypeA,
|
||||
"",
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -237,8 +237,8 @@ func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err erro
|
||||
client.deletedEndpoints,
|
||||
endpoint.NewEndpoint(
|
||||
record.Name,
|
||||
"",
|
||||
endpoint.RecordTypeTXT,
|
||||
"",
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -359,13 +359,13 @@ func TestInfobloxRecords(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("example.com", "123.123.123.122", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("example.com", "\"heritage=external-dns,external-dns/owner=default\"", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("nginx.example.com", "123.123.123.123", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("nginx.example.com", "\"heritage=external-dns,external-dns/owner=default\"", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("whitespace.example.com", "123.123.123.124", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("whitespace.example.com", "\"heritage=external-dns,external-dns/owner=white space\"", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("hack.example.com", "cerberus.infoblox.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""),
|
||||
endpoint.NewEndpoint("nginx.example.com", endpoint.RecordTypeA, "123.123.123.123"),
|
||||
endpoint.NewEndpoint("nginx.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""),
|
||||
endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124"),
|
||||
endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=white space\""),
|
||||
endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"),
|
||||
}
|
||||
validateEndpoints(t, actual, expected)
|
||||
}
|
||||
@ -376,23 +376,23 @@ func TestInfobloxApplyChanges(t *testing.T) {
|
||||
testInfobloxApplyChangesInternal(t, false, &client)
|
||||
|
||||
validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("example.com", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("foo.example.com", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("foo.example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("bar.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("bar.example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("other.com", "5.6.7.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("other.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("new.example.com", "111.222.111.222", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("newcname.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"),
|
||||
endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"),
|
||||
endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
})
|
||||
|
||||
validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("old.example.com", "", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("oldcname.example.com", "", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("deleted.example.com", "", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", "", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""),
|
||||
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""),
|
||||
endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""),
|
||||
})
|
||||
|
||||
validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{})
|
||||
@ -432,34 +432,34 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient
|
||||
)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("example.com", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("foo.example.com", "1.2.3.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("foo.example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("bar.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("bar.example.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("other.com", "5.6.7.8", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("other.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("nope.com", "4.4.4.4", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("nope.com", "tag", endpoint.RecordTypeTXT),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"),
|
||||
endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
|
||||
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
|
||||
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"),
|
||||
}
|
||||
|
||||
updateOldRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("old.example.com", "121.212.121.212", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("oldcname.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("old.nope.com", "121.212.121.212", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
|
||||
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"),
|
||||
}
|
||||
|
||||
updateNewRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("new.example.com", "111.222.111.222", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("newcname.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("new.nope.com", "222.111.222.111", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"),
|
||||
endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("deleted.example.com", "121.212.121.212", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", "other.com", endpoint.RecordTypeCNAME),
|
||||
endpoint.NewEndpoint("deleted.nope.com", "222.111.222.111", endpoint.RecordTypeA),
|
||||
endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"),
|
||||
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
|
||||
endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
|
@ -130,7 +130,7 @@ func (im *InMemoryProvider) Records() ([]*endpoint.Endpoint, error) {
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(record.Name, record.Target, record.Type))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(record.Name, record.Type, record.Target))
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,7 +203,7 @@ func convertToInMemoryRecord(endpoints []*endpoint.Endpoint) []*inMemoryRecord {
|
||||
records = append(records, &inMemoryRecord{
|
||||
Type: ep.RecordType,
|
||||
Name: ep.DNSName,
|
||||
Target: ep.Target,
|
||||
Target: ep.Targets[0],
|
||||
})
|
||||
}
|
||||
return records
|
||||
|
@ -186,16 +186,17 @@ func testInMemoryRecords(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "example.org",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{""},
|
||||
},
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
@ -214,7 +215,7 @@ func testInMemoryRecords(t *testing.T) {
|
||||
assert.EqualError(t, err, ErrZoneNotFound.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.True(t, testutils.SameEndpoints(ti.expected, records))
|
||||
assert.True(t, testutils.SameEndpoints(ti.expected, records), "Endpoints not the same: Expected: %+v Records: %+v", ti.expected, records)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -314,7 +315,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -333,14 +334,14 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -358,14 +359,14 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -383,12 +384,12 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "foo.org",
|
||||
Target: "4.4.4.4",
|
||||
Targets: endpoint.Targets{"4.4.4.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -408,7 +409,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -416,7 +417,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -433,12 +434,12 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -458,7 +459,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "new.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -478,7 +479,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "new.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -497,7 +498,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.org",
|
||||
Target: "5.5.5.5",
|
||||
Targets: endpoint.Targets{"5.5.5.5"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -512,21 +513,21 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.new.org",
|
||||
Target: "4.8.8.9",
|
||||
Targets: endpoint.Targets{"4.8.8.9"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.org",
|
||||
Target: "4.8.8.4",
|
||||
Targets: endpoint.Targets{"4.8.8.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.org",
|
||||
Target: "5.5.5.5",
|
||||
Targets: endpoint.Targets{"5.5.5.5"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -608,7 +609,7 @@ func testInMemoryApplyChanges(t *testing.T) {
|
||||
changes: &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{{
|
||||
DNSName: "example.de",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
}},
|
||||
UpdateNew: []*endpoint.Endpoint{},
|
||||
@ -625,7 +626,7 @@ func testInMemoryApplyChanges(t *testing.T) {
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -633,7 +634,7 @@ func testInMemoryApplyChanges(t *testing.T) {
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -649,7 +650,7 @@ func testInMemoryApplyChanges(t *testing.T) {
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.org",
|
||||
Target: "5.5.5.5",
|
||||
Targets: endpoint.Targets{"5.5.5.5"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -697,28 +698,28 @@ func testInMemoryApplyChanges(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.new.org",
|
||||
Target: "4.8.8.9",
|
||||
Targets: endpoint.Targets{"4.8.8.9"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.org",
|
||||
Target: "4.8.8.4",
|
||||
Targets: endpoint.Targets{"4.8.8.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar.org",
|
||||
Target: "5.5.5.5",
|
||||
Targets: endpoint.Targets{"5.5.5.5"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
|
406
provider/pdns.go
Normal file
406
provider/pdns.go
Normal file
@ -0,0 +1,406 @@
|
||||
/*
|
||||
Copyright 2018 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
pgo "github.com/ffledgling/pdns-go"
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/kubernetes-incubator/external-dns/plan"
|
||||
)
|
||||
|
||||
type pdnsChangeType string
|
||||
|
||||
const (
|
||||
apiBase = "/api/v1"
|
||||
|
||||
// Unless we use something like pdnsproxy (discontinued upsteam), this value will _always_ be localhost
|
||||
defaultServerID = "localhost"
|
||||
defaultTTL = 300
|
||||
|
||||
// PdnsDelete and PdnsReplace are effectively an enum for "pgo.RrSet.changetype"
|
||||
// TODO: Can we somehow get this from the pgo swagger client library itself?
|
||||
|
||||
// PdnsDelete : PowerDNS changetype used for deleting rrsets
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#rrset (see "changetype")
|
||||
PdnsDelete pdnsChangeType = "DELETE"
|
||||
// PdnsReplace : PowerDNS changetype for creating, updating and patching rrsets
|
||||
PdnsReplace pdnsChangeType = "REPLACE"
|
||||
// Number of times to retry failed PDNS requests
|
||||
retryLimit = 3
|
||||
// time in milliseconds
|
||||
retryAfterTime = 250 * time.Millisecond
|
||||
)
|
||||
|
||||
// Function for debug printing
|
||||
func stringifyHTTPResponseBody(r *http.Response) (body string) {
|
||||
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r.Body)
|
||||
body = buf.String()
|
||||
return body
|
||||
|
||||
}
|
||||
|
||||
// PDNSAPIProvider : Interface used and extended by the PDNSAPIClient struct as
|
||||
// well as mock APIClients used in testing
|
||||
type PDNSAPIProvider interface {
|
||||
ListZones() ([]pgo.Zone, *http.Response, error)
|
||||
ListZone(zoneID string) (pgo.Zone, *http.Response, error)
|
||||
PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error)
|
||||
}
|
||||
|
||||
// PDNSAPIClient : Struct that encapsulates all the PowerDNS specific implementation details
|
||||
type PDNSAPIClient struct {
|
||||
dryRun bool
|
||||
authCtx context.Context
|
||||
client *pgo.APIClient
|
||||
}
|
||||
|
||||
// ListZones : Method returns all enabled zones from PowerDNS
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#get--servers-server_id-zones
|
||||
func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err error) {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
zones, resp, err = c.client.ZonesApi.ListZones(c.authCtx, defaultServerID)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to fetch zones %v", err)
|
||||
log.Debugf("Retrying ListZones() ... %d", i)
|
||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||
continue
|
||||
|
||||
}
|
||||
return zones, resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to fetch zones. %v", err)
|
||||
return zones, resp, err
|
||||
|
||||
}
|
||||
|
||||
// ListZone : Method returns the details of a specific zone from PowerDNS
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#get--servers-server_id-zones-zone_id
|
||||
func (c *PDNSAPIClient) ListZone(zoneID string) (zone pgo.Zone, resp *http.Response, err error) {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
zone, resp, err = c.client.ZonesApi.ListZone(c.authCtx, defaultServerID, zoneID)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to fetch zone %v", err)
|
||||
log.Debugf("Retrying ListZone() ... %d", i)
|
||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||
continue
|
||||
|
||||
}
|
||||
return zone, resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to list zone. %v", err)
|
||||
return zone, resp, err
|
||||
|
||||
}
|
||||
|
||||
// PatchZone : Method used to update the contents of a particular zone from PowerDNS
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#patch--servers-server_id-zones-zone_id
|
||||
func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (resp *http.Response, err error) {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
resp, err = c.client.ZonesApi.PatchZone(c.authCtx, defaultServerID, zoneID, zoneStruct)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to patch zone %v", err)
|
||||
log.Debugf("Retrying PatchZone() ... %d", i)
|
||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||
continue
|
||||
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to patch zone. %v", err)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// PDNSProvider is an implementation of the Provider interface for PowerDNS
|
||||
type PDNSProvider struct {
|
||||
client PDNSAPIProvider
|
||||
}
|
||||
|
||||
// NewPDNSProvider initializes a new PowerDNS based Provider.
|
||||
func NewPDNSProvider(server string, apikey string, domainFilter DomainFilter, dryRun bool) (*PDNSProvider, error) {
|
||||
|
||||
// Do some input validation
|
||||
|
||||
if apikey == "" {
|
||||
return nil, errors.New("Missing API Key for PDNS. Specify using --pdns-api-key=")
|
||||
}
|
||||
|
||||
// The default for when no --domain-filter is passed is [""], instead of [], so we check accordingly.
|
||||
if len(domainFilter.filters) != 1 && domainFilter.filters[0] != "" {
|
||||
return nil, errors.New("PDNS Provider does not support domain filter")
|
||||
}
|
||||
// We do not support dry running, exit safely instead of surprising the user
|
||||
// TODO: Add Dry Run support
|
||||
if dryRun {
|
||||
return nil, errors.New("PDNS Provider does not currently support dry-run")
|
||||
}
|
||||
|
||||
if server == "localhost" {
|
||||
log.Warnf("PDNS Server is set to localhost, this may not be what you want. Specify using --pdns-server=")
|
||||
}
|
||||
|
||||
cfg := pgo.NewConfiguration()
|
||||
cfg.Host = server
|
||||
cfg.BasePath = server + apiBase
|
||||
|
||||
provider := &PDNSProvider{
|
||||
client: &PDNSAPIClient{
|
||||
dryRun: dryRun,
|
||||
authCtx: context.WithValue(context.TODO(), pgo.ContextAPIKey, pgo.APIKey{Key: apikey}),
|
||||
client: pgo.NewAPIClient(cfg),
|
||||
},
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (p *PDNSProvider) convertRRSetToEndpoints(rr pgo.RrSet) (endpoints []*endpoint.Endpoint, _ error) {
|
||||
endpoints = []*endpoint.Endpoint{}
|
||||
|
||||
for _, record := range rr.Records {
|
||||
// If a record is "Disabled", it's not supposed to be "visible"
|
||||
if !record.Disabled {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rr.Name, rr.Type_, endpoint.TTL(rr.Ttl), record.Content))
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// ConvertEndpointsToZones marshals endpoints into pdns compatible Zone structs
|
||||
func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error) {
|
||||
|
||||
zonelist = []pgo.Zone{}
|
||||
endpoints := make([]*endpoint.Endpoint, len(eps))
|
||||
copy(endpoints, eps)
|
||||
|
||||
// Sort the endpoints array so we have deterministic inserts
|
||||
sort.SliceStable(endpoints,
|
||||
func(i, j int) bool {
|
||||
// We only care about sorting endpoints with the same dnsname
|
||||
if endpoints[i].DNSName == endpoints[j].DNSName {
|
||||
return endpoints[i].RecordType < endpoints[j].RecordType
|
||||
}
|
||||
return endpoints[i].DNSName < endpoints[j].DNSName
|
||||
})
|
||||
|
||||
zones, _, err := p.client.ListZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort the zone by length of the name in descending order, we use this
|
||||
// property later to ensure we add a record to the longest matching zone
|
||||
|
||||
sort.SliceStable(zones, func(i, j int) bool { return len(zones[i].Name) > len(zones[j].Name) })
|
||||
|
||||
// NOTE: Complexity of this loop is O(Zones*Endpoints).
|
||||
// A possibly faster implementation would be a search of the reversed
|
||||
// DNSName in a trie of Zone names, which should be O(Endpoints), but at this point it's not
|
||||
// necessary.
|
||||
for _, zone := range zones {
|
||||
zone.Rrsets = []pgo.RrSet{}
|
||||
for i := 0; i < len(endpoints); {
|
||||
ep := endpoints[i]
|
||||
dnsname := ensureTrailingDot(ep.DNSName)
|
||||
if strings.HasSuffix(dnsname, zone.Name) {
|
||||
// The assumption here is that there will only ever be one target
|
||||
// per (ep.DNSName, ep.RecordType) tuple, which holds true for
|
||||
// external-dns v5.0.0-alpha onwards
|
||||
records := []pgo.Record{}
|
||||
for _, t := range ep.Targets {
|
||||
if "CNAME" == ep.RecordType {
|
||||
t = ensureTrailingDot(t)
|
||||
}
|
||||
|
||||
records = append(records, pgo.Record{Content: t})
|
||||
}
|
||||
rrset := pgo.RrSet{
|
||||
Name: dnsname,
|
||||
Type_: ep.RecordType,
|
||||
Records: records,
|
||||
Changetype: string(changetype),
|
||||
}
|
||||
|
||||
// DELETEs explicitly forbid a TTL, therefore only PATCHes need the TTL
|
||||
if changetype == PdnsReplace {
|
||||
if int64(ep.RecordTTL) > int64(math.MaxInt32) {
|
||||
return nil, errors.New("Value of record TTL overflows, limited to int32")
|
||||
}
|
||||
if ep.RecordTTL == 0 {
|
||||
// No TTL was sepecified for the record, we use the default
|
||||
rrset.Ttl = int32(defaultTTL)
|
||||
} else {
|
||||
rrset.Ttl = int32(ep.RecordTTL)
|
||||
}
|
||||
}
|
||||
|
||||
zone.Rrsets = append(zone.Rrsets, rrset)
|
||||
|
||||
// "pop" endpoint if it's matched
|
||||
endpoints = append(endpoints[0:i], endpoints[i+1:]...)
|
||||
} else {
|
||||
// If we didn't pop anything, we move to the next item in the list
|
||||
i++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(zone.Rrsets) > 0 {
|
||||
zonelist = append(zonelist, zone)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If we still have some endpoints left, it means we couldn't find a matching zone for them
|
||||
// We warn instead of hard fail here because we don't want a misconfig to cause everything to go down
|
||||
if len(endpoints) > 0 {
|
||||
log.Warnf("No matching zones were found for the following endpoints: %+v", endpoints)
|
||||
}
|
||||
|
||||
log.Debugf("Zone List generated from Endpoints: %+v", zonelist)
|
||||
|
||||
return zonelist, nil
|
||||
}
|
||||
|
||||
// mutateRecords takes a list of endpoints and creates, replaces or deletes them based on the changetype
|
||||
func (p *PDNSProvider) mutateRecords(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) error {
|
||||
zonelist, err := p.ConvertEndpointsToZones(endpoints, changetype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, zone := range zonelist {
|
||||
jso, err := json.Marshal(zone)
|
||||
if err != nil {
|
||||
log.Errorf("JSON Marshal for zone struct failed!")
|
||||
} else {
|
||||
log.Debugf("Struct for PatchZone:\n%s", string(jso))
|
||||
}
|
||||
|
||||
resp, err := p.client.PatchZone(zone.Id, zone)
|
||||
if err != nil {
|
||||
log.Debugf("PDNS API response: %s", stringifyHTTPResponseBody(resp))
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Records returns all DNS records controlled by the configured PDNS server (for all zones)
|
||||
func (p *PDNSProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
|
||||
zones, _, err := p.client.ListZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
z, _, err := p.client.ListZone(zone.Id)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to fetch Records")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rr := range z.Rrsets {
|
||||
e, err := p.convertRRSetToEndpoints(rr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoints = append(endpoints, e...)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Records fetched:\n%+v", endpoints)
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// ApplyChanges takes a list of changes (endpoints) and updates the PDNS server
|
||||
// by sending the correct HTTP PATCH requests to a matching zone
|
||||
func (p *PDNSProvider) ApplyChanges(changes *plan.Changes) error {
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// Create
|
||||
for _, change := range changes.Create {
|
||||
log.Debugf("CREATE: %+v", change)
|
||||
}
|
||||
// We only attempt to mutate records if there are any to mutate. A
|
||||
// call to mutate records with an empty list of endpoints is still a
|
||||
// valid call and a no-op, but we might as well not make the call to
|
||||
// prevent unnecessary logging
|
||||
if len(changes.Create) > 0 {
|
||||
// "Replacing" non-existant records creates them
|
||||
err := p.mutateRecords(changes.Create, PdnsReplace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update
|
||||
for _, change := range changes.UpdateOld {
|
||||
// Since PDNS "Patches", we don't need to specify the "old"
|
||||
// record. The Update New change type will automatically take
|
||||
// care of replacing the old RRSet with the new one We simply
|
||||
// leave this logging here for information
|
||||
log.Debugf("UPDATE-OLD (ignored): %+v", change)
|
||||
}
|
||||
|
||||
for _, change := range changes.UpdateNew {
|
||||
log.Debugf("UPDATE-NEW: %+v", change)
|
||||
}
|
||||
if len(changes.UpdateNew) > 0 {
|
||||
err := p.mutateRecords(changes.UpdateNew, PdnsReplace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Delete
|
||||
for _, change := range changes.Delete {
|
||||
log.Debugf("DELETE: %+v", change)
|
||||
}
|
||||
if len(changes.Delete) > 0 {
|
||||
err := p.mutateRecords(changes.Delete, PdnsDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Changes pushed out to PowerDNS in %s\n", time.Since(startTime))
|
||||
return nil
|
||||
}
|
641
provider/pdns_test.go
Normal file
641
provider/pdns_test.go
Normal file
@ -0,0 +1,641 @@
|
||||
/*
|
||||
Copyright 2018 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 (
|
||||
"errors"
|
||||
//"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
pgo "github.com/ffledgling/pdns-go"
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
)
|
||||
|
||||
// FIXME: What do we do about labels?
|
||||
|
||||
var (
|
||||
// Simple RRSets that contain 1 A record and 1 TXT record
|
||||
RRSetSimpleARecord = pgo.RrSet{
|
||||
Name: "example.com.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "8.8.8.8", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
RRSetSimpleTXTRecord = pgo.RrSet{
|
||||
Name: "example.com.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
RRSetLongARecord = pgo.RrSet{
|
||||
Name: "a.very.long.domainname.example.com.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "8.8.8.8", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
RRSetLongTXTRecord = pgo.RrSet{
|
||||
Name: "a.very.long.domainname.example.com.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
// RRSet with one record disabled
|
||||
RRSetDisabledRecord = pgo.RrSet{
|
||||
Name: "example.com.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "8.8.8.8", Disabled: false, SetPtr: false},
|
||||
{Content: "8.8.4.4", Disabled: true, SetPtr: false},
|
||||
},
|
||||
}
|
||||
|
||||
RRSetCNAMERecord = pgo.RrSet{
|
||||
Name: "cname.example.com.",
|
||||
Type_: "CNAME",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "example.by.any.other.name.com", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
RRSetTXTRecord = pgo.RrSet{
|
||||
Name: "example.com.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "'would smell as sweet'", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
|
||||
// Multiple PDNS records in an RRSet of a single type
|
||||
RRSetMultipleRecords = pgo.RrSet{
|
||||
Name: "example.com.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "8.8.8.8", Disabled: false, SetPtr: false},
|
||||
{Content: "8.8.4.4", Disabled: false, SetPtr: false},
|
||||
{Content: "4.4.4.4", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
|
||||
endpointsDisabledRecord = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
}
|
||||
|
||||
endpointsSimpleRecord = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
}
|
||||
|
||||
endpointsLongRecord = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
}
|
||||
|
||||
endpointsNonexistantZone = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("does.not.exist.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("does.not.exist.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
}
|
||||
endpointsMultipleRecords = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "4.4.4.4"),
|
||||
}
|
||||
|
||||
endpointsMixedRecords = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("cname.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(300), "example.by.any.other.name.com"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "'would smell as sweet'"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "4.4.4.4"),
|
||||
}
|
||||
|
||||
endpointsMultipleZones = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
endpoint.NewEndpointWithTTL("mock.test", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"),
|
||||
endpoint.NewEndpointWithTTL("mock.test", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
}
|
||||
|
||||
endpointsMultipleZones2 = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
endpoint.NewEndpointWithTTL("abcd.mock.test", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"),
|
||||
endpoint.NewEndpointWithTTL("abcd.mock.test", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
}
|
||||
|
||||
endpointsMultipleZonesWithNoExist = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
endpoint.NewEndpointWithTTL("abcd.mock.noexist", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"),
|
||||
endpoint.NewEndpointWithTTL("abcd.mock.noexist", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
|
||||
}
|
||||
|
||||
ZoneEmpty = pgo.Zone{
|
||||
// Opaque zone id (string), assigned by the server, should not be interpreted by the application. Guaranteed to be safe for embedding in URLs.
|
||||
Id: "example.com.",
|
||||
// Name of the zone (e.g. “example.com.”) MUST have a trailing dot
|
||||
Name: "example.com.",
|
||||
// Set to “Zone”
|
||||
Type_: "Zone",
|
||||
// API endpoint for this zone
|
||||
Url: "/api/v1/servers/localhost/zones/example.com.",
|
||||
// Zone kind, one of “Native”, “Master”, “Slave”
|
||||
Kind: "Native",
|
||||
// RRSets in this zone
|
||||
Rrsets: []pgo.RrSet{},
|
||||
}
|
||||
|
||||
ZoneEmptyLong = pgo.Zone{
|
||||
Id: "long.domainname.example.com.",
|
||||
Name: "long.domainname.example.com.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/long.domainname.example.com.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{},
|
||||
}
|
||||
|
||||
ZoneEmpty2 = pgo.Zone{
|
||||
Id: "mock.test.",
|
||||
Name: "mock.test.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/mock.test.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{},
|
||||
}
|
||||
|
||||
ZoneMixed = pgo.Zone{
|
||||
Id: "example.com.",
|
||||
Name: "example.com.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/example.com.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{RRSetCNAMERecord, RRSetTXTRecord, RRSetMultipleRecords},
|
||||
}
|
||||
|
||||
ZoneEmptyToSimplePatch = pgo.Zone{
|
||||
Id: "example.com.",
|
||||
Name: "example.com.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/example.com.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{
|
||||
{
|
||||
Name: "example.com.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "8.8.8.8",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
{
|
||||
Name: "example.com.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ZoneEmptyToLongPatch = pgo.Zone{
|
||||
Id: "long.domainname.example.com.",
|
||||
Name: "long.domainname.example.com.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/long.domainname.example.com.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{
|
||||
{
|
||||
Name: "a.very.long.domainname.example.com.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "8.8.8.8",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
{
|
||||
Name: "a.very.long.domainname.example.com.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ZoneEmptyToSimplePatch2 = pgo.Zone{
|
||||
Id: "mock.test.",
|
||||
Name: "mock.test.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/mock.test.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{
|
||||
{
|
||||
Name: "mock.test.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "9.9.9.9",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
{
|
||||
Name: "mock.test.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ZoneEmptyToSimplePatch3 = pgo.Zone{
|
||||
Id: "mock.test.",
|
||||
Name: "mock.test.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/mock.test.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{
|
||||
{
|
||||
Name: "abcd.mock.test.",
|
||||
Type_: "A",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "9.9.9.9",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
{
|
||||
Name: "abcd.mock.test.",
|
||||
Type_: "TXT",
|
||||
Ttl: 300,
|
||||
Changetype: "REPLACE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ZoneEmptyToSimpleDelete = pgo.Zone{
|
||||
Id: "example.com.",
|
||||
Name: "example.com.",
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/example.com.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{
|
||||
{
|
||||
Name: "example.com.",
|
||||
Type_: "A",
|
||||
Changetype: "DELETE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "8.8.8.8",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
{
|
||||
Name: "example.com.",
|
||||
Type_: "TXT",
|
||||
Changetype: "DELETE",
|
||||
Records: []pgo.Record{
|
||||
{
|
||||
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
|
||||
Disabled: false,
|
||||
SetPtr: false,
|
||||
},
|
||||
},
|
||||
Comments: []pgo.Comment(nil),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
/******************************************************************************/
|
||||
// API that returns a zone with multiple record types
|
||||
type PDNSAPIClientStub struct {
|
||||
}
|
||||
|
||||
func (c *PDNSAPIClientStub) ListZones() ([]pgo.Zone, *http.Response, error) {
|
||||
return []pgo.Zone{ZoneMixed}, nil, nil
|
||||
}
|
||||
func (c *PDNSAPIClientStub) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
|
||||
return ZoneMixed, nil, nil
|
||||
}
|
||||
func (c *PDNSAPIClientStub) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// API that returns a zones with no records
|
||||
type PDNSAPIClientStubEmptyZones struct {
|
||||
// Keep track of all zones we recieve via PatchZone
|
||||
patchedZones []pgo.Zone
|
||||
}
|
||||
|
||||
func (c *PDNSAPIClientStubEmptyZones) ListZones() ([]pgo.Zone, *http.Response, error) {
|
||||
return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2}, nil, nil
|
||||
}
|
||||
func (c *PDNSAPIClientStubEmptyZones) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
|
||||
|
||||
if strings.Contains(zoneID, "example.com") {
|
||||
return ZoneEmpty, nil, nil
|
||||
} else if strings.Contains(zoneID, "mock.test") {
|
||||
return ZoneEmpty2, nil, nil
|
||||
} else if strings.Contains(zoneID, "long.domainname.example.com") {
|
||||
return ZoneEmpty2, nil, nil
|
||||
}
|
||||
return pgo.Zone{}, nil, nil
|
||||
|
||||
}
|
||||
func (c *PDNSAPIClientStubEmptyZones) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) {
|
||||
c.patchedZones = append(c.patchedZones, zoneStruct)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// API that returns error on PatchZone()
|
||||
type PDNSAPIClientStubPatchZoneFailure struct {
|
||||
// Anonymous struct for composition
|
||||
PDNSAPIClientStubEmptyZones
|
||||
}
|
||||
|
||||
// Just overwrite the PatchZone method to introduce a failure
|
||||
func (c *PDNSAPIClientStubPatchZoneFailure) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) {
|
||||
return nil, errors.New("Generic PDNS Error")
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// API that returns error on ListZone()
|
||||
type PDNSAPIClientStubListZoneFailure struct {
|
||||
// Anonymous struct for composition
|
||||
PDNSAPIClientStubEmptyZones
|
||||
}
|
||||
|
||||
// Just overwrite the ListZone method to introduce a failure
|
||||
func (c *PDNSAPIClientStubListZoneFailure) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
|
||||
return pgo.Zone{}, nil, errors.New("Generic PDNS Error")
|
||||
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// API that returns error on ListZones() (Zones - plural)
|
||||
type PDNSAPIClientStubListZonesFailure struct {
|
||||
// Anonymous struct for composition
|
||||
PDNSAPIClientStubEmptyZones
|
||||
}
|
||||
|
||||
// Just overwrite the ListZones method to introduce a failure
|
||||
func (c *PDNSAPIClientStubListZonesFailure) ListZones() ([]pgo.Zone, *http.Response, error) {
|
||||
return []pgo.Zone{}, nil, errors.New("Generic PDNS Error")
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
type NewPDNSProviderTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreate() {
|
||||
// Function definition: NewPDNSProvider(server string, apikey string, domainFilter DomainFilter, dryRun bool) (*PDNSProvider, error)
|
||||
|
||||
_, err := NewPDNSProvider("http://localhost:8081", "", NewDomainFilter([]string{""}), false)
|
||||
assert.Error(suite.T(), err, "--pdns-api-key should be specified")
|
||||
|
||||
_, err = NewPDNSProvider("http://localhost:8081", "foo", NewDomainFilter([]string{"example.com", "example.org"}), false)
|
||||
assert.Error(suite.T(), err, "--domainfilter should raise an error")
|
||||
|
||||
_, err = NewPDNSProvider("http://localhost:8081", "foo", NewDomainFilter([]string{""}), true)
|
||||
assert.Error(suite.T(), err, "--dry-run should raise an error")
|
||||
|
||||
// This is our "regular" code path, no error should be thrown
|
||||
_, err = NewPDNSProvider("http://localhost:8081", "foo", NewDomainFilter([]string{""}), false)
|
||||
assert.Nil(suite.T(), err, "Regular case should raise no error")
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSRRSetToEndpoints() {
|
||||
// Function definition: convertRRSetToEndpoints(rr pgo.RrSet) (endpoints []*endpoint.Endpoint, _ error)
|
||||
|
||||
// Create a new provider to run tests against
|
||||
p := &PDNSProvider{
|
||||
client: &PDNSAPIClientStub{},
|
||||
}
|
||||
|
||||
/* given an RRSet with three records, we test:
|
||||
- We correctly create corresponding endpoints
|
||||
*/
|
||||
eps, err := p.convertRRSetToEndpoints(RRSetMultipleRecords)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), endpointsMultipleRecords, eps)
|
||||
|
||||
/* Given an RRSet with two records, one of which is disabled, we test:
|
||||
- We can correctly convert the RRSet into a list of valid endpoints
|
||||
- We correctly discard/ignore the disabled record.
|
||||
*/
|
||||
eps, err = p.convertRRSetToEndpoints(RRSetDisabledRecord)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), endpointsDisabledRecord, eps)
|
||||
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSRecords() {
|
||||
// Function definition: Records() (endpoints []*endpoint.Endpoint, _ error)
|
||||
|
||||
// Create a new provider to run tests against
|
||||
p := &PDNSProvider{
|
||||
client: &PDNSAPIClientStub{},
|
||||
}
|
||||
|
||||
/* We test that endpoints are returned correctly for a Zone when Records() is called
|
||||
*/
|
||||
eps, err := p.Records()
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), endpointsMixedRecords, eps)
|
||||
|
||||
// Test failures are handled correctly
|
||||
// Create a new provider to run tests against
|
||||
p = &PDNSProvider{
|
||||
client: &PDNSAPIClientStubListZoneFailure{},
|
||||
}
|
||||
eps, err = p.Records()
|
||||
assert.NotNil(suite.T(), err)
|
||||
|
||||
p = &PDNSProvider{
|
||||
client: &PDNSAPIClientStubListZonesFailure{},
|
||||
}
|
||||
eps, err = p.Records()
|
||||
assert.NotNil(suite.T(), err)
|
||||
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZones() {
|
||||
// Function definition: ConvertEndpointsToZones(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error)
|
||||
|
||||
// Create a new provider to run tests against
|
||||
p := &PDNSProvider{
|
||||
client: &PDNSAPIClientStubEmptyZones{},
|
||||
}
|
||||
|
||||
// Check inserting endpoints from a single zone
|
||||
zlist, err := p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist)
|
||||
|
||||
// Check deleting endpoints from a single zone
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsDelete)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, zlist)
|
||||
|
||||
// Check endpoints from multiple zones #1
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch, ZoneEmptyToSimplePatch2}, zlist)
|
||||
|
||||
// Check endpoints from multiple zones #2
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones2, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch, ZoneEmptyToSimplePatch3}, zlist)
|
||||
|
||||
// Check endpoints from multiple zones where some endpoints which don't exist
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithNoExist, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist)
|
||||
|
||||
// Check endpoints from a zone that does not exist
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsNonexistantZone, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{}, zlist)
|
||||
|
||||
// Check endpoints that match multiple zones (one longer than other), is assigned to the right zone
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsLongRecord, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToLongPatch}, zlist)
|
||||
|
||||
// Check endpoints of type CNAME always have their target records end with a dot.
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsMixedRecords, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
|
||||
for _, z := range zlist {
|
||||
for _, rs := range z.Rrsets {
|
||||
if "CNAME" == rs.Type_ {
|
||||
for _, r := range rs.Records {
|
||||
assert.Equal(suite.T(), uint8(0x2e), r.Content[len(r.Content)-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() {
|
||||
// Function definition: mutateRecords(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) error
|
||||
|
||||
// Create a new provider to run tests against
|
||||
c := &PDNSAPIClientStubEmptyZones{}
|
||||
p := &PDNSProvider{
|
||||
client: c,
|
||||
}
|
||||
|
||||
// Check inserting endpoints from a single zone
|
||||
err := p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("REPLACE"))
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, c.patchedZones)
|
||||
|
||||
// Reset the "patchedZones"
|
||||
c.patchedZones = []pgo.Zone{}
|
||||
|
||||
// Check deleting endpoints from a single zone
|
||||
err = p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("DELETE"))
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, c.patchedZones)
|
||||
|
||||
// Check we fail correctly when patching fails for whatever reason
|
||||
p = &PDNSProvider{
|
||||
client: &PDNSAPIClientStubPatchZoneFailure{},
|
||||
}
|
||||
// Check inserting endpoints from a single zone
|
||||
err = p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("REPLACE"))
|
||||
assert.NotNil(suite.T(), err)
|
||||
|
||||
}
|
||||
func TestNewPDNSProviderTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(NewPDNSProviderTestSuite))
|
||||
}
|
@ -26,7 +26,7 @@ func (z zoneIDName) Add(zoneID, zoneName string) {
|
||||
|
||||
func (z zoneIDName) FindZone(hostname string) (suitableZoneID, suitableZoneName string) {
|
||||
for zoneID, zoneName := range z {
|
||||
if strings.HasSuffix(hostname, zoneName) {
|
||||
if hostname == zoneName || strings.HasSuffix(hostname, "."+zoneName) {
|
||||
if suitableZoneName == "" || len(zoneName) > len(suitableZoneName) {
|
||||
suitableZoneID = zoneID
|
||||
suitableZoneName = zoneName
|
||||
|
@ -33,15 +33,28 @@ func TestZoneIDName(t *testing.T) {
|
||||
"654321": "foo.qux.baz",
|
||||
}, z)
|
||||
|
||||
// simple entry in a domain
|
||||
zoneID, zoneName := z.FindZone("name.qux.baz")
|
||||
assert.Equal(t, "qux.baz", zoneName)
|
||||
assert.Equal(t, "123456", zoneID)
|
||||
|
||||
// simple entry in a domain's subdomain.
|
||||
zoneID, zoneName = z.FindZone("name.foo.qux.baz")
|
||||
assert.Equal(t, "foo.qux.baz", zoneName)
|
||||
assert.Equal(t, "654321", zoneID)
|
||||
|
||||
// no possible zone for entry
|
||||
zoneID, zoneName = z.FindZone("name.qux.foo")
|
||||
assert.Equal(t, "", zoneName)
|
||||
assert.Equal(t, "", zoneID)
|
||||
|
||||
// entry's suffix matches a subdomain but doesn't belong there
|
||||
zoneID, zoneName = z.FindZone("name-foo.qux.baz")
|
||||
assert.Equal(t, "qux.baz", zoneName)
|
||||
assert.Equal(t, "123456", zoneID)
|
||||
|
||||
// entry is an exact match of the domain (e.g. azure provider)
|
||||
zoneID, zoneName = z.FindZone("foo.qux.baz")
|
||||
assert.Equal(t, "foo.qux.baz", zoneName)
|
||||
assert.Equal(t, "654321", zoneID)
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func testNoopRecords(t *testing.T) {
|
||||
providerRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "example-lb.com",
|
||||
Targets: endpoint.Targets{"example-lb.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
@ -71,19 +71,19 @@ func testNoopApplyChanges(t *testing.T) {
|
||||
providerRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "old-lb.com",
|
||||
Targets: endpoint.Targets{"old-lb.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
expectedUpdate := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "new-example-lb.com",
|
||||
Targets: endpoint.Targets{"new-example-lb.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "new-record.org",
|
||||
Target: "new-lb.org",
|
||||
Targets: endpoint.Targets{"new-lb.org"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
@ -98,7 +98,7 @@ func testNoopApplyChanges(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "lb.com",
|
||||
Targets: endpoint.Targets{"lb.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
@ -110,21 +110,21 @@ func testNoopApplyChanges(t *testing.T) {
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "new-record.org",
|
||||
Target: "new-lb.org",
|
||||
Targets: endpoint.Targets{"new-lb.org"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "new-example-lb.com",
|
||||
Targets: endpoint.Targets{"new-example-lb.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "old-lb.com",
|
||||
Targets: endpoint.Targets{"old-lb.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
|
@ -66,7 +66,8 @@ func (im *TXTRegistry) Records() ([]*endpoint.Endpoint, error) {
|
||||
endpoints = append(endpoints, record)
|
||||
continue
|
||||
}
|
||||
labels, err := endpoint.NewLabelsFromString(record.Target)
|
||||
// We simply assume that TXT records for the registry will always have only one target.
|
||||
labels, err := endpoint.NewLabelsFromString(record.Targets[0])
|
||||
if err == endpoint.ErrInvalidHeritage {
|
||||
//if no heritage is found or it is invalid
|
||||
//case when value of txt record cannot be identified
|
||||
@ -104,12 +105,12 @@ func (im *TXTRegistry) ApplyChanges(changes *plan.Changes) error {
|
||||
}
|
||||
for _, r := range filteredChanges.Create {
|
||||
r.Labels[endpoint.OwnerLabelKey] = im.ownerID
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), r.Labels.Serialize(true), endpoint.RecordTypeTXT)
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true))
|
||||
filteredChanges.Create = append(filteredChanges.Create, txt)
|
||||
}
|
||||
|
||||
for _, r := range filteredChanges.Delete {
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), r.Labels.Serialize(true), endpoint.RecordTypeTXT)
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true))
|
||||
|
||||
// when we delete TXT records for which value has changed (due to new label) this would still work because
|
||||
// !!! TXT record value is uniquely generated from the Labels of the endpoint. Hence old TXT record can be uniquely reconstructed
|
||||
@ -118,12 +119,12 @@ func (im *TXTRegistry) ApplyChanges(changes *plan.Changes) error {
|
||||
|
||||
// make sure TXT records are consistently updated as well
|
||||
for _, r := range filteredChanges.UpdateNew {
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), r.Labels.Serialize(true), endpoint.RecordTypeTXT)
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true))
|
||||
filteredChanges.UpdateNew = append(filteredChanges.UpdateNew, txt)
|
||||
}
|
||||
// make sure TXT records are consistently updated as well
|
||||
for _, r := range filteredChanges.UpdateOld {
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), r.Labels.Serialize(true), endpoint.RecordTypeTXT)
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true))
|
||||
// when we updateOld TXT records for which value has changed (due to new label) this would still work because
|
||||
// !!! TXT record value is uniquely generated from the Labels of the endpoint. Hence old TXT record can be uniquely reconstructed
|
||||
filteredChanges.UpdateOld = append(filteredChanges.UpdateOld, txt)
|
||||
|
@ -82,7 +82,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.test-zone.example.org",
|
||||
Target: "foo.loadbalancer.com",
|
||||
Targets: endpoint.Targets{"foo.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -90,7 +90,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "bar.test-zone.example.org",
|
||||
Target: "my-domain.com",
|
||||
Targets: endpoint.Targets{"my-domain.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
@ -98,7 +98,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "txt.bar.test-zone.example.org",
|
||||
Target: "baz.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"baz.test-zone.example.org"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -106,7 +106,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "qux.test-zone.example.org",
|
||||
Target: "random",
|
||||
Targets: endpoint.Targets{"random"},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -114,7 +114,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "tar.test-zone.example.org",
|
||||
Target: "tar.loadbalancer.com",
|
||||
Targets: endpoint.Targets{"tar.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner-2",
|
||||
@ -122,7 +122,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "foobar.test-zone.example.org",
|
||||
Target: "foobar.loadbalancer.com",
|
||||
Targets: endpoint.Targets{"foobar.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -155,7 +155,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.test-zone.example.org",
|
||||
Target: "foo.loadbalancer.com",
|
||||
Targets: endpoint.Targets{"foo.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -163,7 +163,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "bar.test-zone.example.org",
|
||||
Target: "my-domain.com",
|
||||
Targets: endpoint.Targets{"my-domain.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -171,7 +171,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "txt.bar.test-zone.example.org",
|
||||
Target: "baz.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"baz.test-zone.example.org"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
@ -180,7 +180,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "qux.test-zone.example.org",
|
||||
Target: "random",
|
||||
Targets: endpoint.Targets{"random"},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -188,7 +188,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "tar.test-zone.example.org",
|
||||
Target: "tar.loadbalancer.com",
|
||||
Targets: endpoint.Targets{"tar.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
@ -196,7 +196,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "foobar.test-zone.example.org",
|
||||
Target: "foobar.loadbalancer.com",
|
||||
Targets: endpoint.Targets{"foobar.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
@ -354,13 +354,13 @@ helper methods
|
||||
*/
|
||||
|
||||
func newEndpointWithOwner(dnsName, target, recordType, ownerID string) *endpoint.Endpoint {
|
||||
e := endpoint.NewEndpoint(dnsName, target, recordType)
|
||||
e := endpoint.NewEndpoint(dnsName, recordType, target)
|
||||
e.Labels[endpoint.OwnerLabelKey] = ownerID
|
||||
return e
|
||||
}
|
||||
|
||||
func newEndpointWithOwnerResource(dnsName, target, recordType, ownerID, resource string) *endpoint.Endpoint {
|
||||
e := endpoint.NewEndpoint(dnsName, target, recordType)
|
||||
e := endpoint.NewEndpoint(dnsName, recordType, target)
|
||||
e.Labels[endpoint.OwnerLabelKey] = ownerID
|
||||
e.Labels[endpoint.ResourceLabelKey] = resource
|
||||
return e
|
||||
|
@ -56,10 +56,10 @@ func legacyEndpointsFromMateService(svc *v1.Service) []*endpoint.Endpoint {
|
||||
// Create a corresponding endpoint for each configured external entrypoint.
|
||||
for _, lb := range svc.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.IP, endpoint.RecordTypeA))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.Hostname, endpoint.RecordTypeCNAME))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,10 +88,10 @@ func legacyEndpointsFromMoleculeService(svc *v1.Service) []*endpoint.Endpoint {
|
||||
// Create a corresponding endpoint for each configured external entrypoint.
|
||||
for _, lb := range svc.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.IP, endpoint.RecordTypeA))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, lb.IP))
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.Hostname, endpoint.RecordTypeCNAME))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeCNAME, lb.Hostname))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
65
source/connector.go
Normal file
65
source/connector.go
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2018 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 source
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
dialTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// connectorSource is an implementation of Source that provides endpoints by connecting
|
||||
// to a remote tcp server. The encoding/decoding is done using encoder/gob package.
|
||||
type connectorSource struct {
|
||||
remoteServer string
|
||||
}
|
||||
|
||||
// NewConnectorSource creates a new connectorSource with the given config.
|
||||
func NewConnectorSource(remoteServer string) (Source, error) {
|
||||
return &connectorSource{
|
||||
remoteServer: remoteServer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Endpoints returns endpoint objects.
|
||||
func (cs *connectorSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", cs.remoteServer, dialTimeout)
|
||||
if err != nil {
|
||||
log.Errorf("Connection error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
decoder := gob.NewDecoder(conn)
|
||||
if err := decoder.Decode(&endpoints); err != nil {
|
||||
log.Errorf("Decode error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Recieved endpoints: %#v", endpoints)
|
||||
|
||||
return endpoints, nil
|
||||
}
|
137
source/connector_test.go
Normal file
137
source/connector_test.go
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2018 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 source
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ConnectorSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *ConnectorSuite) SetupTest() {
|
||||
|
||||
}
|
||||
|
||||
func startServerToServeTargets(t *testing.T, server string, endpoints []*endpoint.Endpoint) {
|
||||
ln, err := net.Listen("tcp", server)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
ln.Close()
|
||||
return
|
||||
}
|
||||
enc := gob.NewEncoder(conn)
|
||||
enc.Encode(endpoints)
|
||||
ln.Close()
|
||||
}()
|
||||
t.Logf("Server listening on %s", server)
|
||||
}
|
||||
|
||||
func TestConnectorSource(t *testing.T) {
|
||||
suite.Run(t, new(ConnectorSuite))
|
||||
t.Run("Interface", testConnectorSourceImplementsSource)
|
||||
t.Run("Endpoints", testConnectorSourceEndpoints)
|
||||
}
|
||||
|
||||
// testConnectorSourceImplementsSource tests that connectorSource is a valid Source.
|
||||
func testConnectorSourceImplementsSource(t *testing.T) {
|
||||
assert.Implements(t, (*Source)(nil), new(connectorSource))
|
||||
}
|
||||
|
||||
// testConnectorSourceEndpoints tests that NewConnectorSource doesn't return an error.
|
||||
func testConnectorSourceEndpoints(t *testing.T) {
|
||||
for _, ti := range []struct {
|
||||
title string
|
||||
serverListenAddress string
|
||||
serverAddress string
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
title: "invalid remote server",
|
||||
serverListenAddress: "",
|
||||
serverAddress: "localhost:8091",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
title: "valid remote server with no endpoints",
|
||||
serverListenAddress: "127.0.0.1:8080",
|
||||
serverAddress: "127.0.0.1:8080",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
title: "valid remote server",
|
||||
serverListenAddress: "127.0.0.1:8081",
|
||||
serverAddress: "127.0.0.1:8081",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "abc.example.org",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 180,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
title: "valid remote server with multiple endpoints",
|
||||
serverListenAddress: "127.0.0.1:8082",
|
||||
serverAddress: "127.0.0.1:8082",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "abc.example.org",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 180,
|
||||
},
|
||||
{DNSName: "xyz.example.org",
|
||||
Targets: endpoint.Targets{"abc.example.org"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
RecordTTL: 180,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(ti.title, func(t *testing.T) {
|
||||
if ti.serverListenAddress != "" {
|
||||
startServerToServeTargets(t, ti.serverListenAddress, ti.expected)
|
||||
}
|
||||
cs, _ := NewConnectorSource(ti.serverAddress)
|
||||
|
||||
endpoints, err := cs.Endpoints()
|
||||
if ti.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Validate returned endpoints against expected endpoints.
|
||||
validateEndpoints(t, endpoints, ti.expected)
|
||||
})
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ func (ms *dedupSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
identifier := ep.DNSName + " / " + ep.Target
|
||||
identifier := ep.DNSName + " / " + ep.Targets.String()
|
||||
|
||||
if _, ok := collected[identifier]; ok {
|
||||
log.Debugf("Removing duplicate endpoint %s", ep)
|
||||
|
@ -40,53 +40,53 @@ func testDedupEndpoints(t *testing.T) {
|
||||
{
|
||||
"one endpoint returns one endpoint",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two different endpoints return two endpoints",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "4.5.6.7"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "4.5.6.7"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two endpoints with same dnsname and different targets return two endpoints",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "4.5.6.7"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "4.5.6.7"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two endpoints with different dnsname and same target return two endpoints",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two endpoints with same dnsname and same target return one endpoint",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
},
|
||||
} {
|
||||
|
@ -68,8 +68,8 @@ func (sc *fakeSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
func (sc *fakeSource) generateEndpoint() (*endpoint.Endpoint, error) {
|
||||
ep := endpoint.NewEndpoint(
|
||||
generateDNSName(4, sc.dnsName),
|
||||
generateIPAddress(),
|
||||
endpoint.RecordTypeA,
|
||||
generateIPAddress(),
|
||||
)
|
||||
|
||||
return ep, nil
|
||||
|
@ -60,7 +60,7 @@ func TestFakeEndpointsResolveToIPAddresses(t *testing.T) {
|
||||
endpoints := generateTestEndpoints()
|
||||
|
||||
for _, e := range endpoints {
|
||||
ip := net.ParseIP(e.Target)
|
||||
ip := net.ParseIP(e.Targets[0])
|
||||
|
||||
if ip == nil {
|
||||
t.Error(e)
|
||||
|
@ -19,6 +19,7 @@ package source
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@ -41,10 +42,11 @@ type ingressSource struct {
|
||||
namespace string
|
||||
annotationFilter string
|
||||
fqdnTemplate *template.Template
|
||||
combineFQDNAnnotation bool
|
||||
}
|
||||
|
||||
// NewIngressSource creates a new ingressSource with the given config.
|
||||
func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string) (Source, error) {
|
||||
func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool) (Source, error) {
|
||||
var (
|
||||
tmpl *template.Template
|
||||
err error
|
||||
@ -63,6 +65,7 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt
|
||||
namespace: namespace,
|
||||
annotationFilter: annotationFilter,
|
||||
fqdnTemplate: tmpl,
|
||||
combineFQDNAnnotation: combineFqdnAnnotation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -92,11 +95,17 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
ingEndpoints := endpointsFromIngress(&ing)
|
||||
|
||||
// apply template if host is missing on ingress
|
||||
if len(ingEndpoints) == 0 && sc.fqdnTemplate != nil {
|
||||
ingEndpoints, err = sc.endpointsFromTemplate(&ing)
|
||||
if (sc.combineFQDNAnnotation || len(ingEndpoints) == 0) && sc.fqdnTemplate != nil {
|
||||
iEndpoints, err := sc.endpointsFromTemplate(&ing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sc.combineFQDNAnnotation {
|
||||
ingEndpoints = append(ingEndpoints, iEndpoints...)
|
||||
} else {
|
||||
ingEndpoints = iEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
if len(ingEndpoints) == 0 {
|
||||
@ -109,61 +118,59 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
endpoints = append(endpoints, ingEndpoints...)
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
sort.Sort(ep.Targets)
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// get endpoints from optional "target" annotation
|
||||
// Returns empty endpoints array if none are found.
|
||||
func getEndpointsFromTargetAnnotation(ing *v1beta1.Ingress, hostname string) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
func getTargetsFromTargetAnnotation(ing *v1beta1.Ingress) endpoint.Targets {
|
||||
var targets endpoint.Targets
|
||||
|
||||
// Get the desired hostname of the ingress from the annotation.
|
||||
targetAnnotation, exists := ing.Annotations[targetAnnotationKey]
|
||||
if exists {
|
||||
ttl, err := getTTLFromAnnotations(ing.Annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
// splits the hostname annotation and removes the trailing periods
|
||||
targetsList := strings.Split(strings.Replace(targetAnnotation, " ", "", -1), ",")
|
||||
for _, targetHostname := range targetsList {
|
||||
targetHostname = strings.TrimSuffix(targetHostname, ".")
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, targetHostname, suitableType(targetHostname), ttl))
|
||||
targets = append(targets, targetHostname)
|
||||
}
|
||||
}
|
||||
return endpoints
|
||||
return targets
|
||||
}
|
||||
|
||||
func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoint.Endpoint, error) {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
// Process the whole template string
|
||||
var buf bytes.Buffer
|
||||
err := sc.fqdnTemplate.Execute(&buf, ing)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to apply template on ingress %s: %v", ing.String(), err)
|
||||
}
|
||||
|
||||
hostname := buf.String()
|
||||
|
||||
endpoints = getEndpointsFromTargetAnnotation(ing, hostname)
|
||||
|
||||
if len(endpoints) != 0 {
|
||||
return endpoints, nil
|
||||
}
|
||||
hostnames := buf.String()
|
||||
|
||||
ttl, err := getTTLFromAnnotations(ing.Annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
for _, lb := range ing.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.IP, endpoint.RecordTypeA, ttl))
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.Hostname, endpoint.RecordTypeCNAME, ttl))
|
||||
}
|
||||
|
||||
targets := getTargetsFromTargetAnnotation(ing)
|
||||
|
||||
if len(targets) == 0 {
|
||||
targets = targetsFromIngressStatus(ing.Status)
|
||||
}
|
||||
|
||||
var endpoints []*endpoint.Endpoint
|
||||
// splits the FQDN template and removes the trailing periods
|
||||
hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",")
|
||||
for _, hostname := range hostnameList {
|
||||
hostname = strings.TrimSuffix(hostname, ".")
|
||||
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
@ -208,32 +215,83 @@ func (sc *ingressSource) setResourceLabel(ingress v1beta1.Ingress, endpoints []*
|
||||
func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.Host == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
annotationEndpoints := getEndpointsFromTargetAnnotation(ing, rule.Host)
|
||||
|
||||
if len(annotationEndpoints) != 0 {
|
||||
endpoints = append(endpoints, annotationEndpoints...)
|
||||
continue
|
||||
}
|
||||
|
||||
ttl, err := getTTLFromAnnotations(ing.Annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
for _, lb := range ing.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rule.Host, lb.IP, endpoint.RecordTypeA, ttl))
|
||||
targets := getTargetsFromTargetAnnotation(ing)
|
||||
|
||||
if len(targets) == 0 {
|
||||
targets = targetsFromIngressStatus(ing.Status)
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rule.Host, lb.Hostname, endpoint.RecordTypeCNAME, ttl))
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.Host == "" {
|
||||
continue
|
||||
}
|
||||
endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl)...)
|
||||
}
|
||||
|
||||
hostnameList := getHostnamesFromAnnotations(ing.Annotations)
|
||||
for _, hostname := range hostnameList {
|
||||
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
|
||||
}
|
||||
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
var aTargets endpoint.Targets
|
||||
var cnameTargets endpoint.Targets
|
||||
|
||||
for _, t := range targets {
|
||||
switch suitableType(t) {
|
||||
case endpoint.RecordTypeA:
|
||||
aTargets = append(aTargets, t)
|
||||
default:
|
||||
cnameTargets = append(cnameTargets, t)
|
||||
}
|
||||
}
|
||||
|
||||
if len(aTargets) > 0 {
|
||||
epA := &endpoint.Endpoint{
|
||||
DNSName: strings.TrimSuffix(hostname, "."),
|
||||
Targets: aTargets,
|
||||
RecordTTL: ttl,
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Labels: endpoint.NewLabels(),
|
||||
}
|
||||
endpoints = append(endpoints, epA)
|
||||
}
|
||||
|
||||
if len(cnameTargets) > 0 {
|
||||
epCNAME := &endpoint.Endpoint{
|
||||
DNSName: strings.TrimSuffix(hostname, "."),
|
||||
Targets: cnameTargets,
|
||||
RecordTTL: ttl,
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: endpoint.NewLabels(),
|
||||
}
|
||||
endpoints = append(endpoints, epCNAME)
|
||||
}
|
||||
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func targetsFromIngressStatus(status v1beta1.IngressStatus) endpoint.Targets {
|
||||
var targets endpoint.Targets
|
||||
|
||||
for _, lb := range status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
targets = append(targets, lb.IP)
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
targets = append(targets, lb.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ func (suite *IngressSuite) SetupTest() {
|
||||
"",
|
||||
"",
|
||||
"{{.Name}}",
|
||||
false,
|
||||
)
|
||||
suite.NoError(err, "should initialize ingress source")
|
||||
|
||||
@ -81,6 +82,7 @@ func TestNewIngressSource(t *testing.T) {
|
||||
title string
|
||||
annotationFilter string
|
||||
fqdnTemplate string
|
||||
combineFQDNAndAnnotation bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
@ -97,6 +99,17 @@ func TestNewIngressSource(t *testing.T) {
|
||||
expectError: false,
|
||||
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
|
||||
},
|
||||
{
|
||||
title: "valid template",
|
||||
expectError: false,
|
||||
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com, {{.Name}}-{{.Namespace}}.ext-dna.test.com",
|
||||
},
|
||||
{
|
||||
title: "valid template",
|
||||
expectError: false,
|
||||
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com, {{.Name}}-{{.Namespace}}.ext-dna.test.com",
|
||||
combineFQDNAndAnnotation: true,
|
||||
},
|
||||
{
|
||||
title: "non-empty annotation filter label",
|
||||
expectError: false,
|
||||
@ -109,6 +122,7 @@ func TestNewIngressSource(t *testing.T) {
|
||||
"",
|
||||
ti.annotationFilter,
|
||||
ti.fqdnTemplate,
|
||||
ti.combineFQDNAndAnnotation,
|
||||
)
|
||||
if ti.expectError {
|
||||
assert.Error(t, err)
|
||||
@ -134,7 +148,7 @@ func testEndpointsFromIngress(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar",
|
||||
Target: "lb.com",
|
||||
Targets: endpoint.Targets{"lb.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -147,7 +161,7 @@ func testEndpointsFromIngress(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -161,19 +175,11 @@ func testEndpointsFromIngress(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.bar",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8", "127.0.0.1"},
|
||||
},
|
||||
{
|
||||
DNSName: "foo.bar",
|
||||
Target: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
DNSName: "foo.bar",
|
||||
Target: "elb.com",
|
||||
},
|
||||
{
|
||||
DNSName: "foo.bar",
|
||||
Target: "alb.com",
|
||||
Targets: endpoint.Targets{"elb.com", "alb.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -219,6 +225,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
fqdnTemplate string
|
||||
combineFQDNAndAnnotation bool
|
||||
}{
|
||||
{
|
||||
title: "no ingress",
|
||||
@ -244,11 +251,11 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
{
|
||||
DNSName: "new.org",
|
||||
Target: "lb.com",
|
||||
Targets: endpoint.Targets{"lb.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -272,11 +279,11 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
{
|
||||
DNSName: "new.org",
|
||||
Target: "lb.com",
|
||||
Targets: endpoint.Targets{"lb.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -300,7 +307,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -322,7 +329,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -379,7 +386,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -417,7 +424,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -455,11 +462,11 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fake1.ext-dns.test.com",
|
||||
Target: "8.8.8.8",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
},
|
||||
{
|
||||
DNSName: "fake1.ext-dns.test.com",
|
||||
Target: "elb.com",
|
||||
Targets: endpoint.Targets{"elb.com"},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.ext-dns.test.com",
|
||||
@ -481,6 +488,83 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{},
|
||||
fqdnTemplate: "{{.Name}}.ext-dns.test.com",
|
||||
},
|
||||
{
|
||||
title: "multiple FQDN template hostnames",
|
||||
targetNamespace: "",
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
annotations: map[string]string{},
|
||||
dnsnames: []string{},
|
||||
ips: []string{"8.8.8.8"},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fake1.ext-dns.test.com",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "fake1.ext-dna.test.com",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.ext-dns.test.com, {{.Name}}.ext-dna.test.com",
|
||||
},
|
||||
{
|
||||
title: "multiple FQDN template hostnames",
|
||||
targetNamespace: "",
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
annotations: map[string]string{},
|
||||
dnsnames: []string{},
|
||||
ips: []string{"8.8.8.8"},
|
||||
},
|
||||
{
|
||||
name: "fake2",
|
||||
namespace: namespace,
|
||||
annotations: map[string]string{
|
||||
targetAnnotationKey: "ingress-target.com",
|
||||
},
|
||||
dnsnames: []string{"example.org"},
|
||||
ips: []string{},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fake1.ext-dns.test.com",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "fake1.ext-dna.test.com",
|
||||
Targets: endpoint.Targets{"8.8.8.8"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "fake2.ext-dns.test.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "fake2.ext-dna.test.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.ext-dns.test.com, {{.Name}}.ext-dna.test.com",
|
||||
combineFQDNAndAnnotation: true,
|
||||
},
|
||||
{
|
||||
title: "ingress rules with annotation",
|
||||
targetNamespace: "",
|
||||
@ -516,21 +600,108 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "ingress-target.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "example2.org",
|
||||
Target: "ingress-target.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "example3.org",
|
||||
Target: "1.2.3.4",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ingress rules with hostname annotation",
|
||||
targetNamespace: "",
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
annotations: map[string]string{
|
||||
hostnameAnnotationKey: "dns-through-hostname.com",
|
||||
},
|
||||
dnsnames: []string{"example.org"},
|
||||
ips: []string{"1.2.3.4"},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "dns-through-hostname.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ingress rules with hostname annotation having multiple hostnames",
|
||||
targetNamespace: "",
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
annotations: map[string]string{
|
||||
hostnameAnnotationKey: "dns-through-hostname.com, another-dns-through-hostname.com",
|
||||
},
|
||||
dnsnames: []string{"example.org"},
|
||||
ips: []string{"1.2.3.4"},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "dns-through-hostname.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "another-dns-through-hostname.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ingress rules with hostname and target annotation",
|
||||
targetNamespace: "",
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
annotations: map[string]string{
|
||||
hostnameAnnotationKey: "dns-through-hostname.com",
|
||||
targetAnnotationKey: "ingress-target.com",
|
||||
},
|
||||
dnsnames: []string{"example.org"},
|
||||
ips: []string{},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "dns-through-hostname.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ingress rules with annotation and custom TTL",
|
||||
targetNamespace: "",
|
||||
@ -559,12 +730,12 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Target: "ingress-target.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordTTL: endpoint.TTL(6),
|
||||
},
|
||||
{
|
||||
DNSName: "example2.org",
|
||||
Target: "ingress-target.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
},
|
||||
},
|
||||
@ -606,17 +777,17 @@ func testIngressEndpoints(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fake1.ext-dns.test.com",
|
||||
Target: "ingress-target.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "fake2.ext-dns.test.com",
|
||||
Target: "ingress-target.com",
|
||||
Targets: endpoint.Targets{"ingress-target.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "fake3.ext-dns.test.com",
|
||||
Target: "1.2.3.4",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
},
|
||||
@ -635,6 +806,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
ti.targetNamespace,
|
||||
ti.annotationFilter,
|
||||
ti.fqdnTemplate,
|
||||
ti.combineFQDNAndAnnotation,
|
||||
)
|
||||
for _, ingress := range ingresses {
|
||||
_, err := fakeClient.Extensions().Ingresses(ingress.Namespace).Create(ingress)
|
||||
|
@ -40,8 +40,8 @@ func testMultiSourceImplementsSource(t *testing.T) {
|
||||
|
||||
// testMultiSourceEndpoints tests merged endpoints from children are returned.
|
||||
func testMultiSourceEndpoints(t *testing.T) {
|
||||
foo := &endpoint.Endpoint{DNSName: "foo", Target: "8.8.8.8"}
|
||||
bar := &endpoint.Endpoint{DNSName: "bar", Target: "8.8.4.4"}
|
||||
foo := &endpoint.Endpoint{DNSName: "foo", Targets: endpoint.Targets{"8.8.8.8"}}
|
||||
bar := &endpoint.Endpoint{DNSName: "bar", Targets: endpoint.Targets{"8.8.4.4"}}
|
||||
|
||||
for _, tc := range []struct {
|
||||
title string
|
||||
|
@ -19,6 +19,7 @@ package source
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@ -32,6 +33,10 @@ import (
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTargetsCapacity = 10
|
||||
)
|
||||
|
||||
// serviceSource is an implementation of Source for Kubernetes service objects.
|
||||
// It will find all services that are under our jurisdiction, i.e. annotated
|
||||
// desired hostname and matching or no controller annotation. For each of the
|
||||
@ -44,11 +49,12 @@ type serviceSource struct {
|
||||
// process Services with legacy annotations
|
||||
compatibility string
|
||||
fqdnTemplate *template.Template
|
||||
combineFQDNAnnotation bool
|
||||
publishInternal bool
|
||||
}
|
||||
|
||||
// NewServiceSource creates a new serviceSource with the given config.
|
||||
func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate, compatibility string, publishInternal bool) (Source, error) {
|
||||
func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool) (Source, error) {
|
||||
var (
|
||||
tmpl *template.Template
|
||||
err error
|
||||
@ -68,6 +74,7 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt
|
||||
annotationFilter: annotationFilter,
|
||||
compatibility: compatibility,
|
||||
fqdnTemplate: tmpl,
|
||||
combineFQDNAnnotation: combineFqdnAnnotation,
|
||||
publishInternal: publishInternal,
|
||||
}, nil
|
||||
}
|
||||
@ -102,11 +109,17 @@ func (sc *serviceSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
}
|
||||
|
||||
// apply template if none of the above is found
|
||||
if len(svcEndpoints) == 0 && sc.fqdnTemplate != nil {
|
||||
svcEndpoints, err = sc.endpointsFromTemplate(&svc)
|
||||
if (sc.combineFQDNAnnotation || len(svcEndpoints) == 0) && sc.fqdnTemplate != nil {
|
||||
sEndpoints, err := sc.endpointsFromTemplate(&svc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sc.combineFQDNAnnotation {
|
||||
svcEndpoints = append(svcEndpoints, sEndpoints...)
|
||||
} else {
|
||||
svcEndpoints = sEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
if len(svcEndpoints) == 0 {
|
||||
@ -119,15 +132,17 @@ func (sc *serviceSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
endpoints = append(endpoints, svcEndpoints...)
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
sort.Sort(ep.Targets)
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (sc *serviceSource) extractHeadlessEndpoint(svc *v1.Service, hostname string) []*endpoint.Endpoint {
|
||||
|
||||
func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname string, ttl endpoint.TTL) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
pods, err := sc.client.CoreV1().Pods(svc.Namespace).List(metav1.ListOptions{LabelSelector: labels.Set(svc.Spec.Selector).AsSelectorPreValidated().String()})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("List Pods of service[%s] error:%v", svc.GetName(), err)
|
||||
return endpoints
|
||||
@ -138,10 +153,15 @@ func (sc *serviceSource) extractHeadlessEndpoint(svc *v1.Service, hostname strin
|
||||
if v.Spec.Hostname != "" {
|
||||
headlessDomain = v.Spec.Hostname + "." + headlessDomain
|
||||
}
|
||||
log.Debugf("Generating matching endpoint %s with HostIP %s", headlessDomain, v.Status.HostIP)
|
||||
|
||||
log.Debugf("Generating matching endpoint %s with PodIP %s", headlessDomain, v.Status.PodIP)
|
||||
// To reduce traffice on the DNS API only add record for running Pods. Good Idea?
|
||||
if v.Status.Phase == v1.PodRunning {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, v.Status.HostIP, endpoint.RecordTypeA))
|
||||
if ttl.IsConfigured() {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessDomain, endpoint.RecordTypeA, ttl, v.Status.PodIP))
|
||||
} else {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, endpoint.RecordTypeA, v.Status.PodIP))
|
||||
}
|
||||
} else {
|
||||
log.Debugf("Pod %s is not in running phase", v.Spec.Hostname)
|
||||
}
|
||||
@ -152,15 +172,17 @@ func (sc *serviceSource) extractHeadlessEndpoint(svc *v1.Service, hostname strin
|
||||
func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service) ([]*endpoint.Endpoint, error) {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
// Process the whole template string
|
||||
var buf bytes.Buffer
|
||||
err := sc.fqdnTemplate.Execute(&buf, svc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to apply template on service %s: %v", svc.String(), err)
|
||||
}
|
||||
|
||||
hostname := buf.String()
|
||||
|
||||
endpoints = sc.generateEndpoints(svc, hostname)
|
||||
hostnameList := strings.Split(strings.Replace(buf.String(), " ", "", -1), ",")
|
||||
for _, hostname := range hostnameList {
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname)...)
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
@ -169,13 +191,7 @@ func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service) ([]*endpoint.End
|
||||
func (sc *serviceSource) endpoints(svc *v1.Service) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
// Get the desired hostname of the service from the annotation.
|
||||
hostnameAnnotation, exists := svc.Annotations[hostnameAnnotationKey]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
hostnameList := strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")
|
||||
hostnameList := getHostnamesFromAnnotations(svc.Annotations)
|
||||
for _, hostname := range hostnameList {
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname)...)
|
||||
}
|
||||
@ -221,54 +237,82 @@ func (sc *serviceSource) setResourceLabel(service v1.Service, endpoints []*endpo
|
||||
}
|
||||
|
||||
func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
hostname = strings.TrimSuffix(hostname, ".")
|
||||
ttl, err := getTTLFromAnnotations(svc.Annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
epA := &endpoint.Endpoint{
|
||||
RecordTTL: ttl,
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Labels: endpoint.NewLabels(),
|
||||
Targets: make(endpoint.Targets, 0, defaultTargetsCapacity),
|
||||
DNSName: hostname,
|
||||
}
|
||||
|
||||
epCNAME := &endpoint.Endpoint{
|
||||
RecordTTL: ttl,
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: endpoint.NewLabels(),
|
||||
Targets: make(endpoint.Targets, 0, defaultTargetsCapacity),
|
||||
DNSName: hostname,
|
||||
}
|
||||
|
||||
var endpoints []*endpoint.Endpoint
|
||||
var targets endpoint.Targets
|
||||
|
||||
switch svc.Spec.Type {
|
||||
case v1.ServiceTypeLoadBalancer:
|
||||
endpoints = append(endpoints, extractLoadBalancerEndpoints(svc, hostname)...)
|
||||
targets = append(targets, extractLoadBalancerTargets(svc)...)
|
||||
case v1.ServiceTypeClusterIP:
|
||||
if sc.publishInternal {
|
||||
endpoints = append(endpoints, extractServiceIps(svc, hostname)...)
|
||||
targets = append(targets, extractServiceIps(svc)...)
|
||||
}
|
||||
if svc.Spec.ClusterIP == v1.ClusterIPNone {
|
||||
endpoints = append(endpoints, sc.extractHeadlessEndpoint(svc, hostname)...)
|
||||
endpoints = append(endpoints, sc.extractHeadlessEndpoints(svc, hostname, ttl)...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, t := range targets {
|
||||
if suitableType(t) == endpoint.RecordTypeA {
|
||||
epA.Targets = append(epA.Targets, t)
|
||||
}
|
||||
if suitableType(t) == endpoint.RecordTypeCNAME {
|
||||
epCNAME.Targets = append(epCNAME.Targets, t)
|
||||
}
|
||||
}
|
||||
|
||||
if len(epA.Targets) > 0 {
|
||||
endpoints = append(endpoints, epA)
|
||||
}
|
||||
if len(epCNAME.Targets) > 0 {
|
||||
endpoints = append(endpoints, epCNAME)
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func extractServiceIps(svc *v1.Service, hostname string) []*endpoint.Endpoint {
|
||||
ttl, err := getTTLFromAnnotations(svc.Annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
func extractServiceIps(svc *v1.Service) endpoint.Targets {
|
||||
if svc.Spec.ClusterIP == v1.ClusterIPNone {
|
||||
log.Debugf("Unable to associate %s headless service with a Cluster IP", svc.Name)
|
||||
return []*endpoint.Endpoint{}
|
||||
return endpoint.Targets{}
|
||||
}
|
||||
return endpoint.Targets{svc.Spec.ClusterIP}
|
||||
}
|
||||
|
||||
return []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(hostname, svc.Spec.ClusterIP, endpoint.RecordTypeA, ttl)}
|
||||
}
|
||||
func extractLoadBalancerTargets(svc *v1.Service) endpoint.Targets {
|
||||
var targets endpoint.Targets
|
||||
|
||||
func extractLoadBalancerEndpoints(svc *v1.Service, hostname string) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
ttl, err := getTTLFromAnnotations(svc.Annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
// Create a corresponding endpoint for each configured external entrypoint.
|
||||
for _, lb := range svc.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
//TODO(ideahitme): consider retrieving record type from resource annotation instead of empty
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.IP, endpoint.RecordTypeA, ttl))
|
||||
targets = append(targets, lb.IP)
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.Hostname, endpoint.RecordTypeCNAME, ttl))
|
||||
targets = append(targets, lb.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints
|
||||
return targets
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ func (suite *ServiceSuite) SetupTest() {
|
||||
"",
|
||||
"",
|
||||
"{{.Name}}",
|
||||
false,
|
||||
"",
|
||||
false,
|
||||
)
|
||||
@ -128,6 +129,7 @@ func testServiceSourceNewServiceSource(t *testing.T) {
|
||||
"",
|
||||
ti.annotationFilter,
|
||||
ti.fqdnTemplate,
|
||||
false,
|
||||
"",
|
||||
false,
|
||||
)
|
||||
@ -152,6 +154,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
svcType v1.ServiceType
|
||||
compatibility string
|
||||
fqdnTemplate string
|
||||
combineFQDNAndAnnotation bool
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
clusterIP string
|
||||
@ -168,6 +171,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
"",
|
||||
@ -184,6 +188,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -191,7 +196,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -204,6 +209,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeClusterIP,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -213,6 +219,50 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
[]*endpoint.Endpoint{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"FQDN template with multiple hostnames return an endpoint with target IP",
|
||||
"",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"FQDN template and annotation both with multiple hostnames return an endpoint with target IP",
|
||||
"",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
|
||||
true,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org., bar.example.org.",
|
||||
},
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"annotated services with multiple hostnames return an endpoint with target IP",
|
||||
"",
|
||||
@ -222,6 +272,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org., bar.example.org.",
|
||||
@ -229,8 +280,8 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -243,6 +294,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org, bar.example.org",
|
||||
@ -250,8 +302,8 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -264,6 +316,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -271,7 +324,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"lb.example.com"}, // Kubernetes omits the trailing dot
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "lb.example.com"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -284,6 +337,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted
|
||||
@ -291,8 +345,8 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "lb.example.com"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -305,6 +359,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
controllerAnnotationKey: controllerAnnotationValue,
|
||||
@ -313,7 +368,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -326,6 +381,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"{{.Name}}.ext-dns.test.com",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
controllerAnnotationKey: "some-other-tool",
|
||||
@ -345,6 +401,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -352,7 +409,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -365,6 +422,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -383,6 +441,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -390,7 +449,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -403,6 +462,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -411,7 +471,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -424,6 +484,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -443,6 +504,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -462,6 +524,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -470,7 +533,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -483,6 +546,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -502,6 +566,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -512,7 +577,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple external entrypoints return multiple endpoints",
|
||||
"multiple external entrypoints return a single endpoint with multiple targets",
|
||||
"",
|
||||
"",
|
||||
"testing",
|
||||
@ -520,6 +585,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -527,8 +593,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4", "8.8.8.8"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "8.8.8.8"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -541,6 +606,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
"zalando.org/dnsname": "foo.example.org.",
|
||||
@ -559,6 +625,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"mate",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
"zalando.org/dnsname": "foo.example.org.",
|
||||
@ -566,7 +633,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -579,6 +646,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"molecule",
|
||||
"",
|
||||
false,
|
||||
map[string]string{
|
||||
"dns": "route53",
|
||||
},
|
||||
@ -588,8 +656,8 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "bar.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -602,13 +670,14 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"{{.Name}}.bar.example.com",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
"",
|
||||
[]string{"1.2.3.4", "elb.com"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.bar.example.com", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.bar.example.com", Target: "elb.com"},
|
||||
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"elb.com"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -621,6 +690,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"{{.Name}}.bar.example.com",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -628,8 +698,8 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4", "elb.com"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "elb.com"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"elb.com"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -642,6 +712,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"mate",
|
||||
"{{.Name}}.bar.example.com",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
"zalando.org/dnsname": "mate.example.org.",
|
||||
@ -649,7 +720,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "mate.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "mate.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -662,6 +733,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"{{.Calibre}}.bar.example.com",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
"",
|
||||
@ -678,6 +750,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -685,7 +758,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(0)},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -698,6 +771,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -706,7 +780,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(0)},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -719,6 +793,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -727,7 +802,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(10)},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -740,6 +815,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
@ -748,7 +824,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
"",
|
||||
[]string{"1.2.3.4"},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(0)},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -794,6 +870,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
tc.targetNamespace,
|
||||
tc.annotationFilter,
|
||||
tc.fqdnTemplate,
|
||||
tc.combineFQDNAndAnnotation,
|
||||
tc.compatibility,
|
||||
false,
|
||||
)
|
||||
@ -846,7 +923,7 @@ func TestClusterIpServices(t *testing.T) {
|
||||
"1.2.3.4",
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -864,7 +941,7 @@ func TestClusterIpServices(t *testing.T) {
|
||||
"4.5.6.7",
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.bar.example.com", Target: "4.5.6.7"},
|
||||
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -926,6 +1003,7 @@ func TestClusterIpServices(t *testing.T) {
|
||||
tc.targetNamespace,
|
||||
tc.annotationFilter,
|
||||
tc.fqdnTemplate,
|
||||
false,
|
||||
tc.compatibility,
|
||||
true,
|
||||
)
|
||||
@ -957,7 +1035,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
clusterIP string
|
||||
hostIP string
|
||||
podIP string
|
||||
selector map[string]string
|
||||
lbs []string
|
||||
podnames []string
|
||||
@ -988,8 +1066,36 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Target: "1.1.1.1"},
|
||||
{DNSName: "foo-1.service.example.org", Target: "1.1.1.1"},
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"annotated Headless services return endpoints with TTL for each selected Pod",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeClusterIP,
|
||||
"",
|
||||
"",
|
||||
map[string]string{"component": "foo"},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "service.example.org",
|
||||
ttlAnnotationKey: "1",
|
||||
},
|
||||
v1.ClusterIPNone,
|
||||
"1.1.1.1",
|
||||
map[string]string{
|
||||
"component": "foo",
|
||||
},
|
||||
[]string{},
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
|
||||
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -1015,7 +1121,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"foo-0", "foo-1"},
|
||||
[]v1.PodPhase{v1.PodRunning, v1.PodFailed},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo-0.service.example.org", Target: "1.1.1.1"},
|
||||
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -1041,8 +1147,8 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{"", ""},
|
||||
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "service.example.org", Target: "1.1.1.1"},
|
||||
{DNSName: "service.example.org", Target: "1.1.1.1"},
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -1081,7 +1187,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
Annotations: tc.annotations,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
HostIP: tc.hostIP,
|
||||
PodIP: tc.podIP,
|
||||
Phase: tc.phases[i],
|
||||
},
|
||||
}
|
||||
@ -1096,6 +1202,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
tc.targetNamespace,
|
||||
"",
|
||||
tc.fqdnTemplate,
|
||||
false,
|
||||
tc.compatibility,
|
||||
true,
|
||||
)
|
||||
@ -1138,7 +1245,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
|
||||
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
||||
require.NoError(b, err)
|
||||
|
||||
client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", "", false)
|
||||
client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false)
|
||||
require.NoError(b, err)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
@ -39,8 +39,8 @@ func validateEndpoint(t *testing.T, endpoint, expected *endpoint.Endpoint) {
|
||||
t.Errorf("expected %s, got %s", expected.DNSName, endpoint.DNSName)
|
||||
}
|
||||
|
||||
if endpoint.Target != expected.Target {
|
||||
t.Errorf("expected %s, got %s", expected.Target, endpoint.Target)
|
||||
if !endpoint.Targets.Same(expected.Targets) {
|
||||
t.Errorf("expected %s, got %s", expected.Targets, endpoint.Targets)
|
||||
}
|
||||
|
||||
if endpoint.RecordTTL != expected.RecordTTL {
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
)
|
||||
@ -64,6 +65,15 @@ func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error)
|
||||
return endpoint.TTL(ttlValue), nil
|
||||
}
|
||||
|
||||
func getHostnamesFromAnnotations(annotations map[string]string) []string {
|
||||
hostnameAnnotation, exists := annotations[hostnameAnnotationKey]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")
|
||||
}
|
||||
|
||||
// suitableType returns the DNS resource record type suitable for the target.
|
||||
// In this case type A for IPs and type CNAME for everything else.
|
||||
func suitableType(target string) string {
|
||||
|
@ -38,8 +38,10 @@ type Config struct {
|
||||
Namespace string
|
||||
AnnotationFilter string
|
||||
FQDNTemplate string
|
||||
CombineFQDNAndAnnotation bool
|
||||
Compatibility string
|
||||
PublishInternal bool
|
||||
ConnectorServer string
|
||||
}
|
||||
|
||||
// ClientGenerator provides clients
|
||||
@ -87,15 +89,17 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.Compatibility, cfg.PublishInternal)
|
||||
return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal)
|
||||
case "ingress":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate)
|
||||
return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation)
|
||||
case "fake":
|
||||
return NewFakeSource(cfg.FQDNTemplate)
|
||||
case "connector":
|
||||
return NewConnectorSource(cfg.ConnectorServer)
|
||||
}
|
||||
return nil, ErrSourceNotFound
|
||||
}
|
||||
|
11
vendor/cloud.google.com/go/.travis.yml
generated
vendored
11
vendor/cloud.google.com/go/.travis.yml
generated
vendored
@ -1,11 +0,0 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
install:
|
||||
- go get -v cloud.google.com/go/...
|
||||
script:
|
||||
- openssl aes-256-cbc -K $encrypted_912ff8fa81ad_key -iv $encrypted_912ff8fa81ad_iv -in key.json.enc -out key.json -d
|
||||
- GCLOUD_TESTS_GOLANG_PROJECT_ID="dulcet-port-762" GCLOUD_TESTS_GOLANG_KEY="$(pwd)/key.json"
|
||||
go test -race -v cloud.google.com/go/...
|
15
vendor/cloud.google.com/go/AUTHORS
generated
vendored
15
vendor/cloud.google.com/go/AUTHORS
generated
vendored
@ -1,15 +0,0 @@
|
||||
# This is the official list of cloud authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as:
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Google Inc.
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Palm Stone Games, Inc.
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
126
vendor/cloud.google.com/go/CONTRIBUTING.md
generated
vendored
126
vendor/cloud.google.com/go/CONTRIBUTING.md
generated
vendored
@ -1,126 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
1. Sign one of the contributor license agreements below.
|
||||
1. `go get golang.org/x/review/git-codereview` to install the code reviewing tool.
|
||||
1. Get the cloud package by running `go get -d cloud.google.com/go`.
|
||||
1. If you have already checked out the source, make sure that the remote git
|
||||
origin is https://code.googlesource.com/gocloud:
|
||||
|
||||
git remote set-url origin https://code.googlesource.com/gocloud
|
||||
1. Make sure your auth is configured correctly by visiting
|
||||
https://code.googlesource.com, clicking "Generate Password", and following
|
||||
the directions.
|
||||
1. Make changes and create a change by running `git codereview change <name>`,
|
||||
provide a commit message, and use `git codereview mail` to create a Gerrit CL.
|
||||
1. Keep amending to the change and mail as your receive feedback.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
In addition to the unit tests, you may run the integration test suite.
|
||||
|
||||
To run the integrations tests, creating and configuration of a project in the
|
||||
Google Developers Console is required.
|
||||
|
||||
After creating a project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount).
|
||||
Ensure the project-level **Owner** [IAM role](console.cloud.google.com/iam-admin/iam/project)
|
||||
(or **Editor** and **Logs Configuration Writer** roles) are added to the
|
||||
service account.
|
||||
|
||||
Once you create a project, set the following environment variables to be able to
|
||||
run the against the actual APIs.
|
||||
|
||||
- **GCLOUD_TESTS_GOLANG_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455)
|
||||
- **GCLOUD_TESTS_GOLANG_KEY**: The path to the JSON key file.
|
||||
|
||||
Install the [gcloud command-line tool][gcloudcli] to your machine and use it
|
||||
to create the indexes used in the datastore integration tests with indexes
|
||||
found in `datastore/testdata/index.yaml`:
|
||||
|
||||
From the project's root directory:
|
||||
|
||||
``` sh
|
||||
# Set the default project in your env
|
||||
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Authenticate the gcloud tool with your account
|
||||
$ gcloud auth login
|
||||
|
||||
# Create the indexes
|
||||
$ gcloud preview datastore create-indexes datastore/testdata/index.yaml
|
||||
```
|
||||
|
||||
The Sink integration tests in preview/logging require a Google Cloud storage
|
||||
bucket with the same name as your test project, and with the Stackdriver Logging
|
||||
service account as owner:
|
||||
``` sh
|
||||
$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
$ gsutil acl ch -g cloud-logs@google.com:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
```
|
||||
|
||||
Once you've set the environment variables, you can run the integration tests by
|
||||
running:
|
||||
|
||||
``` sh
|
||||
$ go test -v cloud.google.com/go/...
|
||||
```
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
Before we can accept your pull requests you'll need to sign a Contributor
|
||||
License Agreement (CLA):
|
||||
|
||||
- **If you are an individual writing original source code** and **you own the
|
||||
- intellectual property**, then you'll need to sign an [individual CLA][indvcla].
|
||||
- **If you work for a company that wants to allow you to contribute your work**,
|
||||
then you'll need to sign a [corporate CLA][corpcla].
|
||||
|
||||
You can sign these electronically (just scroll to the bottom). After that,
|
||||
we'll be able to accept your pull requests.
|
||||
|
||||
## Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
|
||||
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
|
||||
[indvcla]: https://developers.google.com/open-source/cla/individual
|
||||
[corpcla]: https://developers.google.com/open-source/cla/corporate
|
34
vendor/cloud.google.com/go/CONTRIBUTORS
generated
vendored
34
vendor/cloud.google.com/go/CONTRIBUTORS
generated
vendored
@ -1,34 +0,0 @@
|
||||
# People who have agreed to one of the CLAs and can contribute patches.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# https://developers.google.com/open-source/cla/individual
|
||||
# https://developers.google.com/open-source/cla/corporate
|
||||
#
|
||||
# Names should be added to this file as:
|
||||
# Name <email address>
|
||||
|
||||
# Keep the list alphabetically sorted.
|
||||
|
||||
Andreas Litt <andreas.litt@gmail.com>
|
||||
Andrew Gerrand <adg@golang.org>
|
||||
Brad Fitzpatrick <bradfitz@golang.org>
|
||||
Burcu Dogan <jbd@google.com>
|
||||
Dave Day <djd@golang.org>
|
||||
David Sansome <me@davidsansome.com>
|
||||
David Symonds <dsymonds@golang.org>
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Glenn Lewis <gmlewis@google.com>
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Jonathan Amsterdam <jba@google.com>
|
||||
Luna Duclos <luna.duclos@palmstonegames.com>
|
||||
Michael McGreevy <mcgreevy@golang.org>
|
||||
Omar Jarjur <ojarjur@google.com>
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Sarah Adams <shadams@google.com>
|
||||
Toby Burress <kurin@google.com>
|
||||
Tuo Shan <shantuo@google.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014 Google Inc.
|
||||
|
||||
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.
|
245
vendor/cloud.google.com/go/README.md
generated
vendored
245
vendor/cloud.google.com/go/README.md
generated
vendored
@ -1,245 +0,0 @@
|
||||
# Google Cloud for Go
|
||||
|
||||
[](https://travis-ci.org/GoogleCloudPlatform/google-cloud-go)
|
||||
[](https://godoc.org/cloud.google.com/go)
|
||||
|
||||
``` go
|
||||
import "cloud.google.com/go"
|
||||
```
|
||||
|
||||
Go packages for Google Cloud Platform services.
|
||||
|
||||
**NOTE:** These packages are under development, and may occasionally make
|
||||
backwards-incompatible changes.
|
||||
|
||||
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
|
||||
|
||||
## News
|
||||
|
||||
_September 8, 2016_
|
||||
|
||||
* New clients for some of Google's Machine Learning APIs: Vision, Speech, and
|
||||
Natural Language.
|
||||
|
||||
* Preview version of a new [Stackdriver Logging][cloud-logging] client in
|
||||
[`cloud.google.com/go/preview/logging`](https://godoc.org/cloud.google.com/go/preview/logging).
|
||||
This client uses gRPC as its transport layer, and supports log reading, sinks
|
||||
and metrics. It will replace the current client at `cloud.google.com/go/logging` shortly.
|
||||
|
||||
## Supported APIs
|
||||
|
||||
Google API | Status | Package
|
||||
-------------------------------|--------------|-----------------------------------------------------------
|
||||
[Datastore][cloud-datastore] | beta | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
|
||||
[Storage][cloud-storage] | beta | [`cloud.google.com/go/storage`][cloud-storage-ref]
|
||||
[Pub/Sub][cloud-pubsub] | experimental | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
|
||||
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
|
||||
[BigQuery][cloud-bigquery] | experimental | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
|
||||
[Logging][cloud-logging] | experimental | [`cloud.google.com/go/logging`][cloud-logging-ref]
|
||||
[Vision][cloud-vision] | experimental | [`cloud.google.com/go/vision`][cloud-vision-ref]
|
||||
[Language][cloud-language] | experimental | [`cloud.google.com/go/language/apiv1beta1`][cloud-language-ref]
|
||||
[Speech][cloud-speech] | experimental | [`cloud.google.com/go/speech/apiv1beta`][cloud-speech-ref]
|
||||
|
||||
|
||||
> **Experimental status**: the API is still being actively developed. As a
|
||||
> result, it might change in backward-incompatible ways and is not recommended
|
||||
> for production use.
|
||||
>
|
||||
> **Beta status**: the API is largely complete, but still has outstanding
|
||||
> features and bugs to be addressed. There may be minor backwards-incompatible
|
||||
> changes where necessary.
|
||||
>
|
||||
> **Stable status**: the API is mature and ready for production use. We will
|
||||
> continue addressing bugs and feature requests.
|
||||
|
||||
Documentation and examples are available at
|
||||
https://godoc.org/cloud.google.com/go
|
||||
|
||||
Visit or join the
|
||||
[google-api-go-announce group](https://groups.google.com/forum/#!forum/google-api-go-announce)
|
||||
for updates on these packages.
|
||||
|
||||
## Go Versions Supported
|
||||
|
||||
We support the two most recent major versions of Go. If Google App Engine uses
|
||||
an older version, we support that as well. You can see which versions are
|
||||
currently supported by looking at the lines following `go:` in
|
||||
[`.travis.yml`](.travis.yml).
|
||||
|
||||
## Authorization
|
||||
|
||||
By default, each API will use [Google Application Default Credentials][default-creds]
|
||||
for authorization credentials used in calling the API endpoints. This will allow your
|
||||
application to run in many environments without requiring explicit configuration.
|
||||
|
||||
Manually-configured authorization can be achieved using the
|
||||
[`golang.org/x/oauth2`](https://godoc.org/golang.org/x/oauth2) package to
|
||||
create an `oauth2.TokenSource`. This token source can be passed to the `NewClient`
|
||||
function for the relevant API using a
|
||||
[`option.WithTokenSource`](https://godoc.org/google.golang.org/api/option#WithTokenSource)
|
||||
option.
|
||||
|
||||
## Google Cloud Datastore [](https://godoc.org/cloud.google.com/go/datastore)
|
||||
|
||||
[Google Cloud Datastore][cloud-datastore] ([docs][cloud-datastore-docs]) is a fully-
|
||||
managed, schemaless database for storing non-relational data. Cloud Datastore
|
||||
automatically scales with your users and supports ACID transactions, high availability
|
||||
of reads and writes, strong consistency for reads and ancestor queries, and eventual
|
||||
consistency for all other queries.
|
||||
|
||||
Follow the [activation instructions][cloud-datastore-activation] to use the Google
|
||||
Cloud Datastore API with your project.
|
||||
|
||||
First create a `datastore.Client` to use throughout your application:
|
||||
|
||||
```go
|
||||
client, err := datastore.NewClient(ctx, "my-project-id")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
```
|
||||
|
||||
Then use that client to interact with the API:
|
||||
|
||||
```go
|
||||
type Post struct {
|
||||
Title string
|
||||
Body string `datastore:",noindex"`
|
||||
PublishedAt time.Time
|
||||
}
|
||||
keys := []*datastore.Key{
|
||||
datastore.NewKey(ctx, "Post", "post1", 0, nil),
|
||||
datastore.NewKey(ctx, "Post", "post2", 0, nil),
|
||||
}
|
||||
posts := []*Post{
|
||||
{Title: "Post 1", Body: "...", PublishedAt: time.Now()},
|
||||
{Title: "Post 2", Body: "...", PublishedAt: time.Now()},
|
||||
}
|
||||
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Google Cloud Storage [](https://godoc.org/cloud.google.com/go/storage)
|
||||
|
||||
[Google Cloud Storage][cloud-storage] ([docs][cloud-storage-docs]) allows you to store
|
||||
data on Google infrastructure with very high reliability, performance and availability,
|
||||
and can be used to distribute large data objects to users via direct download.
|
||||
|
||||
https://godoc.org/cloud.google.com/go/storage
|
||||
|
||||
First create a `storage.Client` to use throughout your application:
|
||||
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Read the object1 from bucket.
|
||||
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rc.Close()
|
||||
body, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Google Cloud Pub/Sub [](https://godoc.org/cloud.google.com/go/pubsub)
|
||||
|
||||
[Google Cloud Pub/Sub][cloud-pubsub] ([docs][cloud-pubsub-docs]) allows you to connect
|
||||
your services with reliable, many-to-many, asynchronous messaging hosted on Google's
|
||||
infrastructure. Cloud Pub/Sub automatically scales as you need it and provides a foundation
|
||||
for building your own robust, global services.
|
||||
|
||||
First create a `pubsub.Client` to use throughout your application:
|
||||
|
||||
```go
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Publish "hello world" on topic1.
|
||||
topic := client.Topic("topic1")
|
||||
msgIDs, err := topic.Publish(ctx, &pubsub.Message{
|
||||
Data: []byte("hello world"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create an iterator to pull messages via subscription1.
|
||||
it, err := client.Subscription("subscription1").Pull(ctx)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
defer it.Stop()
|
||||
|
||||
// Consume N messages from the iterator.
|
||||
for i := 0; i < N; i++ {
|
||||
msg, err := it.Next()
|
||||
if err == pubsub.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to retrieve message: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Message %d: %s\n", i, msg.Data)
|
||||
msg.Done(true) // Acknowledge that we've consumed the message.
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the
|
||||
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md)
|
||||
document for details. We're using Gerrit for our code reviews. Please don't open pull
|
||||
requests against this repo, new pull requests will be automatically closed.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
|
||||
[cloud-datastore]: https://cloud.google.com/datastore/
|
||||
[cloud-datastore-ref]: https://godoc.org/cloud.google.com/go/datastore
|
||||
[cloud-datastore-docs]: https://cloud.google.com/datastore/docs
|
||||
[cloud-datastore-activation]: https://cloud.google.com/datastore/docs/activate
|
||||
|
||||
[cloud-pubsub]: https://cloud.google.com/pubsub/
|
||||
[cloud-pubsub-ref]: https://godoc.org/cloud.google.com/go/pubsub
|
||||
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
|
||||
|
||||
[cloud-storage]: https://cloud.google.com/storage/
|
||||
[cloud-storage-ref]: https://godoc.org/cloud.google.com/go/storage
|
||||
[cloud-storage-docs]: https://cloud.google.com/storage/docs/overview
|
||||
[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
|
||||
|
||||
[cloud-bigtable]: https://cloud.google.com/bigtable/
|
||||
[cloud-bigtable-ref]: https://godoc.org/cloud.google.com/go/bigtable
|
||||
|
||||
[cloud-bigquery]: https://cloud.google.com/bigquery/
|
||||
[cloud-bigquery-ref]: https://godoc.org/cloud.google.com/go/bigquery
|
||||
|
||||
[cloud-logging]: https://cloud.google.com/logging/
|
||||
[cloud-logging-ref]: https://godoc.org/cloud.google.com/go/logging
|
||||
|
||||
[cloud-vision]: https://cloud.google.com/vision/
|
||||
[cloud-vision-ref]: https://godoc.org/cloud.google.com/go/vision
|
||||
|
||||
[cloud-language]: https://cloud.google.com/natural-language
|
||||
[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1beta1
|
||||
|
||||
[cloud-speech]: https://cloud.google.com/speech
|
||||
[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1beta1
|
||||
|
||||
[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials
|
26
vendor/cloud.google.com/go/appveyor.yml
generated
vendored
26
vendor/cloud.google.com/go/appveyor.yml
generated
vendored
@ -1,26 +0,0 @@
|
||||
# This file configures AppVeyor (http://www.appveyor.com),
|
||||
# a Windows-based CI service similar to Travis.
|
||||
|
||||
# Identifier for this run
|
||||
version: "{build}"
|
||||
|
||||
# Clone the repo into this path, which conforms to the standard
|
||||
# Go workspace structure.
|
||||
clone_folder: c:\gopath\src\cloud.google.com\go
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
|
||||
install:
|
||||
# Info for debugging.
|
||||
- echo %PATH%
|
||||
- go version
|
||||
- go env
|
||||
- go get -v -d -t ./...
|
||||
|
||||
# Provide a build script, or AppVeyor will call msbuild.
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- go test -short -v ./...
|
60
vendor/cloud.google.com/go/authexample_test.go
generated
vendored
60
vendor/cloud.google.com/go/authexample_test.go
generated
vendored
@ -1,60 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 cloud_test
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/datastore"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
func Example_applicationDefaultCredentials() {
|
||||
ctx := context.Background()
|
||||
// Use Google Application Default Credentials to authorize and authenticate the client.
|
||||
// More information about Application Default Credentials and how to enable is at
|
||||
// https://developers.google.com/identity/protocols/application-default-credentials.
|
||||
//
|
||||
// This is the recommended way of authorizing and authenticating.
|
||||
//
|
||||
// Note: The example uses the datastore client, but the same steps apply to
|
||||
// the other client libraries underneath this package.
|
||||
client, err := datastore.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: handle error.
|
||||
}
|
||||
// Use the client.
|
||||
_ = client
|
||||
}
|
||||
|
||||
func Example_serviceAccountFile() {
|
||||
// Warning: The better way to use service accounts is to set GOOGLE_APPLICATION_CREDENTIALS
|
||||
// and use the Application Default Credentials.
|
||||
ctx := context.Background()
|
||||
// Use a JSON key file associated with a Google service account to
|
||||
// authenticate and authorize.
|
||||
// Go to https://console.developers.google.com/permissions/serviceaccounts to create
|
||||
// and download a service account key for your project.
|
||||
//
|
||||
// Note: The example uses the datastore client, but the same steps apply to
|
||||
// the other client libraries underneath this package.
|
||||
client, err := datastore.NewClient(ctx,
|
||||
"project-id",
|
||||
option.WithServiceAccountFile("/path/to/service-account-key.json"))
|
||||
if err != nil {
|
||||
// TODO: handle error.
|
||||
}
|
||||
// Use the client.
|
||||
_ = client
|
||||
}
|
175
vendor/cloud.google.com/go/bigquery/bigquery.go
generated
vendored
175
vendor/cloud.google.com/go/bigquery/bigquery.go
generated
vendored
@ -1,175 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
// TODO(mcgreevy): support dry-run mode when creating jobs.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/transport"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
bq "google.golang.org/api/bigquery/v2"
|
||||
)
|
||||
|
||||
const prodAddr = "https://www.googleapis.com/bigquery/v2/"
|
||||
|
||||
// A Source is a source of data for the Copy function.
|
||||
type Source interface {
|
||||
implementsSource()
|
||||
}
|
||||
|
||||
// A Destination is a destination of data for the Copy function.
|
||||
type Destination interface {
|
||||
implementsDestination()
|
||||
}
|
||||
|
||||
// An Option is an optional argument to Copy.
|
||||
type Option interface {
|
||||
implementsOption()
|
||||
}
|
||||
|
||||
// A ReadSource is a source of data for the Read function.
|
||||
type ReadSource interface {
|
||||
implementsReadSource()
|
||||
}
|
||||
|
||||
// A ReadOption is an optional argument to Read.
|
||||
type ReadOption interface {
|
||||
customizeRead(conf *pagingConf)
|
||||
}
|
||||
|
||||
const Scope = "https://www.googleapis.com/auth/bigquery"
|
||||
const userAgent = "gcloud-golang-bigquery/20160429"
|
||||
|
||||
// Client may be used to perform BigQuery operations.
|
||||
type Client struct {
|
||||
service service
|
||||
projectID string
|
||||
}
|
||||
|
||||
// NewClient constructs a new Client which can perform BigQuery operations.
|
||||
// Operations performed via the client are billed to the specified GCP project.
|
||||
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
|
||||
o := []option.ClientOption{
|
||||
option.WithEndpoint(prodAddr),
|
||||
option.WithScopes(Scope),
|
||||
option.WithUserAgent(userAgent),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
httpClient, endpoint, err := transport.NewHTTPClient(ctx, o...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dialing: %v", err)
|
||||
}
|
||||
|
||||
s, err := newBigqueryService(httpClient, endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("constructing bigquery client: %v", err)
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
service: s,
|
||||
projectID: projectID,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// initJobProto creates and returns a bigquery Job proto.
|
||||
// The proto is customized using any jobOptions in options.
|
||||
// The list of Options is returned with the jobOptions removed.
|
||||
func initJobProto(projectID string, options []Option) (*bq.Job, []Option) {
|
||||
job := &bq.Job{}
|
||||
|
||||
var other []Option
|
||||
for _, opt := range options {
|
||||
if o, ok := opt.(jobOption); ok {
|
||||
o.customizeJob(job, projectID)
|
||||
} else {
|
||||
other = append(other, opt)
|
||||
}
|
||||
}
|
||||
return job, other
|
||||
}
|
||||
|
||||
// Copy starts a BigQuery operation to copy data from a Source to a Destination.
|
||||
func (c *Client) Copy(ctx context.Context, dst Destination, src Source, options ...Option) (*Job, error) {
|
||||
switch dst := dst.(type) {
|
||||
case *Table:
|
||||
switch src := src.(type) {
|
||||
case *GCSReference:
|
||||
return c.load(ctx, dst, src, options)
|
||||
case *Table:
|
||||
return c.cp(ctx, dst, Tables{src}, options)
|
||||
case Tables:
|
||||
return c.cp(ctx, dst, src, options)
|
||||
case *Query:
|
||||
return c.query(ctx, dst, src, options)
|
||||
}
|
||||
case *GCSReference:
|
||||
if src, ok := src.(*Table); ok {
|
||||
return c.extract(ctx, dst, src, options)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no Copy operation matches dst/src pair: dst: %T ; src: %T", dst, src)
|
||||
}
|
||||
|
||||
// Query creates a query with string q. You may optionally set
|
||||
// DefaultProjectID and DefaultDatasetID on the returned query before using it.
|
||||
func (c *Client) Query(q string) *Query {
|
||||
return &Query{Q: q, client: c}
|
||||
}
|
||||
|
||||
// Read submits a query for execution and returns the results via an Iterator.
|
||||
//
|
||||
// Read uses a temporary table to hold the results of the query job.
|
||||
//
|
||||
// For more control over how a query is performed, don't use this method but
|
||||
// instead pass the Query as a Source to Client.Copy, and call Read on the
|
||||
// resulting Job.
|
||||
func (q *Query) Read(ctx context.Context, options ...ReadOption) (*Iterator, error) {
|
||||
dest := &Table{}
|
||||
job, err := q.client.Copy(ctx, dest, q, WriteTruncate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return job.Read(ctx, options...)
|
||||
}
|
||||
|
||||
// executeQuery submits a query for execution and returns the results via an Iterator.
|
||||
func (c *Client) executeQuery(ctx context.Context, q *Query, options ...ReadOption) (*Iterator, error) {
|
||||
dest := &Table{}
|
||||
job, err := c.Copy(ctx, dest, q, WriteTruncate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Read(ctx, job, options...)
|
||||
}
|
||||
|
||||
// Dataset creates a handle to a BigQuery dataset in the client's project.
|
||||
func (c *Client) Dataset(id string) *Dataset {
|
||||
return c.DatasetInProject(c.projectID, id)
|
||||
}
|
||||
|
||||
// DatasetInProject creates a handle to a BigQuery dataset in the specified project.
|
||||
func (c *Client) DatasetInProject(projectID, datasetID string) *Dataset {
|
||||
return &Dataset{
|
||||
projectID: projectID,
|
||||
id: datasetID,
|
||||
service: c.service,
|
||||
}
|
||||
}
|
47
vendor/cloud.google.com/go/bigquery/copy_op.go
generated
vendored
47
vendor/cloud.google.com/go/bigquery/copy_op.go
generated
vendored
@ -1,47 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
bq "google.golang.org/api/bigquery/v2"
|
||||
)
|
||||
|
||||
type copyOption interface {
|
||||
customizeCopy(conf *bq.JobConfigurationTableCopy)
|
||||
}
|
||||
|
||||
func (c *Client) cp(ctx context.Context, dst *Table, src Tables, options []Option) (*Job, error) {
|
||||
job, options := initJobProto(c.projectID, options)
|
||||
payload := &bq.JobConfigurationTableCopy{}
|
||||
|
||||
dst.customizeCopyDst(payload)
|
||||
src.customizeCopySrc(payload)
|
||||
|
||||
for _, opt := range options {
|
||||
o, ok := opt.(copyOption)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("option (%#v) not applicable to dst/src pair: dst: %T ; src: %T", opt, dst, src)
|
||||
}
|
||||
o.customizeCopy(payload)
|
||||
}
|
||||
|
||||
job.Configuration = &bq.JobConfiguration{
|
||||
Copy: payload,
|
||||
}
|
||||
return c.service.insertJob(ctx, job, c.projectID)
|
||||
}
|
104
vendor/cloud.google.com/go/bigquery/copy_test.go
generated
vendored
104
vendor/cloud.google.com/go/bigquery/copy_test.go
generated
vendored
@ -1,104 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
bq "google.golang.org/api/bigquery/v2"
|
||||
)
|
||||
|
||||
func defaultCopyJob() *bq.Job {
|
||||
return &bq.Job{
|
||||
Configuration: &bq.JobConfiguration{
|
||||
Copy: &bq.JobConfigurationTableCopy{
|
||||
DestinationTable: &bq.TableReference{
|
||||
ProjectId: "d-project-id",
|
||||
DatasetId: "d-dataset-id",
|
||||
TableId: "d-table-id",
|
||||
},
|
||||
SourceTables: []*bq.TableReference{
|
||||
{
|
||||
ProjectId: "s-project-id",
|
||||
DatasetId: "s-dataset-id",
|
||||
TableId: "s-table-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
testCases := []struct {
|
||||
dst *Table
|
||||
src Tables
|
||||
options []Option
|
||||
want *bq.Job
|
||||
}{
|
||||
{
|
||||
dst: &Table{
|
||||
ProjectID: "d-project-id",
|
||||
DatasetID: "d-dataset-id",
|
||||
TableID: "d-table-id",
|
||||
},
|
||||
src: Tables{
|
||||
{
|
||||
ProjectID: "s-project-id",
|
||||
DatasetID: "s-dataset-id",
|
||||
TableID: "s-table-id",
|
||||
},
|
||||
},
|
||||
want: defaultCopyJob(),
|
||||
},
|
||||
{
|
||||
dst: &Table{
|
||||
ProjectID: "d-project-id",
|
||||
DatasetID: "d-dataset-id",
|
||||
TableID: "d-table-id",
|
||||
},
|
||||
src: Tables{
|
||||
{
|
||||
ProjectID: "s-project-id",
|
||||
DatasetID: "s-dataset-id",
|
||||
TableID: "s-table-id",
|
||||
},
|
||||
},
|
||||
options: []Option{CreateNever, WriteTruncate},
|
||||
want: func() *bq.Job {
|
||||
j := defaultCopyJob()
|
||||
j.Configuration.Copy.CreateDisposition = "CREATE_NEVER"
|
||||
j.Configuration.Copy.WriteDisposition = "WRITE_TRUNCATE"
|
||||
return j
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s := &testService{}
|
||||
c := &Client{
|
||||
service: s,
|
||||
}
|
||||
if _, err := c.Copy(context.Background(), tc.dst, tc.src, tc.options...); err != nil {
|
||||
t.Errorf("err calling cp: %v", err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(s.Job, tc.want) {
|
||||
t.Errorf("copying: got:\n%v\nwant:\n%v", s.Job, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
79
vendor/cloud.google.com/go/bigquery/create_table_test.go
generated
vendored
79
vendor/cloud.google.com/go/bigquery/create_table_test.go
generated
vendored
@ -1,79 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
bq "google.golang.org/api/bigquery/v2"
|
||||
)
|
||||
|
||||
type createTableRecorder struct {
|
||||
conf *createTableConf
|
||||
service
|
||||
}
|
||||
|
||||
func (rec *createTableRecorder) createTable(ctx context.Context, conf *createTableConf) error {
|
||||
rec.conf = conf
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateTableOptions(t *testing.T) {
|
||||
s := &createTableRecorder{}
|
||||
c := &Client{
|
||||
projectID: "p",
|
||||
service: s,
|
||||
}
|
||||
ds := c.Dataset("d")
|
||||
table := ds.Table("t")
|
||||
exp := time.Now()
|
||||
q := "query"
|
||||
if err := table.Create(context.Background(), TableExpiration(exp), ViewQuery(q)); err != nil {
|
||||
t.Fatalf("err calling Table.Create: %v", err)
|
||||
}
|
||||
want := createTableConf{
|
||||
projectID: "p",
|
||||
datasetID: "d",
|
||||
tableID: "t",
|
||||
expiration: exp,
|
||||
viewQuery: q,
|
||||
}
|
||||
if !reflect.DeepEqual(*s.conf, want) {
|
||||
t.Errorf("createTableConf: got:\n%v\nwant:\n%v", *s.conf, want)
|
||||
}
|
||||
|
||||
sc := Schema{fieldSchema("desc", "name", "STRING", false, true)}
|
||||
if err := table.Create(context.Background(), TableExpiration(exp), sc); err != nil {
|
||||
t.Fatalf("err calling Table.Create: %v", err)
|
||||
}
|
||||
want = createTableConf{
|
||||
projectID: "p",
|
||||
datasetID: "d",
|
||||
tableID: "t",
|
||||
expiration: exp,
|
||||
// No need for an elaborate schema, that is tested in schema_test.go.
|
||||
schema: &bq.TableSchema{
|
||||
Fields: []*bq.TableFieldSchema{
|
||||
bqTableFieldSchema("desc", "name", "STRING", "REQUIRED"),
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(*s.conf, want) {
|
||||
t.Errorf("createTableConf: got:\n%v\nwant:\n%v", *s.conf, want)
|
||||
}
|
||||
}
|
55
vendor/cloud.google.com/go/bigquery/dataset.go
generated
vendored
55
vendor/cloud.google.com/go/bigquery/dataset.go
generated
vendored
@ -1,55 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
// Dataset is a reference to a BigQuery dataset.
|
||||
type Dataset struct {
|
||||
projectID string
|
||||
id string
|
||||
service service
|
||||
}
|
||||
|
||||
// ListTables returns a list of all the tables contained in the Dataset.
|
||||
func (d *Dataset) ListTables(ctx context.Context) ([]*Table, error) {
|
||||
var tables []*Table
|
||||
|
||||
err := getPages("", func(pageToken string) (string, error) {
|
||||
ts, tok, err := d.service.listTables(ctx, d.projectID, d.id, pageToken)
|
||||
if err == nil {
|
||||
tables = append(tables, ts...)
|
||||
}
|
||||
return tok, err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
// Create creates a dataset in the BigQuery service. An error will be returned
|
||||
// if the dataset already exists.
|
||||
func (d *Dataset) Create(ctx context.Context) error {
|
||||
return d.service.insertDataset(ctx, d.id, d.projectID)
|
||||
}
|
||||
|
||||
// Table creates a handle to a BigQuery table in the dataset.
|
||||
// To determine if a table exists, call Table.Metadata.
|
||||
// If the table does not already exist, use Table.Create to create it.
|
||||
func (d *Dataset) Table(tableID string) *Table {
|
||||
return &Table{ProjectID: d.projectID, DatasetID: d.id, TableID: tableID, service: d.service}
|
||||
}
|
105
vendor/cloud.google.com/go/bigquery/dataset_test.go
generated
vendored
105
vendor/cloud.google.com/go/bigquery/dataset_test.go
generated
vendored
@ -1,105 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// readServiceStub services read requests by returning data from an in-memory list of values.
|
||||
type listTablesServiceStub struct {
|
||||
expectedProject, expectedDataset string
|
||||
values [][]*Table // contains pages of tables.
|
||||
pageTokens map[string]string // maps incoming page token to returned page token.
|
||||
|
||||
service
|
||||
}
|
||||
|
||||
func (s *listTablesServiceStub) listTables(ctx context.Context, projectID, datasetID, pageToken string) ([]*Table, string, error) {
|
||||
if projectID != s.expectedProject {
|
||||
return nil, "", errors.New("wrong project id")
|
||||
}
|
||||
if datasetID != s.expectedDataset {
|
||||
return nil, "", errors.New("wrong dataset id")
|
||||
}
|
||||
|
||||
tables := s.values[0]
|
||||
s.values = s.values[1:]
|
||||
return tables, s.pageTokens[pageToken], nil
|
||||
}
|
||||
|
||||
func TestListTables(t *testing.T) {
|
||||
t1 := &Table{ProjectID: "p1", DatasetID: "d1", TableID: "t1"}
|
||||
t2 := &Table{ProjectID: "p1", DatasetID: "d1", TableID: "t2"}
|
||||
t3 := &Table{ProjectID: "p1", DatasetID: "d1", TableID: "t3"}
|
||||
testCases := []struct {
|
||||
data [][]*Table
|
||||
pageTokens map[string]string
|
||||
want []*Table
|
||||
}{
|
||||
{
|
||||
data: [][]*Table{{t1, t2}, {t3}},
|
||||
pageTokens: map[string]string{"": "a", "a": ""},
|
||||
want: []*Table{t1, t2, t3},
|
||||
},
|
||||
{
|
||||
data: [][]*Table{{t1, t2}, {t3}},
|
||||
pageTokens: map[string]string{"": ""}, // no more pages after first one.
|
||||
want: []*Table{t1, t2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
c := &Client{
|
||||
service: &listTablesServiceStub{
|
||||
expectedProject: "x",
|
||||
expectedDataset: "y",
|
||||
values: tc.data,
|
||||
pageTokens: tc.pageTokens,
|
||||
},
|
||||
projectID: "x",
|
||||
}
|
||||
got, err := c.Dataset("y").ListTables(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("err calling ListTables: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("reading: got:\n%v\nwant:\n%v", got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTablesError(t *testing.T) {
|
||||
c := &Client{
|
||||
service: &listTablesServiceStub{
|
||||
expectedProject: "x",
|
||||
expectedDataset: "y",
|
||||
},
|
||||
projectID: "x",
|
||||
}
|
||||
// Test that service read errors are propagated back to the caller.
|
||||
// Passing "not y" as the dataset id will cause the service to return an error.
|
||||
_, err := c.Dataset("not y").ListTables(context.Background())
|
||||
if err == nil {
|
||||
// Read should not return an error; only Err should.
|
||||
t.Errorf("ListTables expected: non-nil err, got: nil")
|
||||
}
|
||||
}
|
18
vendor/cloud.google.com/go/bigquery/doc.go
generated
vendored
18
vendor/cloud.google.com/go/bigquery/doc.go
generated
vendored
@ -1,18 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery provides a client for the BigQuery service.
|
||||
//
|
||||
// Note: This package is a work-in-progress. Backwards-incompatible changes should be expected.
|
||||
package bigquery // import "cloud.google.com/go/bigquery"
|
82
vendor/cloud.google.com/go/bigquery/error.go
generated
vendored
82
vendor/cloud.google.com/go/bigquery/error.go
generated
vendored
@ -1,82 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 bigquery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
bq "google.golang.org/api/bigquery/v2"
|
||||
)
|
||||
|
||||
// An Error contains detailed information about a failed bigquery operation.
|
||||
type Error struct {
|
||||
// Mirrors bq.ErrorProto, but drops DebugInfo
|
||||
Location, Message, Reason string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("{Location: %q; Message: %q; Reason: %q}", e.Location, e.Message, e.Reason)
|
||||
}
|
||||
|
||||
func errorFromErrorProto(ep *bq.ErrorProto) *Error {
|
||||
if ep == nil {
|
||||
return nil
|
||||
}
|
||||
return &Error{
|
||||
Location: ep.Location,
|
||||
Message: ep.Message,
|
||||
Reason: ep.Reason,
|
||||
}
|
||||
}
|
||||
|
||||
// A MultiError contains multiple related errors.
|
||||
type MultiError []error
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
switch len(m) {
|
||||
case 0:
|
||||
return "(0 errors)"
|
||||
case 1:
|
||||
return m[0].Error()
|
||||
case 2:
|
||||
return m[0].Error() + " (and 1 other error)"
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d other errors)", m[0].Error(), len(m)-1)
|
||||
}
|
||||
|
||||
// RowInsertionError contains all errors that occurred when attempting to insert a row.
|
||||
type RowInsertionError struct {
|
||||
InsertID string // The InsertID associated with the affected row.
|
||||
RowIndex int // The 0-based index of the affected row in the batch of rows being inserted.
|
||||
Errors MultiError
|
||||
}
|
||||
|
||||
func (e *RowInsertionError) Error() string {
|
||||
errFmt := "insertion of row [insertID: %q; insertIndex: %v] failed with error: %s"
|
||||
return fmt.Sprintf(errFmt, e.InsertID, e.RowIndex, e.Errors.Error())
|
||||
}
|
||||
|
||||
// PutMultiError contains an error for each row which was not successfully inserted
|
||||
// into a BigQuery table.
|
||||
type PutMultiError []RowInsertionError
|
||||
|
||||
func (pme PutMultiError) Error() string {
|
||||
plural := "s"
|
||||
if len(pme) == 1 {
|
||||
plural = ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v row insertion%s failed", len(pme), plural)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user