From 3159225e377bd350c931e158ff9f57d70d15af9b Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 14 May 2026 15:47:35 -0600 Subject: [PATCH 1/4] feat: [CLI-54049]: Add experimental Gateway API route types and BackendTLSPolicy Adds templates for GRPCRoute, TCPRoute, TLSRoute, UDPRoute, and BackendTLSPolicy. All rendered automatically via renderIngress when global.gatewayAPI.enabled is true. Fixes nil pointer safety across all gateway templates by switching to dig-based access. Co-Authored-By: Claude Opus 4.6 AI-Session-Id: 4f9cdbba-9363-442e-a473-42572bf70f96 AI-Tool: claude-code AI-Model: unknown --- CHANGELOG.md | 14 + .../ci-values/gateway-backendtlspolicy.yaml | 31 + .../ci-values/gateway-grpcroute.yaml | 36 ++ ci/test-chart/ci-values/gateway-tcproute.yaml | 37 ++ ci/test-chart/ci-values/gateway-tlsroute.yaml | 27 + ci/test-chart/ci-values/gateway-udproute.yaml | 25 + .../tests/gateway_backendtlspolicy_test.yaml | 82 +++ .../tests/gateway_grpcroute_test.yaml | 86 +++ .../tests/gateway_tcproute_test.yaml | 82 +++ .../tests/gateway_tlsroute_test.yaml | 68 +++ .../tests/gateway_udproute_test.yaml | 59 ++ docs/GATEWAYAPI.md | 531 +++++++++++++++++- src/common/Chart.yaml | 2 +- .../templates/_gateway_backendtlspolicy.tpl | 74 +++ .../_gateway_backendtrafficpolicy.tpl | 2 +- .../_gateway_clienttrafficpolicy.tpl | 2 +- src/common/templates/_gateway_grpcroute.tpl | 135 +++++ src/common/templates/_gateway_httproute.tpl | 2 +- .../templates/_gateway_migration_helper.tpl | 2 +- .../templates/_gateway_securitypolicy.tpl | 2 +- src/common/templates/_gateway_tcproute.tpl | 82 +++ src/common/templates/_gateway_tlsroute.tpl | 88 +++ src/common/templates/_gateway_udproute.tpl | 81 +++ src/common/templates/_ingress.tpl | 5 + 24 files changed, 1547 insertions(+), 8 deletions(-) create mode 100644 ci/test-chart/ci-values/gateway-backendtlspolicy.yaml create mode 100644 ci/test-chart/ci-values/gateway-grpcroute.yaml create mode 100644 ci/test-chart/ci-values/gateway-tcproute.yaml create mode 100644 ci/test-chart/ci-values/gateway-tlsroute.yaml create mode 100644 ci/test-chart/ci-values/gateway-udproute.yaml create mode 100644 ci/test-chart/tests/gateway_backendtlspolicy_test.yaml create mode 100644 ci/test-chart/tests/gateway_grpcroute_test.yaml create mode 100644 ci/test-chart/tests/gateway_tcproute_test.yaml create mode 100644 ci/test-chart/tests/gateway_tlsroute_test.yaml create mode 100644 ci/test-chart/tests/gateway_udproute_test.yaml create mode 100644 src/common/templates/_gateway_backendtlspolicy.tpl create mode 100644 src/common/templates/_gateway_grpcroute.tpl create mode 100644 src/common/templates/_gateway_tcproute.tpl create mode 100644 src/common/templates/_gateway_tlsroute.tpl create mode 100644 src/common/templates/_gateway_udproute.tpl diff --git a/CHANGELOG.md b/CHANGELOG.md index da9bfe8..8e06d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.7.0] - 2026-05-14 + +### Added +- **GRPCRoute**: Native gRPC routing with service/method-level matching (`ingress.grpcRoutes`) +- **TCPRoute**: Raw TCP traffic routing for databases, Redis, custom protocols (`ingress.tcpRoutes`) +- **TLSRoute**: TLS passthrough routing based on SNI hostname (`ingress.tlsRoutes`) +- **UDPRoute**: UDP traffic routing for DNS, game servers, etc. (`ingress.udpRoutes`) +- **BackendTLSPolicy**: TLS configuration for gateway-to-backend connections (`ingress.backendTLSPolicies`) +- All new route types support per-route `parentRef` override of global gateway reference +- All new route types support weighted backend traffic splitting + +### Fixed +- Nil pointer safety: all 10 gateway templates now use `dig` instead of direct nested map access for `global.gatewayAPI.enabled` guard + ## [1.6.3] - 2026-05-14 ### Added diff --git a/ci/test-chart/ci-values/gateway-backendtlspolicy.yaml b/ci/test-chart/ci-values/gateway-backendtlspolicy.yaml new file mode 100644 index 0000000..26401b4 --- /dev/null +++ b/ci/test-chart/ci-values/gateway-backendtlspolicy.yaml @@ -0,0 +1,31 @@ +global: + ingress: + enabled: true + hosts: + - api.example.com + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + backendTLSPolicies: + - name: api-backend-tls + targetRef: + name: api-service + port: 8443 + validation: + hostname: api-service.default.svc.cluster.local + caCertificateRefs: + - name: backend-ca-cert + - name: system-trust-tls + targetRef: + name: external-service + validation: + hostname: external.example.com + wellKnownCACertificates: "System" + +service: + port: 8080 diff --git a/ci/test-chart/ci-values/gateway-grpcroute.yaml b/ci/test-chart/ci-values/gateway-grpcroute.yaml new file mode 100644 index 0000000..5f669d5 --- /dev/null +++ b/ci/test-chart/ci-values/gateway-grpcroute.yaml @@ -0,0 +1,36 @@ +global: + ingress: + enabled: true + hosts: + - grpc.example.com + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + sectionName: grpc + +ingress: + grpcRoutes: + - name: grpc-api + hostnames: + - grpc.example.com + rules: + - matches: + - method: + service: mycompany.UserService + method: GetUser + type: Exact + backendRefs: + - name: user-grpc-svc + port: 9090 + - matches: + - method: + service: mycompany.OrderService + backendRefs: + - name: order-grpc-svc + port: 9090 + +service: + port: 8080 diff --git a/ci/test-chart/ci-values/gateway-tcproute.yaml b/ci/test-chart/ci-values/gateway-tcproute.yaml new file mode 100644 index 0000000..dc1147e --- /dev/null +++ b/ci/test-chart/ci-values/gateway-tcproute.yaml @@ -0,0 +1,37 @@ +global: + ingress: + enabled: true + hosts: + - api.example.com + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + tcpRoutes: + - name: postgres-route + parentRef: + sectionName: tcp-5432 + port: 5432 + rules: + - backendRefs: + - name: postgres-svc + port: 5432 + - name: redis-route + parentRef: + sectionName: tcp-6379 + port: 6379 + rules: + - backendRefs: + - name: redis-svc + port: 6379 + weight: 80 + - name: redis-replica-svc + port: 6379 + weight: 20 + +service: + port: 8080 diff --git a/ci/test-chart/ci-values/gateway-tlsroute.yaml b/ci/test-chart/ci-values/gateway-tlsroute.yaml new file mode 100644 index 0000000..424be8e --- /dev/null +++ b/ci/test-chart/ci-values/gateway-tlsroute.yaml @@ -0,0 +1,27 @@ +global: + ingress: + enabled: true + hosts: + - api.example.com + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + tlsRoutes: + - name: db-passthrough + hostnames: + - db.example.com + - db-replica.example.com + parentRef: + sectionName: tls-passthrough + rules: + - backendRefs: + - name: db-svc + port: 5432 + +service: + port: 8080 diff --git a/ci/test-chart/ci-values/gateway-udproute.yaml b/ci/test-chart/ci-values/gateway-udproute.yaml new file mode 100644 index 0000000..a165996 --- /dev/null +++ b/ci/test-chart/ci-values/gateway-udproute.yaml @@ -0,0 +1,25 @@ +global: + ingress: + enabled: true + hosts: + - api.example.com + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + udpRoutes: + - name: dns-route + parentRef: + sectionName: udp-53 + port: 53 + rules: + - backendRefs: + - name: dns-svc + port: 53 + +service: + port: 8080 diff --git a/ci/test-chart/tests/gateway_backendtlspolicy_test.yaml b/ci/test-chart/tests/gateway_backendtlspolicy_test.yaml new file mode 100644 index 0000000..c7f45ac --- /dev/null +++ b/ci/test-chart/tests/gateway_backendtlspolicy_test.yaml @@ -0,0 +1,82 @@ +suite: Gateway API BackendTLSPolicy (harnesscommon.v2.renderBackendTLSPolicy) +values: + - ../values.yaml + - ../ci-values/gateway-backendtlspolicy.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: should render BackendTLSPolicy with correct kind and apiVersion + documentSelector: + path: spec.targetRefs[0].name + value: api-service + asserts: + - isKind: + of: BackendTLSPolicy + - equal: + path: apiVersion + value: gateway.networking.k8s.io/v1alpha3 + + - it: should have correct metadata for CA cert policy + documentSelector: + path: spec.targetRefs[0].name + value: api-service + asserts: + - equal: + path: metadata.name + value: api-backend-tls + - equal: + path: metadata.namespace + value: default + + - it: should target correct service with sectionName for port + documentSelector: + path: spec.targetRefs[0].name + value: api-service + asserts: + - equal: + path: spec.targetRefs[0].group + value: "" + - equal: + path: spec.targetRefs[0].kind + value: Service + - equal: + path: spec.targetRefs[0].name + value: api-service + - equal: + path: spec.targetRefs[0].sectionName + value: "8443" + + - it: should have correct validation with CA certificate ref + documentSelector: + path: spec.targetRefs[0].name + value: api-service + asserts: + - equal: + path: spec.validation.hostname + value: api-service.default.svc.cluster.local + - equal: + path: spec.validation.caCertificateRefs[0].name + value: backend-ca-cert + - equal: + path: spec.validation.caCertificateRefs[0].kind + value: Secret + + - it: should render policy with system trust store + documentSelector: + path: spec.targetRefs[0].name + value: external-service + asserts: + - isKind: + of: BackendTLSPolicy + - equal: + path: metadata.name + value: system-trust-tls + - equal: + path: spec.validation.hostname + value: external.example.com + - equal: + path: spec.validation.wellKnownCACertificates + value: System diff --git a/ci/test-chart/tests/gateway_grpcroute_test.yaml b/ci/test-chart/tests/gateway_grpcroute_test.yaml new file mode 100644 index 0000000..49881b0 --- /dev/null +++ b/ci/test-chart/tests/gateway_grpcroute_test.yaml @@ -0,0 +1,86 @@ +suite: Gateway API GRPCRoute (harnesscommon.v2.renderGRPCRoute) +values: + - ../values.yaml + - ../ci-values/gateway-grpcroute.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: should render GRPCRoute with correct kind and apiVersion + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: user-grpc-svc + asserts: + - isKind: + of: GRPCRoute + - equal: + path: apiVersion + value: gateway.networking.k8s.io/v1 + + - it: should have correct metadata + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: user-grpc-svc + asserts: + - equal: + path: metadata.name + value: grpc-api + - equal: + path: metadata.namespace + value: default + + - it: should reference parent gateway + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: user-grpc-svc + asserts: + - equal: + path: spec.parentRefs[0].name + value: test-gateway + - equal: + path: spec.parentRefs[0].namespace + value: gateway-system + - equal: + path: spec.parentRefs[0].sectionName + value: grpc + + - it: should have correct hostnames + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: user-grpc-svc + asserts: + - contains: + path: spec.hostnames + content: grpc.example.com + + - it: should have correct gRPC method matching for first rule + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: user-grpc-svc + asserts: + - equal: + path: spec.rules[0].matches[0].method.service + value: mycompany.UserService + - equal: + path: spec.rules[0].matches[0].method.method + value: GetUser + - equal: + path: spec.rules[0].matches[0].method.type + value: Exact + + - it: should have correct service-level match for second rule + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: user-grpc-svc + asserts: + - equal: + path: spec.rules[1].matches[0].method.service + value: mycompany.OrderService + - equal: + path: spec.rules[1].backendRefs[0].name + value: order-grpc-svc + - equal: + path: spec.rules[1].backendRefs[0].port + value: 9090 diff --git a/ci/test-chart/tests/gateway_tcproute_test.yaml b/ci/test-chart/tests/gateway_tcproute_test.yaml new file mode 100644 index 0000000..ea72657 --- /dev/null +++ b/ci/test-chart/tests/gateway_tcproute_test.yaml @@ -0,0 +1,82 @@ +suite: Gateway API TCPRoute (harnesscommon.v2.renderTCPRoute) +values: + - ../values.yaml + - ../ci-values/gateway-tcproute.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: should render TCPRoute with correct kind and apiVersion + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: postgres-svc + asserts: + - isKind: + of: TCPRoute + - equal: + path: apiVersion + value: gateway.networking.k8s.io/v1alpha2 + + - it: should have correct metadata for postgres route + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: postgres-svc + asserts: + - equal: + path: metadata.name + value: postgres-route + - equal: + path: metadata.namespace + value: default + + - it: should reference parent gateway with local sectionName override + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: postgres-svc + asserts: + - equal: + path: spec.parentRefs[0].name + value: test-gateway + - equal: + path: spec.parentRefs[0].namespace + value: gateway-system + - equal: + path: spec.parentRefs[0].sectionName + value: tcp-5432 + - equal: + path: spec.parentRefs[0].port + value: 5432 + + - it: should have correct backend ref for postgres + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: postgres-svc + asserts: + - equal: + path: spec.rules[0].backendRefs[0].name + value: postgres-svc + - equal: + path: spec.rules[0].backendRefs[0].port + value: 5432 + + - it: should render redis route with weighted backends + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: redis-svc + asserts: + - isKind: + of: TCPRoute + - equal: + path: metadata.name + value: redis-route + - equal: + path: spec.rules[0].backendRefs[0].weight + value: 80 + - equal: + path: spec.rules[0].backendRefs[1].name + value: redis-replica-svc + - equal: + path: spec.rules[0].backendRefs[1].weight + value: 20 diff --git a/ci/test-chart/tests/gateway_tlsroute_test.yaml b/ci/test-chart/tests/gateway_tlsroute_test.yaml new file mode 100644 index 0000000..22a796f --- /dev/null +++ b/ci/test-chart/tests/gateway_tlsroute_test.yaml @@ -0,0 +1,68 @@ +suite: Gateway API TLSRoute (harnesscommon.v2.renderTLSRoute) +values: + - ../values.yaml + - ../ci-values/gateway-tlsroute.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: should render TLSRoute with correct kind and apiVersion + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: db-svc + asserts: + - isKind: + of: TLSRoute + - equal: + path: apiVersion + value: gateway.networking.k8s.io/v1alpha2 + + - it: should have correct metadata + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: db-svc + asserts: + - equal: + path: metadata.name + value: db-passthrough + - equal: + path: metadata.namespace + value: default + + - it: should reference parent gateway with sectionName override + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: db-svc + asserts: + - equal: + path: spec.parentRefs[0].name + value: test-gateway + - equal: + path: spec.parentRefs[0].sectionName + value: tls-passthrough + + - it: should have correct hostnames for SNI matching + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: db-svc + asserts: + - contains: + path: spec.hostnames + content: db.example.com + - contains: + path: spec.hostnames + content: db-replica.example.com + + - it: should have correct backend ref + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: db-svc + asserts: + - equal: + path: spec.rules[0].backendRefs[0].name + value: db-svc + - equal: + path: spec.rules[0].backendRefs[0].port + value: 5432 diff --git a/ci/test-chart/tests/gateway_udproute_test.yaml b/ci/test-chart/tests/gateway_udproute_test.yaml new file mode 100644 index 0000000..449adad --- /dev/null +++ b/ci/test-chart/tests/gateway_udproute_test.yaml @@ -0,0 +1,59 @@ +suite: Gateway API UDPRoute (harnesscommon.v2.renderUDPRoute) +values: + - ../values.yaml + - ../ci-values/gateway-udproute.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: should render UDPRoute with correct kind and apiVersion + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: dns-svc + asserts: + - isKind: + of: UDPRoute + - equal: + path: apiVersion + value: gateway.networking.k8s.io/v1alpha2 + + - it: should have correct metadata + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: dns-svc + asserts: + - equal: + path: metadata.name + value: dns-route + - equal: + path: metadata.namespace + value: default + + - it: should reference parent gateway with sectionName and port + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: dns-svc + asserts: + - equal: + path: spec.parentRefs[0].name + value: test-gateway + - equal: + path: spec.parentRefs[0].sectionName + value: udp-53 + - equal: + path: spec.parentRefs[0].port + value: 53 + + - it: should have correct backend ref + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: dns-svc + asserts: + - equal: + path: spec.rules[0].backendRefs[0].name + value: dns-svc + - equal: + path: spec.rules[0].backendRefs[0].port + value: 53 diff --git a/docs/GATEWAYAPI.md b/docs/GATEWAYAPI.md index eacd9d9..1d3fe69 100644 --- a/docs/GATEWAYAPI.md +++ b/docs/GATEWAYAPI.md @@ -6,10 +6,20 @@ This document describes how to use the GatewayAPI templates to generate Kubernet The helm-common library provides comprehensive Gateway API support through multiple templates: +**Route Types:** - **`_gateway_httproute.tpl`** - HTTPRoute with header manipulation and URL rewriting +- **`_gateway_grpcroute.tpl`** - Native GRPCRoute with method-level matching +- **`_gateway_tcproute.tpl`** - TCPRoute for raw TCP traffic (databases, Redis, etc.) +- **`_gateway_tlsroute.tpl`** - TLSRoute for TLS passthrough without termination +- **`_gateway_udproute.tpl`** - UDPRoute for UDP traffic (DNS, game servers, etc.) + +**Policies:** - **`_gateway_backendtrafficpolicy.tpl`** - Backend timeouts, connection settings, protocol, retries - **`_gateway_clienttrafficpolicy.tpl`** - Client-side connection limits and timeouts - **`_gateway_securitypolicy.tpl`** - IP whitelisting, CORS, JWT authentication +- **`_gateway_backendtlspolicy.tpl`** - TLS configuration for backend connections + +**Helpers:** - **`_gateway_migration_helper.tpl`** - Prints migration suggestions for nginx annotations Gateway API is the next-generation ingress solution for Kubernetes, offering more expressiveness, extensibility, and role-oriented design compared to traditional Ingress resources. @@ -21,6 +31,456 @@ Gateway API is the next-generation ingress solution for Kubernetes, offering mor - Support for modern proxy features (gRPC, HTTP/2, etc.) - Hybrid policy approach: shared defaults + per-route overrides +## Supported Resource Types + +All resources are rendered automatically by a single `renderIngress` call when `global.gatewayAPI.enabled: true`. + +**Stable Route Types:** + +| Resource | API Version | Description | Config Location | +|----------|-------------|-------------|-----------------| +| **HTTPRoute** | `gateway.networking.k8s.io/v1` | L7 HTTP routing with regex paths, header manipulation, URL rewriting | `ingress.objects` | +| **GRPCRoute** | `gateway.networking.k8s.io/v1` | Native gRPC routing with service/method matching | `ingress.grpcRoutes` | + +**Experimental Route Types:** + +| Resource | API Version | Description | Config Location | +|----------|-------------|-------------|-----------------| +| **TCPRoute** | `gateway.networking.k8s.io/v1alpha2` | Raw TCP traffic (databases, Redis, custom protocols) | `ingress.tcpRoutes` | +| **TLSRoute** | `gateway.networking.k8s.io/v1alpha2` | TLS passthrough without termination (SNI-based) | `ingress.tlsRoutes` | +| **UDPRoute** | `gateway.networking.k8s.io/v1alpha2` | UDP traffic (DNS, game servers, custom protocols) | `ingress.udpRoutes` | + +**Envoy Gateway Policies:** + +| Resource | API Version | Description | Config Location | +|----------|-------------|-------------|-----------------| +| **BackendTrafficPolicy** | `gateway.envoyproxy.io/v1alpha1` | Timeouts, protocol, retries, load balancing | `global.gatewayAPI.policies.backendTraffic` or per-route | +| **ClientTrafficPolicy** | `gateway.envoyproxy.io/v1alpha1` | Client connection limits, HTTP/2 settings | `global.gatewayAPI.policies.clientTraffic` | +| **SecurityPolicy** | `gateway.envoyproxy.io/v1alpha1` | IP whitelisting, CORS, JWT | `global.gatewayAPI.policies.security` or per-route | +| **HTTPRouteFilter** | `gateway.envoyproxy.io/v1alpha1` | URL rewrite rules (auto-generated from rewrite-target annotation) | Auto | + +**Gateway API Policies:** + +| Resource | API Version | Description | Config Location | +|----------|-------------|-------------|-----------------| +| **BackendTLSPolicy** | `gateway.networking.k8s.io/v1alpha3` | TLS config for gateway-to-backend connections | `ingress.backendTLSPolicies` | + +## gRPC Support + +**A single Envoy Gateway handles both HTTP and gRPC traffic.** You do NOT need a separate gateway for gRPC. There are two approaches: + +| Approach | Resource | When to Use | +|----------|----------|-------------| +| **HTTPRoute + protocol** | `HTTPRoute` + `BackendTrafficPolicy.protocol: "GRPC"` | Simple path-based gRPC routing, migrating from nginx | +| **Native GRPCRoute** | `GRPCRoute` | Method-level matching (service + method name), header-based matching | + +### Approach 1: HTTPRoute + BackendTrafficPolicy Protocol + +The simpler approach. Define gRPC service paths in `ingress.objects` like HTTP paths and set `protocol: "GRPC"` on the BackendTrafficPolicy. + +```yaml +global: + ingress: + enabled: true + hosts: + - api.example.com + gatewayAPI: + enabled: true + parentRef: + name: envoy-gateway + +ingress: + objects: + # HTTP service - uses default HTTP protocol + - name: "web-api" + paths: + - path: "/api/v1/.*" + backend: + service: + name: web-api-svc + port: 8080 + + # gRPC service - override protocol per-route + - name: "grpc-service" + gatewayAPI: + backendTraffic: + protocol: "GRPC" + paths: + - path: "/grpc\\.health\\.v1\\.Health/.*" + backend: + service: + name: grpc-svc + port: 9090 + - path: "/my\\.package\\.MyService/.*" + backend: + service: + name: grpc-svc + port: 9090 +``` + +This generates: +- One `HTTPRoute` for `web-api` (standard HTTP backend) +- One `HTTPRoute` for `grpc-service` (same kind, same gateway) +- One `BackendTrafficPolicy` for `grpc-service` with `protocol: GRPC` + +If **all** services use gRPC, set it globally instead of per-route: + +```yaml +global: + gatewayAPI: + policies: + backendTraffic: + enabled: true + protocol: "GRPC" +``` + +### Approach 2: Native GRPCRoute + +For native gRPC service/method matching without regex paths. Define routes under `ingress.grpcRoutes`: + +```yaml +ingress: + grpcRoutes: + - name: grpc-api + hostnames: + - grpc.example.com + rules: + # Match a specific method on a service + - matches: + - method: + service: my.package.UserService + method: GetUser + type: Exact + backendRefs: + - name: user-grpc-svc + port: 9090 + + # Match all methods on a service + - matches: + - method: + service: my.package.OrderService + backendRefs: + - name: order-grpc-svc + port: 9090 + + # Match with header conditions + - matches: + - method: + service: my.package.AdminService + headers: + - name: x-admin-token + value: "valid" + backendRefs: + - name: admin-grpc-svc + port: 9090 +``` + +**Generated output:** + +```yaml +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GRPCRoute +metadata: + name: grpc-api + namespace: default +spec: + parentRefs: + - name: envoy-gateway + namespace: default + hostnames: + - "grpc.example.com" + rules: + - matches: + - method: + service: "my.package.UserService" + method: "GetUser" + type: Exact + backendRefs: + - name: user-grpc-svc + port: 9090 + - matches: + - method: + service: "my.package.OrderService" + backendRefs: + - name: order-grpc-svc + port: 9090 +``` + +GRPCRoute supports the same `parentRef` override pattern as other routes (local overrides global). + +### gRPC Timeouts + +gRPC services often need longer timeouts for streaming RPCs: + +```yaml +ingress: + objects: + - name: "grpc-streaming" + gatewayAPI: + backendTraffic: + protocol: "GRPC" + timeout: + http: + requestTimeout: "3600s" # 1 hour for long-running streams + connectionIdleTimeout: "600s" + paths: + - path: "/my\\.package\\.StreamService/.*" +``` + +## TCPRoute + +Routes raw TCP traffic to backend services. Use for databases, Redis, custom TCP protocols, or any non-HTTP service. + +**Gateway requirement:** The Gateway must have a TCP listener configured for the target port. + +### Example: Database and Redis + +```yaml +ingress: + tcpRoutes: + # PostgreSQL + - name: postgres-route + parentRef: + sectionName: tcp-5432 # Must match a Gateway listener name + port: 5432 + rules: + - backendRefs: + - name: postgres-svc + port: 5432 + + # Redis with weighted traffic splitting + - name: redis-route + parentRef: + sectionName: tcp-6379 + port: 6379 + rules: + - backendRefs: + - name: redis-primary + port: 6379 + weight: 80 + - name: redis-replica + port: 6379 + weight: 20 +``` + +**Generated output:** + +```yaml +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: postgres-route + namespace: default +spec: + parentRefs: + - name: envoy-gateway + namespace: default + sectionName: tcp-5432 + port: 5432 + rules: + - backendRefs: + - name: postgres-svc + port: 5432 +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: redis-route + namespace: default +spec: + parentRefs: + - name: envoy-gateway + namespace: default + sectionName: tcp-6379 + port: 6379 + rules: + - backendRefs: + - name: redis-primary + port: 6379 + weight: 80 + - name: redis-replica + port: 6379 + weight: 20 +``` + +### TCPRoute `parentRef` Override + +Each TCPRoute can override any field from `global.gatewayAPI.parentRef`: + +```yaml +ingress: + tcpRoutes: + - name: my-tcp-route + parentRef: + name: tcp-gateway # Override gateway name + sectionName: tcp-3306 # Target specific listener + port: 3306 + rules: + - backendRefs: + - name: mysql-svc + port: 3306 +``` + +Fields not specified in the local `parentRef` fall back to the global `parentRef`. + +## TLSRoute + +Routes TLS traffic without termination (passthrough) based on SNI hostname. The Gateway does NOT decrypt the traffic — it forwards the encrypted connection to the backend based on the TLS Server Name Indication. + +**Gateway requirement:** The Gateway must have a TLS listener with `mode: Passthrough`. + +### Example: TLS Passthrough + +```yaml +ingress: + tlsRoutes: + - name: db-passthrough + hostnames: + - db.example.com + - db-replica.example.com + parentRef: + sectionName: tls-passthrough + rules: + - backendRefs: + - name: db-svc + port: 5432 +``` + +**Generated output:** + +```yaml +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TLSRoute +metadata: + name: db-passthrough + namespace: default +spec: + parentRefs: + - name: envoy-gateway + namespace: default + sectionName: tls-passthrough + hostnames: + - "db.example.com" + - "db-replica.example.com" + rules: + - backendRefs: + - name: db-svc + port: 5432 +``` + +### When to Use TLSRoute vs TCPRoute + +| Use Case | Route Type | Why | +|----------|------------|-----| +| Backend handles its own TLS, you want SNI routing | **TLSRoute** | Routes by hostname without decrypting | +| Raw TCP, no TLS, or gateway terminates TLS | **TCPRoute** | No SNI available for routing | +| Multiple TLS backends on same port, different hostnames | **TLSRoute** | SNI lets you multiplex | + +## UDPRoute + +Routes UDP traffic to backend services. Use for DNS servers, game servers, or custom UDP protocols. + +**Gateway requirement:** The Gateway must have a UDP listener configured for the target port. + +### Example: DNS Server + +```yaml +ingress: + udpRoutes: + - name: dns-route + parentRef: + sectionName: udp-53 + port: 53 + rules: + - backendRefs: + - name: coredns-svc + port: 53 +``` + +**Generated output:** + +```yaml +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: UDPRoute +metadata: + name: dns-route + namespace: default +spec: + parentRefs: + - name: envoy-gateway + namespace: default + sectionName: udp-53 + port: 53 + rules: + - backendRefs: + - name: coredns-svc + port: 53 +``` + +## BackendTLSPolicy + +Configures TLS settings for connections **from the Gateway to backend Services**. Use when your backend expects TLS connections (mTLS, internal TLS). + +This is a standard Gateway API resource (not Envoy-specific). Define under `ingress.backendTLSPolicies`. + +### Example: CA Certificate Reference + +```yaml +ingress: + backendTLSPolicies: + - name: api-backend-tls + targetRef: + name: api-service # Target Service name + port: 8443 # Optional: specific port + validation: + hostname: api-service.default.svc.cluster.local + caCertificateRefs: + - name: backend-ca-cert # Secret containing the CA cert + kind: Secret # Optional, defaults to Secret +``` + +**Generated output:** + +```yaml +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: api-backend-tls + namespace: default +spec: + targetRefs: + - group: "" + kind: Service + name: api-service + sectionName: "8443" + validation: + hostname: "api-service.default.svc.cluster.local" + caCertificateRefs: + - name: backend-ca-cert + group: "" + kind: Secret +``` + +### Example: System Trust Store + +Use the system CA bundle instead of explicit certificates: + +```yaml +ingress: + backendTLSPolicies: + - name: external-tls + targetRef: + name: external-service + validation: + hostname: external.example.com + wellKnownCACertificates: "System" +``` + ## Migration Approach: Guided, Not Automatic **Important:** This library does NOT auto-translate nginx annotations to Gateway API policies. Instead, when nginx annotations are detected, the templates **print migration suggestions** in the rendered YAML output showing exactly what to add to your `values.yaml`. @@ -373,10 +833,15 @@ both traditional Ingress AND Gateway API resources (HTTPRoute + policies) when This single include handles everything: - Traditional `Ingress` objects (always, when `global.ingress.enabled`) -- `HTTPRoute` resources (when `global.gatewayAPI.enabled`) +- `HTTPRoute` resources (when `global.gatewayAPI.enabled`, from `ingress.objects`) +- `GRPCRoute` resources (when `global.gatewayAPI.enabled`, from `ingress.grpcRoutes`) +- `TCPRoute` resources (when `global.gatewayAPI.enabled`, from `ingress.tcpRoutes`) +- `TLSRoute` resources (when `global.gatewayAPI.enabled`, from `ingress.tlsRoutes`) +- `UDPRoute` resources (when `global.gatewayAPI.enabled`, from `ingress.udpRoutes`) - `BackendTrafficPolicy` (when `global.gatewayAPI.policies.backendTraffic.enabled`) - `ClientTrafficPolicy` (when `global.gatewayAPI.policies.clientTraffic.enabled`) - `SecurityPolicy` (when `global.gatewayAPI.policies.security.enabled`) +- `BackendTLSPolicy` (when `global.gatewayAPI.enabled`, from `ingress.backendTLSPolicies`) With custom ingress configuration: @@ -470,7 +935,7 @@ global: | `disableHostInIngress` | bool | `false` | Use wildcard `*` hostname instead of specific hosts | | `objects.annotations` | object | `{}` | Annotations applied to all ingress/HTTPRoute objects | -### ingress.objects (service-level) +### ingress.objects (service-level, used by HTTPRoute) | Parameter | Type | Description | |-----------|------|-------------| @@ -488,6 +953,61 @@ global: | `paths[].backend.service.name` | string | Backend service name (defaults to Chart.Name) | | `paths[].backend.service.port` | int | Backend service port (defaults to `.Values.service.port`) | +### ingress.grpcRoutes + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | string | Route name (optional, auto-generated as `-grpc-`) | +| `hostnames` | array | SNI hostnames for the route | +| `annotations` | object | Custom annotations | +| `parentRef` | object | Override `global.gatewayAPI.parentRef` (name/namespace/sectionName/port) | +| `rules[].matches[].method.service` | string | gRPC service name (e.g., `my.package.MyService`) | +| `rules[].matches[].method.method` | string | gRPC method name (optional, empty matches all methods) | +| `rules[].matches[].method.type` | string | Match type: `Exact` (default) or `RegularExpression` | +| `rules[].matches[].headers` | array | Header match conditions (name/value/type) | +| `rules[].filters` | array | Request/response filters (rendered as-is) | +| `rules[].backendRefs` | array | Backend services (name/port/weight) | + +### ingress.tcpRoutes + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | string | Route name (optional, auto-generated as `-tcp-`) | +| `annotations` | object | Custom annotations | +| `parentRef` | object | Override `global.gatewayAPI.parentRef` (name/namespace/sectionName/port) | +| `rules[].backendRefs` | array | Backend services (name/port/weight) | + +### ingress.tlsRoutes + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | string | Route name (optional, auto-generated as `-tls-`) | +| `hostnames` | array | SNI hostnames for passthrough routing | +| `annotations` | object | Custom annotations | +| `parentRef` | object | Override `global.gatewayAPI.parentRef` (name/namespace/sectionName/port) | +| `rules[].backendRefs` | array | Backend services (name/port/weight) | + +### ingress.udpRoutes + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | string | Route name (optional, auto-generated as `-udp-`) | +| `annotations` | object | Custom annotations | +| `parentRef` | object | Override `global.gatewayAPI.parentRef` (name/namespace/sectionName/port) | +| `rules[].backendRefs` | array | Backend services (name/port/weight) | + +### ingress.backendTLSPolicies + +| Parameter | Type | Description | +|-----------|------|-------------| +| `name` | string | Policy name (optional, auto-generated as `-backend-tls-`) | +| `annotations` | object | Custom annotations | +| `targetRef.name` | string | Target Service name (required) | +| `targetRef.port` | int | Target Service port (optional, rendered as sectionName) | +| `validation.hostname` | string | Expected backend certificate hostname (required) | +| `validation.caCertificateRefs` | array | CA certificate references (name, optional group/kind) | +| `validation.wellKnownCACertificates` | string | Use system trust store: `"System"` | + ## Migration Example with Suggestions When you have existing nginx annotations, the template prints migration suggestions: @@ -690,6 +1210,8 @@ To add GatewayAPI support to an existing service using nginx-ingress: 5. **Server alias regex limitation**: Gateway API supports wildcards (`*.domain.com`) but NOT regex patterns like nginx `server-alias` 6. **ClientTrafficPolicy scope**: ClientTrafficPolicy attaches to the Gateway itself, not individual routes, so settings affect all routes through that Gateway 7. **Policy merge behavior**: When multiple policies target the same resource, Envoy Gateway merges them (Gateway → HTTPRoute → Service precedence) +8. **Experimental API versions**: TCPRoute, TLSRoute, and UDPRoute use `v1alpha2`; BackendTLSPolicy uses `v1alpha3`. These APIs may change in future Gateway API releases +9. **Gateway listener requirements**: TCPRoute, TLSRoute, and UDPRoute require matching listeners on the Gateway (TCP/TLS/UDP respectively). HTTPRoute and GRPCRoute use HTTP/HTTPS listeners ## Advanced Use Cases @@ -830,8 +1352,13 @@ kubectl get httproute -n your-namespace -o yaml - `_ingress.tpl` - Unified entry point: renders Ingress + all Gateway API resources via `renderIngress` - `_gateway_httproute.tpl` - HTTPRoute generation with header manipulation and additional hostnames +- `_gateway_grpcroute.tpl` - Native GRPCRoute with service/method matching +- `_gateway_tcproute.tpl` - TCPRoute for raw TCP traffic +- `_gateway_tlsroute.tpl` - TLSRoute for TLS passthrough +- `_gateway_udproute.tpl` - UDPRoute for UDP traffic - `_gateway_backendtrafficpolicy.tpl` - Backend timeouts, connection settings, protocol, retries - `_gateway_clienttrafficpolicy.tpl` - Client-side connection limits and timeouts - `_gateway_securitypolicy.tpl` - IP whitelisting, CORS, JWT authentication +- `_gateway_backendtlspolicy.tpl` - TLS config for gateway-to-backend connections - `_gateway_migration_helper.tpl` - Prints migration suggestions for nginx annotations - `_service.tpl` - Service resource template diff --git a/src/common/Chart.yaml b/src/common/Chart.yaml index 00e1632..d1867cd 100644 --- a/src/common/Chart.yaml +++ b/src/common/Chart.yaml @@ -15,7 +15,7 @@ type: library # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.6.3 +version: 1.7.0 # This is the version number of the application being deployed. This version number should be diff --git a/src/common/templates/_gateway_backendtlspolicy.tpl b/src/common/templates/_gateway_backendtlspolicy.tpl new file mode 100644 index 0000000..29c13b7 --- /dev/null +++ b/src/common/templates/_gateway_backendtlspolicy.tpl @@ -0,0 +1,74 @@ +{{/* +BackendTLSPolicy template for Gateway API +Configures TLS settings for connections from the Gateway to backend Services + +USAGE (automatically called by renderIngress when gatewayAPI is enabled): +Define policies in your consuming chart's values.yaml under ingress.backendTLSPolicies: + +ingress: + backendTLSPolicies: + - name: my-backend-tls # optional, auto-generated if omitted + targetRef: + name: my-service # required: target Service name + port: 8443 # optional: specific port + validation: + hostname: my-service.ns.svc.cluster.local # required: expected backend cert hostname + caCertificateRefs: # option 1: explicit CA certs + - name: backend-ca + group: "" # optional, defaults to "" + kind: Secret # optional, defaults to Secret + wellKnownCACertificates: "" # option 2: "System" to use system trust store +*/}} +{{- define "harnesscommon.v2.renderBackendTLSPolicy" }} +{{- $ := .ctx }} +{{- $ingress := $.Values.ingress }} +{{- if .ingress -}} + {{- $ingress = .ingress }} +{{- end }} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} +{{- $policies := dig "backendTLSPolicies" list $ingress }} +{{- range $index, $policy := $policies }} +{{- $policyName := dig "name" ((cat (coalesce $ingress.name $.Values.nameOverride $.Chart.Name | trunc 63 | trimSuffix "-") "-backend-tls-" $index) | nospace) $policy }} +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: {{ $policyName }} + namespace: {{ $.Release.Namespace }} + {{- if $.Values.global.commonLabels }} + labels: + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or $.Values.global.commonAnnotations (dig "annotations" false $policy) }} + annotations: + {{- if $.Values.global.commonAnnotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $policy.annotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $policy.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + targetRefs: + - group: "" + kind: Service + name: {{ $policy.targetRef.name }} + {{- if $policy.targetRef.port }} + sectionName: {{ $policy.targetRef.port | quote }} + {{- end }} + validation: + hostname: {{ $policy.validation.hostname | quote }} + {{- if $policy.validation.caCertificateRefs }} + caCertificateRefs: + {{- range $ref := $policy.validation.caCertificateRefs }} + - name: {{ $ref.name }} + group: {{ $ref.group | default "" | quote }} + kind: {{ $ref.kind | default "Secret" }} + {{- end }} + {{- end }} + {{- if $policy.validation.wellKnownCACertificates }} + wellKnownCACertificates: {{ $policy.validation.wellKnownCACertificates | quote }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/src/common/templates/_gateway_backendtrafficpolicy.tpl b/src/common/templates/_gateway_backendtrafficpolicy.tpl index d39b059..40568a3 100644 --- a/src/common/templates/_gateway_backendtrafficpolicy.tpl +++ b/src/common/templates/_gateway_backendtrafficpolicy.tpl @@ -15,7 +15,7 @@ Supports hybrid approach (Option C): {{- if .ingress -}} {{- $ingress = .ingress }} {{- end }} -{{- if and $.Values.global.gatewayAPI.enabled $.Values.global.ingress.enabled -}} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} {{- $globalBackendPolicy := dig "policies" "backendTraffic" dict $.Values.global.gatewayAPI }} {{- $hasGlobalPolicy := and $globalBackendPolicy (dig "enabled" false $globalBackendPolicy) }} diff --git a/src/common/templates/_gateway_clienttrafficpolicy.tpl b/src/common/templates/_gateway_clienttrafficpolicy.tpl index 6d3e448..34e532f 100644 --- a/src/common/templates/_gateway_clienttrafficpolicy.tpl +++ b/src/common/templates/_gateway_clienttrafficpolicy.tpl @@ -8,7 +8,7 @@ USAGE: */}} {{- define "harnesscommon.v2.renderClientTrafficPolicy" }} {{- $ := .ctx }} -{{- if and $.Values.global.gatewayAPI.enabled $.Values.global.ingress.enabled -}} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} {{- $clientPolicy := dig "policies" "clientTraffic" dict $.Values.global.gatewayAPI }} {{- if and $clientPolicy (dig "enabled" false $clientPolicy) }} diff --git a/src/common/templates/_gateway_grpcroute.tpl b/src/common/templates/_gateway_grpcroute.tpl new file mode 100644 index 0000000..854e3f5 --- /dev/null +++ b/src/common/templates/_gateway_grpcroute.tpl @@ -0,0 +1,135 @@ +{{/* +GRPCRoute template for Gateway API +Native gRPC routing with method-level matching (alternative to HTTPRoute + BackendTrafficPolicy protocol: GRPC) + +USAGE (automatically called by renderIngress when gatewayAPI is enabled): +Define routes in your consuming chart's values.yaml under ingress.grpcRoutes: + +ingress: + grpcRoutes: + - name: grpc-api + hostnames: + - grpc.example.com + rules: + - matches: + - method: + service: mycompany.MyService + method: MyMethod # optional, empty matches all methods + type: Exact # optional: Exact (default) or RegularExpression + backendRefs: + - name: grpc-svc + port: 9090 + filters: # optional + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: x-custom + value: val +*/}} +{{- define "harnesscommon.v2.renderGRPCRoute" }} +{{- $ := .ctx }} +{{- $ingress := $.Values.ingress }} +{{- if .ingress -}} + {{- $ingress = .ingress }} +{{- end }} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} +{{- $grpcRoutes := dig "grpcRoutes" list $ingress }} +{{- range $index, $route := $grpcRoutes }} +{{- $routeName := dig "name" ((cat (coalesce $ingress.name $.Values.nameOverride $.Chart.Name | trunc 63 | trimSuffix "-") "-grpc-" $index) | nospace) $route }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GRPCRoute +metadata: + name: {{ $routeName }} + namespace: {{ $.Release.Namespace }} + {{- if $.Values.global.commonLabels }} + labels: + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or $.Values.global.commonAnnotations (dig "annotations" false $route) }} + annotations: + {{- if $.Values.global.commonAnnotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $route.annotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $route.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- $globalParentRef := $.Values.global.gatewayAPI.parentRef }} + {{- $localParentRef := dig "parentRef" dict $route }} + {{- $parentRefName := dig "name" (dig "name" "" $globalParentRef) $localParentRef }} + {{- $parentRefNamespace := dig "namespace" (dig "namespace" "" $globalParentRef) $localParentRef | default $.Release.Namespace }} + {{- $parentRefSectionName := dig "sectionName" (dig "sectionName" "" $globalParentRef) $localParentRef }} + {{- $parentRefPort := dig "port" (dig "port" "" $globalParentRef) $localParentRef }} + {{- if $parentRefName }} + parentRefs: + - name: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefName "context" $) }} + {{- if $parentRefNamespace }} + namespace: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefNamespace "context" $) }} + {{- end }} + {{- if $parentRefSectionName }} + sectionName: {{ $parentRefSectionName }} + {{- end }} + {{- if $parentRefPort }} + port: {{ $parentRefPort }} + {{- end }} + {{- end }} + {{- if $route.hostnames }} + hostnames: + {{- range $hostname := $route.hostnames }} + - {{ $hostname | quote }} + {{- end }} + {{- end }} + rules: + {{- range $rule := $route.rules }} + {{- if $rule.matches }} + - matches: + {{- range $match := $rule.matches }} + - method: + {{- if $match.method.service }} + service: {{ $match.method.service | quote }} + {{- end }} + {{- if $match.method.method }} + method: {{ $match.method.method | quote }} + {{- end }} + {{- if $match.method.type }} + type: {{ $match.method.type }} + {{- end }} + {{- if $match.headers }} + headers: + {{- range $header := $match.headers }} + - name: {{ $header.name | quote }} + value: {{ $header.value | quote }} + {{- if $header.type }} + type: {{ $header.type }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if $rule.filters }} + filters: + {{- include "harnesscommon.tplvalues.render" ( dict "value" $rule.filters "context" $ ) | nindent 8 }} + {{- end }} + backendRefs: + {{- range $ref := $rule.backendRefs }} + - name: {{ $ref.name }} + port: {{ $ref.port }} + {{- if $ref.weight }} + weight: {{ $ref.weight }} + {{- end }} + {{- end }} + {{- else }} + - backendRefs: + {{- range $ref := $rule.backendRefs }} + - name: {{ $ref.name }} + port: {{ $ref.port }} + {{- if $ref.weight }} + weight: {{ $ref.weight }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/src/common/templates/_gateway_httproute.tpl b/src/common/templates/_gateway_httproute.tpl index bb17533..b226cae 100644 --- a/src/common/templates/_gateway_httproute.tpl +++ b/src/common/templates/_gateway_httproute.tpl @@ -10,7 +10,7 @@ or {{- if .ingress -}} {{- $ingress = .ingress }} {{- end }} -{{- if and $.Values.global.gatewayAPI.enabled $.Values.global.ingress.enabled -}} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} {{- range $index, $object := $ingress.objects }} {{- $routeName := dig "name" ((cat (coalesce $ingress.name $.Values.nameOverride $.Chart.Name | trunc 63 | trimSuffix "-") "-" $index) | nospace) $object }} {{- $objectAnnotations := dig "annotations" dict $object }} diff --git a/src/common/templates/_gateway_migration_helper.tpl b/src/common/templates/_gateway_migration_helper.tpl index 075016d..e6d1a87 100644 --- a/src/common/templates/_gateway_migration_helper.tpl +++ b/src/common/templates/_gateway_migration_helper.tpl @@ -11,7 +11,7 @@ USAGE: {{- $routeName := .routeName }} {{- $annotations := .annotations }} -{{- if and $.Values.global.gatewayAPI.enabled $annotations }} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) $annotations }} {{- $hasNginxAnnotations := false }} {{- $suggestions := list }} diff --git a/src/common/templates/_gateway_securitypolicy.tpl b/src/common/templates/_gateway_securitypolicy.tpl index 0ef433d..6e7c272 100644 --- a/src/common/templates/_gateway_securitypolicy.tpl +++ b/src/common/templates/_gateway_securitypolicy.tpl @@ -15,7 +15,7 @@ Supports hybrid approach (Option C): {{- if .ingress -}} {{- $ingress = .ingress }} {{- end }} -{{- if and $.Values.global.gatewayAPI.enabled $.Values.global.ingress.enabled -}} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} {{- $globalSecurityPolicy := dig "policies" "security" dict $.Values.global.gatewayAPI }} {{- $hasGlobalPolicy := and $globalSecurityPolicy (dig "enabled" false $globalSecurityPolicy) }} diff --git a/src/common/templates/_gateway_tcproute.tpl b/src/common/templates/_gateway_tcproute.tpl new file mode 100644 index 0000000..b368316 --- /dev/null +++ b/src/common/templates/_gateway_tcproute.tpl @@ -0,0 +1,82 @@ +{{/* +TCPRoute template for Gateway API +Routes raw TCP traffic to backend services (databases, Redis, custom TCP protocols) + +USAGE (automatically called by renderIngress when gatewayAPI is enabled): +Define routes in your consuming chart's values.yaml under ingress.tcpRoutes: + +ingress: + tcpRoutes: + - name: postgres-route + parentRef: # optional override of global parentRef + sectionName: tcp-5432 + port: 5432 + rules: + - backendRefs: + - name: postgres-svc + port: 5432 + weight: 1 # optional, defaults to 1 +*/}} +{{- define "harnesscommon.v2.renderTCPRoute" }} +{{- $ := .ctx }} +{{- $ingress := $.Values.ingress }} +{{- if .ingress -}} + {{- $ingress = .ingress }} +{{- end }} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} +{{- $tcpRoutes := dig "tcpRoutes" list $ingress }} +{{- range $index, $route := $tcpRoutes }} +{{- $routeName := dig "name" ((cat (coalesce $ingress.name $.Values.nameOverride $.Chart.Name | trunc 63 | trimSuffix "-") "-tcp-" $index) | nospace) $route }} +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: {{ $routeName }} + namespace: {{ $.Release.Namespace }} + {{- if $.Values.global.commonLabels }} + labels: + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or $.Values.global.commonAnnotations (dig "annotations" false $route) }} + annotations: + {{- if $.Values.global.commonAnnotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $route.annotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $route.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- $globalParentRef := $.Values.global.gatewayAPI.parentRef }} + {{- $localParentRef := dig "parentRef" dict $route }} + {{- $parentRefName := dig "name" (dig "name" "" $globalParentRef) $localParentRef }} + {{- $parentRefNamespace := dig "namespace" (dig "namespace" "" $globalParentRef) $localParentRef | default $.Release.Namespace }} + {{- $parentRefSectionName := dig "sectionName" (dig "sectionName" "" $globalParentRef) $localParentRef }} + {{- $parentRefPort := dig "port" (dig "port" "" $globalParentRef) $localParentRef }} + {{- if $parentRefName }} + parentRefs: + - name: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefName "context" $) }} + {{- if $parentRefNamespace }} + namespace: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefNamespace "context" $) }} + {{- end }} + {{- if $parentRefSectionName }} + sectionName: {{ $parentRefSectionName }} + {{- end }} + {{- if $parentRefPort }} + port: {{ $parentRefPort }} + {{- end }} + {{- end }} + rules: + {{- range $rule := $route.rules }} + - backendRefs: + {{- range $ref := $rule.backendRefs }} + - name: {{ $ref.name }} + port: {{ $ref.port }} + {{- if $ref.weight }} + weight: {{ $ref.weight }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/src/common/templates/_gateway_tlsroute.tpl b/src/common/templates/_gateway_tlsroute.tpl new file mode 100644 index 0000000..23a2c46 --- /dev/null +++ b/src/common/templates/_gateway_tlsroute.tpl @@ -0,0 +1,88 @@ +{{/* +TLSRoute template for Gateway API +Routes TLS traffic without termination (passthrough) based on SNI hostname + +USAGE (automatically called by renderIngress when gatewayAPI is enabled): +Define routes in your consuming chart's values.yaml under ingress.tlsRoutes: + +ingress: + tlsRoutes: + - name: tls-passthrough + hostnames: + - db.example.com + parentRef: # optional override of global parentRef + sectionName: tls-passthrough + rules: + - backendRefs: + - name: db-svc + port: 5432 +*/}} +{{- define "harnesscommon.v2.renderTLSRoute" }} +{{- $ := .ctx }} +{{- $ingress := $.Values.ingress }} +{{- if .ingress -}} + {{- $ingress = .ingress }} +{{- end }} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} +{{- $tlsRoutes := dig "tlsRoutes" list $ingress }} +{{- range $index, $route := $tlsRoutes }} +{{- $routeName := dig "name" ((cat (coalesce $ingress.name $.Values.nameOverride $.Chart.Name | trunc 63 | trimSuffix "-") "-tls-" $index) | nospace) $route }} +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TLSRoute +metadata: + name: {{ $routeName }} + namespace: {{ $.Release.Namespace }} + {{- if $.Values.global.commonLabels }} + labels: + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or $.Values.global.commonAnnotations (dig "annotations" false $route) }} + annotations: + {{- if $.Values.global.commonAnnotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $route.annotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $route.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- $globalParentRef := $.Values.global.gatewayAPI.parentRef }} + {{- $localParentRef := dig "parentRef" dict $route }} + {{- $parentRefName := dig "name" (dig "name" "" $globalParentRef) $localParentRef }} + {{- $parentRefNamespace := dig "namespace" (dig "namespace" "" $globalParentRef) $localParentRef | default $.Release.Namespace }} + {{- $parentRefSectionName := dig "sectionName" (dig "sectionName" "" $globalParentRef) $localParentRef }} + {{- $parentRefPort := dig "port" (dig "port" "" $globalParentRef) $localParentRef }} + {{- if $parentRefName }} + parentRefs: + - name: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefName "context" $) }} + {{- if $parentRefNamespace }} + namespace: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefNamespace "context" $) }} + {{- end }} + {{- if $parentRefSectionName }} + sectionName: {{ $parentRefSectionName }} + {{- end }} + {{- if $parentRefPort }} + port: {{ $parentRefPort }} + {{- end }} + {{- end }} + {{- if $route.hostnames }} + hostnames: + {{- range $hostname := $route.hostnames }} + - {{ $hostname | quote }} + {{- end }} + {{- end }} + rules: + {{- range $rule := $route.rules }} + - backendRefs: + {{- range $ref := $rule.backendRefs }} + - name: {{ $ref.name }} + port: {{ $ref.port }} + {{- if $ref.weight }} + weight: {{ $ref.weight }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/src/common/templates/_gateway_udproute.tpl b/src/common/templates/_gateway_udproute.tpl new file mode 100644 index 0000000..86adb4b --- /dev/null +++ b/src/common/templates/_gateway_udproute.tpl @@ -0,0 +1,81 @@ +{{/* +UDPRoute template for Gateway API +Routes UDP traffic to backend services (DNS, game servers, custom UDP protocols) + +USAGE (automatically called by renderIngress when gatewayAPI is enabled): +Define routes in your consuming chart's values.yaml under ingress.udpRoutes: + +ingress: + udpRoutes: + - name: dns-route + parentRef: # optional override of global parentRef + sectionName: udp-53 + port: 53 + rules: + - backendRefs: + - name: dns-svc + port: 53 +*/}} +{{- define "harnesscommon.v2.renderUDPRoute" }} +{{- $ := .ctx }} +{{- $ingress := $.Values.ingress }} +{{- if .ingress -}} + {{- $ingress = .ingress }} +{{- end }} +{{- if and (dig "gatewayAPI" "enabled" false $.Values.global) (dig "ingress" "enabled" false $.Values.global) -}} +{{- $udpRoutes := dig "udpRoutes" list $ingress }} +{{- range $index, $route := $udpRoutes }} +{{- $routeName := dig "name" ((cat (coalesce $ingress.name $.Values.nameOverride $.Chart.Name | trunc 63 | trimSuffix "-") "-udp-" $index) | nospace) $route }} +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: UDPRoute +metadata: + name: {{ $routeName }} + namespace: {{ $.Release.Namespace }} + {{- if $.Values.global.commonLabels }} + labels: + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or $.Values.global.commonAnnotations (dig "annotations" false $route) }} + annotations: + {{- if $.Values.global.commonAnnotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $.Values.global.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $route.annotations }} + {{- include "harnesscommon.tplvalues.render" ( dict "value" $route.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- $globalParentRef := $.Values.global.gatewayAPI.parentRef }} + {{- $localParentRef := dig "parentRef" dict $route }} + {{- $parentRefName := dig "name" (dig "name" "" $globalParentRef) $localParentRef }} + {{- $parentRefNamespace := dig "namespace" (dig "namespace" "" $globalParentRef) $localParentRef | default $.Release.Namespace }} + {{- $parentRefSectionName := dig "sectionName" (dig "sectionName" "" $globalParentRef) $localParentRef }} + {{- $parentRefPort := dig "port" (dig "port" "" $globalParentRef) $localParentRef }} + {{- if $parentRefName }} + parentRefs: + - name: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefName "context" $) }} + {{- if $parentRefNamespace }} + namespace: {{ include "harnesscommon.tplvalues.render" ( dict "value" $parentRefNamespace "context" $) }} + {{- end }} + {{- if $parentRefSectionName }} + sectionName: {{ $parentRefSectionName }} + {{- end }} + {{- if $parentRefPort }} + port: {{ $parentRefPort }} + {{- end }} + {{- end }} + rules: + {{- range $rule := $route.rules }} + - backendRefs: + {{- range $ref := $rule.backendRefs }} + - name: {{ $ref.name }} + port: {{ $ref.port }} + {{- if $ref.weight }} + weight: {{ $ref.weight }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/src/common/templates/_ingress.tpl b/src/common/templates/_ingress.tpl index 70f4c85..c7433dc 100644 --- a/src/common/templates/_ingress.tpl +++ b/src/common/templates/_ingress.tpl @@ -109,8 +109,13 @@ spec: {{- if and (hasKey .ctx.Values.global "gatewayAPI") (dig "gatewayAPI" "enabled" false .ctx.Values.global) }} # Gateway API resources (rendered by harnesscommon.v1.renderIngress) {{- include "harnesscommon.v2.renderHTTPRoute" . }} +{{- include "harnesscommon.v2.renderGRPCRoute" . }} +{{- include "harnesscommon.v2.renderTCPRoute" . }} +{{- include "harnesscommon.v2.renderTLSRoute" . }} +{{- include "harnesscommon.v2.renderUDPRoute" . }} {{- include "harnesscommon.v2.renderBackendTrafficPolicy" . }} {{- include "harnesscommon.v2.renderClientTrafficPolicy" . }} {{- include "harnesscommon.v2.renderSecurityPolicy" . }} +{{- include "harnesscommon.v2.renderBackendTLSPolicy" . }} {{- end }} {{- end }} From be7240c866fda628350a39bb046d3df30ff6bef2 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 15 May 2026 17:29:38 -0600 Subject: [PATCH 2/4] fix: [CLI-54049]: Omit HTTPRoute hostnames when disableHostInIngress is true Bare "*" is rejected by Gateway API CRD hostname validation (regex requires "*." or specific hostname). Omitting the field lets HTTPRoute inherit from the parent Gateway listener. Co-Authored-By: Claude Opus 4.6 AI-Session-Id: 4f9cdbba-9363-442e-a473-42572bf70f96 AI-Tool: claude-code AI-Model: unknown --- CHANGELOG.md | 5 ++++ .../ci-values/gateway-disable-host.yaml | 25 +++++++++++++++++++ .../tests/gateway_disable_host_test.yaml | 19 ++++++++++++++ src/common/Chart.yaml | 2 +- src/common/templates/_gateway_httproute.tpl | 7 +++--- 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 ci/test-chart/ci-values/gateway-disable-host.yaml create mode 100644 ci/test-chart/tests/gateway_disable_host_test.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e06d52..7a3504e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [1.7.1] - 2026-05-15 + +### Fixed +- HTTPRoute hostname validation error when `global.ingress.disableHostInIngress: true`. Previously emitted bare `*` which is rejected by Gateway API CRD validation (regex requires `*.` or specific hostname). Now omits the `hostnames` field entirely so the HTTPRoute inherits from the parent Gateway listener. + ## [1.7.0] - 2026-05-14 ### Added diff --git a/ci/test-chart/ci-values/gateway-disable-host.yaml b/ci/test-chart/ci-values/gateway-disable-host.yaml new file mode 100644 index 0000000..369b9e9 --- /dev/null +++ b/ci/test-chart/ci-values/gateway-disable-host.yaml @@ -0,0 +1,25 @@ +global: + ingress: + enabled: true + disableHostInIngress: true + hosts: + - api.example.com + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + objects: + - name: api-routes + paths: + - path: /api/.* + backend: + service: + name: test-service + port: 8080 + +service: + port: 8080 diff --git a/ci/test-chart/tests/gateway_disable_host_test.yaml b/ci/test-chart/tests/gateway_disable_host_test.yaml new file mode 100644 index 0000000..e5ce299 --- /dev/null +++ b/ci/test-chart/tests/gateway_disable_host_test.yaml @@ -0,0 +1,19 @@ +suite: Gateway API HTTPRoute disableHostInIngress (harnesscommon.v2.renderHTTPRoute) +values: + - ../values.yaml + - ../ci-values/gateway-disable-host.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: HTTPRoute should omit hostnames field when disableHostInIngress is true + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: test-service + asserts: + - isKind: + of: HTTPRoute + - notExists: + path: spec.hostnames diff --git a/src/common/Chart.yaml b/src/common/Chart.yaml index d1867cd..b88f264 100644 --- a/src/common/Chart.yaml +++ b/src/common/Chart.yaml @@ -15,7 +15,7 @@ type: library # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.7.0 +version: 1.7.1 # This is the version number of the application being deployed. This version number should be diff --git a/src/common/templates/_gateway_httproute.tpl b/src/common/templates/_gateway_httproute.tpl index b226cae..5b1bba4 100644 --- a/src/common/templates/_gateway_httproute.tpl +++ b/src/common/templates/_gateway_httproute.tpl @@ -78,10 +78,11 @@ spec: port: {{ $.Values.global.gatewayAPI.parentRef.port }} {{- end }} {{- end }} + {{- /* When disableHostInIngress is true, omit hostnames so HTTPRoute inherits from + the parent Gateway listener. Gateway API hostname validation rejects bare "*"; + valid wildcard form is "*.example.com". */}} + {{- if not $.Values.global.ingress.disableHostInIngress }} hostnames: - {{- if $.Values.global.ingress.disableHostInIngress }} - - "*" - {{- else }} {{- range $.Values.global.ingress.hosts }} - {{ . | quote }} {{- end }} From 4cc5410d7cbde242a6e1cad9cb0f9e6f9f4606fc Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 15 May 2026 17:48:07 -0600 Subject: [PATCH 3/4] fix: [CLI-54049]: Filter bare "*" from HTTPRoute hostnames Gateway API CRD hostname validation rejects bare "*". Filter it out of hosts, global additionalHostnames, and per-route additionalHostnames. When the resulting list is empty, omit the hostnames field so the route inherits from the parent Gateway listener. Co-Authored-By: Claude Opus 4.6 AI-Session-Id: 4f9cdbba-9363-442e-a473-42572bf70f96 AI-Tool: claude-code AI-Model: unknown --- CHANGELOG.md | 2 +- .../ci-values/gateway-example-host.yaml | 26 ++++++++++++++ .../ci-values/gateway-wildcard-host.yaml | 25 +++++++++++++ .../tests/gateway_example_host_test.yaml | 26 ++++++++++++++ .../tests/gateway_wildcard_host_test.yaml | 19 ++++++++++ src/common/templates/_gateway_httproute.tpl | 35 ++++++++++++------- 6 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 ci/test-chart/ci-values/gateway-example-host.yaml create mode 100644 ci/test-chart/ci-values/gateway-wildcard-host.yaml create mode 100644 ci/test-chart/tests/gateway_example_host_test.yaml create mode 100644 ci/test-chart/tests/gateway_wildcard_host_test.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a3504e..0112c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## [1.7.1] - 2026-05-15 ### Fixed -- HTTPRoute hostname validation error when `global.ingress.disableHostInIngress: true`. Previously emitted bare `*` which is rejected by Gateway API CRD validation (regex requires `*.` or specific hostname). Now omits the `hostnames` field entirely so the HTTPRoute inherits from the parent Gateway listener. +- HTTPRoute hostname validation error when `global.ingress.disableHostInIngress: true` or when `global.ingress.hosts` contains bare `"*"`. Gateway API CRD validation rejects bare `*` (regex requires `*.` or specific hostname). The template now filters bare `*` entries from all hostname sources (`hosts`, `additionalHostnames` global and per-route) and omits the `hostnames` field entirely when the resulting list is empty, so the HTTPRoute inherits from the parent Gateway listener. ## [1.7.0] - 2026-05-14 diff --git a/ci/test-chart/ci-values/gateway-example-host.yaml b/ci/test-chart/ci-values/gateway-example-host.yaml new file mode 100644 index 0000000..50d039f --- /dev/null +++ b/ci/test-chart/ci-values/gateway-example-host.yaml @@ -0,0 +1,26 @@ +global: + ingress: + enabled: true + hosts: + - example.com + - "*.example.com" + disableHostInIngress: false + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + objects: + - name: api-routes + paths: + - path: /api/.* + backend: + service: + name: test-service + port: 8080 + +service: + port: 8080 diff --git a/ci/test-chart/ci-values/gateway-wildcard-host.yaml b/ci/test-chart/ci-values/gateway-wildcard-host.yaml new file mode 100644 index 0000000..7382258 --- /dev/null +++ b/ci/test-chart/ci-values/gateway-wildcard-host.yaml @@ -0,0 +1,25 @@ +global: + ingress: + enabled: true + hosts: + - "*" + disableHostInIngress: false + + gatewayAPI: + enabled: true + parentRef: + name: test-gateway + namespace: gateway-system + +ingress: + objects: + - name: api-routes + paths: + - path: /api/.* + backend: + service: + name: test-service + port: 8080 + +service: + port: 8080 diff --git a/ci/test-chart/tests/gateway_example_host_test.yaml b/ci/test-chart/tests/gateway_example_host_test.yaml new file mode 100644 index 0000000..b76458a --- /dev/null +++ b/ci/test-chart/tests/gateway_example_host_test.yaml @@ -0,0 +1,26 @@ +suite: Gateway API HTTPRoute example hostnames (harnesscommon.v2.renderHTTPRoute) +values: + - ../values.yaml + - ../ci-values/gateway-example-host.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: HTTPRoute should render concrete and wildcard subdomain hostnames + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: test-service + asserts: + - isKind: + of: HTTPRoute + - equal: + path: spec.hostnames[0] + value: example.com + - equal: + path: spec.hostnames[1] + value: "*.example.com" + - lengthEqual: + path: spec.hostnames + count: 2 diff --git a/ci/test-chart/tests/gateway_wildcard_host_test.yaml b/ci/test-chart/tests/gateway_wildcard_host_test.yaml new file mode 100644 index 0000000..24e9265 --- /dev/null +++ b/ci/test-chart/tests/gateway_wildcard_host_test.yaml @@ -0,0 +1,19 @@ +suite: Gateway API HTTPRoute hosts=["*"] (harnesscommon.v2.renderHTTPRoute) +values: + - ../values.yaml + - ../ci-values/gateway-wildcard-host.yaml +templates: + - ingress.yaml +release: + name: harness-common-test + namespace: default +tests: + - it: HTTPRoute should omit hostnames field when hosts is exactly ["*"] + documentSelector: + path: spec.rules[0].backendRefs[0].name + value: test-service + asserts: + - isKind: + of: HTTPRoute + - notExists: + path: spec.hostnames diff --git a/src/common/templates/_gateway_httproute.tpl b/src/common/templates/_gateway_httproute.tpl index 5b1bba4..d9abe0d 100644 --- a/src/common/templates/_gateway_httproute.tpl +++ b/src/common/templates/_gateway_httproute.tpl @@ -78,27 +78,38 @@ spec: port: {{ $.Values.global.gatewayAPI.parentRef.port }} {{- end }} {{- end }} - {{- /* When disableHostInIngress is true, omit hostnames so HTTPRoute inherits from - the parent Gateway listener. Gateway API hostname validation rejects bare "*"; - valid wildcard form is "*.example.com". */}} + {{- /* Gateway API hostname validation rejects bare "*"; valid wildcard form is "*.example.com". + Build a filtered hostname list, dropping bare "*". If the resulting list is empty (or + disableHostInIngress is true), omit the hostnames field so the HTTPRoute inherits from the + parent Gateway listener. */}} + {{- $hostnameList := list }} {{- if not $.Values.global.ingress.disableHostInIngress }} - hostnames: {{- range $.Values.global.ingress.hosts }} - - {{ . | quote }} + {{- if ne . "*" }} + {{- $hostnameList = append $hostnameList . }} + {{- end }} {{- end }} - {{- /* Add additional hostnames from global config */}} {{- $globalHttpRoute := dig "httpRoute" dict $.Values.global.gatewayAPI }} {{- if $globalHttpRoute.additionalHostnames }} - {{- range $hostname := $globalHttpRoute.additionalHostnames }} - - {{ $hostname | quote }} - {{- end }} + {{- range $hostname := $globalHttpRoute.additionalHostnames }} + {{- if ne $hostname "*" }} + {{- $hostnameList = append $hostnameList $hostname }} + {{- end }} + {{- end }} {{- end }} - {{- /* Add additional hostnames from per-route config */}} {{- $perRouteHttpRoute := dig "gatewayAPI" dict $object }} {{- if $perRouteHttpRoute.additionalHostnames }} - {{- range $hostname := $perRouteHttpRoute.additionalHostnames }} - - {{ $hostname | quote }} + {{- range $hostname := $perRouteHttpRoute.additionalHostnames }} + {{- if ne $hostname "*" }} + {{- $hostnameList = append $hostnameList $hostname }} + {{- end }} + {{- end }} {{- end }} + {{- end }} + {{- if $hostnameList }} + hostnames: + {{- range $hostname := $hostnameList }} + - {{ $hostname | quote }} {{- end }} {{- end }} rules: From 709ad106e3a336052ddf1a749911dbc0d3f7cdae Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 18 May 2026 09:37:47 -0600 Subject: [PATCH 4/4] chore: [CLI-54049]: Fixing some items related to hostnames --- CHANGELOG.md | 7 +++++ ci/test-chart/Chart.lock | 6 ++-- src/common/Chart.yaml | 2 +- src/common/templates/_gateway_grpcroute.tpl | 33 ++++++++++++++++++++- src/common/templates/_gateway_tlsroute.tpl | 33 ++++++++++++++++++++- 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0112c78..8349712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.7.2] - 2026-05-18 + +### Fixed +- **GRPCRoute & TLSRoute hostname inheritance**: Both now inherit hostnames from `global.ingress.hosts` when `hostnames` is not explicitly set per-route, matching HTTPRoute behavior. Previously these routes only emitted `spec.hostnames` when explicitly set per-route, requiring duplicate hostname configuration. +- Bare `*` filtering also applied to GRPCRoute and TLSRoute hostname sources for consistency with HTTPRoute (Gateway API CRD validation rejects bare `*`). +- Per-route `additionalHostnames` and global `gatewayAPI.grpcRoute.additionalHostnames` / `gatewayAPI.tlsRoute.additionalHostnames` are now supported as additive sources, mirroring the HTTPRoute pattern. + ## [1.7.1] - 2026-05-15 ### Fixed diff --git a/ci/test-chart/Chart.lock b/ci/test-chart/Chart.lock index 40351f6..1508780 100644 --- a/ci/test-chart/Chart.lock +++ b/ci/test-chart/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: harness-common repository: file://../../src/common - version: 1.6.3 -digest: sha256:b5bf10bcf60184437a0b08ff170b4c7d67630c7239571f18af13824a64250253 -generated: "2026-05-14T13:46:18.921158-06:00" + version: 1.7.1 +digest: sha256:d52254d3aae6648b3d1d7a4c1c0e44660a4898a3af182bb9710d00bebe2933ff +generated: "2026-05-15T17:47:13.81928-06:00" diff --git a/src/common/Chart.yaml b/src/common/Chart.yaml index b88f264..1b62569 100644 --- a/src/common/Chart.yaml +++ b/src/common/Chart.yaml @@ -15,7 +15,7 @@ type: library # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.7.1 +version: 1.7.2 # This is the version number of the application being deployed. This version number should be diff --git a/src/common/templates/_gateway_grpcroute.tpl b/src/common/templates/_gateway_grpcroute.tpl index 854e3f5..b65d668 100644 --- a/src/common/templates/_gateway_grpcroute.tpl +++ b/src/common/templates/_gateway_grpcroute.tpl @@ -75,9 +75,40 @@ spec: port: {{ $parentRefPort }} {{- end }} {{- end }} + {{- /* Hostname inheritance: per-route hostnames override; otherwise inherit from global.ingress.hosts. + Gateway API hostname validation rejects bare "*"; filter it from any source. */}} + {{- $hostnameList := list }} {{- if $route.hostnames }} + {{- range $route.hostnames }} + {{- if ne . "*" }} + {{- $hostnameList = append $hostnameList . }} + {{- end }} + {{- end }} + {{- else if not (dig "ingress" "disableHostInIngress" false $.Values.global) }} + {{- range (dig "ingress" "hosts" list $.Values.global) }} + {{- if ne . "*" }} + {{- $hostnameList = append $hostnameList . }} + {{- end }} + {{- end }} + {{- $globalGRPCRoute := dig "grpcRoute" dict (dig "gatewayAPI" dict $.Values.global) }} + {{- if $globalGRPCRoute.additionalHostnames }} + {{- range $hostname := $globalGRPCRoute.additionalHostnames }} + {{- if ne $hostname "*" }} + {{- $hostnameList = append $hostnameList $hostname }} + {{- end }} + {{- end }} + {{- end }} + {{- if $route.additionalHostnames }} + {{- range $hostname := $route.additionalHostnames }} + {{- if ne $hostname "*" }} + {{- $hostnameList = append $hostnameList $hostname }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if $hostnameList }} hostnames: - {{- range $hostname := $route.hostnames }} + {{- range $hostname := $hostnameList }} - {{ $hostname | quote }} {{- end }} {{- end }} diff --git a/src/common/templates/_gateway_tlsroute.tpl b/src/common/templates/_gateway_tlsroute.tpl index 23a2c46..c9a3439 100644 --- a/src/common/templates/_gateway_tlsroute.tpl +++ b/src/common/templates/_gateway_tlsroute.tpl @@ -66,9 +66,40 @@ spec: port: {{ $parentRefPort }} {{- end }} {{- end }} + {{- /* Hostname inheritance: per-route hostnames override; otherwise inherit from global.ingress.hosts. + Gateway API hostname validation rejects bare "*"; filter it from any source. */}} + {{- $hostnameList := list }} {{- if $route.hostnames }} + {{- range $route.hostnames }} + {{- if ne . "*" }} + {{- $hostnameList = append $hostnameList . }} + {{- end }} + {{- end }} + {{- else if not (dig "ingress" "disableHostInIngress" false $.Values.global) }} + {{- range (dig "ingress" "hosts" list $.Values.global) }} + {{- if ne . "*" }} + {{- $hostnameList = append $hostnameList . }} + {{- end }} + {{- end }} + {{- $globalTLSRoute := dig "tlsRoute" dict (dig "gatewayAPI" dict $.Values.global) }} + {{- if $globalTLSRoute.additionalHostnames }} + {{- range $hostname := $globalTLSRoute.additionalHostnames }} + {{- if ne $hostname "*" }} + {{- $hostnameList = append $hostnameList $hostname }} + {{- end }} + {{- end }} + {{- end }} + {{- if $route.additionalHostnames }} + {{- range $hostname := $route.additionalHostnames }} + {{- if ne $hostname "*" }} + {{- $hostnameList = append $hostnameList $hostname }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if $hostnameList }} hostnames: - {{- range $hostname := $route.hostnames }} + {{- range $hostname := $hostnameList }} - {{ $hostname | quote }} {{- end }} {{- end }}