diff --git a/docs/tutorials/coredns.md b/docs/tutorials/coredns.md index 081f144a31..08bc38c30e 100644 --- a/docs/tutorials/coredns.md +++ b/docs/tutorials/coredns.md @@ -260,3 +260,78 @@ dnstools# dig @10.100.4.143 nginx.example.org +short 10.0.2.15 dnstools# ``` + +## Specific service annotation options + +### Groups + +Groups can be used to group set of services together. The main use of this is to limit recursion, +i.e. don't return all records, but only a subset. Let's say we have a configuration like this: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: a + annotations: + external-dns.alpha.kubernetes.io/hostname: a.domain.local + external-dns.alpha.kubernetes.io/coredns-group: "g1" +spec: + type: LoadBalancer + ... +status: + loadBalancer: + ingress: + - ip: 127.0.0.1 +--- +apiVersion: v1 +kind: Service +metadata: + name: b + annotations: + external-dns.alpha.kubernetes.io/hostname: b.domain.local + external-dns.alpha.kubernetes.io/coredns-group: "g1" +spec: + type: LoadBalancer + ... +status: + loadBalancer: + ingress: + - ip: 127.0.0.2 +--- +apiVersion: v1 +kind: Service +metadata: + name: c + annotations: + external-dns.alpha.kubernetes.io/hostname: c.subdom.domain.local + external-dns.alpha.kubernetes.io/coredns-group: "g2" +spec: + type: LoadBalancer + ... +status: + loadBalancer: + ingress: + - ip: 127.0.0.3 +--- +apiVersion: v1 +kind: Service +metadata: + name: d + annotations: + external-dns.alpha.kubernetes.io/hostname: d.subdom.domain.local + external-dns.alpha.kubernetes.io/coredns-group: "g2" +spec: + type: LoadBalancer + ... +status: + loadBalancer: + ingress: + - ip: 127.0.0.4 + +``` + +And we want domain.local to return (127.0.0.1 and 127.0.0.2) and subdom.domain.local to return (127.0.0.3 and 127.0.0.4). +For this the two domains, need to be in different groups. What those groups are does not matter, +as long as a and b belong to the same group which is different from the group c and d belong to. +If a service is found without a group it is always included. diff --git a/provider/coredns/coredns.go b/provider/coredns/coredns.go index 57ab8130f2..55e0cd1764 100644 --- a/provider/coredns/coredns.go +++ b/provider/coredns/coredns.go @@ -41,7 +41,8 @@ const ( priority = 10 // default priority when nothing is set etcdTimeout = 5 * time.Second - randomPrefixLabel = "prefix" + randomPrefixLabel = "prefix" + providerSpecificGroup = "coredns/group" ) // coreDNSClient is an interface to work with CoreDNS service records in etcd @@ -260,6 +261,9 @@ func (p coreDNSProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error endpoint.TTL(service.TTL), service.Host, ) + if service.Group != "" { + ep.WithProviderSpecific(providerSpecificGroup, service.Group) + } log.Debugf("Creating new ep (%s) with new service host (%s)", ep, service.Host) } ep.Labels["originalText"] = service.Text @@ -346,12 +350,17 @@ func (p coreDNSProvider) createServicesForEndpoint(dnsName string, ep *endpoint. prefix = fmt.Sprintf("%08x", rand.Int31()) log.Infof("Generating new prefix: (%s)", prefix) } + group := "" + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGroup); ok { + group = prop + } service := Service{ Host: target, Text: ep.Labels["originalText"], Key: p.etcdKeyFor(prefix + "." + dnsName), TargetStrip: strings.Count(prefix, ".") + 1, TTL: uint32(ep.RecordTTL), + Group: group, } services = append(services, &service) ep.Labels[target] = prefix diff --git a/provider/coredns/coredns_test.go b/provider/coredns/coredns_test.go index 1d84c41177..b7d530aa81 100644 --- a/provider/coredns/coredns_test.go +++ b/provider/coredns/coredns_test.go @@ -460,7 +460,7 @@ func validateServices(services map[string]Service, expectedServices map[string][ } found := false for i, expectedServiceEntry := range expectedServiceEntries { - if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text { + if value.Host == expectedServiceEntry.Host && value.Text == expectedServiceEntry.Text && value.Group == expectedServiceEntry.Group { expectedServiceEntries = append(expectedServiceEntries[:i], expectedServiceEntries[i+1:]...) found = true break @@ -863,3 +863,48 @@ func TestCoreDNSProvider_updateTXTRecords_ClearsExtraText(t *testing.T) { assert.Equal(t, "txt-value", services[0].Text) assert.Empty(t, services[1].Text) } + +func TestApplyChangesAWithGroupServiceTranslation(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{}, + } + coredns := coreDNSProvider{ + client: client, + coreDNSPrefix: defaultCoreDNSPrefix, + } + + changes1 := &plan.Changes{ + Create: []*endpoint.Endpoint{ + endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "5.5.5.5").WithProviderSpecific(providerSpecificGroup, "test1"), + endpoint.NewEndpoint("domain2.local", endpoint.RecordTypeA, "5.5.5.6").WithProviderSpecific(providerSpecificGroup, "test1"), + endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "5.5.5.7").WithProviderSpecific(providerSpecificGroup, "test2"), + }, + } + coredns.ApplyChanges(context.Background(), changes1) + + expectedServices1 := map[string][]*Service{ + "/skydns/local/domain1": {{Host: "5.5.5.5", Group: "test1"}}, + "/skydns/local/domain2": {{Host: "5.5.5.6", Group: "test1"}}, + "/skydns/local/domain3": {{Host: "5.5.5.7", Group: "test2"}}, + } + validateServices(client.services, expectedServices1, t, 1) +} + +func TestRecordsAWithGroupServiceTranslation(t *testing.T) { + client := fakeETCDClient{ + map[string]Service{ + "/skydns/local/domain1": {Host: "5.5.5.5", Group: "test1"}, + }, + } + coredns := coreDNSProvider{ + client: client, + coreDNSPrefix: defaultCoreDNSPrefix, + } + endpoints, err := coredns.Records(context.Background()) + require.NoError(t, err) + if prop, ok := endpoints[0].GetProviderSpecificProperty(providerSpecificGroup); !ok { + t.Error("go no Group name") + } else if prop != "test1" { + t.Errorf("got unexpected Group name: %s != %s", prop, "test1") + } +} diff --git a/source/annotations/annotations.go b/source/annotations/annotations.go index 9e1f19c2b5..ba7695fdf9 100644 --- a/source/annotations/annotations.go +++ b/source/annotations/annotations.go @@ -29,6 +29,7 @@ const ( CloudflareRecordCommentKey = AnnotationKeyPrefix + "cloudflare-record-comment" AWSPrefix = AnnotationKeyPrefix + "aws-" + CoreDNSPrefix = AnnotationKeyPrefix + "coredns-" SCWPrefix = AnnotationKeyPrefix + "scw-" WebhookPrefix = AnnotationKeyPrefix + "webhook-" CloudflarePrefix = AnnotationKeyPrefix + "cloudflare-" diff --git a/source/annotations/provider_specific.go b/source/annotations/provider_specific.go index dbccfec87f..0518effe5c 100644 --- a/source/annotations/provider_specific.go +++ b/source/annotations/provider_specific.go @@ -49,6 +49,11 @@ func ProviderSpecificAnnotations(annotations map[string]string) (endpoint.Provid Name: fmt.Sprintf("webhook/%s", attr), Value: v, }) + } else if attr, ok := strings.CutPrefix(k, CoreDNSPrefix); ok { + providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ + Name: fmt.Sprintf("coredns/%s", attr), + Value: v, + }) } else if strings.HasPrefix(k, CloudflarePrefix) { if strings.Contains(k, CloudflareCustomHostnameKey) { providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ diff --git a/source/annotations/provider_specific_test.go b/source/annotations/provider_specific_test.go index c13d123cca..eb56e1d138 100644 --- a/source/annotations/provider_specific_test.go +++ b/source/annotations/provider_specific_test.go @@ -64,6 +64,16 @@ func TestProviderSpecificAnnotations(t *testing.T) { }, setIdentifier: "", }, + { + name: "CoreDNS annotation", + annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/coredns-group": "g1", + }, + expected: endpoint.ProviderSpecific{ + {Name: "coredns/group", Value: "g1"}, + }, + setIdentifier: "", + }, { name: "Set identifier annotation", annotations: map[string]string{