Skip to content

Commit

Permalink
Implement target annotation for more sources
Browse files Browse the repository at this point in the history
  • Loading branch information
johngmyers committed Sep 28, 2023
1 parent 859892f commit 4a32aae
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 78 deletions.
41 changes: 21 additions & 20 deletions docs/annotations/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 7 additions & 4 deletions source/ambassador_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
41 changes: 8 additions & 33 deletions source/f5_virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
38 changes: 38 additions & 0 deletions source/f5_virtualserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: "",
Expand All @@ -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{
{
Expand Down
11 changes: 8 additions & 3 deletions source/gloo_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
129 changes: 128 additions & 1 deletion source/gloo_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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{})
Expand All @@ -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{})
Expand All @@ -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{})
Expand All @@ -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",
Expand Down Expand Up @@ -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",
},
},
},
})
}
Loading

0 comments on commit 4a32aae

Please sign in to comment.