Skip to content

feat: add Gateway support for LRSQL chart#114

Merged
apkatsikas merged 4 commits intomainfrom
feat/gateway-lrsql
May 6, 2026
Merged

feat: add Gateway support for LRSQL chart#114
apkatsikas merged 4 commits intomainfrom
feat/gateway-lrsql

Conversation

@apkatsikas
Copy link
Copy Markdown
Contributor

@apkatsikas apkatsikas commented Apr 23, 2026

Adds Gateway API support to the lrsql chart via HTTPRoute and optional ListenerSet resources, alongside cleanup of faulty and misleading ingress values.

All Gateway API resources are gated behind a single gateway.enabled flag.

HTTPRoute

When gateway.enabled is true, an HTTPRoute is rendered that:

  • Parents directly to the configured gateway.httpRoute.gatewayName by default
  • Auto-wires the backendRef to the chart's own service (consistent with how ingress works)
  • Defaults to a PathPrefix / rule, overridable via gateway.httpRoute.path / gateway.httpRoute.pathType
  • Supports optional sectionName and port to target a specific listener on the parent Gateway
  • Supports a full rules override for advanced routing (redirects, rewrites, weighted backends)

ListenerSet (optional)

When gateway.listenerSet.enabled is true, a ListenerSet is rendered alongside the HTTPRoute. The ListenerSet:

  • Parents to its own Gateway via gateway.listenerSet.gatewayName / gateway.listenerSet.gatewayNamespace
  • The HTTPRoute parents to the ListenerSet instead of the Gateway directly
  • Always sets allowedRoutes.namespaces.from: Same

This avoids having to declare the hostname in both the Gateway listener and the HTTPRoute, and removes the need for wildcard listener entries on the Gateway.

Two ways to configure listeners:

  • gateway.listenerSet.https.enabled: true — convenience shorthand that generates a default HTTPS listener on port 443 with mode: Terminate, deriving the cert secret name from gateway.hostname (e.g. example.com-tls) unless overridden via gateway.listenerSet.https.secretName
  • gateway.listenerSet.listeners — full override, replacing the auto-generated listener entirely

Setting gateway.httpRoute.gatewayName, gatewayNamespace, sectionName, or port while gateway.listenerSet.enabled is true is a render-time error — those fields are unused in that path and the conflict is flagged explicitly.

Ingress cleanup

  • Removed selfSigned parameter — it only gated the TLS stanza rendering but no template existed to generate a self-signed cert, making it dead and misleading
  • Updated ingress.tls comment to accurately describe what it does (enables the TLS stanza; does not create the secret itself)
  • Fixed typo: "relay" → "rely"

Testing

All commands run from the repo root (charts/).

HTTPRoute only — default rule, parents to Gateway

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.httpRoute.gatewayName=my-gateway \
  --set gateway.httpRoute.gatewayNamespace=infra

Expected: HTTPRoute with parentRef pointing to my-gateway in infra, PathPrefix / rule, backendRef to test-lrsql:80.


HTTPRoute only — custom path

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.httpRoute.gatewayName=my-gateway \
  --set gateway.httpRoute.path=/api \
  --set gateway.httpRoute.pathType=Exact

Expected: same as above but path.type: Exact, path.value: /api.


HTTPRoute only — sectionName and port

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.httpRoute.gatewayName=my-gateway \
  --set gateway.httpRoute.sectionName=https \
  --set gateway.httpRoute.port=443

Expected: parentRef includes sectionName: https and port: 443.


HTTPRoute + ListenerSet — https.enabled (default secret name)

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --show-only templates/listenerset.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.listenerSet.enabled=true \
  --set gateway.listenerSet.gatewayName=my-gateway

Expected:

  • HTTPRoute parentRef points to the ListenerSet (kind: ListenerSet, same name/namespace)
  • ListenerSet parentRef points to my-gateway with no namespace (same-namespace assumed)
  • ListenerSet has one HTTPS listener on port 443, hostname: "example.com", certificateRefs[0].name: "example.com-tls", allowedRoutes.namespaces.from: Same

HTTPRoute + ListenerSet — custom secret name

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --show-only templates/listenerset.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.listenerSet.enabled=true \
  --set gateway.listenerSet.gatewayName=my-gateway \
  --set gateway.listenerSet.https.secretName=my-custom-cert

Expected: same as above but certificateRefs[0].name: "my-custom-cert".


HTTPRoute + ListenerSet — full listeners override

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --show-only templates/listenerset.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.listenerSet.enabled=true \
  --set gateway.listenerSet.gatewayName=my-gateway \
  --set 'gateway.listenerSet.listeners[0].name=https' \
  --set 'gateway.listenerSet.listeners[0].protocol=HTTPS' \
  --set 'gateway.listenerSet.listeners[0].port=443' \
  --set 'gateway.listenerSet.listeners[0].tls.mode=Terminate' \
  --set 'gateway.listenerSet.listeners[0].tls.certificateRefs[0].name=my-cert-secret'

Expected: ListenerSet uses the provided listener entry verbatim.


listenerSet.enabled without https.enabled or listeners — should fail

helm template test charts/lrsql \
  --show-only templates/listenerset.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.listenerSet.enabled=true \
  --set gateway.listenerSet.gatewayName=my-gateway \
  --set gateway.listenerSet.https.enabled=false

Expected: render fails with gateway.listenerSet requires either gateway.listenerSet.https.enabled or a non-empty gateway.listenerSet.listeners.


httpRoute gateway fields set with listenerSet.enabled — should fail

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com \
  --set gateway.listenerSet.enabled=true \
  --set gateway.listenerSet.gatewayName=my-gateway \
  --set gateway.httpRoute.gatewayName=other-gateway

Expected: render fails with gateway.httpRoute.gatewayName/gatewayNamespace/sectionName/port are ignored when gateway.listenerSet.enabled is true.


gateway.httpRoute.gatewayName missing when listenerSet disabled — should fail

helm template test charts/lrsql \
  --show-only templates/httproute.yaml \
  --set gateway.enabled=true \
  --set gateway.hostname=example.com

Expected: render fails with gateway.httpRoute.gatewayName is required when gateway.enabled is true and gateway.listenerSet.enabled is false.

@apkatsikas apkatsikas requested a review from emmanuel April 23, 2026 19:41
@emmanuel
Copy link
Copy Markdown
Contributor

emmanuel commented Apr 23, 2026

Ideally this would have a couple of layers/knobs:

  1. Ingress and gateway independently configured (enable zero, one, the other, or both)
    1. this supports either/both API and migration between
    2. looks good here
  2. Gateway supports both full Gateway config OR ListenerSet
    1. this is an exclusive or, the chart logic should make it impossible to select both
  3. Chart's values (in values.yaml) for Ingress should remain as is, and new Gateway values should be as simple as possible (but no simpler)
    1. in general it's undesirable to have the Chart values slavishly follow the structure of the rendered objects (just a note, not saying that's the case here)
    2. instead, try to structure the Chart values in terms of the simplest/most-terse structure that can express the full range of desired configuration

I think that the structure surfaced in this article is a good place to start, eg:

ingress:
  enabled: false
  className: nginx
  annotations: {}
  ...

gateway:
  enabled: true
  gatewayName: default-gateway
  # only one of `gatewayClassName` or `listenerSetEnabled: true` is allowed
  # gatewayClassName: envoy-gateway
  listenerSetEnabled: true
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      hostname: app.example.com
    - name: https
      protocol: HTTPS
      port: 443
      hostname: app.example.com
      tls:
        certificateRefs:
          - name: app-tls
  routes:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-service
          port: 8080

Then then listeners: [] array values get rendered out as a ListenerSet or raw Gateway depending on the presence of gatewayClassName vs listenerSetEnabled.

@apkatsikas
Copy link
Copy Markdown
Contributor Author

Ideally this would have a couple of layers/knobs:

  1. Ingress and gateway independently configured (enable zero, one, the other, or both)

    1. this supports either/both API and migration between
    2. looks good here
  2. Gateway supports both full Gateway config OR ListenerSet

    1. this is an exclusive or, the chart logic should make it impossible to select both
  3. Chart's values (in values.yaml) for Ingress should remain as is, and new Gateway values should be as simple as possible (but no simpler)

    1. in general it's undesirable to have the Chart values slavishly follow the structure of the rendered objects (just a note, not saying that's the case here)
    2. instead, try to structure the Chart values in terms of the simplest/most-terse structure that can express the full range of desired configuration

I think that the structure surfaced in this article is a good place to start, eg:

ingress:
  enabled: false
  className: nginx
  annotations: {}
  ...

gateway:
  enabled: true
  gatewayName: default-gateway
  # only one of `gatewayClassName` or `listenerSetEnabled: true` is allowed
  # gatewayClassName: envoy-gateway
  listenerSetEnabled: true
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      hostname: app.example.com
    - name: https
      protocol: HTTPS
      port: 443
      hostname: app.example.com
      tls:
        certificateRefs:
          - name: app-tls
  routes:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api-service
          port: 8080

Then then listeners: [] array values get rendered out as a ListenerSet or raw Gateway depending on the presence of gatewayClassName vs listenerSetEnabled.

Thanks for meeting with me on this! As discussed, I will avoid supporting the creation of a Gateway resource itself, but use gateway.enabled as the gating mechanism for gateway support and strive for a terse values structure.

@emmanuel
Copy link
Copy Markdown
Contributor

emmanuel commented May 4, 2026

One question: any reason for gateway.httproute and gateway.listenerset to accept separate args for gatewayName?

I can't think of a case to configure listeners on one gateway and httproute on another (i.e., different gatewayName values).

@apkatsikas
Copy link
Copy Markdown
Contributor Author

One question: any reason for gateway.httproute and gateway.listenerset to accept separate args for gatewayName?

I can't think of a case to configure listeners on one gateway and httproute on another (i.e., different gatewayName values).

While there is never a case where you would want to configure different gateway names/namespaces on httproute and listenerset, I kept them separate because they refer to different resources' parent refs. httpRoute.gatewayName is what the HTTPRoute attaches to, while listenerSet.gatewayName is what the ListenerSet attaches to (in listenerSet mode the HTTPRoute's actual parent is the ListenerSet, not the Gateway). A shared gateway.gatewayName would mean two different things depending on the mode, which felt more misleading than the duplication. And further, the sectionName and port on the httproute refers to the parent gateway and needs to be nested under the httproute, as it is not applicable for a listener set, and I didn't want to have some gateway references at the top level and then others as a subsection to httproute.

@apkatsikas apkatsikas merged commit 8b7cba5 into main May 6, 2026
3 checks passed
@apkatsikas apkatsikas deleted the feat/gateway-lrsql branch May 6, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants