From e87b34c618d8358d86da80817135546ceb6142f9 Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Wed, 23 Apr 2025 16:48:21 +0200 Subject: [PATCH 1/6] 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 059ccedfae..4a78716b23 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 445352838b..1ba08a7d26 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -377,7 +377,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 2edbf89654..b569cd6b2a 100644 --- a/source/gateway_httproute_test.go +++ b/source/gateway_httproute_test.go @@ -1362,6 +1362,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 139dc249ca9b96739f04157fc78cfd2492804e2a Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Wed, 23 Apr 2025 17:22:55 +0200 Subject: [PATCH 2/6] 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 4a78716b23..c21cc3c576 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 a0517c85cdd4efba02fbdd0f9f8c5e71dad00b0a 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/6] 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 c21cc3c576..4a34046b84 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 097542b63658f0aafd7a606925e2d69c6c5dad6c 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/6] 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 4a34046b84..d62c424694 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. From 5be8267e09f0ee5b5581fb85712947d898963dbf Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Fri, 5 Sep 2025 12:25:05 +0200 Subject: [PATCH 5/6] fix: add more configurable target strategy Signed-off-by: David van der Spek --- docs/sources/gateway.md | 74 ++- source/annotations/annotations.go | 7 + source/gateway.go | 43 +- source/gateway_httproute_test.go | 831 +++++++++++++++++++++++++----- source/source.go | 1 + 5 files changed, 800 insertions(+), 156 deletions(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index d62c424694..2e64d9f1ae 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -104,22 +104,74 @@ Iterates over all listeners for the parent's `parentRef.sectionName`: ## Targets -The targets of the DNS entries created from a \*Route are sourced from the following places: +Targets are derived from a combination of Route annotations, Gateway annotations, and +the Gateway's `status.addresses`. How these sources are combined is controlled (per Route) +by the optional strategy annotation: -1. If the route has the `external-dns.alpha.kubernetes.io/target` annotation - with a non-empty value, uses the value from that. +```yaml +external-dns.alpha.kubernetes.io/target-strategy: +``` + +Supported strategies (current behavior for Gateway *Route sources): + +| Strategy | Behavior | Fallback Order | Publishes Multiple Target Sets? | +|------------------|----------|----------------|----------------------------------| +| `route-preferred` (default when omitted or unrecognized) | Use Route targets if present; otherwise Gateway targets if present; otherwise addresses | Route → Gateway → Addresses | No (at most one set) | +| `route-only` | Use Route targets only if present; otherwise fall back directly to addresses (Gateway targets are ignored even if set) | Route → Addresses | No | +| `gateway-only` | Use Gateway targets only if present; otherwise fall back to addresses (Route targets are ignored) | Gateway → Addresses | No | +| `merge` | Combine Route and Gateway targets (deduped). If neither annotation supplies targets, fall back to addresses | (Route ∪ Gateway) → Addresses | Yes | + +Where the individual sources come from: + +1. Route targets: values from a non-empty `external-dns.alpha.kubernetes.io/target` annotation on the Route. +2. Gateway targets: values from a non-empty `external-dns.alpha.kubernetes.io/target` annotation on the matching parent Gateway. +3. Addresses: each `value` in the parent Gateway's `status.addresses` field. + +Notes & nuances: + +- The empty string (`external-dns.alpha.kubernetes.io/target: ""`) on a Route or Gateway does not disable the other annotation; it simply contributes no targets. +- Under `route-only`, any Gateway target annotation is intentionally ignored even if populated. +- Under `gateway-only`, any Route target annotation is intentionally ignored. +- Under `merge`, duplicate targets between Route and Gateway are removed before creating DNS records. +- If (after applying the strategy rules) no annotation targets are selected, Gateway `status.addresses` are always used as a safety fallback to avoid producing zero endpoints for an otherwise valid attachment. + +Example usages on a *Route: + +```yaml +metadata: + annotations: + external-dns.alpha.kubernetes.io/target-strategy: route-only +``` -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. +Publishes the hostname(s) from the route using the Gateway addresses, ignoring target annotation on the Gateway. -3. If a matching parent Gateway has the `external-dns.alpha.kubernetes.io/target` annotation, uses - the values from that. +```yaml +metadata: + annotations: + external-dns.alpha.kubernetes.io/target: canary.lb.example.net + external-dns.alpha.kubernetes.io/target-strategy: route-preferred +``` + +Publishes only `canary.lb.example.net` (even if the Gateway also has a target annotation). + +```yaml +metadata: + annotations: + external-dns.alpha.kubernetes.io/target-strategy: merge +``` + +Route has no target annotation; if the Gateway has `external-dns.alpha.kubernetes.io/target: edge.lb.example.net` the record will include that value; otherwise the Gateway addresses are used. + +```yaml +metadata: + annotations: + external-dns.alpha.kubernetes.io/target: direct.lb.example.net + external-dns.alpha.kubernetes.io/target-strategy: merge +``` -4. Otherwise, iterates over that parent Gateway's `status.addresses`, - adding each address's `value`. +If the Gateway also has `external-dns.alpha.kubernetes.io/target: edge.lb.example.net` both targets are published (deduped if identical). -The targets from each parent Gateway matching the \*Route are then combined and de-duplicated. +The combined targets from each matching parent Gateway are gathered per hostname and de-duplicated before generating DNS records. ## Dualstack Routes diff --git a/source/annotations/annotations.go b/source/annotations/annotations.go index 9e1f19c2b5..bccd4bb5b6 100644 --- a/source/annotations/annotations.go +++ b/source/annotations/annotations.go @@ -57,4 +57,11 @@ const ( ControllerValue = "dns-controller" // InternalHostnameKey The annotation used for defining the desired hostname InternalHostnameKey = AnnotationKeyPrefix + "internal-hostname" + // TargetStrategyKey controls how targets are selected for sources that support multiple target origins (e.g. Gateway Route sources). + // Supported values (for Gateway Route sources): + // route-preferred -> prefer Route target annotation if non-empty; otherwise fall back to Gateway target annotation (if any) or gateway addresses + // route-only -> only use the Route target annotation if non-empty; otherwise fall back to gateway addresses + // gateway-only -> only use the Gateway target annotation (if any) otherwise fall back to gateway addresses + // merge -> merge Route and Gateway target annotations (deduped); if neither present use gateway addresses + TargetStrategyKey = AnnotationKeyPrefix + "target-strategy" ) diff --git a/source/gateway.go b/source/gateway.go index 1ba08a7d26..5b58e4fb73 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -377,14 +377,43 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar if !ok { continue } + // Determine target selection strategy. + strategy := meta.Annotations[targetStrategyAnnotationKey] + // Normalize unexpected values to "" which defaults to current semantics but here + // we intentionally treat empty (or unknown) as "merge" for explicit behavior. + switch strategy { + case "route-preferred", "route-only", "gateway-only", "merge": + // valid, keep as is + default: + strategy = "route-preferred" + } 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...) + + routeTargets := annotations.TargetsFromTargetAnnotation(meta.Annotations) + gatewayTargets := annotations.TargetsFromTargetAnnotation(gw.gateway.Annotations) + + switch strategy { + case "route-preferred": + if len(routeTargets) > 0 { + override = append(override, routeTargets...) + } else if len(gatewayTargets) > 0 { + override = append(override, gatewayTargets...) + } + case "route-only": + if len(routeTargets) > 0 { + override = append(override, routeTargets...) + } + case "gateway-only": + if len(gatewayTargets) > 0 { + override = append(override, gatewayTargets...) + } + case "merge": + if len(routeTargets) > 0 { + override = append(override, routeTargets...) + } + if len(gatewayTargets) > 0 { + override = append(override, gatewayTargets...) + } } hostTargets[host] = append(hostTargets[host], override...) if len(override) == 0 { diff --git a/source/gateway_httproute_test.go b/source/gateway_httproute_test.go index b569cd6b2a..9b5cf9a766 100644 --- a/source/gateway_httproute_test.go +++ b/source/gateway_httproute_test.go @@ -1362,144 +1362,6 @@ 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{ @@ -1678,6 +1540,699 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { "Parent reference gateway-namespace/other-gateway not found in routeParentRefs for HTTPRoute route-namespace/test", }, }, + { + title: "TargetStrategyMergeBothAnnotations", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-merge", + Namespace: "gateway-namespace", + Annotations: map[string]string{targetAnnotationKey: "5.5.5.5"}, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("1.1.1.1"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-merge", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "4.4.4.4", + targetStrategyAnnotationKey: "merge", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("merge.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-merge"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-merge"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("merge.example.internal", "A", "4.4.4.4", "5.5.5.5"), + }, + }, + { + title: "TargetStrategyMergeOnlyRouteAnnotation", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-merge-route-only", + Namespace: "gateway-namespace", + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("50.50.50.50"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-merge-route-only", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "44.44.44.44", + targetStrategyAnnotationKey: "merge", + }}, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("mergerouteonly.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-merge-route-only"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-merge-route-only"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("mergerouteonly.example.internal", "A", "44.44.44.44"), + }, + }, + { + title: "TargetStrategyMergeOnlyGatewayAnnotation", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-merge-gw-only", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "55.55.55.55", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("50.50.50.50"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-merge-gw-only", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetStrategyAnnotationKey: "merge", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("mergegwonly.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-merge-gw-only"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-merge-gw-only"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("mergegwonly.example.internal", "A", "55.55.55.55"), + }, + }, + { + title: "TargetStrategyMergeNoAnnotationsFallsBackToAddresses", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("gateway-namespace", "gw-merge-none"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("50.50.50.50"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-merge-none", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetStrategyAnnotationKey: "merge", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("mergenone.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-merge-none"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-merge-none"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("mergenone.example.internal", "A", "50.50.50.50"), + }, + }, + { + title: "TargetStrategyRouteOnlyHasRouteTargets", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("gateway-namespace", "gw-route-only"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("9.9.9.9"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-only", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "7.7.7.7", + targetStrategyAnnotationKey: "route-only", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeonly.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-only"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-only"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeonly.example.internal", "A", "7.7.7.7"), + }, + }, + { + title: "TargetStrategyRouteOnlyNoRouteTargetsFallsBackToAddresses", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("gateway-namespace", "gw-route-fallback"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("9.9.9.9"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-fallback", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "", // empty should not contribute targets + targetStrategyAnnotationKey: "route-only", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeonlyfallback.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-fallback"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-fallback"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeonlyfallback.example.internal", "A", "9.9.9.9"), + }, + }, + { + title: "TargetStrategyGatewayOnlyGatewayTargets", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-gateway-only", + Namespace: "gateway-namespace", + Annotations: map[string]string{targetAnnotationKey: "5.5.5.5"}, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("1.1.1.1"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-gateway-only", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "7.7.7.7", // ignored + targetStrategyAnnotationKey: "gateway-only", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("gatewayonly.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-gateway-only"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-gateway-only"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("gatewayonly.example.internal", "A", "5.5.5.5"), + }, + }, + { + title: "TargetStrategyGatewayOnlyNoGatewayTargetsFallsBackToAddresses", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("gateway-namespace", "gw-gateway-fallback"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("1.1.1.1"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-gateway-fallback", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetStrategyAnnotationKey: "gateway-only", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("gatewayonlyfallback.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-gateway-fallback"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-gateway-fallback"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("gatewayonlyfallback.example.internal", "A", "1.1.1.1"), + }, + }, + { + title: "TargetStrategyDefaultRoutePreferredBothAnnotations", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-default-pref-both", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "22.22.22.22", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-default-pref-both", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "11.11.11.11", + }, // strategy annotation intentionally omitted + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("defaultprefboth.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-default-pref-both"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-default-pref-both"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("defaultprefboth.example.internal", "A", "11.11.11.11"), + }, + }, + { + title: "TargetStrategyDefaultRoutePreferredNoRouteTargetsUsesGatewayAnnotation", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-default-pref-gwonly", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "22.22.22.22", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-default-pref-gwonly", + Namespace: "route-namespace", + }, // no annotations + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("defaultprefgwonly.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-default-pref-gwonly"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-default-pref-gwonly"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("defaultprefgwonly.example.internal", "A", "22.22.22.22"), + }, + }, + { + title: "TargetStrategyDefaultRoutePreferredNoTargetsFallsBackToAddresses", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("gateway-namespace", "gw-default-pref-none"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-default-pref-none", + Namespace: "route-namespace", + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("defaultprefnone.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-default-pref-none"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-default-pref-none"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("defaultprefnone.example.internal", "A", "30.30.30.30"), + }, + }, + { + title: "TargetStrategyRoutePreferredBothAnnotations", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-route-pref-both", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "20.20.20.20", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-pref-both", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "10.10.10.10", + targetStrategyAnnotationKey: "route-preferred", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeprefboth.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-pref-both"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-pref-both"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeprefboth.example.internal", "A", "10.10.10.10"), + }, + }, + { + title: "TargetStrategyRoutePreferredOnlyGatewayAnnotation", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-route-pref-gwonly", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "20.20.20.20", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-pref-gwonly", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetStrategyAnnotationKey: "route-preferred", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeprefgwonly.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-pref-gwonly"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-pref-gwonly"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeprefgwonly.example.internal", "A", "20.20.20.20"), + }, + }, + { + title: "TargetStrategyRoutePreferredNoAnnotationsFallsBackToAddresses", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("gateway-namespace", "gw-route-pref-none"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-pref-none", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetStrategyAnnotationKey: "route-preferred", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeprefnone.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-pref-none"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-pref-none"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeprefnone.example.internal", "A", "30.30.30.30"), + }, + }, + { + title: "TargetStrategyRouteOnlyIgnoresGatewayAnnotation", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-route-only-ignore-gw", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "20.20.20.20", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-only-ignore-gw", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "10.10.10.10", + targetStrategyAnnotationKey: "route-only", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeonlyignoregw.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-only-ignore-gw"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-only-ignore-gw"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeonlyignoregw.example.internal", "A", "10.10.10.10"), + }, + }, + { + title: "TargetStrategyRouteOnlyNoRouteTargetsIgnoresGatewayAnnotationAndFallsBackToAddresses", + config: Config{ + GatewayNamespace: "gateway-namespace", + }, + namespaces: namespaces("gateway-namespace", "route-namespace"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-route-only-fallback-ignore-gw", + Namespace: "gateway-namespace", + Annotations: map[string]string{ + targetAnnotationKey: "20.20.20.20", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{ + Protocol: v1.HTTPProtocolType, + AllowedRoutes: allowAllNamespaces, + }}, + }, + Status: gatewayStatus("30.30.30.30"), + }}, + routes: []*v1beta1.HTTPRoute{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rt-route-only-fallback-ignore-gw", + Namespace: "route-namespace", + Annotations: map[string]string{ + targetStrategyAnnotationKey: "route-only", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("routeonlyfallbackignoregw.example.internal"), + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ + gwParentRef("gateway-namespace", "gw-route-only-fallback-ignore-gw"), + }, + }, + }, + Status: httpRouteStatus( + gwParentRef("gateway-namespace", "gw-route-only-fallback-ignore-gw"), + ), + }}, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("routeonlyfallbackignoregw.example.internal", "A", "30.30.30.30"), + }, + }, } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { diff --git a/source/source.go b/source/source.go index aaa2d1dc11..8bb649452a 100644 --- a/source/source.go +++ b/source/source.go @@ -35,6 +35,7 @@ const ( targetAnnotationKey = annotations.TargetKey ttlAnnotationKey = annotations.TtlKey aliasAnnotationKey = annotations.AliasKey + targetStrategyAnnotationKey = annotations.TargetStrategyKey ingressHostnameSourceKey = annotations.IngressHostnameSourceKey controllerAnnotationValue = annotations.ControllerValue internalHostnameAnnotationKey = annotations.InternalHostnameKey From 3f0449fc05d0b4f94948b5a303c360ec2575ab56 Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Wed, 10 Sep 2025 09:52:29 +0200 Subject: [PATCH 6/6] chore: update docs Signed-off-by: David van der Spek --- docs/sources/gateway.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index 2e64d9f1ae..bcfa949008 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -112,11 +112,11 @@ by the optional strategy annotation: external-dns.alpha.kubernetes.io/target-strategy: ``` -Supported strategies (current behavior for Gateway *Route sources): +Supported strategies (on all Gateway *Route resources): | Strategy | Behavior | Fallback Order | Publishes Multiple Target Sets? | |------------------|----------|----------------|----------------------------------| -| `route-preferred` (default when omitted or unrecognized) | Use Route targets if present; otherwise Gateway targets if present; otherwise addresses | Route → Gateway → Addresses | No (at most one set) | +| `route-preferred` (default) | Use Route targets if present; otherwise Gateway targets if present; otherwise addresses | Route → Gateway → Addresses | No (at most one set) | | `route-only` | Use Route targets only if present; otherwise fall back directly to addresses (Gateway targets are ignored even if set) | Route → Addresses | No | | `gateway-only` | Use Gateway targets only if present; otherwise fall back to addresses (Route targets are ignored) | Gateway → Addresses | No | | `merge` | Combine Route and Gateway targets (deduped). If neither annotation supplies targets, fall back to addresses | (Route ∪ Gateway) → Addresses | Yes |