diff --git a/.github/scripts/aisix-render-checks.sh b/.github/scripts/aisix-render-checks.sh index 539ce2d..d0c10f8 100644 --- a/.github/scripts/aisix-render-checks.sh +++ b/.github/scripts/aisix-render-checks.sh @@ -52,6 +52,27 @@ check "extraEnvVars PEMs keep the env wiring: no file keys, no mount, all three check "extraEnvVars PEMs render after the chart's own, exactly as 1.5.0 did" \ query '[e["name"] for e in pod["containers"][0]["env"] if e["name"] == "AISIX_MANAGED__CP_CERT_PEM"] == ["AISIX_MANAGED__CP_CERT_PEM"] * 2 and pod["containers"][0]["env"][[e["name"] for e in pod["containers"][0]["env"]].index("AISIX_MANAGED__CP_CERT_PEM")]["valueFrom"]["secretKeyRef"]["name"] == "aisix-gateway-certificate"' \ --set 'extraEnvVars[0].name=AISIX_MANAGED__CP_CERT_PEM' --set 'extraEnvVars[0].value=pem' +# extraEnvVars wins on a repeated name (Kubernetes takes the last entry), and +# the chart drops its own entry for a name new since 1.5.0: a repeated name +# breaks upgrades after a rollback and is refused by server-side apply. +new_names=(--set 'configSecrets.cache\.redis\.password.secretName=redis-auth' --set 'configSecrets.cache\.redis\.password.key=password' + --set 'extraEnvVars[0].name=AISIX_CACHE__REDIS__PASSWORD' --set 'extraEnvVars[0].value=user' --set 'extraEnvVars[1].name=TZ' --set 'extraEnvVars[1].value=UTC') +no_dups='all(len(names) == len(set(names)) for names in ([e["name"] for e in c.get("env", [])] for c in pod["containers"] + pod.get("initContainers", [])))' +check "extraEnvVars overriding a name new since 1.5.0 leaves no repeated name (control plane)" \ + query "$no_dups and env[\"AISIX_CACHE__REDIS__PASSWORD\"] == {\"name\": \"AISIX_CACHE__REDIS__PASSWORD\", \"value\": \"user\"}" "${new_names[@]}" +check "extraEnvVars overriding a name new since 1.5.0 leaves no repeated name (standalone)" \ + query "$no_dups and env[\"AISIX_CACHE__REDIS__PASSWORD\"][\"value\"] == \"user\"" -f "$chart/ci/standalone-values.yaml" "${new_names[@]}" +# A 1.5.0 release that overrides a name 1.5.0 rendered carries it twice; an +# upgrade whose manifest carries it once deletes both entries, the override +# included. So for those names the chart's own entry stays ahead of the user's. +old_names=(--set 'extraEnvVars[0].name=AISIX_MANAGED__CP_BASE_URL' --set 'extraEnvVars[0].value=https://cp.example.com' + --set 'extraEnvVars[1].name=AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS' --set-string 'extraEnvVars[1].value=42' + --set 'extraEnvVars[2].name=AISIX_PROXY__ADDR' --set 'extraEnvVars[2].value=0.0.0.0:8080') +check "extraEnvVars overriding a name 1.5.0 rendered keeps 1.5.0's pair, the user's entry last" \ + query '[(e["name"], e["value"]) for e in pod["containers"][0]["env"] if e["name"] in ("AISIX_MANAGED__CP_BASE_URL", "AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS", "AISIX_PROXY__ADDR")] == [("AISIX_PROXY__ADDR", "0.0.0.0:3000"), ("AISIX_MANAGED__CP_BASE_URL", "https://dp-manager.example.com:7944"), ("AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS", "15"), ("AISIX_MANAGED__CP_BASE_URL", "https://cp.example.com"), ("AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS", "42"), ("AISIX_PROXY__ADDR", "0.0.0.0:8080")]' \ + "${old_names[@]}" +check "1.5.0 never rendered control-plane names in standalone mode, so none repeats there" \ + query "$no_dups" -f "$chart/ci/standalone-values.yaml" "${old_names[@]:0:8}" check "configSecrets becomes a secretKeyRef env var" \ query 'env["AISIX_CACHE__REDIS__PASSWORD"]["valueFrom"]["secretKeyRef"] == {"name": "redis-auth", "key": "password"}' \ --set 'configSecrets.cache\.redis\.password.secretName=redis-auth' --set 'configSecrets.cache\.redis\.password.key=password' diff --git a/charts/aisix/README.md b/charts/aisix/README.md index 9bce3f9..7e64851 100644 --- a/charts/aisix/README.md +++ b/charts/aisix/README.md @@ -467,6 +467,15 @@ configSecrets: the variables a standalone resources file references. An `AISIX_*` variable set there still overrides the file, as it always has. +Overriding a variable the chart sets itself through `extraEnvVars` is only a +compatibility path for existing deployments, and the `extraEnvVars` value still +wins. For a name chart 1.5.0 set (such as `AISIX_MANAGED__CP_BASE_URL` or +`AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS`) the chart keeps rendering its own +entry ahead of yours, so a Helm 3 upgrade from 1.5.0 does not drop the override; +the container then lists the name twice, which Helm 4 server-side apply +rejects. New installs set the value in its own key in `values.yaml` or under +`config` instead. + ## Parameters ## Values @@ -545,7 +554,7 @@ there still overrides the file, as it always has. | controlPlane.enabled | bool | `true` | Read configuration from an AISIX control plane. Set to false to run standalone, from the `resources.yaml` file configured under `standalone` | | controlPlane.etcdEndpoint | string | `""` | Control-plane etcd endpoint as bare `host:port`. Leave empty unless the control plane publishes an etcd endpoint distinct from `baseURL` | | controlPlane.heartbeatIntervalSeconds | int | `15` | Heartbeat interval in seconds. The control plane marks a gateway connected on its first heartbeat. Clamped to [5, 300] by the gateway | -| extraEnvVars | list | `[]` | Extra environment variables for the gateway container: system-level environment variables (such as `TZ`) and variables referenced by the resources file. Gateway settings belong in `config` | +| extraEnvVars | list | `[]` | Extra environment variables for the gateway container: system-level environment variables (such as `TZ`) and variables referenced by the resources file. Gateway settings belong in `config`. Overriding a variable the chart sets itself is only a compatibility path for existing deployments: for a name chart 1.5.0 set, the rendered container then lists it twice, which Helm 4 server-side apply rejects. New installs set the value in its own key or under `config` | | extraVolumeMounts | list | `[]` | Extra volume mounts for the gateway container | | extraVolumes | list | `[]` | Extra volumes for the gateway pod | | fullnameOverride | string | `""` | Override the fully qualified resource name prefix | diff --git a/charts/aisix/README.md.gotmpl b/charts/aisix/README.md.gotmpl index e62dce7..c984174 100644 --- a/charts/aisix/README.md.gotmpl +++ b/charts/aisix/README.md.gotmpl @@ -461,6 +461,15 @@ configSecrets: the variables a standalone resources file references. An `AISIX_*` variable set there still overrides the file, as it always has. +Overriding a variable the chart sets itself through `extraEnvVars` is only a +compatibility path for existing deployments, and the `extraEnvVars` value still +wins. For a name chart 1.5.0 set (such as `AISIX_MANAGED__CP_BASE_URL` or +`AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS`) the chart keeps rendering its own +entry ahead of yours, so a Helm 3 upgrade from 1.5.0 does not drop the override; +the container then lists the name twice, which Helm 4 server-side apply +rejects. New installs set the value in its own key in `values.yaml` or under +`config` instead. + ## Parameters {{ template "chart.valuesSection" . }} diff --git a/charts/aisix/templates/_helpers.tpl b/charts/aisix/templates/_helpers.tpl index fcf526c..876bc34 100644 --- a/charts/aisix/templates/_helpers.tpl +++ b/charts/aisix/templates/_helpers.tpl @@ -285,6 +285,86 @@ from the bundle Secret (which extraEnvVars then overrides), and no file keys. {{- end }} {{- end }} +{{/* +The gateway container's env: the chart's own variables, then `extraEnvVars`, +whose entries win on a repeated name. The chart leaves out its own entry for a +name `extraEnvVars` also sets — a repeated name breaks `helm upgrade` after a +rollback ("order in patch list") and is refused by server-side apply — except +for the names chart 1.5.0 rendered ("aisix.env150"). A 1.5.0 release that +overrides one of those carries the name twice, and an upgrade whose manifest +carries it once deletes both entries, the override included. For those names +the chart keeps rendering its entry ahead of the override, with the value 1.5.0 +gave it where the chart no longer sets it. +*/}} +{{- define "aisix.env" -}} +{{- $chart := list (dict "name" "AISIX_CONFIG_PATH" "value" (include "aisix.configPath" .)) }} +{{- if and .Values.controlPlane.enabled (include "aisix.cpPemFromEnv" .) }} +{{- range $slot, $key := dict "CERT" .Values.controlPlane.certificate.certKey "KEY" .Values.controlPlane.certificate.keyKey "CA" .Values.controlPlane.certificate.caKey }} +{{- $chart = append $chart (dict "name" (printf "AISIX_MANAGED__CP_%s_PEM" $slot) "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.certSecretName" $) "key" $key))) }} +{{- end }} +{{- end }} +{{- if and (eq .Values.rateLimit.backend "redis") (or .Values.rateLimit.redis.url .Values.rateLimit.redis.existingSecret) }} +{{- $chart = append $chart (dict "name" "AISIX_RATELIMIT__REDIS__URL" "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.redisSecretName" .) "key" (include "aisix.redisSecretKey" .)))) }} +{{- end }} +{{- range $path, $ref := (.Values.configSecrets | default dict) }} +{{- $chart = append $chart (dict "name" (include "aisix.configSecretEnvName" $path) "valueFrom" (dict "secretKeyRef" (dict "name" $ref.secretName "key" $ref.key))) }} +{{- end }} +{{- $extra := .Values.extraEnvVars | default list }} +{{- $overridden := list }} +{{- range $extra }} +{{- $overridden = append $overridden .name }} +{{- end }} +{{- $legacy := include "aisix.env150" . | fromYamlArray }} +{{- $legacyNames := list }} +{{- range $legacy }} +{{- $legacyNames = append $legacyNames .name }} +{{- end }} +{{- $env := list }} +{{- $chartNames := list }} +{{- range $chart }} +{{- $chartNames = append $chartNames .name }} +{{- if or (not (has .name $overridden)) (has .name $legacyNames) }} +{{- $env = append $env . }} +{{- end }} +{{- end }} +{{- range $legacy }} +{{- if and (has .name $overridden) (not (has .name $chartNames)) }} +{{- $env = append $env . }} +{{- end }} +{{- end }} +{{- toYaml (concat $env $extra) }} +{{- end }} + +{{/* +"aisix.env150" is the env list chart 1.5.0 rendered for these values — the +names "aisix.env" must not drop while an older release may still carry them +twice. Only the entries the chart no longer renders use these values. +*/}} +{{- define "aisix.env150" -}} +{{- $env := list (dict "name" "AISIX_CONFIG_PATH" "value" (ternary "/etc/aisix/config.managed.yaml" "/etc/aisix/standalone/config.yaml" .Values.controlPlane.enabled)) }} +{{- $env = append $env (dict "name" "AISIX_PROXY__ADDR" "value" (printf "0.0.0.0:%d" (int .Values.containerPorts.proxy))) }} +{{- if .Values.listeners }} +{{- $env = append $env (dict "name" "AISIX_PROXY__LISTENERS" "value" (include "aisix.proxyListenersJson" .)) }} +{{- end }} +{{- $env = append $env (dict "name" "AISIX_OBSERVABILITY__METRICS__PROMETHEUS__ADDR" "value" (printf "0.0.0.0:%d" (int .Values.containerPorts.metrics))) }} +{{- if .Values.controlPlane.enabled }} +{{- $env = append $env (dict "name" "AISIX_MANAGED__CP_BASE_URL" "value" .Values.controlPlane.baseURL) }} +{{- with .Values.controlPlane.etcdEndpoint }} +{{- $env = append $env (dict "name" "AISIX_MANAGED__CP_ETCD_ENDPOINT" "value" .) }} +{{- end }} +{{- $env = append $env (dict "name" "AISIX_MANAGED__HEARTBEAT_INTERVAL_SECS" "value" (toString .Values.controlPlane.heartbeatIntervalSeconds)) }} +{{- range $slot, $key := dict "CERT" .Values.controlPlane.certificate.certKey "KEY" .Values.controlPlane.certificate.keyKey "CA" .Values.controlPlane.certificate.caKey }} +{{- $env = append $env (dict "name" (printf "AISIX_MANAGED__CP_%s_PEM" $slot) "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.certSecretName" $) "key" $key))) }} +{{- end }} +{{- end }} +{{- /* 1.5.0 refused a Redis backend without one of these. */}} +{{- if and (eq .Values.rateLimit.backend "redis") (or .Values.rateLimit.redis.url .Values.rateLimit.redis.existingSecret) }} +{{- $env = append $env (dict "name" "AISIX_RATELIMIT__BACKEND" "value" "redis") }} +{{- $env = append $env (dict "name" "AISIX_RATELIMIT__REDIS__URL" "valueFrom" (dict "secretKeyRef" (dict "name" (include "aisix.redisSecretName" .) "key" (include "aisix.redisSecretKey" .)))) }} +{{- end }} +{{- toYaml $env }} +{{- end }} + {{/* The environment variable the gateway reads a `configSecrets` path from: AISIX_ plus the path upper-cased with `.` as `__`. diff --git a/charts/aisix/templates/deployment.yaml b/charts/aisix/templates/deployment.yaml index 30d407a..0e01bfa 100644 --- a/charts/aisix/templates/deployment.yaml +++ b/charts/aisix/templates/deployment.yaml @@ -78,36 +78,7 @@ spec: containerPort: {{ .Values.containerPorts.metrics }} protocol: TCP env: - - name: AISIX_CONFIG_PATH - value: {{ include "aisix.configPath" . }} - {{- if and .Values.controlPlane.enabled (include "aisix.cpPemFromEnv" .) }} - {{- range $slot, $key := dict "CERT" .Values.controlPlane.certificate.certKey "KEY" .Values.controlPlane.certificate.keyKey "CA" .Values.controlPlane.certificate.caKey }} - - name: AISIX_MANAGED__CP_{{ $slot }}_PEM - valueFrom: - secretKeyRef: - name: {{ include "aisix.certSecretName" $ }} - key: {{ $key }} - {{- end }} - {{- end }} - {{- if eq .Values.rateLimit.backend "redis" }} - {{- if or .Values.rateLimit.redis.url .Values.rateLimit.redis.existingSecret }} - - name: AISIX_RATELIMIT__REDIS__URL - valueFrom: - secretKeyRef: - name: {{ include "aisix.redisSecretName" . }} - key: {{ include "aisix.redisSecretKey" . }} - {{- end }} - {{- end }} - {{- range $path, $ref := (.Values.configSecrets | default dict) }} - - name: {{ include "aisix.configSecretEnvName" $path }} - valueFrom: - secretKeyRef: - name: {{ $ref.secretName }} - key: {{ $ref.key }} - {{- end }} - {{- with .Values.extraEnvVars }} - {{- toYaml . | nindent 12 }} - {{- end }} + {{- include "aisix.env" . | nindent 12 }} {{- if .Values.startupProbe.enabled }} # /livez answers as soon as the proxy listener is bound, which in # etcd mode is once the first configuration apply has succeeded. diff --git a/charts/aisix/values.yaml b/charts/aisix/values.yaml index 564c0db..6b89e40 100644 --- a/charts/aisix/values.yaml +++ b/charts/aisix/values.yaml @@ -540,7 +540,11 @@ preStopSleepSeconds: 30 # -- Extra environment variables for the gateway container: system-level # environment variables (such as `TZ`) and variables referenced by the -# resources file. Gateway settings belong in `config` +# resources file. Gateway settings belong in `config`. Overriding a variable +# the chart sets itself is only a compatibility path for existing deployments: +# for a name chart 1.5.0 set, the rendered container then lists it twice, which +# Helm 4 server-side apply rejects. New installs set the value in its own key +# or under `config` extraEnvVars: [] # -- Extra volumes for the gateway pod