From 522107696d737157fd6d88e97453e1e3e949467a Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Fri, 1 Jun 2018 09:00:10 -0400 Subject: [PATCH 01/25] Add failing test for TXT record planning (#580) --- plan/plan_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/plan/plan_test.go b/plan/plan_test.go index fc6bb6ade..29c6eb289 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -28,6 +28,7 @@ type PlanTestSuite struct { suite.Suite fooV1Cname *endpoint.Endpoint fooV2Cname *endpoint.Endpoint + fooV2TXT *endpoint.Endpoint fooV2CnameNoLabel *endpoint.Endpoint fooV3CnameSameResource *endpoint.Endpoint fooA5 *endpoint.Endpoint @@ -64,6 +65,10 @@ func (suite *PlanTestSuite) SetupTest() { endpoint.ResourceLabelKey: "ingress/default/foo-v2", }, } + suite.fooV2TXT = &endpoint.Endpoint{ + DNSName: "foo", + RecordType: "TXT", + } suite.fooV2CnameNoLabel = &endpoint.Endpoint{ DNSName: "foo", Targets: endpoint.Targets{"v2"}, @@ -261,6 +266,27 @@ func (suite *PlanTestSuite) TestDifferentTypes() { validateEntries(suite.T(), changes.Delete, expectedDelete) } +func (suite *PlanTestSuite) TestIgnoreTXT() { + current := []*endpoint.Endpoint{suite.fooV2TXT} + desired := []*endpoint.Endpoint{suite.fooV2Cname} + expectedCreate := []*endpoint.Endpoint{suite.fooV2Cname} + expectedUpdateOld := []*endpoint.Endpoint{} + expectedUpdateNew := []*endpoint.Endpoint{} + expectedDelete := []*endpoint.Endpoint{} + + p := &Plan{ + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + } + + changes := p.Calculate().Changes + validateEntries(suite.T(), changes.Create, expectedCreate) + validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew) + validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld) + validateEntries(suite.T(), changes.Delete, expectedDelete) +} + func (suite *PlanTestSuite) TestRemoveEndpoint() { current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A} desired := []*endpoint.Endpoint{suite.fooV1Cname} From d934f693fffb224fc1f8852722a95b8b731c25e6 Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Fri, 1 Jun 2018 09:00:27 -0400 Subject: [PATCH 02/25] Fix TXT record planning. --- plan/plan.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/plan/plan.go b/plan/plan.go index 9fafd7329..6fc93b5dd 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -131,10 +131,10 @@ func (t planTable) getDeletes() (deleteList []*endpoint.Endpoint) { func (p *Plan) Calculate() *Plan { t := newPlanTable() - for _, current := range p.Current { + for _, current := range filterRecordsForPlan(p.Current) { t.addCurrent(current) } - for _, desired := range p.Desired { + for _, desired := range filterRecordsForPlan(p.Desired) { t.addCandidate(desired) } @@ -175,3 +175,18 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool { } return desired.RecordTTL != current.RecordTTL } + +func filterRecordsForPlan(records []*endpoint.Endpoint) []*endpoint.Endpoint { + filtered := []*endpoint.Endpoint{} + + for _, record := range records { + switch record.RecordType { + case endpoint.RecordTypeA, endpoint.RecordTypeCNAME: + filtered = append(filtered, record) + default: + continue + } + } + + return filtered +} From dd036e1b78724a588833bbb77d5883c37e651897 Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Fri, 1 Jun 2018 09:00:40 -0400 Subject: [PATCH 03/25] Fix test where record type is blank. --- controller/controller_test.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/controller/controller_test.go b/controller/controller_test.go index 49e1446e9..909c33a78 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -90,12 +90,14 @@ func TestRunOnce(t *testing.T) { source := new(testutils.MockSource) source.On("Endpoints").Return([]*endpoint.Endpoint{ { - DNSName: "create-record", - Targets: endpoint.Targets{"1.2.3.4"}, + DNSName: "create-record", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.2.3.4"}, }, { - DNSName: "update-record", - Targets: endpoint.Targets{"8.8.4.4"}, + DNSName: "update-record", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"8.8.4.4"}, }, }, nil) @@ -103,26 +105,28 @@ func TestRunOnce(t *testing.T) { provider := newMockProvider( []*endpoint.Endpoint{ { - DNSName: "update-record", - Targets: endpoint.Targets{"8.8.8.8"}, + DNSName: "update-record", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"8.8.8.8"}, }, { - DNSName: "delete-record", - Targets: endpoint.Targets{"4.3.2.1"}, + DNSName: "delete-record", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"4.3.2.1"}, }, }, &plan.Changes{ Create: []*endpoint.Endpoint{ - {DNSName: "create-record", Targets: endpoint.Targets{"1.2.3.4"}}, + {DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, UpdateNew: []*endpoint.Endpoint{ - {DNSName: "update-record", Targets: endpoint.Targets{"8.8.4.4"}}, + {DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}}, }, UpdateOld: []*endpoint.Endpoint{ - {DNSName: "update-record", Targets: endpoint.Targets{"8.8.8.8"}}, + {DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}}, }, Delete: []*endpoint.Endpoint{ - {DNSName: "delete-record", Targets: endpoint.Targets{"4.3.2.1"}}, + {DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}}, }, }, ) From 8cba60b53fed9c9ddd169372fdd4b0e5fda4ba0a Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Sun, 3 Jun 2018 14:27:24 -0400 Subject: [PATCH 04/25] Add comments explaining filterRecordsForPlan. --- plan/plan.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plan/plan.go b/plan/plan.go index 6fc93b5dd..6caa73513 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -176,10 +176,19 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool { return desired.RecordTTL != current.RecordTTL } +// filterRecordsForPlan removes records that are not relevant to the planner. +// Currently this just removes TXT records to prevent them from being +// deleted erroneously by the planner (only the TXT registry should do this.) +// +// Per RFC 1034, CNAME records conflict with all other records - it is the +// only record with this property. The behavior of the planner may need to be +// made more sophisticated to codify this. func filterRecordsForPlan(records []*endpoint.Endpoint) []*endpoint.Endpoint { filtered := []*endpoint.Endpoint{} for _, record := range records { + // Explicitly specify which records we want to use for planning. + // TODO: Add AAAA records as well when they are supported. switch record.RecordType { case endpoint.RecordTypeA, endpoint.RecordTypeCNAME: filtered = append(filtered, record) From ecc6b4cecf218605d5fd6aca0db86a651de90a18 Mon Sep 17 00:00:00 2001 From: Elliot Franford Date: Fri, 31 Aug 2018 10:36:25 -0400 Subject: [PATCH 05/25] Add helper script to update route53 txt owner entries --- scripts/update_route53_k8s_txt_owner.py | 111 ++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 scripts/update_route53_k8s_txt_owner.py diff --git a/scripts/update_route53_k8s_txt_owner.py b/scripts/update_route53_k8s_txt_owner.py new file mode 100644 index 000000000..7e7e5a2a1 --- /dev/null +++ b/scripts/update_route53_k8s_txt_owner.py @@ -0,0 +1,111 @@ +# This is a script that we wrote to try to help the migration over to using external-dns. +# This script looks at kubernetes ingresses and services (which are the two things we have +# external-dns looking at) and compares them to existing TXT and A records in route53 to +# find out where there are gaps. It then assigns the heritage and owner TXT records where +# needed so external-dns can take over managing those resources. You can modify the script +# to only look at one or the other if needed. +# +# pip install kubernetes boto3 +from kubernetes import client, config +import boto3 + +# replace with your hosted zone id +hosted_zone_id = '' +# replace with your txt-owner-id you are using +# inside of your external-dns controller +txt_owner_id = '' + +# change to false if you have external-dns not looking at services +external_dns_manages_services = True + +# change to false if you have external-dns not looking at ingresses +external_dns_manages_ingresses = True + +config.load_kube_config() + +# grab all the domains that k8s thinks it is going to +# manage (services with domainName specified and +# ingress hosts) +k8s_domains = [] + +if external_dns_manages_services: + v1 = client.CoreV1Api() + svcs = v1.list_service_for_all_namespaces() + for i in svcs.items: + annotations = i.metadata.annotations + if annotations is not None and 'domainName' in annotations: + k8s_domains.extend(annotations['domainName'].split(',')) + +if external_dns_manages_ingresses: + ev1 = client.ExtensionsV1beta1Api() + ings = ev1.list_ingress_for_all_namespaces() + for i in ings.items: + for r in i.spec.rules: + if r.host not in k8s_domains: + k8s_domains.append(r.host) + + +r53client = boto3.client('route53') + +# grab the existing route53 domains and identify gaps where a domain may be +# missing a txt record pair +existing_r53_txt_domains=[] +existing_r53_domains=[] +has_next = True +next_record_name, next_record_type='','' + +while has_next: + if next_record_name is not '' and next_record_type is not '': + resource_records = r53client.list_resource_record_sets(HostedZoneId=hosted_zone_id, + StartRecordName=next_record_name, + StartRecordType=next_record_type) + else: + resource_records = r53client.list_resource_record_sets(HostedZoneId=hosted_zone_id) + + for r in resource_records['ResourceRecordSets']: + if r['Type'] == 'TXT': + existing_r53_txt_domains.append(r['Name'][:-1]) + elif r['Type'] == 'A': + existing_r53_domains.append(r['Name'][:-1]) + has_next = resource_records['IsTruncated'] + if has_next: + next_record_name, next_record_type = resource_records['NextRecordName'], resource_records['NextRecordType'] + +# grab only the domains in route53 that kubernetes is managing +r53_k8s_domains = [r for r in k8s_domains if r in existing_r53_domains] +# from those find the ones that do not have matching txt entries +missing_k8s_txt = [r for r in r53_k8s_domains if r not in existing_r53_txt_domains] + +# make the change batch for the route53 call, modify this as needed +change_batch=[] +for r in missing_k8s_txt: + change_batch.append( + { + 'Action': 'CREATE', + 'ResourceRecordSet': { + 'Name': r, + 'Type': 'TXT', + 'TTL': 300, + 'ResourceRecords': [ + { + 'Value': '\heritage=external-dns,owner="' + txt_owner_id + '\"' + }, + ] + } + }) + +print('This will create the following resources') +print(change_batch) +response = input("Good to go? ") + +if response.lower() in ['y', 'yes', 'yup', 'ok', 'sure', 'why not', 'why not?']: + print('Updating route53') + change_response = r53client.change_resource_record_sets( + HostedZoneId=hosted_zone_id, + ChangeBatch={ + 'Changes': change_batch + }) + print('Submitted change request to route53. Details below.') + print(change_response) +else: + print('No changes were made') From e9faea35961664e7885d70acce1d286760b62743 Mon Sep 17 00:00:00 2001 From: Peter Strzyzewski Date: Tue, 2 Oct 2018 14:40:18 -0700 Subject: [PATCH 06/25] Matching entire string for wildcard in txt records with prefixes --- provider/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/aws.go b/provider/aws.go index 951dab0a3..cbb0475ae 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -190,7 +190,7 @@ func (p *AWSProvider) Zones() (map[string]*route53.HostedZone, error) { // wildcardUnescape converts \\052.abc back to *.abc // Route53 stores wildcards escaped: http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html?shortFooter=true#domain-name-format-asterisk func wildcardUnescape(s string) string { - if strings.HasPrefix(s, "\\052") { + if strings.Contains(s, "\\052") { s = strings.Replace(s, "\\052", "*", 1) } return s From 8084a5e41bc9c15e60da80464680fb4acec101ff Mon Sep 17 00:00:00 2001 From: Peter Strzyzewski Date: Thu, 4 Oct 2018 11:28:16 -0700 Subject: [PATCH 07/25] Fixed tests store records with escaped wildcard. Added test to verify wildcard record with prefix. --- provider/aws_test.go | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/provider/aws_test.go b/provider/aws_test.go index aac6a3e6a..2334c0af6 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -89,7 +89,7 @@ func (r *Route53APIStub) ListResourceRecordSetsPages(input *route53.ListResource // Route53 stores wildcards escaped: http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html?shortFooter=true#domain-name-format-asterisk func wildcardEscape(s string) string { - if strings.HasPrefix(s, "*") { + if strings.Contains(s, "*") { s = strings.Replace(s, "*", "\\052", 1) } return s @@ -256,6 +256,7 @@ func TestAWSRecords(t *testing.T) { 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"), + endpoint.NewEndpointWithTTL("prefix-*.wildcard.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "random"), }) records, err := provider.Records() @@ -268,6 +269,7 @@ func TestAWSRecords(t *testing.T) { 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"), + endpoint.NewEndpointWithTTL("prefix-*.wildcard.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "random"), }) } @@ -919,10 +921,13 @@ func setupAWSRecords(t *testing.T, provider *AWSProvider, endpoints []*endpoint. require.NoError(t, provider.CreateRecords(endpoints)) + escapeAWSRecords(t, provider, "/hostedzone/zone-1.ext-dns-test-2.teapot.zalan.do.") + escapeAWSRecords(t, provider, "/hostedzone/zone-2.ext-dns-test-2.teapot.zalan.do.") + escapeAWSRecords(t, provider, "/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do.") + records, err = provider.Records() require.NoError(t, err) - validateEndpoints(t, records, endpoints) } func listAWSRecords(t *testing.T, client Route53API, zone string) []*route53.ResourceRecordSet { @@ -931,10 +936,7 @@ func listAWSRecords(t *testing.T, client Route53API, zone string) []*route53.Res HostedZoneId: aws.String(zone), }, func(resp *route53.ListResourceRecordSetsOutput, _ bool) bool { for _, recordSet := range resp.ResourceRecordSets { - switch aws.StringValue(recordSet.Type) { - case endpoint.RecordTypeA, endpoint.RecordTypeCNAME: - recordSets = append(recordSets, recordSet) - } + recordSets = append(recordSets, recordSet) } return true })) @@ -964,6 +966,29 @@ func clearAWSRecords(t *testing.T, provider *AWSProvider, zone string) { } } +// Route53 stores wildcards escaped: http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html?shortFooter=true#domain-name-format-asterisk +func escapeAWSRecords(t *testing.T, provider *AWSProvider, zone string) { + recordSets := listAWSRecords(t, provider.client, zone) + + changes := make([]*route53.Change, 0, len(recordSets)) + for _, recordSet := range recordSets { + changes = append(changes, &route53.Change{ + Action: aws.String(route53.ChangeActionUpsert), + ResourceRecordSet: recordSet, + }) + } + + if len(changes) != 0 { + _, err := provider.client.ChangeResourceRecordSets(&route53.ChangeResourceRecordSetsInput{ + HostedZoneId: aws.String(zone), + ChangeBatch: &route53.ChangeBatch{ + Changes: changes, + }, + }) + require.NoError(t, err) + } +} + func newAWSProvider(t *testing.T, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) { client := NewRoute53APIStub() From bb80f99e1737542195385f8fb646919e303c296d Mon Sep 17 00:00:00 2001 From: vaegt Date: Tue, 16 Oct 2018 13:30:07 +0200 Subject: [PATCH 08/25] Add alias annotation for ingress --- provider/aws.go | 42 ++++++++++++++++++++++++++++++++++++++ provider/aws_test.go | 29 ++++++++++++++++++++++++++ source/gateway.go | 10 ++++++--- source/ingress.go | 12 +++++++---- source/ingress_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++ source/source.go | 38 ++++++++++++++++++++++++---------- 6 files changed, 159 insertions(+), 18 deletions(-) diff --git a/provider/aws.go b/provider/aws.go index 6aa0f0412..c43de0091 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -364,6 +364,11 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou }, } + rec, err := p.Records() + if err != nil { + log.Errorf("getting records failed: %v", err) + } + if isAWSLoadBalancer(endpoint) { evalTargetHealth := p.evaluateTargetHealth if _, ok := endpoint.ProviderSpecific[providerSpecificEvaluateTargetHealth]; ok { @@ -376,6 +381,20 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])), EvaluateTargetHealth: aws.Bool(evalTargetHealth), } + } else if hostedZone := isAWSAlias(endpoint, rec); hostedZone != "" { + //FIXME should break if err != nil + zones, err := p.Zones() + if err != nil { + log.Errorf("getting zones failed: %v", err) + } + for _, zone := range zones { + change.ResourceRecordSet.Type = aws.String(route53.RRTypeA) + change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{ + DNSName: aws.String(endpoint.Targets[0]), + HostedZoneId: aws.String(cleanZoneID(*zone.Id)), + EvaluateTargetHealth: aws.Bool(p.evaluateTargetHealth), + } + } } else { change.ResourceRecordSet.Type = aws.String(endpoint.RecordType) if !endpoint.RecordTTL.IsConfigured() { @@ -529,6 +548,21 @@ func isAWSLoadBalancer(ep *endpoint.Endpoint) bool { return false } +// isAWSAlias determines if a given hostname belongs to an AWS Alias record by doing an reverse lookup. +func isAWSAlias(ep *endpoint.Endpoint, addrs []*endpoint.Endpoint) string { + if val, exists := ep.ProviderSpecific["alias"]; ep.RecordType == endpoint.RecordTypeCNAME && exists && val == "true" { + for _, addr := range addrs { + if addr.DNSName == ep.Targets[0] { + if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" { + return hostedZone + } + + } + } + } + return "" +} + // canonicalHostedZone returns the matching canonical zone for a given hostname. func canonicalHostedZone(hostname string) string { for suffix, zone := range canonicalHostedZones { @@ -539,3 +573,11 @@ func canonicalHostedZone(hostname string) string { return "" } + +// cleanZoneID removes the "/hostedzone/" prefix +func cleanZoneID(ID string) string { + if strings.HasPrefix(ID, "/hostedzone/") { + ID = strings.TrimPrefix(ID, "/hostedzone/") + } + return ID +} diff --git a/provider/aws_test.go b/provider/aws_test.go index 60c451e99..8ece0f454 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -819,6 +819,35 @@ func TestAWSisLoadBalancer(t *testing.T) { } } +func TestAWSisAWSAlias(t *testing.T) { + for _, tc := range []struct { + target string + recordType string + alias string + expected string + }{ + {"bar.example.org", endpoint.RecordTypeCNAME, "true", "Z215JYRZR1TBD5"}, + {"foo.example.org", endpoint.RecordTypeCNAME, "true", ""}, + } { + ep := &endpoint.Endpoint{ + Targets: endpoint.Targets{tc.target}, + RecordType: tc.recordType, + ProviderSpecific: map[string]string{"alias": tc.alias}, + } + addrs := []*endpoint.Endpoint{ + &endpoint.Endpoint{ + DNSName: "foo.example.org", + Targets: endpoint.Targets{"foobar.example.org"}, + }, + &endpoint.Endpoint{ + DNSName: "bar.example.org", + Targets: endpoint.Targets{"bar.eu-central-1.elb.amazonaws.com"}, + }, + } + assert.Equal(t, tc.expected, isAWSAlias(ep, addrs)) + } +} + func TestAWSCanonicalHostedZone(t *testing.T) { for _, tc := range []struct { hostname string diff --git a/source/gateway.go b/source/gateway.go index 0494f0a4a..30aa53b10 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -172,12 +172,14 @@ func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*en } } + providerSpecific := getProviderSpecificAnnotations(config.Annotations) + 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)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints, nil } @@ -256,18 +258,20 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([ gateway := config.Spec.(*istionetworking.Gateway) + providerSpecific := getProviderSpecificAnnotations(config.Annotations) + for _, server := range gateway.Servers { for _, host := range server.Hosts { if host == "" { continue } - endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...) } } hostnameList := getHostnamesFromAnnotations(config.Annotations) for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints, nil diff --git a/source/ingress.go b/source/ingress.go index a9ab76cfb..cbda3cd92 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -146,12 +146,14 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin targets = targetsFromIngressStatus(ing.Status) } + providerSpecific := getProviderSpecificAnnotations(ing.Annotations) + 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)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints, nil } @@ -208,11 +210,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { targets = targetsFromIngressStatus(ing.Status) } + providerSpecific := getProviderSpecificAnnotations(ing.Annotations) + for _, rule := range ing.Spec.Rules { if rule.Host == "" { continue } - endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific)...) } for _, tls := range ing.Spec.TLS { @@ -220,13 +224,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { if host == "" { continue } - endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...) } } hostnameList := getHostnamesFromAnnotations(ing.Annotations) for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints diff --git a/source/ingress_test.go b/source/ingress_test.go index 2b13287d6..7d0182653 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -817,6 +817,52 @@ func testIngressEndpoints(t *testing.T) { }, }, }, + { + title: "ingress rules with alias and target annotation", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "ingress-target.com", + aliasAnnotationKey: "true", + }, + dnsnames: []string{"example.org"}, + ips: []string{}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"ingress-target.com"}, + RecordType: endpoint.RecordTypeCNAME, + }, + }, + }, + { + title: "ingress rules with alias set false and target annotation", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "ingress-target.com", + aliasAnnotationKey: "false", + }, + dnsnames: []string{"example.org"}, + ips: []string{}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"ingress-target.com"}, + RecordType: endpoint.RecordTypeCNAME, + }, + }, + }, { title: "template for ingress with annotation", targetNamespace: "", diff --git a/source/source.go b/source/source.go index 14f99fe5c..fac716c81 100644 --- a/source/source.go +++ b/source/source.go @@ -35,6 +35,8 @@ const ( targetAnnotationKey = "external-dns.alpha.kubernetes.io/target" // The annotation used for defining the desired DNS record TTL ttlAnnotationKey = "external-dns.alpha.kubernetes.io/ttl" + //// The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME + aliasAnnotationKey = "external-dns.alpha.kubernetes.io/alias" // The value of the controller annotation so that we feel responsible controllerAnnotationValue = "dns-controller" ) @@ -74,6 +76,18 @@ func getHostnamesFromAnnotations(annotations map[string]string) []string { return strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",") } +func getAliasFromAnnotations(annotations map[string]string) bool { + aliasAnnotation, exists := annotations[aliasAnnotationKey] + return exists && aliasAnnotation == "true" +} + +func getProviderSpecificAnnotations(annotations map[string]string) endpoint.ProviderSpecific { + if getAliasFromAnnotations(annotations) { + return map[string]string{"alias": "true"} + } + return map[string]string{} +} + // getTargetsFromTargetAnnotation gets endpoints from optional "target" annotation. // Returns empty endpoints array if none are found. func getTargetsFromTargetAnnotation(annotations map[string]string) endpoint.Targets { @@ -102,7 +116,7 @@ func suitableType(target string) string { } // endpointsForHostname returns the endpoint objects for each host-target combination. -func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL) []*endpoint.Endpoint { +func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL, providerSpecific endpoint.ProviderSpecific) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint var aTargets endpoint.Targets @@ -119,22 +133,24 @@ func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoin if len(aTargets) > 0 { epA := &endpoint.Endpoint{ - DNSName: strings.TrimSuffix(hostname, "."), - Targets: aTargets, - RecordTTL: ttl, - RecordType: endpoint.RecordTypeA, - Labels: endpoint.NewLabels(), + DNSName: strings.TrimSuffix(hostname, "."), + Targets: aTargets, + RecordTTL: ttl, + RecordType: endpoint.RecordTypeA, + Labels: endpoint.NewLabels(), + ProviderSpecific: providerSpecific, } 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(), + DNSName: strings.TrimSuffix(hostname, "."), + Targets: cnameTargets, + RecordTTL: ttl, + RecordType: endpoint.RecordTypeCNAME, + Labels: endpoint.NewLabels(), + ProviderSpecific: providerSpecific, } endpoints = append(endpoints, epCNAME) } From a4b753df6c4e18ecd34c7deba341432a357a3fc4 Mon Sep 17 00:00:00 2001 From: vaegt Date: Tue, 16 Oct 2018 14:29:41 +0200 Subject: [PATCH 09/25] Format changes --- provider/aws.go | 1 - provider/aws_test.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/provider/aws.go b/provider/aws.go index c43de0091..eb08207fe 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -382,7 +382,6 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou EvaluateTargetHealth: aws.Bool(evalTargetHealth), } } else if hostedZone := isAWSAlias(endpoint, rec); hostedZone != "" { - //FIXME should break if err != nil zones, err := p.Zones() if err != nil { log.Errorf("getting zones failed: %v", err) diff --git a/provider/aws_test.go b/provider/aws_test.go index 8ece0f454..7a0302942 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -835,11 +835,11 @@ func TestAWSisAWSAlias(t *testing.T) { ProviderSpecific: map[string]string{"alias": tc.alias}, } addrs := []*endpoint.Endpoint{ - &endpoint.Endpoint{ + { DNSName: "foo.example.org", Targets: endpoint.Targets{"foobar.example.org"}, }, - &endpoint.Endpoint{ + { DNSName: "bar.example.org", Targets: endpoint.Targets{"bar.eu-central-1.elb.amazonaws.com"}, }, From 71b9dac993656f02727f9d8604d31fdbb7536ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helgi=20=C3=9Eormar=20=C3=9Eorbj=C3=B6rnsson?= <70530+helgi@users.noreply.github.com> Date: Wed, 17 Oct 2018 14:41:22 -0700 Subject: [PATCH 10/25] MAINTAINER is deprecated - using LABEL instead https://docs.docker.com/engine/reference/builder/#maintainer-deprecated --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3ea74c573..95dc63f1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN make build # final image FROM registry.opensource.zalan.do/stups/alpine:latest -MAINTAINER Team Teapot @ Zalando SE +LABEL maintainer="Team Teapot @ Zalando SE " COPY --from=builder /go/src/github.com/kubernetes-incubator/external-dns/build/external-dns /bin/external-dns From 04ca5ec22fac1c5878178f3924f1aff6a724f88e Mon Sep 17 00:00:00 2001 From: Otto Yiu Date: Fri, 12 Oct 2018 16:08:28 -0700 Subject: [PATCH 11/25] pdns: Add DomainFilter support --- docs/tutorials/pdns.md | 11 ++- provider/pdns.go | 66 ++++++++++++---- provider/pdns_test.go | 174 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 230 insertions(+), 21 deletions(-) diff --git a/docs/tutorials/pdns.md b/docs/tutorials/pdns.md index 33b43f3bb..b04ccfee0 100644 --- a/docs/tutorials/pdns.md +++ b/docs/tutorials/pdns.md @@ -15,8 +15,7 @@ anyway. The PDNS provider currently does not support: -1. Dry running a configuration is not supported. -2. The `--domain-filter` flag is not supported. +* Dry running a configuration is not supported ## Deployment @@ -47,10 +46,18 @@ spec: - --pdns-server={{ pdns-api-url }} - --pdns-api-key={{ pdns-http-api-key }} - --txt-owner-id={{ owner-id-for-this-external-dns }} + - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the zones matching provided domain; omit to process all available zones in PowerDNS - --log-level=debug - --interval=30s ``` +#### Domain Filter (--domain-filter) +When the domain-filter argument is specified, external-dns will automatically create DNS records based on host names specified in ingress objects and services with the external-dns annotation that match the domain-filter argument in the external-dns deployment manifest. + +eg. ```--domain-filter=example.org``` will allow for zone `example.org` and any zones in PowerDNS that ends in `.example.org`, including `an.example.org`, ie. the subdomains of example.org. + +eg. ```--domain-filter=.example.org``` will allow *only* zones that end in `.example.org`, ie. the subdomains of example.org but not the `example.org` zone itself. + ## RBAC If your cluster is RBAC enabled, you also need to setup the following, before you can run external-dns: diff --git a/provider/pdns.go b/provider/pdns.go index 862dab902..7571c779a 100644 --- a/provider/pdns.go +++ b/provider/pdns.go @@ -132,15 +132,17 @@ func stringifyHTTPResponseBody(r *http.Response) (body string) { // well as mock APIClients used in testing type PDNSAPIProvider interface { ListZones() ([]pgo.Zone, *http.Response, error) + PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) 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 + dryRun bool + authCtx context.Context + client *pgo.APIClient + domainFilter DomainFilter } // ListZones : Method returns all enabled zones from PowerDNS @@ -153,7 +155,6 @@ func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err log.Debugf("Retrying ListZones() ... %d", i) time.Sleep(retryAfterTime * (1 << uint(i))) continue - } return zones, resp, err } @@ -163,6 +164,22 @@ func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err } +// PartitionZones : Method returns a slice of zones that adhere to the domain filter and a slice of ones that does not adhere to the filter +func (c *PDNSAPIClient) PartitionZones(zones []pgo.Zone) (filteredZones []pgo.Zone, residualZones []pgo.Zone) { + if c.domainFilter.IsConfigured() { + for _, zone := range zones { + if c.domainFilter.Match(zone.Name) { + filteredZones = append(filteredZones, zone) + } else { + residualZones = append(residualZones, zone) + } + } + } else { + residualZones = zones + } + return filteredZones, residualZones +} + // 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) { @@ -216,10 +233,6 @@ func NewPDNSProvider(config PDNSConfig) (*PDNSProvider, error) { 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(config.DomainFilter.filters) != 1 && config.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 config.DryRun { @@ -238,9 +251,10 @@ func NewPDNSProvider(config PDNSConfig) (*PDNSProvider, error) { provider := &PDNSProvider{ client: &PDNSAPIClient{ - dryRun: config.DryRun, - authCtx: context.WithValue(context.TODO(), pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}), - client: pgo.NewAPIClient(pdnsClientConfig), + dryRun: config.DryRun, + authCtx: context.WithValue(context.TODO(), pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}), + client: pgo.NewAPIClient(pdnsClientConfig), + domainFilter: config.DomainFilter, }, } @@ -281,22 +295,23 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet if err != nil { return nil, err } + filteredZones, residualZones := p.client.PartitionZones(zones) // 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) }) + sort.SliceStable(filteredZones, func(i, j int) bool { return len(filteredZones[i].Name) > len(filteredZones[j].Name) }) - // NOTE: Complexity of this loop is O(Zones*Endpoints). + // NOTE: Complexity of this loop is O(FilteredZones*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 { + for _, zone := range filteredZones { zone.Rrsets = []pgo.RrSet{} for i := 0; i < len(endpoints); { ep := endpoints[i] dnsname := ensureTrailingDot(ep.DNSName) - if strings.HasSuffix(dnsname, zone.Name) { + if dnsname == zone.Name || 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 @@ -345,7 +360,23 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet } - // If we still have some endpoints left, it means we couldn't find a matching zone for them + // residualZones is unsorted by name length like its counterpart + // since we only care to remove endpoints that do not match domain filter + for _, zone := range residualZones { + for i := 0; i < len(endpoints); { + ep := endpoints[i] + dnsname := ensureTrailingDot(ep.DNSName) + if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) { + // "pop" endpoint if it's matched to a residual zone... essentially a no-op + log.Debugf("Ignoring Endpoint because it was matched to a zone that was not specified within Domain Filter(s): %s", dnsname) + endpoints = append(endpoints[0:i], endpoints[i+1:]...) + } else { + i++ + } + } + } + + // If we still have some endpoints left, it means we couldn't find a matching zone (filtered or residual) 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) @@ -387,8 +418,9 @@ func (p *PDNSProvider) Records() (endpoints []*endpoint.Endpoint, _ error) { if err != nil { return nil, err } + filteredZones, _ := p.client.PartitionZones(zones) - for _, zone := range zones { + for _, zone := range filteredZones { z, _, err := p.client.ListZone(zone.Id) if err != nil { log.Warnf("Unable to fetch Records") diff --git a/provider/pdns_test.go b/provider/pdns_test.go index 0351d3e06..c7c5b592c 100644 --- a/provider/pdns_test.go +++ b/provider/pdns_test.go @@ -158,6 +158,18 @@ var ( 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\""), } + endpointsMultipleZonesWithLongRecordNotInDomainFilter = []*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("a.very.long.domainname.example.com", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), + endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), + } + endpointsMultipleZonesWithSimilarRecordNotInDomainFilter = []*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("test.simexample.com", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), + endpoint.NewEndpointWithTTL("test.simexample.com", 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. @@ -174,6 +186,15 @@ var ( Rrsets: []pgo.RrSet{}, } + ZoneEmptySimilar = pgo.Zone{ + Id: "simexample.com.", + Name: "simexample.com.", + Type_: "Zone", + Url: "/api/v1/servers/localhost/zones/simexample.com.", + Kind: "Native", + Rrsets: []pgo.RrSet{}, + } + ZoneEmptyLong = pgo.Zone{ Id: "long.domainname.example.com.", Name: "long.domainname.example.com.", @@ -239,6 +260,72 @@ var ( }, } + ZoneEmptyToSimplePatchLongRecordIgnoredInDomainFilter = pgo.Zone{ + Id: "example.com.", + Name: "example.com.", + Type_: "Zone", + Url: "/api/v1/servers/localhost/zones/example.com.", + Kind: "Native", + Rrsets: []pgo.RrSet{ + { + Name: "a.very.long.domainname.example.com.", + Type_: "A", + Ttl: 300, + Changetype: "REPLACE", + Records: []pgo.Record{ + { + Content: "9.9.9.9", + 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), + }, + { + 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.", @@ -398,6 +485,9 @@ type PDNSAPIClientStub struct { func (c *PDNSAPIClientStub) ListZones() ([]pgo.Zone, *http.Response, error) { return []pgo.Zone{ZoneMixed}, nil, nil } +func (c *PDNSAPIClientStub) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) { + return zones, nil +} func (c *PDNSAPIClientStub) ListZone(zoneID string) (pgo.Zone, *http.Response, error) { return ZoneMixed, nil, nil } @@ -415,6 +505,9 @@ type PDNSAPIClientStubEmptyZones struct { func (c *PDNSAPIClientStubEmptyZones) ListZones() ([]pgo.Zone, *http.Response, error) { return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2}, nil, nil } +func (c *PDNSAPIClientStubEmptyZones) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) { + return zones, nil +} func (c *PDNSAPIClientStubEmptyZones) ListZone(zoneID string) (pgo.Zone, *http.Response, error) { if strings.Contains(zoneID, "example.com") { @@ -422,7 +515,7 @@ func (c *PDNSAPIClientStubEmptyZones) ListZone(zoneID string) (pgo.Zone, *http.R } 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 ZoneEmptyLong, nil, nil } return pgo.Zone{}, nil, nil @@ -469,6 +562,37 @@ func (c *PDNSAPIClientStubListZonesFailure) ListZones() ([]pgo.Zone, *http.Respo return []pgo.Zone{}, nil, errors.New("Generic PDNS Error") } +/******************************************************************************/ +// API that returns zone partitions given DomainFilter(s) +type PDNSAPIClientStubPartitionZones struct { + // Anonymous struct for composition + PDNSAPIClientStubEmptyZones +} + +func (c *PDNSAPIClientStubPartitionZones) ListZones() ([]pgo.Zone, *http.Response, error) { + return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2, ZoneEmptySimilar}, nil, nil +} + +func (c *PDNSAPIClientStubPartitionZones) 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 ZoneEmptyLong, nil, nil + } else if strings.Contains(zoneID, "simexample.com") { + return ZoneEmptySimilar, nil, nil + } + return pgo.Zone{}, nil, nil +} + +// Just overwrite the ListZones method to introduce a failure +func (c *PDNSAPIClientStubPartitionZones) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) { + return []pgo.Zone{ZoneEmpty}, []pgo.Zone{ZoneEmptyLong, ZoneEmpty2} + +} + /******************************************************************************/ type NewPDNSProviderTestSuite struct { @@ -488,7 +612,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreate() { APIKey: "foo", DomainFilter: NewDomainFilter([]string{"example.com", "example.org"}), }) - assert.Error(suite.T(), err, "--domainfilter should raise an error") + assert.Nil(suite.T(), err, "--domain-filter should raise no error") _, err = NewPDNSProvider(PDNSConfig{ Server: "http://localhost:8081", @@ -711,6 +835,51 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZones() { } } +func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZonesPartitionZones() { + // Test DomainFilters + p := &PDNSProvider{ + client: &PDNSAPIClientStubPartitionZones{}, + } + + // Check inserting endpoints from a single zone which is specified in DomainFilter + 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 which is specified in DomainFilter + zlist, err = p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsDelete) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, zlist) + + // Check endpoints from multiple zones # which one is specified in DomainFilter and one is not + zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones, PdnsReplace) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) + + // Check endpoints from multiple zones where some endpoints which don't exist and one that does + // and is part of DomainFilter + 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 when the longer + // zone is not part of the DomainFilter + zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithLongRecordNotInDomainFilter, PdnsReplace) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatchLongRecordIgnoredInDomainFilter}, zlist) + + // Check endpoints that match multiple zones (one longer than other and one is very similar) + // is assigned to the right zone when the similar zone is not part of the DomainFilter + zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithSimilarRecordNotInDomainFilter, PdnsReplace) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) +} + func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() { // Function definition: mutateRecords(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) error @@ -742,6 +911,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() { assert.NotNil(suite.T(), err) } + func TestNewPDNSProviderTestSuite(t *testing.T) { suite.Run(t, new(NewPDNSProviderTestSuite)) } From f11987ca09883bb27d932885e121d1debf71cb34 Mon Sep 17 00:00:00 2001 From: Pascal Date: Tue, 23 Oct 2018 20:47:44 +0200 Subject: [PATCH 12/25] Update Azure documentation --- docs/tutorials/azure.md | 156 ++++++++++++++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 29 deletions(-) diff --git a/docs/tutorials/azure.md b/docs/tutorials/azure.md index 0df336cf2..7d1a59515 100644 --- a/docs/tutorials/azure.md +++ b/docs/tutorials/azure.md @@ -3,7 +3,7 @@ This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Azure. -Make sure to use **>=0.4.2** version of ExternalDNS for this tutorial. +Make sure to use **>=0.5.7** version of ExternalDNS for this tutorial. This tutorial uses [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) for all Azure commands and assumes that the Kubernetes cluster was created via Azure Container Services and `kubectl` commands @@ -38,22 +38,57 @@ Please consult your registrar's documentation on how to do that. The Azure DNS provider expects, by default, that the configuration file is at `/etc/kubernetes/azure.json`. This can be overridden with the `--azure-config-file` option when starting ExternalDNS. -### Azure Container Services -When your Kubernetes cluster is created by ACS, a file named `/etc/kubernetes/azure.json` is created to store -the Azure credentials for API access. Kubernetes uses this file for the Azure cloud provider. +### Use provisioned VM configuration file +When running within Azure (ACS or AKS), the agent and master VMs are already provisioned with the configuration file at `/etc/kubernetes/azure.json`. -For ExternalDNS to access the Azure API, it also needs access to this file. However, we will be deploying ExternalDNS inside of -the Kubernetes cluster so we will need to use a Kubernetes secret. +If you want to use the file directly, make sure that the service principal that is given there has access to contribute to the resource group containing the Azure DNS zone(s). + +To use the file, replace the directive + +```yaml + volumes: + - name: azure-config-file + secret: + secretName: azure-config-file +``` + +with + +```yaml + volumes: + - name: azure-config-file + hostPath: + path: /etc/kubernetes/azure.json + type: File +``` + +in the manifests below. + +### Use custom configuration file +If you want to customize the configuration, for example because you want to use a different service principal, you have to manually create a secret. +This is also required if the Kubernetes cluster is not hosted in Azure Container Services (ACS or AKS) and you still want to use Azure DNS. + +The secret should contain an object named azure.json with content similar to this: + +```json +{ + "tenantId": "01234abc-de56-ff78-abc1-234567890def", + "subscriptionId": "01234abc-de56-ff78-abc1-234567890def", + "aadClientId": "01234abc-de56-ff78-abc1-234567890def", + "aadClientSecret": "uKiuXeiwui4jo9quae9o", + "resourceGroup": "MyDnsResourceGroup", +} +``` + +You can find the `tenantId` by running `az account show` or by selecting Azure Active Directory in the Azure Portal and checking the _Directory ID_ under Properties. +You can find the `subscriptionId` by running `az account show --query "id"` or by selecting Subscriptions in the Azure Portal. To create the secret: ``` -$ kubectl create secret generic azure-config-file --from-file=/etc/kubernetes/azure.json +$ kubectl create secret generic azure-config-file --from-file=/local/path/to/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. @@ -89,27 +124,20 @@ A Service Principal with a minimum access level of contribute to the resource gr "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: -``` +#### Azure Managed Service Identity (MSI) + +If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal. + +The contents of `azure.json` should be similar to this: + +```json { - "tenantId": "AzureAD tenant Id", - "subscriptionId": "Id", - "aadClientId": "Service Principal AppId", - "aadClientSecret": "Service Principal Password", + "tenantId": "01234abc-de56-ff78-abc1-234567890def", + "subscriptionId": "01234abc-de56-ff78-abc1-234567890def", "resourceGroup": "MyDnsResourceGroup", -} -``` -If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal. The contents of `azure.json` should be similar to this: -``` -{ - "tenantId": "AzureAD tenant Id", - "subscriptionId": "Id", - "resourceGroup": "MyDnsResourceGroup", - "useManagedIdentityExtension": true, + "useManagedIdentityExtension": true } ``` @@ -170,7 +198,7 @@ spec: secretName: azure-config-file ``` -### Manifest (for clusters with RBAC enabled) +### Manifest (for clusters with RBAC enabled, cluster access) ```yaml apiVersion: v1 kind: ServiceAccount @@ -240,6 +268,76 @@ spec: secretName: azure-config-file ``` +### Manifest (for clusters with RBAC enabled, namespace access) +This configuration is the same as above, except it only requires privileges for the current namespace, not for the whole cluster. +However, access to [nodes](https://kubernetes.io/docs/concepts/architecture/nodes/) requires cluster access, so when using this manifest, +services with type `NodePort` will be skipped! + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +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: RoleBinding +metadata: + name: external-dns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns +--- +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:latest + 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 +``` + Create the deployment for ExternalDNS: ``` From 40d1137459e3ed32189aaccc9d5c22318baaa480 Mon Sep 17 00:00:00 2001 From: AdamDang Date: Fri, 26 Oct 2018 23:31:21 +0800 Subject: [PATCH 13/25] Update dyn.go --- provider/dyn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/provider/dyn.go b/provider/dyn.go index 7bb2332dc..36eefdbfb 100644 --- a/provider/dyn.go +++ b/provider/dyn.go @@ -564,7 +564,7 @@ func (d *dynProviderState) commit(client *dynect.Client) error { err = apiRetryLoop(func() error { return client.Do("PUT", fmt.Sprintf("Zone/%s/", zone), &zonePublish, &response) }) - log.Infof("Commiting changes for zone %s: %+v", zone, errorOrValue(err, &response)) + log.Infof("Committing changes for zone %s: %+v", zone, errorOrValue(err, &response)) } switch len(errs) { @@ -597,7 +597,7 @@ func (d *dynProviderState) Records() ([]*endpoint.Endpoint, error) { serial, err := d.fetchZoneSerial(client, zone) if err != nil { if strings.Index(err.Error(), "404 Not Found") >= 0 { - log.Infof("Ignore zone %s as it does not exists", zone) + log.Infof("Ignore zone %s as it does not exist", zone) continue } From c668865e65ff119ab3ba9191414aa5f33dde91fb Mon Sep 17 00:00:00 2001 From: shashidharatd Date: Tue, 21 Aug 2018 22:39:38 +0530 Subject: [PATCH 14/25] Update CoreDNS provider to use etcd v3 client --- provider/coredns.go | 144 ++++++++++++++++++++------------------- provider/coredns_test.go | 8 ++- 2 files changed, 81 insertions(+), 71 deletions(-) diff --git a/provider/coredns.go b/provider/coredns.go index 7454e1691..38403ae61 100644 --- a/provider/coredns.go +++ b/provider/coredns.go @@ -17,7 +17,7 @@ limitations under the License. package provider import ( - "container/list" + "context" "crypto/tls" "crypto/x509" "encoding/json" @@ -26,14 +26,12 @@ import ( "io/ioutil" "math/rand" "net" - "net/http" "os" "strings" "time" - etcd "github.com/coreos/etcd/client" + etcdcv3 "github.com/coreos/etcd/clientv3" log "github.com/sirupsen/logrus" - "golang.org/x/net/context" "github.com/kubernetes-incubator/external-dns/endpoint" "github.com/kubernetes-incubator/external-dns/plan" @@ -43,8 +41,17 @@ func init() { rand.Seed(time.Now().UnixNano()) } -// skyDNSClient is an interface to work with SkyDNS service records in etcd -type skyDNSClient interface { +const ( + priority = 10 // default priority when nothing is set + etcdTimeout = 5 * time.Second + + coreDNSPrefix = "/skydns/" + + randomPrefixLabel = "prefix" +) + +// coreDNSClient is an interface to work with CoreDNS service records in etcd +type coreDNSClient interface { GetServices(prefix string) ([]*Service, error) SaveService(value *Service) error DeleteService(key string) error @@ -53,10 +60,10 @@ type skyDNSClient interface { type coreDNSProvider struct { dryRun bool domainFilter DomainFilter - client skyDNSClient + client coreDNSClient } -// Service represents SkyDNS/CoreDNS etcd record +// Service represents CoreDNS etcd record type Service struct { Host string `json:"host,omitempty"` Port int `json:"port,omitempty"` @@ -83,52 +90,58 @@ type Service struct { } type etcdClient struct { - api etcd.KeysAPI + client *etcdcv3.Client + ctx context.Context } -var _ skyDNSClient = etcdClient{} +var _ coreDNSClient = etcdClient{} // GetService return all Service records stored in etcd stored anywhere under the given key (recursively) func (c etcdClient) GetServices(prefix string) ([]*Service, error) { - var result []*Service - opts := &etcd.GetOptions{Recursive: true} - data, err := c.api.Get(context.Background(), prefix, opts) + ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout) + defer cancel() + + path := prefix + r, err := c.client.Get(ctx, path, etcdcv3.WithPrefix()) if err != nil { - if etcd.IsKeyNotFound(err) { - return nil, nil - } return nil, err } - queue := list.New() - queue.PushFront(data.Node) - for queueNode := queue.Front(); queueNode != nil; queueNode = queueNode.Next() { - node := queueNode.Value.(*etcd.Node) - if node.Dir { - for _, childNode := range node.Nodes { - queue.PushBack(childNode) - } + var svcs []*Service + bx := make(map[Service]bool) + for _, n := range r.Kvs { + svc := new(Service) + if err := json.Unmarshal(n.Value, svc); err != nil { + return nil, fmt.Errorf("%s: %s", n.Key, err.Error()) + } + b := Service{Host: svc.Host, Port: svc.Port, Priority: svc.Priority, Weight: svc.Weight, Text: svc.Text, Key: string(n.Key)} + if _, ok := bx[b]; ok { + // skip the service if already added to service list. + // the same service might be found in multiple etcd nodes. continue } - service := &Service{} - err = json.Unmarshal([]byte(node.Value), service) - if err != nil { - log.Error("Cannot parse JSON value ", node.Value) - continue + bx[b] = true + + svc.Key = string(n.Key) + if svc.Priority == 0 { + svc.Priority = priority } - service.Key = node.Key - result = append(result, service) + svcs = append(svcs, svc) } - return result, nil + + return svcs, nil } // SaveService persists service data into etcd func (c etcdClient) SaveService(service *Service) error { + ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout) + defer cancel() + value, err := json.Marshal(&service) if err != nil { return err } - _, err = c.api.Set(context.Background(), service.Key, string(value), nil) + _, err = c.client.Put(ctx, service.Key, string(value)) if err != nil { return err } @@ -137,9 +150,11 @@ func (c etcdClient) SaveService(service *Service) error { // DeleteService deletes service record from etcd func (c etcdClient) DeleteService(key string) error { - _, err := c.api.Delete(context.Background(), key, nil) - return err + ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout) + defer cancel() + _, err := c.client.Delete(ctx, key) + return err } // loads TLS artifacts and builds tls.Clonfig object @@ -186,21 +201,8 @@ func loadRoots(caPath string) (*x509.CertPool, error) { return roots, nil } -// constructs http.Transport object for https protocol -func newHTTPSTransport(cc *tls.Config) *http.Transport { - return &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: cc, - } -} - // builds etcd client config depending on connection scheme and TLS parameters -func getETCDConfig() (*etcd.Config, error) { +func getETCDConfig() (*etcdcv3.Config, error) { etcdURLsStr := os.Getenv("ETCD_URLS") if etcdURLsStr == "" { etcdURLsStr = "http://localhost:2379" @@ -208,7 +210,7 @@ func getETCDConfig() (*etcd.Config, error) { etcdURLs := strings.Split(etcdURLsStr, ",") firstURL := strings.ToLower(etcdURLs[0]) if strings.HasPrefix(firstURL, "http://") { - return &etcd.Config{Endpoints: etcdURLs}, nil + return &etcdcv3.Config{Endpoints: etcdURLs}, nil } else if strings.HasPrefix(firstURL, "https://") { caFile := os.Getenv("ETCD_CA_FILE") certFile := os.Getenv("ETCD_CERT_FILE") @@ -220,9 +222,9 @@ func getETCDConfig() (*etcd.Config, error) { if err != nil { return nil, err } - return &etcd.Config{ + return &etcdcv3.Config{ Endpoints: etcdURLs, - Transport: newHTTPSTransport(tlsConfig), + TLS: tlsConfig, }, nil } else { return nil, errors.New("etcd URLs must start with either http:// or https://") @@ -230,16 +232,16 @@ func getETCDConfig() (*etcd.Config, error) { } //newETCDClient is an etcd client constructor -func newETCDClient() (skyDNSClient, error) { +func newETCDClient() (coreDNSClient, error) { cfg, err := getETCDConfig() if err != nil { return nil, err } - c, err := etcd.New(*cfg) + c, err := etcdcv3.New(*cfg) if err != nil { return nil, err } - return etcdClient{etcd.NewKeysAPI(c)}, nil + return etcdClient{c, context.Background()}, nil } // NewCoreDNSProvider is a CoreDNS provider constructor @@ -255,16 +257,16 @@ func NewCoreDNSProvider(domainFilter DomainFilter, dryRun bool) (Provider, error }, nil } -// Records returns all DNS records found in SkyDNS/CoreDNS etcd backend. Depending on the record fields +// Records returns all DNS records found in CoreDNS etcd backend. Depending on the record fields // it may be mapped to one or two records of type A, CNAME, TXT, A+TXT, CNAME+TXT func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) { var result []*endpoint.Endpoint - services, err := p.client.GetServices("/skydns") + services, err := p.client.GetServices(coreDNSPrefix) if err != nil { return nil, err } for _, service := range services { - domains := strings.Split(strings.TrimPrefix(service.Key, "/skydns/"), "/") + domains := strings.Split(strings.TrimPrefix(service.Key, coreDNSPrefix), "/") reverse(domains) dnsName := strings.Join(domains[service.TargetStrip:], ".") if !p.domainFilter.Match(dnsName) { @@ -272,13 +274,14 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) { } prefix := strings.Join(domains[:service.TargetStrip], ".") if service.Host != "" { - ep := endpoint.NewEndpoint( + ep := endpoint.NewEndpointWithTTL( dnsName, guessRecordType(service.Host), + endpoint.TTL(service.TTL), service.Host, ) ep.Labels["originalText"] = service.Text - ep.Labels["prefix"] = prefix + ep.Labels[randomPrefixLabel] = prefix result = append(result, ep) } if service.Text != "" { @@ -287,20 +290,21 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) { endpoint.RecordTypeTXT, service.Text, ) - ep.Labels["prefix"] = prefix + ep.Labels[randomPrefixLabel] = prefix result = append(result, ep) } } return result, nil } -// ApplyChanges stores changes back to etcd converting them to SkyDNS format and aggregating A/CNAME and TXT records +// ApplyChanges stores changes back to etcd converting them to CoreDNS format and aggregating A/CNAME and TXT records func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { grouped := map[string][]*endpoint.Endpoint{} for _, ep := range changes.Create { grouped[ep.DNSName] = append(grouped[ep.DNSName], ep) } - for _, ep := range changes.UpdateNew { + for i, ep := range changes.UpdateNew { + ep.Labels[randomPrefixLabel] = changes.UpdateOld[i].Labels[randomPrefixLabel] grouped[ep.DNSName] = append(grouped[ep.DNSName], ep) } for dnsName, group := range grouped { @@ -313,7 +317,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { if ep.RecordType == endpoint.RecordTypeTXT { continue } - prefix := ep.Labels["prefix"] + prefix := ep.Labels[randomPrefixLabel] if prefix == "" { prefix = fmt.Sprintf("%08x", rand.Int31()) } @@ -322,6 +326,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { Text: ep.Labels["originalText"], Key: etcdKeyFor(prefix + "." + dnsName), TargetStrip: strings.Count(prefix, ".") + 1, + TTL: uint32(ep.RecordTTL), } services = append(services, service) } @@ -331,13 +336,14 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { continue } if index >= len(services) { - prefix := ep.Labels["prefix"] + prefix := ep.Labels[randomPrefixLabel] if prefix == "" { prefix = fmt.Sprintf("%08x", rand.Int31()) } services = append(services, Service{ Key: etcdKeyFor(prefix + "." + dnsName), TargetStrip: strings.Count(prefix, ".") + 1, + TTL: uint32(ep.RecordTTL), }) } services[index].Text = ep.Targets[0] @@ -349,7 +355,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { } for _, service := range services { - log.Infof("Add/set key %s to Host=%s, Text=%s", service.Key, service.Host, service.Text) + log.Infof("Add/set key %s to Host=%s, Text=%s, TTL=%d", service.Key, service.Host, service.Text, service.TTL) if !p.dryRun { err := p.client.SaveService(&service) if err != nil { @@ -361,8 +367,8 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { for _, ep := range changes.Delete { dnsName := ep.DNSName - if ep.Labels["prefix"] != "" { - dnsName = ep.Labels["prefix"] + "." + dnsName + if ep.Labels[randomPrefixLabel] != "" { + dnsName = ep.Labels[randomPrefixLabel] + "." + dnsName } key := etcdKeyFor(dnsName) log.Infof("Delete key %s", key) @@ -387,7 +393,7 @@ func guessRecordType(target string) string { func etcdKeyFor(dnsName string) string { domains := strings.Split(dnsName, ".") reverse(domains) - return "/skydns/" + strings.Join(domains, "/") + return coreDNSPrefix + strings.Join(domains, "/") } func reverse(slice []string) { diff --git a/provider/coredns_test.go b/provider/coredns_test.go index 9c4b90ce1..147711743 100644 --- a/provider/coredns_test.go +++ b/provider/coredns_test.go @@ -235,8 +235,6 @@ func TestCoreDNSApplyChanges(t *testing.T) { } validateServices(client.services, expectedServices1, t, 1) - 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", endpoint.RecordTypeA, "7.7.7.7"), @@ -245,6 +243,12 @@ func TestCoreDNSApplyChanges(t *testing.T) { endpoint.NewEndpoint("domain1.local", "A", "6.6.6.6"), }, } + records, _ := coredns.Records() + for _, ep := range records { + if ep.DNSName == "domain1.local" { + changes2.UpdateOld = append(changes2.UpdateOld, ep) + } + } applyServiceChanges(coredns, changes2) expectedServices2 := map[string]*Service{ From a1563f0b44a654b8fe6b0bb717d03367ee9faf7d Mon Sep 17 00:00:00 2001 From: shashidharatd Date: Fri, 26 Oct 2018 22:59:43 +0530 Subject: [PATCH 15/25] Update Gopkg.* vendor management files for github.com/coreos/etcd --- Gopkg.lock | 158 +++++++++++++++++++++++++---------------------------- Gopkg.toml | 4 -- 2 files changed, 75 insertions(+), 87 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index ece5c8e03..7ae69152a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,7 +2,7 @@ [[projects]] - digest = "1:e94ea655a0038d2274be202f77a2ea0eb2d3f74dfee674fd5d1f541e81008039" + digest = "1:ae9d0182a5cf7dbb025a8fc5821234cc1f26ca342fc41d951a99f71b9adc1b87" name = "cloud.google.com/go" packages = [ "compute/metadata", @@ -12,7 +12,7 @@ revision = "3b1ae45394a234c385be014e9a488f2bb6eef821" [[projects]] - digest = "1:b341fb465b057e991b166d073b35a224f5a84228e5ef7e40b4da7a70c152e7ec" + digest = "1:fd38e3b8c27cab6561a7d2e8557205c3ca5c57cbb6d3a79e10f22e73e84fd776" name = "github.com/Azure/azure-sdk-for-go" packages = ["arm/dns"] pruneopts = "" @@ -20,7 +20,7 @@ version = "v10.0.4-beta" [[projects]] - digest = "1:767f5f5dd4fa8e4f7f206726361d29aa0f7622b0bb8294b73d071864368c0d6b" + digest = "1:f719ef698feb8da2923ebda9a8d553b977320b02182f3797e185202e588a94b1" name = "github.com/Azure/go-autorest" packages = [ "autorest", @@ -34,7 +34,7 @@ version = "v10.9.0" [[projects]] - digest = "1:283a95024c33e84b23f24b1b47e3157ff2df2517d786a2e17bb0e6e4955e94e4" + digest = "1:7dc69d1597e4773ec5f64e5c078d55f0f011bb05ec0435346d0649ad978a23fd" name = "github.com/alecthomas/kingpin" packages = ["."] pruneopts = "" @@ -43,7 +43,7 @@ [[projects]] branch = "master" - digest = "1:1399282ad03ac819f0e8a747c888407c5c98bb497d33821a7047c7bae667ede0" + digest = "1:a74730e052a45a3fab1d310fdef2ec17ae3d6af16228421e238320846f2aaec8" name = "github.com/alecthomas/template" packages = [ ".", @@ -61,7 +61,7 @@ revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" [[projects]] - digest = "1:7b6c017b0290ccf1dd98c47a51e1db8b72b0863b6c7c52ddaa5a0d894aa3c2fc" + digest = "1:d2dc5d0ccc137594ea6fb3870964967b96b43daac19b8093930c7b02873fd5ca" name = "github.com/aliyun/alibaba-cloud-sdk-go" packages = [ "sdk", @@ -81,7 +81,7 @@ version = "1.27.7" [[projects]] - digest = "1:f04a72eefe1c7adec1dce30e099cec1e5fea8903a66e2db25bbbdfa66915428d" + digest = "1:1c82dd6a02941a3c4f23a32eca182717ab79691939e97d6b971466b780f06eea" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -121,14 +121,14 @@ [[projects]] branch = "master" - digest = "1:d20bdb6bf44087574af3139835946875bb098440426785282c741865b7bc66d3" + digest = "1:0c5485088ce274fac2e931c1b979f2619345097b39d91af3239977114adf0320" name = "github.com/beorn7/perks" packages = ["quantile"] pruneopts = "" revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" [[projects]] - digest = "1:d9d9c71f9776ef8f15b5c0a20246d5303071294743863ac3f4dde056f8c7b40a" + digest = "1:85fd00554a6ed5b33687684b76635d532c74141508b5bce2843d85e8a3c9dc91" name = "github.com/cloudflare/cloudflare-go" packages = ["."] pruneopts = "" @@ -136,29 +136,22 @@ version = "v0.7.4" [[projects]] - digest = "1:31259dbcb4c073aace59b951f5b471b3d5dbc4051b4a9d7e000f4392e143977e" + digest = "1:eaeede87b418b97f9dee473f8940fd9b65ca5cdac0503350c7c8f8965ea3cf4d" name = "github.com/coreos/etcd" packages = [ - "client", - "pkg/pathutil", - "pkg/srv", + "auth/authpb", + "clientv3", + "etcdserver/api/v3rpc/rpctypes", + "etcdserver/etcdserverpb", + "mvcc/mvccpb", "pkg/types", - "version", ] pruneopts = "" revision = "1b3ac99e8a431b381e633802cc42fe70e663baf5" version = "v3.2.15" [[projects]] - digest = "1:3c3f68ebab415344aef64363d23471e953a4715645115604aaf57923ae904f5e" - name = "github.com/coreos/go-semver" - packages = ["semver"] - pruneopts = "" - revision = "8ab6407b697782a06568d4b7f1db25550ec2e4c6" - version = "v0.2.0" - -[[projects]] - digest = "1:0a39ec8bf5629610a4bc7873a92039ee509246da3cef1a0ea60f1ed7e5f9cea5" + digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "" @@ -167,7 +160,7 @@ [[projects]] branch = "master" - digest = "1:64ee6871ef691c663f910e29bc2f7c10c8c342b06665920f1138b6aa8b11cb5a" + digest = "1:dc166ce7345c060c2153561130d6d49ac580c804226ac675e368d533b36eb169" name = "github.com/denverdino/aliyungo" packages = [ "metadata", @@ -177,7 +170,7 @@ revision = "69560d9530f5265ba00ffad2520d7ef01c2cddd4" [[projects]] - digest = "1:2426da75f49e5b8507a6ed5d4c49b06b2ff795f4aec401c106b7db8fb2625cd7" + digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6" name = "github.com/dgrijalva/jwt-go" packages = ["."] pruneopts = "" @@ -185,7 +178,7 @@ version = "v3.2.0" [[projects]] - digest = "1:3da5806ef37ea163fee80ed179d40a5e013e671ccbe321a04c47c5aee3d5080a" + digest = "1:32d1941b093bb945de75b0276348494be318d34f3df39c4413d61e002c800bc6" name = "github.com/digitalocean/godo" packages = [ ".", @@ -196,7 +189,7 @@ version = "v1.1.1" [[projects]] - digest = "1:ca3b228bf258217cff2070f4045e53729886c66a27bf9cce30dcbf8a575ea86a" + digest = "1:5ffd39844bdd1259a6227d544f582c6686ce43c8c44399a46052fe3bd2bed93c" name = "github.com/dnsimple/dnsimple-go" packages = ["dnsimple"] pruneopts = "" @@ -204,7 +197,7 @@ version = "v0.14.0" [[projects]] - digest = "1:bfce2cc5b829073f93962e742275d45913948e22d182fbc5464104da1c5f2f89" + digest = "1:e17d18b233f506404061c27ac4a08624dad38dcd0d49f9cfdae67a7772a4fb8c" name = "github.com/exoscale/egoscale" packages = ["."] pruneopts = "" @@ -213,7 +206,7 @@ [[projects]] branch = "master" - digest = "1:bc12846e4bae094e01a33ef98cad0a1afa35da37090e5126513be6f747e074ab" + digest = "1:ae7fb2062735e966ab69d14d2a091f3778b0d676dc8d1f01d092bcb0fb8ed45b" name = "github.com/ffledgling/pdns-go" packages = ["."] pruneopts = "" @@ -228,7 +221,7 @@ version = "v1.0.0" [[projects]] - digest = "1:bbc763f3c703dc3c6a99a22c1318760099b52bc00a47a36dc4462e88eee7846b" + digest = "1:a00483fe4106b86fb1187a92b5cf6915c85f294ed4c129ccbe7cb1f1a06abd46" name = "github.com/go-ini/ini" packages = ["."] pruneopts = "" @@ -236,7 +229,7 @@ version = "v1.32.0" [[projects]] - digest = "1:cdeb6a9eb9f2356b2987c401d013d41e018b819ee1e8d5a1b32a5b714e53c392" + digest = "1:8e67153fc0a9fb0d6c9707e36cf80e217a012364307b222eb4ba6828f7e881e6" name = "github.com/go-resty/resty" packages = ["."] pruneopts = "" @@ -244,7 +237,7 @@ version = "v1.8.0" [[projects]] - digest = "1:d7b2f8af8341e15d0239dab17cb49fbf4f01029ecf2d3b5924aa53d95c5a452d" + digest = "1:54d5c6a784a9de9c836fc070d51d0689c3e99ee6d24dba8a36f0762039dae830" name = "github.com/gogo/googleapis" packages = ["google/rpc"] pruneopts = "" @@ -252,7 +245,7 @@ version = "v1.1.0" [[projects]] - digest = "1:673df1d02ca0c6f51458fe94bbb6fae0b05e54084a31db2288f1c4321255c2da" + digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -276,11 +269,12 @@ source = "github.com/kubermatic/glog-logrus" [[projects]] - digest = "1:815d45503dceeca8ffecce0081d7edeae5e75b126107ef763d1c617154d72359" + digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18" name = "github.com/golang/protobuf" packages = [ "jsonpb", "proto", + "protoc-gen-go/descriptor", "ptypes", "ptypes/any", "ptypes/duration", @@ -315,7 +309,7 @@ revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c" [[projects]] - digest = "1:1962b5d00f5285d08504697049627d45ad876912894528d31cdc1c05cdc853f6" + digest = "1:16b2837c8b3cf045fa2cdc82af0cf78b19582701394484ae76b2c3bc3c99ad73" name = "github.com/googleapis/gnostic" packages = [ "OpenAPIv2", @@ -328,7 +322,7 @@ [[projects]] branch = "master" - digest = "1:815036d12757902f85888f3cb0440c2e00220dd4177e4c2bb048e03259db077a" + digest = "1:54a44d48a24a104e078ef5f94d82f025a6be757e7c42b4370c621a3928d6ab7c" name = "github.com/gophercloud/gophercloud" packages = [ ".", @@ -362,7 +356,7 @@ [[projects]] branch = "master" - digest = "1:8c4d156acec272201ffc4d1bdb9302de1c48314e0451eb38c70150cf11bdb33a" + digest = "1:009a1928b8c096338b68b5822d838a72b4d8520715c1463614476359f3282ec8" name = "github.com/gregjones/httpcache" packages = [ ".", @@ -422,14 +416,14 @@ revision = "61dc5f9b0a655ebf43026f0d8a837ad1e28e4b96" [[projects]] - digest = "1:4f767a115bc8e08576f6d38ab73c376fc1b1cd3bb5041171c9e8668cc7739b52" + digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e" name = "github.com/jmespath/go-jmespath" packages = ["."] pruneopts = "" revision = "0b12d6b5" [[projects]] - digest = "1:890dd7615573f096655600bbe7beb2f532a437f6d8ef237831894301fca31f23" + digest = "1:53ac4e911e12dde0ab68655e2006449d207a5a681f084974da2b06e5dbeaca72" name = "github.com/json-iterator/go" packages = ["."] pruneopts = "" @@ -437,14 +431,14 @@ version = "1.1.4" [[projects]] - digest = "1:def40684a573560241c8344da452fa3574dfc2c7da525903992a3790d2262625" + digest = "1:1c88ec29544b281964ed7a9a365b2802a523cd06c50cdee87eb3eec89cd864f4" name = "github.com/kubernetes/repo-infra" packages = ["verify/boilerplate/test"] pruneopts = "" revision = "c2f9667a4c29e70a39b0e89db2d4f0cab907dbee" [[projects]] - digest = "1:c3aa5f9d5119ca1cfdaa41a5084e3deceef0460eef3e6c71b58fa50e500f01a0" + digest = "1:7c23a751ce2f84663fa411acb87eae0da2d09c39a8e99b08bd8f65fae75d8928" name = "github.com/linki/instrumented_http" packages = ["."] pruneopts = "" @@ -452,7 +446,7 @@ version = "v0.2.0" [[projects]] - digest = "1:93d29291d0c37678592d77ee847031aec2ce1631f3ce4cf975b77216e8bd4a01" + digest = "1:1c41354ef11c9dbae2fe1ceee8369fcb2634977ba07e701e19ea53e8742c5420" name = "github.com/linode/linodego" packages = ["."] pruneopts = "" @@ -461,14 +455,14 @@ [[projects]] branch = "master" - digest = "1:49a8b01a6cd6558d504b65608214ca40a78000e1b343ed0da5c6a9ccd83d6d30" + digest = "1:63722a4b1e1717be7b98fc686e0b30d5e7f734b9e93d7dee86293b6deab7ea28" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] pruneopts = "" revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" [[projects]] - digest = "1:f0bad0fece0fb73c6ea249c18d8e80ffbe86be0457715b04463068f04686cf39" + digest = "1:4c8d8358c45ba11ab7bb15df749d4df8664ff1582daead28bae58cf8cbe49890" name = "github.com/miekg/dns" packages = ["."] pruneopts = "" @@ -500,7 +494,7 @@ version = "v2.1" [[projects]] - digest = "1:7aef6d4ad1b4a613d66ed554010c552a249e9afabcb079f54528a298474549cc" + digest = "1:d8b5d0ecca348c835914a1ed8589f17a6a7f309befab7327b0470324531f7ac4" name = "github.com/nesv/go-dynect" packages = ["dynect"] pruneopts = "" @@ -508,7 +502,7 @@ version = "v0.6.0" [[projects]] - digest = "1:2062e45c462d0327f680340dce46fe11ae2d34bf802e15e397cb1d6c4d159b39" + digest = "1:70df8e71a953626770223d4982801fa73e7e99cbfcca068b95127f72af9b9edd" name = "github.com/oracle/oci-go-sdk" packages = [ "common", @@ -520,14 +514,14 @@ [[projects]] branch = "master" - digest = "1:b7be9a944fe102bf466420fa8a064534dd12547a0482f5b684d228425b559b56" + digest = "1:c24598ffeadd2762552269271b3b1510df2d83ee6696c1e543a0ff653af494bc" name = "github.com/petar/GoLLRB" packages = ["llrb"] pruneopts = "" revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" [[projects]] - digest = "1:6db21ad64a13fe79220e47fcc895e13b8da923676a3a024f98210fca57a10d9a" + digest = "1:b46305723171710475f2dd37547edd57b67b9de9f2a6267cafdd98331fd6897f" name = "github.com/peterbourgon/diskv" packages = ["."] pruneopts = "" @@ -550,7 +544,7 @@ version = "v1.0.0" [[projects]] - digest = "1:7e88bda1bec34ddf3c8aded1326c652793069a673b0f751484953e7d65a2386c" + digest = "1:2f69dc6b2685b31a1a410ef697410aa8a669704fb201d45dbd8c1911728afa75" name = "github.com/prometheus/client_golang" packages = [ "prometheus", @@ -562,7 +556,7 @@ [[projects]] branch = "master" - digest = "1:83bf37d060fca77e959fe5ceee81e58bbd1b01836f4addc70043a948e9912547" + digest = "1:60aca47f4eeeb972f1b9da7e7db51dee15ff6c59f7b401c1588b8e6771ba15ef" name = "github.com/prometheus/client_model" packages = ["go"] pruneopts = "" @@ -570,7 +564,7 @@ [[projects]] branch = "master" - digest = "1:7221d79e41a24b2245d06f331d0825b479a9acd0bd05a8353806c7bf38395795" + digest = "1:e3aa5178be4fc4ae8cdb37d11c02f7490c00450a9f419e6aa84d02d3b47e90d2" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -581,7 +575,7 @@ revision = "2e54d0b93cba2fd133edc32211dcc32c06ef72ca" [[projects]] - digest = "1:91345f4cce04248cf4998c4f70a82579c1468101767636acf5af2e1556904933" + digest = "1:a6a85fc81f2a06ccac3d45005523afbeee45138d781d4f3cb7ad9889d5c65aab" name = "github.com/prometheus/procfs" packages = [ ".", @@ -599,7 +593,7 @@ version = "v1.2.0" [[projects]] - digest = "1:75e2c10fd48881dc9400b7b70281270923e01c44f1f5cb4bbc5ba8cac8ca3026" + digest = "1:3ac248add5bb40a3c631c5334adcd09aa72d15af2768a5bc0274084ea7b2e5ba" name = "github.com/sirupsen/logrus" packages = ["."] pruneopts = "" @@ -607,7 +601,7 @@ version = "v1.0.3" [[projects]] - digest = "1:39c598f67d5d68846c05832bb351e897091edcbee4689c57d3697f68f25f928d" + digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6" name = "github.com/spf13/cobra" packages = ["."] pruneopts = "" @@ -623,7 +617,7 @@ version = "v1.0.2" [[projects]] - digest = "1:ba8fed52de60135b7efd5d832b997fb5b10fa09f227fa385174faa69f4219e4e" + digest = "1:306417ea2f31ea733df356a2b895de63776b6a5107085b33458e5cd6eb1d584d" name = "github.com/stretchr/objx" packages = ["."] pruneopts = "" @@ -631,7 +625,7 @@ version = "v0.1" [[projects]] - digest = "1:a70d585d45f695f2e8e6782569bdf181419667a35e6035ceb086706b495aa21a" + digest = "1:a30066593578732a356dc7e5d7f78d69184ca65aeeff5939241a3ab10559bb06" name = "github.com/stretchr/testify" packages = [ "assert", @@ -652,14 +646,7 @@ revision = "ac974c61c2f990f4115b119354b5e0b47550e888" [[projects]] - digest = "1:f98e0b7c7bd110a49d8bb56c9eefcef4f547f5d789025d3bfe9bd6b83125221b" - name = "github.com/ugorji/go" - packages = ["codec"] - pruneopts = "" - revision = "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74" - -[[projects]] - digest = "1:5e30725e7522642910b34208061b21bb0cd77b8ce115c3133a1431c52054e004" + digest = "1:74f86c458e82e1c4efbab95233e0cf51b7cc02dc03193be9f62cd81224e10401" name = "go.uber.org/atomic" packages = ["."] pruneopts = "" @@ -675,7 +662,7 @@ version = "v1.1.0" [[projects]] - digest = "1:2fabb14a874994210af33633091bd0eb070b50aa527767abaac1b5483db03d75" + digest = "1:246f378f80fba6fcf0f191c486b6613265abd2bc0f2fa55a36b928c67352021e" name = "go.uber.org/zap" packages = [ ".", @@ -692,7 +679,7 @@ [[projects]] branch = "master" - digest = "1:16db3d6f4f8bbe4b7b42cb8808e68457fea4bd7aea410b77c8c9a6dc26253a60" + digest = "1:b2d8b39397ca07929a3de3a3fd2b6ca4c8d48e9cadaa7cf2b083e27fd9e78107" name = "golang.org/x/crypto" packages = [ "ed25519", @@ -703,7 +690,7 @@ revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4" [[projects]] - digest = "1:02feed0dbc44ce5bef5670a6e5ac9c2c4e3b879575a9d074199b487af1b7c4f9" + digest = "1:782723d6fc27d202f1943219d68d58b3f6bcab6212c85294b1ddd8b586b1d356" name = "golang.org/x/net" packages = [ "bpf", @@ -725,7 +712,7 @@ revision = "161cd47e91fd58ac17490ef4d742dc98bb4cf60e" [[projects]] - digest = "1:2fef2e19e90f29efd775d58d66b5e100139fedbe24cf749f1c085c0a5aee86d3" + digest = "1:dad5a319c4710358be1f2bf68f9fb7f90a71d7c641221b79801d5667b28f19e3" name = "golang.org/x/oauth2" packages = [ ".", @@ -738,7 +725,7 @@ revision = "3c3a985cb79f52a3190fbc056984415ca6763d01" [[projects]] - digest = "1:d4315e749759007a597c9ad09eef29112bea98030da19ed29c33959ad6744130" + digest = "1:39d88a855976e160babdd254802e1c2ae75ed380328c39742b57928852da6207" name = "golang.org/x/sys" packages = [ "unix", @@ -748,7 +735,7 @@ revision = "13d03a9a82fba647c21a0ef8fba44a795d0f0835" [[projects]] - digest = "1:af9bfca4298ef7502c52b1459df274eed401a4f5498b900e9a92d28d3d87ac5a" + digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4" name = "golang.org/x/text" packages = [ "collate", @@ -779,7 +766,7 @@ revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" [[projects]] - digest = "1:ab84306e7e74b9f01b9f1480d46cca9325f8c512567a0e7b8888d04ff627a5ba" + digest = "1:2ad38d79865e33dde6157b7048debd6e7d47e0709df7b5e11bb888168e316908" name = "google.golang.org/api" packages = [ "dns/v1", @@ -791,7 +778,7 @@ revision = "a0ff90edab763c86aa88f2b1eb4f3301b82f6336" [[projects]] - digest = "1:0b45fac4876cbd496ed7b95406b05c8c1eba559b43c82f2dda1b0e1bbe6cd1b6" + digest = "1:41e2b7e287117f6136f75286d48072ecf781ba54823ffeb2098e152e7dc45ef6" name = "google.golang.org/appengine" packages = [ ".", @@ -810,14 +797,17 @@ [[projects]] branch = "master" - digest = "1:7040eaf95eb09f6f69e1415074049a9a66236d59d8767f2d17b759b916f79fb1" + digest = "1:e43f1cb3f488a0c2be85939c2a594636f60b442a12a196c778bd2d6c9aca3df7" name = "google.golang.org/genproto" - packages = ["googleapis/rpc/status"] + packages = [ + "googleapis/api/annotations", + "googleapis/rpc/status", + ] pruneopts = "" revision = "11092d34479b07829b72e10713b159248caf5dad" [[projects]] - digest = "1:cb1330030248de97a11d9f9664f3944fce0df947e5ed94dbbd9cb6e77068bd46" + digest = "1:ca75b3775a5d4e5d1fb48f57ef0865b4aaa8b3f00e6b52be68db991c4594e0a7" name = "google.golang.org/grpc" packages = [ ".", @@ -830,6 +820,7 @@ "encoding", "encoding/proto", "grpclog", + "health/grpc_health_v1", "internal", "internal/backoff", "internal/channelz", @@ -869,7 +860,7 @@ [[projects]] branch = "release-1.0" - digest = "1:159d72863d2fdc7f8a7bf5178554bad51c45b003ba27021c3481f84b3cc8e155" + digest = "1:bc43af6616d8ca12a7b8e806874387f0f1e181c08f547e9cd77f6cbac8cefd86" name = "istio.io/api" packages = [ "authentication/v1alpha1", @@ -883,7 +874,7 @@ revision = "76349c53b87f03f1e610b3aa3843dba3c38138d7" [[projects]] - digest = "1:2e63e5a0a6abb75c20ea575ee82a72c117a811ab8472ed34a48a09ba25a0a7d7" + digest = "1:7eb25280e1f610470bb0c43ab6c91573cfc78672a58542106b9b71705581429a" name = "istio.io/istio" packages = [ "pilot/pkg/config/kube/crd", @@ -899,7 +890,7 @@ version = "1.0.1" [[projects]] - digest = "1:d42e6aef075bfc20da9c3991adfd8b09e3e158ac619028e15271677b705be5f0" + digest = "1:f420c8548c93242d8e5dcfa5b34e0243883b4e660f65076e869daafac877144d" name = "k8s.io/api" packages = [ "admissionregistration/v1alpha1", @@ -937,7 +928,7 @@ version = "kubernetes-1.11.0" [[projects]] - digest = "1:a14992570e0e2b0291594f505d2b2ed1a6ba4482d4166ace9714c2ba8cbfe252" + digest = "1:66d1421ecff35bc48ee0b11a3f891f3af6f775ed6bb1d3e0deeaba221bf42490" name = "k8s.io/apiextensions-apiserver" packages = [ "pkg/apis/apiextensions", @@ -951,7 +942,7 @@ version = "kubernetes-1.10.4" [[projects]] - digest = "1:a855f74be59f83ed0950a9a2b70d8c8af01fb5782d060c7dec67ae39033f30dc" + digest = "1:b6b2fb7b4da1ac973b64534ace2299a02504f16bc7820cb48edb8ca4077183e1" name = "k8s.io/apimachinery" packages = [ "pkg/api/errors", @@ -1001,7 +992,7 @@ version = "kubernetes-1.11.0" [[projects]] - digest = "1:3c4611c2b28fdc62391698bba7f212050f0f9ed75f3648f37ec3bcf8a83bf96d" + digest = "1:d04779a8de7d5465e0463bd986506348de5e89677c74777f695d3145a7a8d15e" name = "k8s.io/client-go" packages = [ "discovery", @@ -1107,7 +1098,7 @@ [[projects]] branch = "master" - digest = "1:d93d8bcb5f04d6b59eafdb9fa1a80f187d2542611670bfabc0ea8e031ab874a2" + digest = "1:526095379da1098c3f191a0008cc59c9bf9927492e63da7689e5de424219c162" name = "k8s.io/kube-openapi" packages = ["pkg/util/proto"] pruneopts = "" @@ -1134,7 +1125,7 @@ "github.com/aws/aws-sdk-go/service/route53", "github.com/aws/aws-sdk-go/service/servicediscovery", "github.com/cloudflare/cloudflare-go", - "github.com/coreos/etcd/client", + "github.com/coreos/etcd/clientv3", "github.com/denverdino/aliyungo/metadata", "github.com/digitalocean/godo", "github.com/digitalocean/godo/context", @@ -1173,6 +1164,7 @@ "istio.io/istio/pilot/pkg/model", "k8s.io/api/core/v1", "k8s.io/api/extensions/v1beta1", + "k8s.io/apimachinery/pkg/api/errors", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/labels", "k8s.io/apimachinery/pkg/runtime", diff --git a/Gopkg.toml b/Gopkg.toml index b7569514c..328d11f42 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,10 +24,6 @@ ignored = ["github.com/kubernetes/repo-infra/kazel"] name = "github.com/cloudflare/cloudflare-go" version = "0.7.3" -[[constraint]] - name = "github.com/coreos/etcd" - version = "~3.2.15" - [[constraint]] name = "github.com/digitalocean/godo" version = "~1.1.0" From 2c602631ddc55975cdc75888e6235d030dde3945 Mon Sep 17 00:00:00 2001 From: Patrick Galbraith Date: Tue, 6 Nov 2018 14:58:18 -0500 Subject: [PATCH 16/25] Oracle doc fix (add "key:" to secret) (#750) * fix domain filter match logic to not match similar domain names * MAINTAINER is deprecated - using LABEL instead https://docs.docker.com/engine/reference/builder/#maintainer-deprecated * Fix to documentation for Oracle to include `key:` --- docs/tutorials/oracle.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tutorials/oracle.md b/docs/tutorials/oracle.md index adfab29c2..9fb6de42a 100644 --- a/docs/tutorials/oracle.md +++ b/docs/tutorials/oracle.md @@ -22,6 +22,7 @@ auth: region: us-phoenix-1 tenancy: ocid1.tenancy.oc1... user: ocid1.user.oc1... + key: | -----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- fingerprint: af:81:71:8e... From 15d5fa1f963ba349240e028a1c5550d22fe53c14 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Wed, 7 Nov 2018 00:17:51 -0800 Subject: [PATCH 17/25] Add Traefik to the supported list of ingress controllers. --- docs/faq.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/faq.md b/docs/faq.md index dcf6873fb..d5a1666ab 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -69,6 +69,7 @@ Regarding Ingress, we'll support: * Google's Ingress Controller on GKE that integrates with their Layer 7 load balancers (GLBC) * nginx-ingress-controller v0.9.x with a fronting Service * Zalando's [AWS Ingress controller](https://github.com/zalando-incubator/kube-ingress-aws-controller), based on AWS ALBs and [Skipper](https://github.com/zalando/skipper) +* [Traefik](https://github.com/containous/traefik) 1.7 and above, when [`kubernetes.ingressEndpoint`](https://docs.traefik.io/v1.7/configuration/backends/kubernetes/#ingressendpoint) is configured (`kubernetes.ingressEndpoint.useDefaultPublishedService` in the [Helm chart](https://github.com/helm/charts/tree/master/stable/traefik#configuration)) ### Are other Ingress Controllers supported? From a4e4d2c08b488f9c4176f91a6f614411bc52009a Mon Sep 17 00:00:00 2001 From: xianlubird Date: Fri, 9 Nov 2018 13:40:45 +0800 Subject: [PATCH 18/25] Fix Multiple subdomains bug --- provider/alibaba_cloud.go | 16 +++++++++++----- provider/alibaba_cloud_test.go | 10 ++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/provider/alibaba_cloud.go b/provider/alibaba_cloud.go index 7100bf5bb..79663577d 100644 --- a/provider/alibaba_cloud.go +++ b/provider/alibaba_cloud.go @@ -684,18 +684,24 @@ func (p *AlibabaCloudProvider) splitDNSName(endpoint *endpoint.Endpoint) (rr str } if !found { - idx := strings.Index(name, ".") - if idx >= 0 { - rr = name[0:idx] - domain = name[idx+1:] - } else { + parts := strings.Split(name, ".") + if len(parts) < 2 { rr = name domain = "" + } else { + domain = parts[len(parts)-2] + "." + parts[len(parts)-1] + rrIndex := strings.Index(name, domain) + if rrIndex < 1 { + rrIndex = 1 + } + rr = name[0 : rrIndex-1] } } + if rr == "" { rr = nullHostAlibabaCloud } + return rr, domain } diff --git a/provider/alibaba_cloud_test.go b/provider/alibaba_cloud_test.go index 5d4b97a10..d9d70a1cc 100644 --- a/provider/alibaba_cloud_test.go +++ b/provider/alibaba_cloud_test.go @@ -404,6 +404,16 @@ func TestAlibabaCloudProvider_splitDNSName(t *testing.T) { if rr != "@" || domain != "container-service.top" { t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) } + endpoint.DNSName = "a.b.container-service.top" + rr, domain = p.splitDNSName(endpoint) + if rr != "a.b" || domain != "container-service.top" { + t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) + } + endpoint.DNSName = "a.b.c.container-service.top" + rr, domain = p.splitDNSName(endpoint) + if rr != "a.b.c" || domain != "container-service.top" { + t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain) + } } func TestAlibabaCloudProvider_TXTEndpoint(t *testing.T) { From 0af588317b26279aa497867577fa255b7dd1c636 Mon Sep 17 00:00:00 2001 From: vaegt Date: Fri, 9 Nov 2018 11:11:39 +0100 Subject: [PATCH 19/25] Remove unnecessary slashes --- source/source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/source.go b/source/source.go index fac716c81..338397dfc 100644 --- a/source/source.go +++ b/source/source.go @@ -35,7 +35,7 @@ const ( targetAnnotationKey = "external-dns.alpha.kubernetes.io/target" // The annotation used for defining the desired DNS record TTL ttlAnnotationKey = "external-dns.alpha.kubernetes.io/ttl" - //// The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME + // The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME aliasAnnotationKey = "external-dns.alpha.kubernetes.io/alias" // The value of the controller annotation so that we feel responsible controllerAnnotationValue = "dns-controller" From 38dc658270d0728cb7c7a261acf49327de7d5690 Mon Sep 17 00:00:00 2001 From: vaegt Date: Fri, 9 Nov 2018 11:19:19 +0100 Subject: [PATCH 20/25] Change log level --- provider/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/aws.go b/provider/aws.go index eb08207fe..411255bdc 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -366,7 +366,7 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou rec, err := p.Records() if err != nil { - log.Errorf("getting records failed: %v", err) + log.Infof("getting records failed: %v", err) } if isAWSLoadBalancer(endpoint) { From 4b985ab04af9515a96029532bd36aa6afb7f14f2 Mon Sep 17 00:00:00 2001 From: vaegt Date: Fri, 9 Nov 2018 13:07:55 +0100 Subject: [PATCH 21/25] Add docs for alias annotation --- docs/tutorials/aws.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index dc9be8ad9..5ab4050de 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -171,6 +171,13 @@ This list is not the full list, but a few arguments that where chosen. `aws-zone-type` allows filtering for private and public zones +## Annotations + +Annotations which are specific to AWS. + +### alias + +`external-dns.alpha.kubernetes.io/alias` if set to `true` on an ingress, it will create an ALIAS record when the target is an ALIAS as well. ## Verify ExternalDNS works (Ingress example) From c0968ab857201b61a300aeb1a0b1b41a6f0daad3 Mon Sep 17 00:00:00 2001 From: Bily Zhang Date: Mon, 12 Nov 2018 15:33:53 +0800 Subject: [PATCH 22/25] Fix typos: sychronized->synchronized, resouce->resource, sepecified->specified (#769) Signed-off-by: mooncake --- pkg/apis/externaldns/types.go | 2 +- provider/azure_test.go | 2 +- provider/pdns.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index cad2bfd35..f0637abe8 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -285,7 +285,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("rfc2136-tsig-axfr", "When using the RFC2136 provider, specify the TSIG (base64) value to attached to DNS messages (required when --rfc2136-insecure=false)").BoolVar(&cfg.RFC2136TAXFR) // 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") + app.Flag("policy", "Modify how DNS records are synchronized between sources and providers (default: sync, options: sync, upsert-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only") // Flags related to the registry app.Flag("registry", "The registry implementation to use to keep track of DNS record ownership (default: txt, options: txt, noop, aws-sd)").Default(defaultConfig.Registry).EnumVar(&cfg.Registry, "txt", "noop", "aws-sd") diff --git a/provider/azure_test.go b/provider/azure_test.go index bdae835e8..e9da5c286 100644 --- a/provider/azure_test.go +++ b/provider/azure_test.go @@ -48,7 +48,7 @@ func createMockZone(zone string, id string) dns.Zone { } func (client *mockZonesClient) ListByResourceGroup(resourceGroupName string, top *int32) (dns.ZoneListResult, error) { - // Don't bother filtering by resouce group or implementing paging since that's the responsibility + // Don't bother filtering by resource group or implementing paging since that's the responsibility // of the Azure DNS service return *client.mockZoneListResult, nil } diff --git a/provider/pdns.go b/provider/pdns.go index 862dab902..fd21797c5 100644 --- a/provider/pdns.go +++ b/provider/pdns.go @@ -321,7 +321,7 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet 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 + // No TTL was specified for the record, we use the default rrset.Ttl = int32(defaultTTL) } else { rrset.Ttl = int32(ep.RecordTTL) From 10134b26a9042ab9150f39559cecd8099adf6e56 Mon Sep 17 00:00:00 2001 From: Bily Zhang Date: Mon, 12 Nov 2018 15:36:08 +0800 Subject: [PATCH 23/25] Remove dupplicated words:have,aliyun (#768) Signed-off-by: mooncake --- docs/tutorials/alibabacloud.md | 2 +- provider/oci.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/alibabacloud.md b/docs/tutorials/alibabacloud.md index b9b5a5789..2fc509579 100644 --- a/docs/tutorials/alibabacloud.md +++ b/docs/tutorials/alibabacloud.md @@ -286,7 +286,7 @@ spec: After roughly two minutes check that a corresponding DNS record for your service was created. ```console -$ aliyun aliyun alidns DescribeDomainRecords --DomainName=external-dns-test.com +$ aliyun alidns DescribeDomainRecords --DomainName=external-dns-test.com { "PageNumber": 1, "TotalCount": 1, diff --git a/provider/oci.go b/provider/oci.go index 5ff1ba8f8..aa0c03411 100644 --- a/provider/oci.go +++ b/provider/oci.go @@ -290,7 +290,7 @@ func operationsByZone(zones map[string]*dns.ZoneSummary, ops []dns.RecordOperati } } - // Remove zones that don't have have any changes. + // Remove zones that don't have any changes. for zone, ops := range changes { if len(ops) == 0 { delete(changes, zone) From 1a9f8f08ae162bd0ee61728d68ae67e351862704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nick=20J=C3=BCttner?= Date: Wed, 14 Nov 2018 14:32:51 +0100 Subject: [PATCH 24/25] adding kubernetes adder --- scripts/update_route53_k8s_txt_owner.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/update_route53_k8s_txt_owner.py b/scripts/update_route53_k8s_txt_owner.py index 7e7e5a2a1..630f902ef 100644 --- a/scripts/update_route53_k8s_txt_owner.py +++ b/scripts/update_route53_k8s_txt_owner.py @@ -1,3 +1,14 @@ +# 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. + # This is a script that we wrote to try to help the migration over to using external-dns. # This script looks at kubernetes ingresses and services (which are the two things we have # external-dns looking at) and compares them to existing TXT and A records in route53 to From 41624a84a3069ac63a18ea987e38d63728ab150b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nick=20J=C3=BCttner?= Date: Wed, 14 Nov 2018 14:53:12 +0100 Subject: [PATCH 25/25] adding kubernetes adder --- scripts/update_route53_k8s_txt_owner.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/update_route53_k8s_txt_owner.py b/scripts/update_route53_k8s_txt_owner.py index 630f902ef..f1a0c7056 100644 --- a/scripts/update_route53_k8s_txt_owner.py +++ b/scripts/update_route53_k8s_txt_owner.py @@ -1,12 +1,17 @@ -# Copyright 2017 The Kubernetes Authors. +#!/usr/bin/env python + +# 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 +# See the License for the specific language governing permissions and # limitations under the License. # This is a script that we wrote to try to help the migration over to using external-dns. @@ -17,8 +22,9 @@ # to only look at one or the other if needed. # # pip install kubernetes boto3 -from kubernetes import client, config + import boto3 +from kubernetes import client, config # replace with your hosted zone id hosted_zone_id = ''