Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement target annotation for more sources #3944

Merged
merged 1 commit into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading