Skip to content

Remove or implement unwired APIGateway CRD spec fields #3287

Description

@renuka-fernando

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.generationreliable 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions