Commit b0f437a4 authored by Martin Linkhorst's avatar Martin Linkhorst Committed by GitHub
Browse files

support hostnames as endpoint targets (CNAME support) (#122)

* feat(aws): support hostnames as endpoint targets

* docs: describe how to run ExternalDNS on AWS

* docs: update changelog with CNAME feature

* docs: update changelog to include AWS documentation

* fix(aws): test that updating records removes the old value

* feat(google): add CNAME support to Google provider

* fix(source): sanitize source and target hostnames

* docs: update changelog to include latest changes

* docs(aws): mention that ExternalDNS takes full ownership of a hosted zone

* fix(aws): switch route53 tests to use endpoint pointers

* docs: add TODO to remove record filtering once ownership is in place
parent 5c794e1b
Features:
- Support creation of CNAME records when endpoint target is a hostname.
- Allow omitting the trailing dot in Service annotations.
Bug fixes:
- AWS Route 53: Do not submit request when there are no changes.
Documentation:
- Add documentation on how to setup ExternalDNS for Services on AWS.
## v0.1.0 - 2017-03-30 (KubeCon)
Features:
......
......@@ -42,7 +42,7 @@ $ kubectl run nginx --image=nginx --replicas=1 --port=80
$ kubectl expose deployment nginx --port=80 --target-port=80 --type=LoadBalancer
```
Annotate the Service with your desired external DNS name. Make sure to change `example.org` to your domain (and that it includes the trailing dot):
Annotate the Service with your desired external DNS name. Make sure to change `example.org` to your domain.
```console
$ kubectl annotate service nginx "external-dns.alpha.kubernetes.io/hostname=nginx.example.org."
......
# Setting up ExternalDNS for Services on AWS
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on AWS.
Create a DNS zone which will contain the managed DNS records.
```console
$ aws route53 create-hosted-zone --name "external-dns-test.teapot.zalan.do." --caller-reference "external-dns-test-$(date +%s)"
```
Make a note of the ID of the hosted zone you just created.
```console
$ aws route53 list-hosted-zones-by-name --dns-name "external-dns-test.teapot.zalan.do." | jq -r '.HostedZones[0].Id'
/hostedzone/Z16P7IEWFWZ4RB
```
Make a note of the nameservers that were assigned to your new zone.
```console
$ aws route53 list-resource-record-sets --hosted-zone-id "/hostedzone/Z16P7IEWFWZ4RB" \
--query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value'
ns-1455.awsdns-53.org.
ns-1694.awsdns-19.co.uk.
ns-764.awsdns-31.net.
ns-62.awsdns-07.com.
```
In this case it's the ones shown above but your's will differ.
If you decide not to create a new zone but reuse an existing one, make sure it's currently **unused** and **empty**. This version of ExternalDNS will remove all records it doesn't recognize from the zone.
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply the following manifest file to deploy ExternalDNS.
```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:v0.2.0-beta.0
args:
- --in-cluster
- --zone=external-dns-test.teapot.zalan.do.
- --source=service
- --provider=aws
- --dry-run=false
```
Create the following sample application to test that ExternalDNS works.
```yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.teapot.zalan.do.
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
```
After roughly two minutes check that a corresponding DNS record for your service was created.
```console
$ aws route53 list-resource-record-sets --hosted-zone-id "/hostedzone/Z16P7IEWFWZ4RB" \
--query "ResourceRecordSets[?Name == 'nginx.external-dns-test.teapot.zalan.do.']|[?Type == 'CNAME']"
[
{
"ResourceRecords": [
{
"Value": "ae11c2360188411e7951602725593fd1-1224345803.eu-central-1.elb.amazonaws.com"
}
],
"Type": "CNAME",
"Name": "nginx.external-dns-test.teapot.zalan.do.",
"TTL": 300
}
]
```
Let's check that we can resolve this DNS name. We'll ask the nameservers assigned to your zone first.
```console
$ dig +short @ns-1455.awsdns-53.org. nginx.external-dns-test.teapot.zalan.do.
ae11c2360188411e7951602725593fd1-1224345803.eu-central-1.elb.amazonaws.com.
```
If you hooked up your DNS zone with its parent zone correctly you can use `curl` to access your site.
```console
$ curl nginx.external-dns-test.teapot.zalan.do.
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
<body>
...
</body>
</html>
```
Ingress objects on AWS require a separately deployed Ingress controller which we'll describe in another tutorial.
## Clean up
Make sure to delete all Service objects before terminating the cluster so all load balancers get cleaned up correctly.
```console
$ kubectl delete service nginx
```
Give ExternalDNS some time to clean up the DNS records for you. Then delete the hosted zone.
```console
$ aws route53 delete-hosted-zone --id /hostedzone/Z16P7IEWFWZ4RB
```
# Setting up ExternalDNS on Google Container Engine
This tutorial describes how to setup `external-dns` for usage within a GKE cluster.
This tutorial describes how to setup ExternalDNS for usage within a GKE cluster.
Setup your environment to work with Google Cloud Platform. Fill in your values as needed, e.g. target project.
......@@ -48,7 +48,7 @@ $ gcloud dns record-sets transaction add ns-cloud-e{1..4}.googledomains.com. \
$ gcloud dns record-sets transaction execute --zone "gcp-zalan-do"
```
If you decide not to create a new zone but reuse an existing one, make sure it's currently **unused** and **empty**. This version of `external-dns` will remove all records it doesn't recognize from the zone.
If you decide not to create a new zone but reuse an existing one, make sure it's currently **unused** and **empty**. This version of ExternalDNS will remove all records it doesn't recognize from the zone.
Connect your `kubectl` client to the cluster you just created.
......@@ -56,7 +56,7 @@ Connect your `kubectl` client to the cluster you just created.
gcloud container clusters get-credentials "external-dns"
```
Apply the following manifest file to deploy `external-dns`.
Apply the following manifest file to deploy ExternalDNS.
```yaml
apiVersion: extensions/v1beta1
......@@ -86,7 +86,7 @@ spec:
Use `dry-run=true` if you want to be extra careful on the first run. Note, that you will not see any records created when you are running in dry-run mode. You can, however, inspect the logs and watch what would have been done.
Create the following sample application to test that `external-dns` works.
Create the following sample application to test that ExternalDNS works.
```yaml
apiVersion: v1
......@@ -216,7 +216,7 @@ $ kubectl delete service nginx
$ kubectl delete ingress nginx
```
Give `external-dns` some time to clean up the DNS records for you. Then delete the managed zone and cluster.
Give ExternalDNS some time to clean up the DNS records for you. Then delete the managed zone and cluster.
```console
$ gcloud dns managed-zones delete "external-dns-test-gcp-zalan-do"
......
......@@ -149,7 +149,12 @@ func (p *AWSProvider) Records(zone string) ([]*endpoint.Endpoint, error) {
f := func(resp *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) {
for _, r := range resp.ResourceRecordSets {
if aws.StringValue(r.Type) != route53.RRTypeA {
// TODO(linki, ownership): Remove once ownership system is in place.
// See: https://github.com/kubernetes-incubator/external-dns/pull/122/files/74e2c3d3e237411e619aefc5aab694742001cdec#r109863370
switch aws.StringValue(r.Type) {
case route53.RRTypeA, route53.RRTypeCname:
break
default:
continue
}
......@@ -255,7 +260,7 @@ func newChange(action string, endpoint *endpoint.Endpoint) *route53.Change {
},
},
TTL: aws.Int64(300),
Type: aws.String(route53.RRTypeA),
Type: aws.String(suitableType(endpoint.Target)),
},
}
......
......@@ -18,6 +18,7 @@ package provider
import (
"fmt"
"net"
"strings"
"testing"
......@@ -83,6 +84,14 @@ func (r *Route53APIStub) ChangeResourceRecordSets(input *route53.ChangeResourceR
}
for _, change := range input.ChangeBatch.Changes {
if aws.StringValue(change.ResourceRecordSet.Type) == route53.RRTypeA {
for _, rrs := range change.ResourceRecordSet.ResourceRecords {
if net.ParseIP(aws.StringValue(rrs.Value)) == nil {
return nil, fmt.Errorf("A records must point to IPs")
}
}
}
key := aws.StringValue(change.ResourceRecordSet.Name) + "::" + aws.StringValue(change.ResourceRecordSet.Type)
switch aws.StringValue(change.Action) {
case route53.ChangeActionCreate:
......@@ -358,6 +367,20 @@ func TestAWSUpdateRecords(t *testing.T) {
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 1.2.3.4")
}
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 1.2.3.4")
}
}
func TestAWSDeleteRecords(t *testing.T) {
......@@ -472,6 +495,20 @@ func TestAWSApply(t *testing.T) {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 1.2.3.4")
}
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 1.2.3.4")
}
// delete validation
found = false
......@@ -580,6 +617,20 @@ func TestAWSUpdateRecordDryRun(t *testing.T) {
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should not point to 1.2.3.4")
}
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 8.8.8.8")
}
}
func TestAWSDeleteRecordDryRun(t *testing.T) {
......@@ -698,6 +749,20 @@ func TestAWSApplyDryRun(t *testing.T) {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should not point to 1.2.3.4")
}
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "8.8.8.8" {
found = true
}
}
}
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to 8.8.8.8")
}
// delete validation
found = false
......@@ -713,6 +778,238 @@ func TestAWSApplyDryRun(t *testing.T) {
}
}
func TestAWSCreateRecordsCNAME(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
records := []*endpoint.Endpoint{{DNSName: "create-test.ext-dns-test.teapot.zalan.do.", Target: "foo.elb.amazonaws.com"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "create-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "foo.elb.amazonaws.com" {
found = true
}
}
}
if !found {
t.Fatal("create-test.ext-dns-test.teapot.zalan.do. should be there")
}
}
func TestAWSUpdateRecordsCNAME(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
oldRecords := []*endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "foo.elb.amazonaws.com"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", oldRecords)
if err != nil {
t.Fatal(err)
}
newRecords := []*endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "bar.elb.amazonaws.com"}}
err = provider.UpdateRecords("ext-dns-test.teapot.zalan.do.", newRecords, oldRecords)
if err != nil {
t.Fatal(err)
}
records, err := provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "bar.elb.amazonaws.com" {
found = true
}
}
}
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to bar.elb.amazonaws.com")
}
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "foo.elb.amazonaws.com" {
found = true
}
}
}
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to bar.elb.amazonaws.com")
}
}
func TestAWSDeleteRecordsCNAME(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
records := []*endpoint.Endpoint{{DNSName: "delete-test.ext-dns-test.teapot.zalan.do.", Target: "baz.elb.amazonaws.com"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
err = provider.DeleteRecords("ext-dns-test.teapot.zalan.do.", records)
if err != nil {
t.Fatal(err)
}
records, err = provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "delete-test.ext-dns-test.teapot.zalan.do." {
found = true
}
}
if found {
t.Fatal("delete-test.ext-dns-test.teapot.zalan.do. should be gone")
}
}
func TestAWSApplyCNAME(t *testing.T) {
provider := newAWSProvider(t, false)
_, err := provider.CreateZone("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
updateRecords := []*endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "foo.elb.amazonaws.com"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", updateRecords)
if err != nil {
t.Fatal(err)
}
deleteRecords := []*endpoint.Endpoint{{DNSName: "delete-test.ext-dns-test.teapot.zalan.do.", Target: "baz.elb.amazonaws.com"}}
err = provider.CreateRecords("ext-dns-test.teapot.zalan.do.", deleteRecords)
if err != nil {
t.Fatal(err)
}
createRecords := []*endpoint.Endpoint{{DNSName: "create-test.ext-dns-test.teapot.zalan.do.", Target: "foo.elb.amazonaws.com"}}
updateNewRecords := []*endpoint.Endpoint{{DNSName: "update-test.ext-dns-test.teapot.zalan.do.", Target: "bar.elb.amazonaws.com"}}
changes := &plan.Changes{
Create: createRecords,
UpdateNew: updateNewRecords,
UpdateOld: updateRecords,
Delete: deleteRecords,
}
err = provider.ApplyChanges("ext-dns-test.teapot.zalan.do.", changes)
if err != nil {
t.Fatal(err)
}
// create validation
records, err := provider.Records("ext-dns-test.teapot.zalan.do.")
if err != nil {
t.Fatal(err)
}
found := false
for _, r := range records {
if r.DNSName == "create-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "foo.elb.amazonaws.com" {
found = true
}
}
}
if !found {
t.Fatal("create-test.ext-dns-test.teapot.zalan.do. should be there")
}
// update validation
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "bar.elb.amazonaws.com" {
found = true
}
}
}
if !found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to bar.elb.amazonaws.com")
}
found = false
for _, r := range records {
if r.DNSName == "update-test.ext-dns-test.teapot.zalan.do." {
if r.Target == "foo.elb.amazonaws.com" {
found = true
}
}
}
if found {
t.Fatal("update-test.ext-dns-test.teapot.zalan.do. should point to bar.elb.amazonaws.com")
}
// delete validation
found = false
for _, r := range records {
if r.DNSName == "delete-test.ext-dns-test.teapot.zalan.do." {
found = true
}
}
if found {
t.Fatal("delete-test.ext-dns-test.teapot.zalan.do. should be gone")
}
}
func newAWSProvider(t *testing.T, dryRun bool) *AWSProvider {
client := NewRoute53APIStub()
......
......@@ -182,7 +182,12 @@ func (p *googleProvider) DeleteZone(name string) error {
func (p *googleProvider) Records(zone string) (endpoints []*endpoint.Endpoint, _ error) {
f := func(resp *dns.ResourceRecordSetsListResponse) error {
for _, r := range resp.Rrsets {
if r.Type != "A" {
// TODO(linki, ownership): Remove once ownership system is in place.
// See: https://github.com/kubernetes-incubator/external-dns/pull/122/files/74e2c3d3e237411e619aefc5aab694742001cdec#r109863370
switch r.Type {
case "A", "CNAME":
break
default:
continue
}
......@@ -289,7 +294,7 @@ func newRecord(endpoint *endpoint.Endpoint) *dns.ResourceRecordSet {
Name: endpoint.DNSName,
Rrdatas: []string{endpoint.Target},
Ttl: 300,
Type: "A",
Type: suitableType(endpoint.Target),
}
}
......
......@@ -34,11 +34,16 @@ var (
expectedRecordSets = []*dns.ResourceRecordSet{
{
Type: "A",
Name: "expected",
Rrdatas: []string{"target"},
Name: "expected-1",
Rrdatas: []string{"8.8.8.8"},
},
{
Type: "CNAME",
Name: "expected-2",
Rrdatas: []string{"target.com"},
},
{
Type: "NS",
Name: "unexpected",
Rrdatas: []string{"target"},
},
......
......@@ -17,6 +17,8 @@ limitations under the License.
package provider
import (
"net"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
......@@ -26,3 +28,13 @@ type Provider interface {
Records(zone string) ([]*endpoint.Endpoint, error)
ApplyChanges(zone string, changes *plan.Changes) error
}
// 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 {
if net.ParseIP(target) == nil {
return "CNAME"
}
return "A"
}
......@@ -17,8 +17,6 @@ limitations under the License.
package source
import (
"strings"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
......@@ -76,15 +74,10 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
endpoints = append(endpoints, endpoint.NewEndpoint(sanitizeHostname(rule.Host), lb.IP))
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(sanitizeHostname(rule.Host), lb.Hostname))
endpoints = append(endpoints, endpoint.NewEndpoint(sanitizeHostname(rule.Host), sanitizeHostname(lb.Hostname)))
}
}
}
return endpoints
}
// sanitizeHostname appends a trailing dot to a hostname if it's missing.
func sanitizeHostname(hostname string) string {
return strings.Trim(hostname, ".") + "."
}
......@@ -42,13 +42,13 @@ func testEndpointsFromIngress(t *testing.T) {
{
title: "one rule.host one lb.hostname",
ingress: fakeIngress{
dnsnames: []string{"foo.bar"},
hostnames: []string{"lb.com"},
dnsnames: []string{"foo.bar"}, // Kubernetes requires removal of trailing dot
hostnames: []string{"lb.com"}, // Kubernetes omits the trailing dot
},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar.",
Target: "lb.com",
Target: "lb.com.",
},
},
},
......@@ -83,11 +83,11 @@ func testEndpointsFromIngress(t *testing.T) {
},
{
DNSName: "foo.bar.",
Target: "elb.com",
Target: "elb.com.",
},
{
DNSName: "foo.bar.",
Target: "alb.com",
Target: "alb.com.",
},
},
},
......@@ -159,7 +159,7 @@ func testIngressEndpoints(t *testing.T) {
},
{
DNSName: "new.org.",
Target: "lb.com",
Target: "lb.com.",
},
},
},
......@@ -187,7 +187,7 @@ func testIngressEndpoints(t *testing.T) {
},
{
DNSName: "new.org.",
Target: "lb.com",
Target: "lb.com.",
},
},
},
......
......@@ -76,10 +76,10 @@ func endpointsFromService(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))
endpoints = append(endpoints, endpoint.NewEndpoint(sanitizeHostname(hostname), lb.IP))
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.Hostname))
endpoints = append(endpoints, endpoint.NewEndpoint(sanitizeHostname(hostname), sanitizeHostname(lb.Hostname)))
}
}
......
......@@ -59,11 +59,11 @@ func testServiceEndpoints(t *testing.T) {
"testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org.", Target: "1.2.3.4"},
},
},
{
......@@ -72,11 +72,25 @@ func testServiceEndpoints(t *testing.T) {
"testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"lb.example.com"},
[]string{"lb.example.com"}, // Kubernetes omits the trailing dot
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "lb.example.com"},
{DNSName: "foo.example.org.", Target: "lb.example.com."},
},
},
{
"annotated services can omit trailing dot",
"",
"testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted
},
[]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."},
},
},
{
......@@ -86,11 +100,11 @@ func testServiceEndpoints(t *testing.T) {
"foo",
map[string]string{
controllerAnnotationKey: controllerAnnotationValue,
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org.", Target: "1.2.3.4"},
},
},
{
......@@ -100,7 +114,7 @@ func testServiceEndpoints(t *testing.T) {
"foo",
map[string]string{
controllerAnnotationKey: "some-other-tool",
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{},
......@@ -111,11 +125,11 @@ func testServiceEndpoints(t *testing.T) {
"testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org.", Target: "1.2.3.4"},
},
},
{
......@@ -124,7 +138,7 @@ func testServiceEndpoints(t *testing.T) {
"other-testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{},
......@@ -135,11 +149,11 @@ func testServiceEndpoints(t *testing.T) {
"other-testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org.", Target: "1.2.3.4"},
},
},
{
......@@ -148,7 +162,7 @@ func testServiceEndpoints(t *testing.T) {
"testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]string{},
[]*endpoint.Endpoint{},
......@@ -159,12 +173,12 @@ func testServiceEndpoints(t *testing.T) {
"testing",
"foo",
map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
[]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.", Target: "1.2.3.4"},
{DNSName: "foo.example.org.", Target: "8.8.8.8"},
},
},
} {
......@@ -222,7 +236,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
Namespace: "testing",
Name: "foo",
Annotations: map[string]string{
hostnameAnnotationKey: "foo.example.org",
hostnameAnnotationKey: "foo.example.org.",
},
},
Status: v1.ServiceStatus{
......
......@@ -17,6 +17,8 @@ limitations under the License.
package source
import (
"strings"
"github.com/kubernetes-incubator/external-dns/endpoint"
)
......@@ -33,3 +35,8 @@ const (
type Source interface {
Endpoints() ([]*endpoint.Endpoint, error)
}
// sanitizeHostname appends a trailing dot to a hostname if it's missing.
func sanitizeHostname(hostname string) string {
return strings.Trim(hostname, ".") + "."
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment