diff --git a/CHANGELOG.md b/CHANGELOG.md index da9bfe8..8349712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # 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 +- 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 + +### 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/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/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-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/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-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/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_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_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/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_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/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/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..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.6.3 +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_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..b65d668 --- /dev/null +++ b/src/common/templates/_gateway_grpcroute.tpl @@ -0,0 +1,166 @@ +{{/* +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 }} + {{- /* 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 := $hostnameList }} + - {{ $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..d9abe0d 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 }} @@ -78,26 +78,38 @@ spec: port: {{ $.Values.global.gatewayAPI.parentRef.port }} {{- end }} {{- end }} - hostnames: - {{- if $.Values.global.ingress.disableHostInIngress }} - - "*" - {{- else }} + {{- /* 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 }} {{- 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: 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..c9a3439 --- /dev/null +++ b/src/common/templates/_gateway_tlsroute.tpl @@ -0,0 +1,119 @@ +{{/* +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 }} + {{- /* 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 := $hostnameList }} + - {{ $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 }}