Description
The APIGateway CRD (gateway.api-platform.wso2.com/v1) defines several typed spec fields that the API server accepts but the operator's reconcile/deploy path silently ignores, so setting them has no effect on the deployed gateway. The only field that actually configures the gateway today is spec.configRef (a ConfigMap of Helm values deep-merged over the operator default). This is a confusing false affordance — e.g. a user sets spec.infrastructure.image or spec.infrastructure.replicas and nothing changes.
Root cause: buildCRValuesOverlay (kubernetes/gateway-operator/internal/controller/apigateway_controller.go:757-779), the only function that turns spec.infrastructure into Helm values, reads only infra.Labels and infra.Annotations. Nothing reads infra.Image/RouterImage/Replicas/Resources/NodeSelector/Tolerations/Affinity, spec.storage, or spec.controlPlane.tls/tokenSecretRef. spec.controlPlane.host is only recorded in the operator's in-memory registry (registerGatewayInRegistry), never applied to the deployed gateway.
What is wired vs not (APIGateway CRD path):
| Field |
Status |
spec.apiSelector |
✅ implemented (required) |
spec.configRef |
✅ implemented (the real config surface) |
spec.infrastructure.labels / annotations |
✅ implemented (→ commonLabels/commonAnnotations) |
spec.controlPlane.host |
⚠️ recorded in operator registry only, not deployed |
spec.controlPlane.tls / tokenSecretRef |
❌ ignored |
spec.infrastructure.image / routerImage |
❌ ignored |
spec.infrastructure.replicas / resources |
❌ ignored |
spec.infrastructure.nodeSelector / tolerations / affinity |
❌ ignored |
spec.storage.* |
❌ ignored |
Sample CR showing the gap:
apiVersion: gateway.api-platform.wso2.com/v1
kind: APIGateway
metadata:
name: sample-gw
namespace: sample-gw
spec:
apiSelector: # ✅ implemented (required)
scope: Namespaced
configRef: # ✅ implemented — the real config surface
name: sample-gw-values
infrastructure:
labels: { team: platform } # ✅ implemented → commonLabels
annotations: # ✅ implemented → commonAnnotations
prometheus.io/scrape: "true"
image: ghcr.io/wso2/api-platform/gateway-controller:1.2.0 # ❌ ignored
routerImage: ghcr.io/wso2/api-platform/gateway-runtime:1.2.0 # ❌ ignored
replicas: 3 # ❌ ignored
resources: # ❌ ignored
requests: { cpu: "500m", memory: 1Gi }
nodeSelector: { disktype: ssd } # ❌ ignored
tolerations: [] # ❌ ignored
affinity: {} # ❌ ignored
controlPlane:
host: "cp.example.svc.cluster.local:8443" # ⚠️ registry-only, not deployed
tls: { enabled: true } # ❌ ignored
tokenSecretRef: { name: cp-token, key: token } # ❌ ignored
storage:
type: sqlite # ❌ ignored
Where each ignored field must be set today (via spec.configRef values.yaml):
| CRD field (ignored) |
Equivalent configRef key |
infrastructure.image |
gateway.controller.image.{repository,tag,pullPolicy} |
infrastructure.routerImage |
gateway.gatewayRuntime.image.{repository,tag,pullPolicy} |
infrastructure.replicas |
gateway.controller.deployment.replicaCount (+ gatewayRuntime) |
infrastructure.resources |
gateway.controller.deployment.resources (+ gatewayRuntime) |
infrastructure.nodeSelector/tolerations/affinity |
gateway.controller.deployment.{nodeSelector,tolerations,affinity} (+ gatewayRuntime) |
storage.type |
gateway.config.controller.storage.type |
controlPlane.host/token |
gateway.controller.controlPlane.{host,token} |
spec.configRef is a superset — most of these are already settable there.
Every ignored field in the table above already has a configRef equivalent, so configRef fully covers them today, which makes the typed infrastructure.* / storage fields effectively redundant duplication of the chart's values surface. Two exceptions:
spec.controlPlane.tls maps to nothing in the gateway chart — control-plane TLS is implied by the host authority (HTTPS/WSS scheme + port); the chart's controller.controlPlane block has only host + token. This field has no backing at all and should simply be removed.
spec.apiSelector is operator-level API-selection logic, not a chart value — and it is already implemented.
Proposed resolution — pick one surface, don't leave both half-wired:
Option A — Implement the typed fields as the HIGHEST-priority override (recommended). Wire them in buildCRValuesOverlay → the chart keys above, and give the typed CRD fields precedence over configRef, so resolution becomes (highest wins):
typed spec.infrastructure fields (image, replicas, resources, ...) <-- highest
> spec.configRef ConfigMap
> spec.infrastructure labels/annotations
> operator default (GATEWAY_HELM_VALUES_FILE_PATH)
> gateway-helm-chart built-in values <-- lowest
Rationale: typed fields are validated + discoverable, and a .spec change bumps metadata.generation → reliable rollout (unlike a configRef edit, which is only picked up via the fragile ConfigMap-watch/hash path). This mirrors the params model other Gateway API operators use (kgateway GatewayParameters, Envoy Gateway EnvoyProxy). It is also backward-compatible for a GA CRD — additive wiring, no field removal. Behaviour-change caveat: a CR that already sets one of these (ignored today) would start taking effect and now win over its configRef — call this out in release notes.
Option B — Remove the redundant fields. Since configRef already covers them, drop infrastructure.{image,routerImage,replicas,resources,nodeSelector,tolerations,affinity} and storage from the CRD, leaving configRef as the single surface. Simpler, but the CRD is GA, so removal is a breaking schema change that needs a deprecation cycle.
Either way: remove spec.controlPlane.tls (no chart backing), and make the precedence explicit in each field's CRD description: and in the sample CR — e.g. annotate image as "Overrides gateway.controller.image in configRef and the operator default" (Option A) or "Deprecated: set via configRef" (Option B) — so kubectl explain / the YAML itself tells the user what the field does.
The same gap exists on the Gateway API path overlay (gateway_infrastructure_overlay.go also handles only labels/annotations/service).
Version
No response
Related Issue
No response
Description
The
APIGatewayCRD (gateway.api-platform.wso2.com/v1) defines several typedspecfields that the API server accepts but the operator's reconcile/deploy path silently ignores, so setting them has no effect on the deployed gateway. The only field that actually configures the gateway today isspec.configRef(a ConfigMap of Helm values deep-merged over the operator default). This is a confusing false affordance — e.g. a user setsspec.infrastructure.imageorspec.infrastructure.replicasand nothing changes.Root cause:
buildCRValuesOverlay(kubernetes/gateway-operator/internal/controller/apigateway_controller.go:757-779), the only function that turnsspec.infrastructureinto Helm values, reads onlyinfra.Labelsandinfra.Annotations. Nothing readsinfra.Image/RouterImage/Replicas/Resources/NodeSelector/Tolerations/Affinity,spec.storage, orspec.controlPlane.tls/tokenSecretRef.spec.controlPlane.hostis only recorded in the operator's in-memory registry (registerGatewayInRegistry), never applied to the deployed gateway.What is wired vs not (APIGateway CRD path):
spec.apiSelectorspec.configRefspec.infrastructure.labels/annotationscommonLabels/commonAnnotations)spec.controlPlane.hostspec.controlPlane.tls/tokenSecretRefspec.infrastructure.image/routerImagespec.infrastructure.replicas/resourcesspec.infrastructure.nodeSelector/tolerations/affinityspec.storage.*Sample CR showing the gap:
Where each ignored field must be set today (via
spec.configRefvalues.yaml):configRefkeyinfrastructure.imagegateway.controller.image.{repository,tag,pullPolicy}infrastructure.routerImagegateway.gatewayRuntime.image.{repository,tag,pullPolicy}infrastructure.replicasgateway.controller.deployment.replicaCount(+gatewayRuntime)infrastructure.resourcesgateway.controller.deployment.resources(+gatewayRuntime)infrastructure.nodeSelector/tolerations/affinitygateway.controller.deployment.{nodeSelector,tolerations,affinity}(+gatewayRuntime)storage.typegateway.config.controller.storage.typecontrolPlane.host/tokengateway.controller.controlPlane.{host,token}spec.configRefis a superset — most of these are already settable there.Every ignored field in the table above already has a
configRefequivalent, soconfigReffully covers them today, which makes the typedinfrastructure.*/storagefields effectively redundant duplication of the chart's values surface. Two exceptions:spec.controlPlane.tlsmaps to nothing in the gateway chart — control-plane TLS is implied by thehostauthority (HTTPS/WSS scheme + port); the chart'scontroller.controlPlaneblock has onlyhost+token. This field has no backing at all and should simply be removed.spec.apiSelectoris operator-level API-selection logic, not a chart value — and it is already implemented.Proposed resolution — pick one surface, don't leave both half-wired:
Option A — Implement the typed fields as the HIGHEST-priority override (recommended). Wire them in
buildCRValuesOverlay→ the chart keys above, and give the typed CRD fields precedence overconfigRef, so resolution becomes (highest wins):Rationale: typed fields are validated + discoverable, and a
.specchange bumpsmetadata.generation→ reliable rollout (unlike aconfigRefedit, which is only picked up via the fragile ConfigMap-watch/hash path). This mirrors the params model other Gateway API operators use (kgatewayGatewayParameters, Envoy GatewayEnvoyProxy). It is also backward-compatible for a GA CRD — additive wiring, no field removal. Behaviour-change caveat: a CR that already sets one of these (ignored today) would start taking effect and now win over itsconfigRef— call this out in release notes.Option B — Remove the redundant fields. Since
configRefalready covers them, dropinfrastructure.{image,routerImage,replicas,resources,nodeSelector,tolerations,affinity}andstoragefrom the CRD, leavingconfigRefas the single surface. Simpler, but the CRD is GA, so removal is a breaking schema change that needs a deprecation cycle.Either way: remove
spec.controlPlane.tls(no chart backing), and make the precedence explicit in each field's CRDdescription:and in the sample CR — e.g. annotateimageas "Overridesgateway.controller.imagein configRef and the operator default" (Option A) or "Deprecated: set via configRef" (Option B) — sokubectl explain/ the YAML itself tells the user what the field does.The same gap exists on the Gateway API path overlay (
gateway_infrastructure_overlay.goalso handles only labels/annotations/service).Version
No response
Related Issue
No response