From fe0c426b0231e16eafe53e59e023f67d761d0dec Mon Sep 17 00:00:00 2001 From: Musa Misto <64855513+MusaMisto@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:44:53 +0300 Subject: [PATCH] fix(chart): harden Gateway API routing and fix fatal secrets-loop scoping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway value schema already matched s9genericchart-v2 exactly (the HTTPRoute template is byte-identical), so this changes no schema. It fixes the four ways that schema could fail silently or fatally, which matters because the mtm chart backs six live releases (funride, kwickbox and tamweelna x staging/production) that are all about to move onto the shared Cilium Gateway. - httproute/ingress/NOTES: guard `.Values.gateway` and `.Values.ingress` before dereferencing `.enabled`. Nulling either block raised a nil-pointer error instead of simply rendering no route. - httproute: normalise `backendRef` to a dict before use. A route carrying an explicitly empty `backendRef:` — the exact shape produced by commenting out the two children shipped in values.yaml — crashed with "interface conversion: interface {} is nil". - httproute: `path` is now `required`. It previously rendered `value:` (null), producing an HTTPRoute that Helm accepted and the API server rejected. - httproute: emit `backendRef.weight` via `hasKey` rather than `with`, so an explicit `weight: 0` is honoured instead of silently discarded. - deployment: pass `$root` to project.fullname inside the `.Values.secrets` range. `.` is rebound to the map value there, so ANY release that set `secrets.*` failed to render with "can't evaluate field Values in type string". `$root` was already captured for this and never used. Verified: `helm lint --strict` clean; all four failures reproduced before and confirmed fixed after; and rendering the chart against the live user-supplied values of all six production/staging releases produces byte-identical output, so no existing deployment changes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01VHkpqv6dB6wjALyFe9ocMU --- charts/default/templates/NOTES.txt | 4 +-- charts/default/templates/deployment.yaml | 9 ++++- charts/default/templates/httproute.yaml | 45 +++++++++++++++++++----- charts/default/templates/ingress.yaml | 7 +++- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/charts/default/templates/NOTES.txt b/charts/default/templates/NOTES.txt index 60dc10f..44715df 100644 --- a/charts/default/templates/NOTES.txt +++ b/charts/default/templates/NOTES.txt @@ -1,5 +1,5 @@ 1. Get the application URL by running these commands: -{{- if .Values.gateway.enabled }} +{{- if and .Values.gateway .Values.gateway.enabled }} Gateway API (HTTPRoute) is enabled. {{- range .Values.gateway.hostnames }} {{- $host := . -}} @@ -7,7 +7,7 @@ https://{{ $host }}{{ .path }} {{- end }} {{- end }} -{{- else if .Values.ingress.enabled }} +{{- else if and .Values.ingress .Values.ingress.enabled }} {{- range .Values.ingress.hosts }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} {{- end }} diff --git a/charts/default/templates/deployment.yaml b/charts/default/templates/deployment.yaml index afe4093..7a1e190 100644 --- a/charts/default/templates/deployment.yaml +++ b/charts/default/templates/deployment.yaml @@ -124,7 +124,14 @@ spec: - name: {{ $key }} valueFrom: secretKeyRef: - name: {{ template "project.fullname" . }} + {{- /* + $root, not `.`: inside a range over a map, `.` is rebound to + the map VALUE (a string), so `template "project.fullname" .` + failed with `can't evaluate field Values in type string` and + made ANY release that set `secrets.*` fail to render at all. + $root was already captured above for exactly this purpose. + */}} + name: {{ template "project.fullname" $root }} key: {{ $key }} {{- end }} diff --git a/charts/default/templates/httproute.yaml b/charts/default/templates/httproute.yaml index abe31c0..4c38059 100644 --- a/charts/default/templates/httproute.yaml +++ b/charts/default/templates/httproute.yaml @@ -1,4 +1,14 @@ -{{- if .Values.gateway.enabled -}} +{{- /* +Gateway API routing. Schema is intentionally identical to s9genericchart-v2's +httproute.yaml so that one set of gateway values deploys against either chart +(see charts/default/README.md § Gateway API). + +`and .Values.gateway .Values.gateway.enabled` rather than a bare +`.Values.gateway.enabled`: a caller that nulls the whole block (`--set +gateway=null`, or a values file with a bare `gateway:` key) would otherwise +crash with a nil-pointer error instead of simply rendering no route. +*/ -}} +{{- if and .Values.gateway .Values.gateway.enabled -}} {{- $fullName := include "project.fullname" . -}} apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -9,10 +19,17 @@ metadata: spec: parentRefs: {{- range .Values.gateway.parentRefs }} - - name: {{ .name }} + - name: {{ required "gateway.parentRefs[].name is required — it must name an existing Gateway." .name }} {{- with .namespace }} namespace: {{ . }} {{- end }} + {{- /* + sectionName pins the route to ONE named listener on the Gateway. Omitting + it attaches the route to every listener whose hostname matches, which on a + multi-domain gateway is rarely what you want. A sectionName that does not + match a live listener makes the route report Accepted=False / + NoMatchingParent — loud, and therefore safe. + */ -}} {{- with .sectionName }} sectionName: {{ . }} {{- end }} @@ -25,10 +42,17 @@ spec: {{- end }} rules: {{- range .Values.gateway.routes }} + {{- /* + Normalise backendRef to a dict up front. The previous `dig "backendRef" ...` + form raised "interface conversion: interface {} is nil" when a route carried + an explicitly empty `backendRef:` key — which is exactly the shape a user + produces by commenting out the two children in values.yaml. + */ -}} + {{- $backendRef := default (dict) .backendRef }} - matches: - path: type: {{ .pathType | default "PathPrefix" }} - value: {{ .path | quote }} + value: {{ required "gateway.routes[].path is required (e.g. \"/\" or \"/mtm\")." .path | quote }} {{- with .timeout }} timeouts: {{- with .request }} @@ -39,13 +63,18 @@ spec: {{- end }} {{- end }} backendRefs: - - name: {{ dig "backendRef" "name" "" . | default $fullName }} - port: {{ dig "backendRef" "port" 0 . | default $.Values.service.port }} - {{- with dig "backendRef" "namespace" "" . }} + - name: {{ $backendRef.name | default $fullName }} + port: {{ $backendRef.port | default $.Values.service.port }} + {{- with $backendRef.namespace }} namespace: {{ . }} {{- end }} - {{- with dig "backendRef" "weight" 0 . }} - weight: {{ . }} + {{- /* + hasKey, not `with`: `with` treats 0 as falsy, so an explicit + `weight: 0` (a legal Gateway API value meaning "drain this backend") + was silently discarded and the backend kept its implicit weight of 1. + */ -}} + {{- if hasKey $backendRef "weight" }} + weight: {{ $backendRef.weight }} {{- end }} {{- end }} {{- end }} diff --git a/charts/default/templates/ingress.yaml b/charts/default/templates/ingress.yaml index a47437e..0ff27bb 100644 --- a/charts/default/templates/ingress.yaml +++ b/charts/default/templates/ingress.yaml @@ -1,4 +1,9 @@ -{{- if and .Values.ingress.enabled (not .Values.gateway.enabled) -}} +{{- /* +Ingress is the legacy routing mode and is suppressed whenever Gateway API +routing is on, so the two can never render at once. Both blocks are +nil-guarded so nulling either one renders no route instead of crashing. +*/ -}} +{{- if and .Values.ingress .Values.ingress.enabled (not (and .Values.gateway .Values.gateway.enabled)) -}} {{- $fullName := include "project.fullname" . -}} {{- $servicePort := .Values.service.port -}} {{- $ingressPath := .Values.ingress.path -}}