From 4a32aaec01441c193beb81ab63204464e7256a30 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Tue, 19 Sep 2023 14:50:14 -0700 Subject: [PATCH] Implement target annotation for more sources --- docs/annotations/annotations.md | 41 +++++----- source/ambassador_host.go | 11 ++- source/f5_virtualserver.go | 41 ++-------- source/f5_virtualserver_test.go | 38 ++++++++++ source/gloo_proxy.go | 11 ++- source/gloo_proxy_test.go | 129 +++++++++++++++++++++++++++++++- source/kong_tcpingress.go | 16 ++-- source/kong_tcpingress_test.go | 59 +++++++++++++++ source/node.go | 9 ++- source/node_test.go | 11 +++ source/pod.go | 28 +++++-- source/pod_test.go | 72 ++++++++++++++++++ 12 files changed, 388 insertions(+), 78 deletions(-) diff --git a/docs/annotations/annotations.md b/docs/annotations/annotations.md index 890b0bc323..6f54dd4db4 100644 --- a/docs/annotations/annotations.md +++ b/docs/annotations/annotations.md @@ -4,30 +4,31 @@ ExternalDNS sources support a number of annotations on the Kubernetes resources The following table documents which sources support which annotations: -| Source | controller | hostname | internal-hostname | target | ttl | (provider-specific) | -|--------------|------------|----------|-------------------|---------|-----|---------------------| -| Ambassador | | | | | Yes | | -| Connector | | | | | | | -| Contour | Yes | Yes[^1] | | Yes | Yes | Yes | -| CloudFoundry | | | | | | | -| CRD | | | | | | | -| F5 | | | | | Yes | | -| Gateway | Yes | Yes[^1] | | Yes[^4] | Yes | Yes | -| Gloo | | | | | Yes | Yes | -| Ingress | Yes | Yes[^1] | | Yes | Yes | Yes | -| Istio | Yes | Yes[^1] | | Yes | Yes | Yes | -| Kong | | Yes | | | Yes | Yes | -| Node | Yes | | | | Yes | | -| OpenShift | Yes | Yes[^1] | | Yes | Yes | Yes | -| Pod | | Yes | Yes | | | | -| Service | Yes | Yes[^1] | Yes[^1][^2] | Yes[^3] | Yes | Yes | -| Skipper | Yes | Yes[^1] | | Yes | Yes | Yes | -| Traefik | | Yes | | Yes | Yes | Yes | +| Source | controller | hostname | internal-hostname | target | ttl | (provider-specific) | +|--------------|------------|----------|-------------------|---------|---------|---------------------| +| Ambassador | | | | Yes | Yes | | +| Connector | | | | | | | +| Contour | Yes | Yes[^1] | | Yes | Yes | Yes | +| CloudFoundry | | | | | | | +| CRD | | | | | | | +| F5 | | | | Yes | Yes | | +| Gateway | Yes | Yes[^1] | | Yes[^4] | Yes | Yes | +| Gloo | | | | Yes | Yes[^5] | Yes[^5] | +| Ingress | Yes | Yes[^1] | | Yes | Yes | Yes | +| Istio | Yes | Yes[^1] | | Yes | Yes | Yes | +| Kong | | Yes | | Yes | Yes | Yes | +| Node | Yes | | | Yes | Yes | | +| OpenShift | Yes | Yes[^1] | | Yes | Yes | Yes | +| Pod | | Yes | Yes | Yes | | | +| Service | Yes | Yes[^1] | Yes[^1][^2] | Yes[^3] | Yes | Yes | +| Skipper | Yes | Yes[^1] | | Yes | Yes | Yes | +| Traefik | | Yes | | Yes | Yes | Yes | [^1]: Unless the `--ignore-hostname-annotation` flag is specified. [^2]: Only behaves differently than `hostname` for `Service`s of type `ClusterIP` or `LoadBalancer`. [^3]: Also supported on `Pods` referenced from a headless `Service`'s `Endpoints`. -[^4]: The annotation should be on the `Gateway` +[^4]: The annotation must be on the `Gateway`. +[^5]: The annotation must be on the listener's `VirtualService`. ## external-dns.alpha.kubernetes.io/access diff --git a/source/ambassador_host.go b/source/ambassador_host.go index c681be54db..32fc6effbe 100644 --- a/source/ambassador_host.go +++ b/source/ambassador_host.go @@ -133,10 +133,13 @@ func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endp continue } - targets, err := sc.targetsFromAmbassadorLoadBalancer(ctx, service) - if err != nil { - log.Warningf("Could not find targets for service %s for Host %s: %v", service, fullname, err) - continue + targets := getTargetsFromTargetAnnotation(host.Annotations) + if len(targets) == 0 { + targets, err = sc.targetsFromAmbassadorLoadBalancer(ctx, service) + if err != nil { + log.Warningf("Could not find targets for service %s for Host %s: %v", service, fullname, err) + continue + } } hostEndpoints, err := sc.endpointsFromHost(ctx, host, targets) diff --git a/source/f5_virtualserver.go b/source/f5_virtualserver.go index 016cf260d0..ed3ef853a0 100644 --- a/source/f5_virtualserver.go +++ b/source/f5_virtualserver.go @@ -152,37 +152,16 @@ func (vs *f5VirtualServerSource) endpointsFromVirtualServers(virtualServers []*f return nil, err } - if virtualServer.Spec.VirtualServerAddress != "" { - ep := &endpoint.Endpoint{ - Targets: endpoint.Targets{ - virtualServer.Spec.VirtualServerAddress, - }, - RecordType: "A", - DNSName: virtualServer.Spec.Host, - Labels: endpoint.NewLabels(), - RecordTTL: ttl, - } - - vs.setResourceLabel(virtualServer, ep) - endpoints = append(endpoints, ep) - continue + targets := getTargetsFromTargetAnnotation(virtualServer.Annotations) + if len(targets) == 0 && virtualServer.Spec.VirtualServerAddress != "" { + targets = append(targets, virtualServer.Spec.VirtualServerAddress) } - - if virtualServer.Status.VSAddress != "" { - ep := &endpoint.Endpoint{ - Targets: endpoint.Targets{ - virtualServer.Status.VSAddress, - }, - RecordType: "A", - DNSName: virtualServer.Spec.Host, - Labels: endpoint.NewLabels(), - RecordTTL: ttl, - } - - vs.setResourceLabel(virtualServer, ep) - endpoints = append(endpoints, ep) - continue + if len(targets) == 0 && virtualServer.Status.VSAddress != "" { + targets = append(targets, virtualServer.Status.VSAddress) } + + resource := fmt.Sprintf("f5-virtualserver/%s/%s", virtualServer.Namespace, virtualServer.Name) + endpoints = append(endpoints, endpointsForHostname(virtualServer.Spec.Host, targets, ttl, nil, "", resource)...) } return endpoints, nil @@ -234,7 +213,3 @@ func (vs *f5VirtualServerSource) filterByAnnotations(virtualServers []*f5.Virtua return filteredList, nil } - -func (vs *f5VirtualServerSource) setResourceLabel(virtualServer *f5.VirtualServer, ep *endpoint.Endpoint) { - ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("f5-virtualserver/%s/%s", virtualServer.Namespace, virtualServer.Name) -} diff --git a/source/f5_virtualserver_test.go b/source/f5_virtualserver_test.go index c08ba34574..bb5d9f0578 100644 --- a/source/f5_virtualserver_test.go +++ b/source/f5_virtualserver_test.go @@ -44,6 +44,41 @@ func TestF5VirtualServerEndpoints(t *testing.T) { virtualServer f5.VirtualServer expected []*endpoint.Endpoint }{ + { + name: "F5 VirtualServer with target annotation", + annotationFilter: "", + virtualServer: f5.VirtualServer{ + TypeMeta: metav1.TypeMeta{ + APIVersion: f5VirtualServerGVR.GroupVersion().String(), + Kind: "VirtualServer", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vs", + Namespace: defaultF5VirtualServerNamespace, + Annotations: map[string]string{ + targetAnnotationKey: "192.168.1.150", + }, + }, + Spec: f5.VirtualServerSpec{ + Host: "www.example.com", + VirtualServerAddress: "192.168.1.100", + }, + Status: f5.VirtualServerStatus{ + VSAddress: "192.168.1.200", + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "www.example.com", + Targets: []string{"192.168.1.150"}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 0, + Labels: endpoint.Labels{ + "resource": "f5-virtualserver/virtualserver/test-vs", + }, + }, + }, + }, { name: "F5 VirtualServer with host and virtualServerAddress set", annotationFilter: "", @@ -60,6 +95,9 @@ func TestF5VirtualServerEndpoints(t *testing.T) { Host: "www.example.com", VirtualServerAddress: "192.168.1.100", }, + Status: f5.VirtualServerStatus{ + VSAddress: "192.168.1.200", + }, }, expected: []*endpoint.Endpoint{ { diff --git a/source/gloo_proxy.go b/source/gloo_proxy.go index cbacecc20e..19bbf62cff 100644 --- a/source/gloo_proxy.go +++ b/source/gloo_proxy.go @@ -132,11 +132,16 @@ func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro return nil, err } log.Debugf("Gloo: Find %s proxy", proxy.Metadata.Name) - proxyTargets, err := gs.proxyTargets(ctx, proxy.Metadata.Name, ns) - if err != nil { - return nil, err + + proxyTargets := getTargetsFromTargetAnnotation(proxy.Metadata.Annotations) + if len(proxyTargets) == 0 { + proxyTargets, err = gs.proxyTargets(ctx, proxy.Metadata.Name, ns) + if err != nil { + return nil, err + } } log.Debugf("Gloo[%s]: Find %d target(s) (%+v)", proxy.Metadata.Name, len(proxyTargets), proxyTargets) + proxyEndpoints, err := gs.generateEndpointsFromProxy(ctx, &proxy, proxyTargets) if err != nil { return nil, err diff --git a/source/gloo_proxy_test.go b/source/gloo_proxy_test.go index 1b72861cc7..03eb12b364 100644 --- a/source/gloo_proxy_test.go +++ b/source/gloo_proxy_test.go @@ -302,6 +302,96 @@ var proxyMetadataStaticSource = metav1.PartialObjectMetadata{ }, } +// Proxy with target annotation test +var targetAnnotatedProxy = proxy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: proxyGVR.GroupVersion().String(), + Kind: "Proxy", + }, + Metadata: metav1.ObjectMeta{ + Name: "target-ann", + Namespace: defaultGlooNamespace, + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/target": "203.2.45.7", + }, + }, + Spec: proxySpec{ + Listeners: []proxySpecListener{ + { + HTTPListener: proxySpecHTTPListener{ + VirtualHosts: []proxyVirtualHost{ + { + Domains: []string{"i.test"}, + Metadata: proxyVirtualHostMetadata{ + Source: []proxyVirtualHostMetadataSource{ + { + Kind: "*v1.Unknown", + Name: "my-unknown-svc", + Namespace: "unknown", + }, + }, + }, + }, + { + Domains: []string{"j.test"}, + Metadata: proxyVirtualHostMetadata{ + Source: []proxyVirtualHostMetadataSource{ + { + Kind: "*v1.VirtualService", + Name: "my-annotated-svc", + Namespace: "internal", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var targetAnnotatedProxySvc = corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetAnnotatedProxy.Metadata.Name, + Namespace: targetAnnotatedProxy.Metadata.Namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + IP: "203.1.115.1", + }, + { + IP: "203.1.115.2", + }, + { + IP: "203.1.115.3", + }, + }, + }, + }, +} + +var targetAnnotatedProxySource = metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: virtualServiceGVR.GroupVersion().String(), + Kind: "VirtualService", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: targetAnnotatedProxy.Spec.Listeners[0].HTTPListener.VirtualHosts[1].Metadata.Source[0].Name, + Namespace: targetAnnotatedProxy.Spec.Listeners[0].HTTPListener.VirtualHosts[1].Metadata.Source[0].Namespace, + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/ttl": "460", + "external-dns.alpha.kubernetes.io/aws-geolocation-country-code": "IT", + "external-dns.alpha.kubernetes.io/set-identifier": "identifier-annotated", + }, + }, +} + func TestGlooSource(t *testing.T) { t.Parallel() @@ -318,10 +408,12 @@ func TestGlooSource(t *testing.T) { internalProxyUnstructured := unstructured.Unstructured{} externalProxyUnstructured := unstructured.Unstructured{} proxyMetadataStaticUnstructured := unstructured.Unstructured{} + targetAnnotatedProxyUnstructured := unstructured.Unstructured{} internalProxySourceUnstructured := unstructured.Unstructured{} externalProxySourceUnstructured := unstructured.Unstructured{} proxyMetadataStaticSourceUnstructured := unstructured.Unstructured{} + targetAnnotatedProxySourceUnstructured := unstructured.Unstructured{} internalProxyAsJSON, err := json.Marshal(internalProxy) assert.NoError(t, err) @@ -332,6 +424,9 @@ func TestGlooSource(t *testing.T) { proxyMetadataStaticAsJSON, err := json.Marshal(proxyMetadataStatic) assert.NoError(t, err) + targetAnnotatedProxyAsJSON, err := json.Marshal(targetAnnotatedProxy) + assert.NoError(t, err) + internalProxySvcAsJSON, err := json.Marshal(internalProxySource) assert.NoError(t, err) @@ -341,13 +436,18 @@ func TestGlooSource(t *testing.T) { proxyMetadataStaticSvcAsJSON, err := json.Marshal(proxyMetadataStaticSource) assert.NoError(t, err) + targetAnnotatedProxySvcAsJSON, err := json.Marshal(targetAnnotatedProxySource) + assert.NoError(t, err) + assert.NoError(t, internalProxyUnstructured.UnmarshalJSON(internalProxyAsJSON)) assert.NoError(t, externalProxyUnstructured.UnmarshalJSON(externalProxyAsJSON)) assert.NoError(t, proxyMetadataStaticUnstructured.UnmarshalJSON(proxyMetadataStaticAsJSON)) + assert.NoError(t, targetAnnotatedProxyUnstructured.UnmarshalJSON(targetAnnotatedProxyAsJSON)) assert.NoError(t, internalProxySourceUnstructured.UnmarshalJSON(internalProxySvcAsJSON)) assert.NoError(t, externalProxySourceUnstructured.UnmarshalJSON(externalProxySvcAsJSON)) assert.NoError(t, proxyMetadataStaticSourceUnstructured.UnmarshalJSON(proxyMetadataStaticSvcAsJSON)) + assert.NoError(t, targetAnnotatedProxySourceUnstructured.UnmarshalJSON(targetAnnotatedProxySvcAsJSON)) // Create proxy resources _, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &internalProxyUnstructured, metav1.CreateOptions{}) @@ -356,6 +456,8 @@ func TestGlooSource(t *testing.T) { assert.NoError(t, err) _, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &proxyMetadataStaticUnstructured, metav1.CreateOptions{}) assert.NoError(t, err) + _, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &targetAnnotatedProxyUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) // Create proxy source _, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(internalProxySource.Namespace).Create(context.Background(), &internalProxySourceUnstructured, metav1.CreateOptions{}) @@ -364,6 +466,8 @@ func TestGlooSource(t *testing.T) { assert.NoError(t, err) _, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(proxyMetadataStaticSource.Namespace).Create(context.Background(), &proxyMetadataStaticSourceUnstructured, metav1.CreateOptions{}) assert.NoError(t, err) + _, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(targetAnnotatedProxySource.Namespace).Create(context.Background(), &targetAnnotatedProxySourceUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) // Create proxy service resources _, err = fakeKubernetesClient.CoreV1().Services(internalProxySvc.GetNamespace()).Create(context.Background(), &internalProxySvc, metav1.CreateOptions{}) @@ -372,10 +476,12 @@ func TestGlooSource(t *testing.T) { assert.NoError(t, err) _, err = fakeKubernetesClient.CoreV1().Services(proxyMetadataStaticSvc.GetNamespace()).Create(context.Background(), &proxyMetadataStaticSvc, metav1.CreateOptions{}) assert.NoError(t, err) + _, err = fakeKubernetesClient.CoreV1().Services(targetAnnotatedProxySvc.GetNamespace()).Create(context.Background(), &targetAnnotatedProxySvc, metav1.CreateOptions{}) + assert.NoError(t, err) endpoints, err := source.Endpoints(context.Background()) assert.NoError(t, err) - assert.Len(t, endpoints, 8) + assert.Len(t, endpoints, 10) assert.ElementsMatch(t, endpoints, []*endpoint.Endpoint{ { DNSName: "a.test", @@ -459,5 +565,26 @@ func TestGlooSource(t *testing.T) { }, }, }, + { + DNSName: "i.test", + Targets: []string{"203.2.45.7"}, + RecordType: endpoint.RecordTypeA, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + { + DNSName: "j.test", + Targets: []string{"203.2.45.7"}, + RecordType: endpoint.RecordTypeA, + SetIdentifier: "identifier-annotated", + RecordTTL: 460, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "aws/geolocation-country-code", + Value: "IT", + }, + }, + }, }) } diff --git a/source/kong_tcpingress.go b/source/kong_tcpingress.go index 7cd7c1be6f..f1a264760c 100644 --- a/source/kong_tcpingress.go +++ b/source/kong_tcpingress.go @@ -124,13 +124,15 @@ func (sc *kongTCPIngressSource) Endpoints(ctx context.Context) ([]*endpoint.Endp var endpoints []*endpoint.Endpoint for _, tcpIngress := range tcpIngresses { - var targets endpoint.Targets - for _, lb := range tcpIngress.Status.LoadBalancer.Ingress { - if lb.IP != "" { - targets = append(targets, lb.IP) - } - if lb.Hostname != "" { - targets = append(targets, lb.Hostname) + targets := getTargetsFromTargetAnnotation(tcpIngress.Annotations) + if len(targets) == 0 { + for _, lb := range tcpIngress.Status.LoadBalancer.Ingress { + if lb.IP != "" { + targets = append(targets, lb.IP) + } + if lb.Hostname != "" { + targets = append(targets, lb.Hostname) + } } } diff --git a/source/kong_tcpingress_test.go b/source/kong_tcpingress_test.go index 9686da678d..bb3db2c657 100644 --- a/source/kong_tcpingress_test.go +++ b/source/kong_tcpingress_test.go @@ -220,6 +220,65 @@ func TestKongTCPIngressEndpoints(t *testing.T) { }, }, }, + { + title: "TCPIngress with target annotation", + tcpProxy: TCPIngress{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kongGroupdVersionResource.GroupVersion().String(), + Kind: "TCPIngress", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp-ingress-sni", + Namespace: defaultKongNamespace, + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "kong", + "external-dns.alpha.kubernetes.io/target": "203.2.45.7", + }, + }, + Spec: tcpIngressSpec{ + Rules: []tcpIngressRule{ + { + Port: 30002, + Host: "b.example.com", + }, + { + Port: 30003, + Host: "c.example.com", + }, + }, + }, + Status: tcpIngressStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + Hostname: "a123456769a314e71861a4303f06a3bd-1291189659.us-east-1.elb.amazonaws.com", + }, + }, + }, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "b.example.com", + Targets: []string{"203.2.45.7"}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 0, + Labels: endpoint.Labels{ + "resource": "tcpingress/kong/tcp-ingress-sni", + }, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + { + DNSName: "c.example.com", + Targets: []string{"203.2.45.7"}, + RecordType: endpoint.RecordTypeA, + Labels: endpoint.Labels{ + "resource": "tcpingress/kong/tcp-ingress-sni", + }, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + }, + }, } { ti := ti t.Run(ti.title, func(t *testing.T) { diff --git a/source/node.go b/source/node.go index 2610020570..1fcfce6c00 100644 --- a/source/node.go +++ b/source/node.go @@ -130,9 +130,12 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro log.Debugf("not applying template for %s", node.Name) } - addrs, err := ns.nodeAddresses(node) - if err != nil { - return nil, fmt.Errorf("failed to get node address from %s: %w", node.Name, err) + addrs := getTargetsFromTargetAnnotation(node.Annotations) + if len(addrs) == 0 { + addrs, err = ns.nodeAddresses(node) + if err != nil { + return nil, fmt.Errorf("failed to get node address from %s: %w", node.Name, err) + } } ep.Labels = endpoint.NewLabels() diff --git a/source/node_test.go b/source/node_test.go index 217e4a38ac..9ce4591dfb 100644 --- a/source/node_test.go +++ b/source/node_test.go @@ -205,6 +205,17 @@ func testNodeSourceEndpoints(t *testing.T) { nodeAddresses: []v1.NodeAddress{}, expectError: true, }, + { + title: "node with target annotation", + nodeName: "node1.example.org", + nodeAddresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}}, + annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/target": "203.2.45.7", + }, + expected: []*endpoint.Endpoint{ + {RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"203.2.45.7"}}, + }, + }, { title: "annotated node without annotation filter returns endpoint", nodeName: "node1", diff --git a/source/pod.go b/source/pod.go index 2441d28908..4a5ea4c3bf 100644 --- a/source/pod.go +++ b/source/pod.go @@ -89,22 +89,36 @@ func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error continue } + targets := getTargetsFromTargetAnnotation(pod.Annotations) + if domainAnnotation, ok := pod.Annotations[internalHostnameAnnotationKey]; ok { domainList := splitHostnameAnnotation(domainAnnotation) for _, domain := range domainList { - addToEndpointMap(endpointMap, domain, suitableType(pod.Status.PodIP), pod.Status.PodIP) + if len(targets) == 0 { + addToEndpointMap(endpointMap, domain, suitableType(pod.Status.PodIP), pod.Status.PodIP) + } else { + for _, target := range targets { + addToEndpointMap(endpointMap, domain, suitableType(target), target) + } + } } } if domainAnnotation, ok := pod.Annotations[hostnameAnnotationKey]; ok { domainList := splitHostnameAnnotation(domainAnnotation) for _, domain := range domainList { - node, _ := ps.nodeInformer.Lister().Get(pod.Spec.NodeName) - for _, address := range node.Status.Addresses { - recordType := suitableType(address.Address) - // IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well. - if address.Type == corev1.NodeExternalIP || (address.Type == corev1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA) { - addToEndpointMap(endpointMap, domain, recordType, address.Address) + if len(targets) == 0 { + node, _ := ps.nodeInformer.Lister().Get(pod.Spec.NodeName) + for _, address := range node.Status.Addresses { + recordType := suitableType(address.Address) + // IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well. + if address.Type == corev1.NodeExternalIP || (address.Type == corev1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA) { + addToEndpointMap(endpointMap, domain, recordType, address.Address) + } + } + } else { + for _, target := range targets { + addToEndpointMap(endpointMap, domain, suitableType(target), target) } } } diff --git a/source/pod_test.go b/source/pod_test.go index 549a9ebf90..24ce65d018 100644 --- a/source/pod_test.go +++ b/source/pod_test.go @@ -316,6 +316,78 @@ func TestPodSource(t *testing.T) { }, }, }, + { + "create records based on pod's target annotation", + "", + "", + []*endpoint.Endpoint{ + {DNSName: "a.foo.example.org", Targets: endpoint.Targets{"208.1.2.1", "208.1.2.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "internal.a.foo.example.org", Targets: endpoint.Targets{"208.1.2.1", "208.1.2.2"}, RecordType: endpoint.RecordTypeA}, + }, + false, + []*corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node1", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeExternalIP, Address: "54.10.11.1"}, + {Type: corev1.NodeInternalIP, Address: "10.0.1.1"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node2", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeExternalIP, Address: "54.10.11.2"}, + {Type: corev1.NodeInternalIP, Address: "10.0.1.2"}, + }, + }, + }, + }, + []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod1", + Namespace: "kube-system", + Annotations: map[string]string{ + internalHostnameAnnotationKey: "internal.a.foo.example.org", + hostnameAnnotationKey: "a.foo.example.org", + targetAnnotationKey: "208.1.2.1", + }, + }, + Spec: corev1.PodSpec{ + HostNetwork: true, + NodeName: "my-node1", + }, + Status: corev1.PodStatus{ + PodIP: "10.0.1.1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod2", + Namespace: "kube-system", + Annotations: map[string]string{ + internalHostnameAnnotationKey: "internal.a.foo.example.org", + hostnameAnnotationKey: "a.foo.example.org", + targetAnnotationKey: "208.1.2.2", + }, + }, + Spec: corev1.PodSpec{ + HostNetwork: true, + NodeName: "my-node2", + }, + Status: corev1.PodStatus{ + PodIP: "10.0.1.2", + }, + }, + }, + }, { "create multiple records", "",