From 0c82db10b7a746b99843e5281f5b46ad71286df9 Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Wed, 23 Apr 2025 16:48:21 +0200 Subject: [PATCH 1/4] feat(gateway-api): add ability to override target on *Route resources Signed-off-by: David van der Spek --- docs/sources/gateway.md | 8 +- source/gateway.go | 10 ++- source/gateway_httproute_test.go | 138 +++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 4 deletions(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index 059ccedfa..4a78716b2 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -106,10 +106,12 @@ Iterates over all listeners for the parent's `parentRef.sectionName`: The targets of the DNS entries created from a \*Route are sourced from the following places: -1. If a matching parent Gateway has an `external-dns.alpha.kubernetes.io/target` annotation, uses - the values from that. +1. If a matching parent Gateway has the `external-dns.alpha.kubernetes.io/target` annotation, uses + the values from that unless the route has the `external-dns.alpha.kubernetes.io/target: ""` annotation in which case it iterates over that parent Gateway's `status.addresses` adding each address's `value`. -2. Otherwise, iterates over that parent Gateway's `status.addresses`, +2. If the route has the route has the `external-dns.alpha.kubernetes.io/target` annotation with a non-empty value, uses the value from that. + +3. Otherwise, iterates over that parent Gateway's `status.addresses`, adding each address's `value`. The targets from each parent Gateway matching the \*Route are then combined and de-duplicated. diff --git a/source/gateway.go b/source/gateway.go index 1bba6c58e..98aef5f31 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -376,7 +376,15 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar if !ok { continue } - override := annotations.TargetsFromTargetAnnotation(gw.gateway.Annotations) + var override endpoint.Targets + targetAnnotation, exists := meta.Annotations[targetAnnotationKey] + if exists && targetAnnotation != "" { + override = annotations.TargetsFromTargetAnnotation(meta.Annotations) + hostTargets[host] = append(hostTargets[host], override...) + } else if !exists { + override = annotations.TargetsFromTargetAnnotation(gw.gateway.Annotations) + hostTargets[host] = append(hostTargets[host], override...) + } hostTargets[host] = append(hostTargets[host], override...) if len(override) == 0 { for _, addr := range gw.gateway.Status.Addresses { diff --git a/source/gateway_httproute_test.go b/source/gateway_httproute_test.go index a30bf0f3c..9033a1be4 100644 --- a/source/gateway_httproute_test.go +++ b/source/gateway_httproute_test.go @@ -1401,6 +1401,144 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { newTestEndpoint("test.example.internal", "A", "4.3.2.1"), }, }, + { + title: "RouteAnnotationOverride", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{ + { + ObjectMeta: objectMeta("gateway-namespace", "test"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("1.2.3.4"), + }, + }, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "4.3.2.1", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("test.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "test"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "test"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("test.example.internal", "A", "4.3.2.1"), + }, + }, + { + title: "RouteAnnotationGatewayOverride", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "overriden-gateway", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "4.3.2.1", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("1.2.3.4"), + }, + }, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "2.3.4.5", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("test.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "overriden-gateway"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "overriden-gateway"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("test.example.internal", "A", "2.3.4.5"), + }, + }, + { + title: "RouteAnnotationGatewayOverrideEmpty", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "overriden-gateway", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "4.3.2.1", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("1.2.3.4"), + }, + }, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("test.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "overriden-gateway"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "overriden-gateway"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("test.example.internal", "A", "1.2.3.4"), + }, + }, { title: "MutlipleGatewaysOneAnnotationOverride", config: Config{ From e17248cd0535b7d9e54938da000ef4c42b848c94 Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Wed, 23 Apr 2025 17:22:55 +0200 Subject: [PATCH 2/4] docs: clarify the workflow for the target annotation Signed-off-by: David van der Spek --- docs/sources/gateway.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index 4a78716b2..c21cc3c57 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -106,12 +106,17 @@ Iterates over all listeners for the parent's `parentRef.sectionName`: The targets of the DNS entries created from a \*Route are sourced from the following places: -1. If a matching parent Gateway has the `external-dns.alpha.kubernetes.io/target` annotation, uses - the values from that unless the route has the `external-dns.alpha.kubernetes.io/target: ""` annotation in which case it iterates over that parent Gateway's `status.addresses` adding each address's `value`. +1. If the route has the route has the `external-dns.alpha.kubernetes.io/target` annotation + with a non-empty value, uses the value from that. -2. If the route has the route has the `external-dns.alpha.kubernetes.io/target` annotation with a non-empty value, uses the value from that. +2. If the route has the route has the `external-dns.alpha.kubernetes.io/target: ""` it + will disable the `external-dns.alpha.kubernetes.io/target` on the matching parent + Gateway(s) and continue the regular flow from step 4. -3. Otherwise, iterates over that parent Gateway's `status.addresses`, +3. If a matching parent Gateway has the `external-dns.alpha.kubernetes.io/target` annotation, uses + the values from that. + +4. Otherwise, iterates over that parent Gateway's `status.addresses`, adding each address's `value`. The targets from each parent Gateway matching the \*Route are then combined and de-duplicated. From 9b9c563ff264455240b2dc430583fe48eb044eec Mon Sep 17 00:00:00 2001 From: David van der Spek <28541758+davidspek@users.noreply.github.com> Date: Wed, 23 Apr 2025 18:03:22 +0200 Subject: [PATCH 3/4] docs: correct regactor error Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> --- docs/sources/gateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index c21cc3c57..4a34046b8 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -106,7 +106,7 @@ Iterates over all listeners for the parent's `parentRef.sectionName`: The targets of the DNS entries created from a \*Route are sourced from the following places: -1. If the route has the route has the `external-dns.alpha.kubernetes.io/target` annotation +1. If the route has the `external-dns.alpha.kubernetes.io/target` annotation with a non-empty value, uses the value from that. 2. If the route has the route has the `external-dns.alpha.kubernetes.io/target: ""` it From aacc8dfa2391093da2fc036732679414fc829287 Mon Sep 17 00:00:00 2001 From: David van der Spek <28541758+davidspek@users.noreply.github.com> Date: Wed, 23 Apr 2025 18:03:40 +0200 Subject: [PATCH 4/4] docs: correct regactor error Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> --- docs/sources/gateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index 4a34046b8..d62c42469 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -109,7 +109,7 @@ The targets of the DNS entries created from a \*Route are sourced from the follo 1. If the route has the `external-dns.alpha.kubernetes.io/target` annotation with a non-empty value, uses the value from that. -2. If the route has the route has the `external-dns.alpha.kubernetes.io/target: ""` it +2. If the route has the `external-dns.alpha.kubernetes.io/target: ""` it will disable the `external-dns.alpha.kubernetes.io/target` on the matching parent Gateway(s) and continue the regular flow from step 4.