From adfc1ac79130ecb3771eb028d39c56fbb69b4127 Mon Sep 17 00:00:00 2001 From: Nicacio Oliveira Date: Tue, 18 Aug 2026 18:09:37 -0300 Subject: [PATCH] fix(preview): the config ConfigMap is created after the Job that mounts it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migrate Job takes both the ConfigMap and the Secret via envFrom. The ExternalSecret was placed at wave -30 for exactly that reason, and its comment says so — but the ConfigMap got no wave at all, so it landed at the default 0, after the Job at -10. The Job is then scheduled with a configMapRef that does not exist yet, sits in CreateContainerConfigError, and the whole sync stalls waiting for the hook to complete. Nothing on any object reports a problem; the Application just says OutOfSync/Missing with 'waiting for completion of hook'. Found on the very first real preview (#6205). helm template renders but never sequences, so no amount of render assertions could have caught it — helm-test now walks every Job, Deployment and StatefulSet, resolves each envFrom source, and fails when a consumer is sequenced before something it mounts. Verified against the pre-fix chart: it flags the exact pair. Fixes the chart-managed Secret too, which had the same gap for non-ESO installs. --- .github/workflows/helm-test.yml | 40 +++++++++++++++++++++ deploy/helm/studio/Chart.yaml | 9 ++++- deploy/helm/studio/templates/configmap.yaml | 9 +++++ deploy/helm/studio/templates/secret.yaml | 9 +++++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.github/workflows/helm-test.yml b/.github/workflows/helm-test.yml index fb3a2ed37d..bc9faf758f 100644 --- a/.github/workflows/helm-test.yml +++ b/.github/workflows/helm-test.yml @@ -251,6 +251,46 @@ jobs: print("every preview pod is pinned to the preview node pool") POOL + # Ordering between waves is not enough: a resource also has to come + # AFTER everything it consumes. The migrate Job takes both the + # ConfigMap and the Secret via envFrom, and the ConfigMap had no wave + # at all — so it landed at 0, after the Job at -10, and the Job sat in + # CreateContainerConfigError while the whole sync stalled waiting on + # the hook. Nothing on the object said anything was wrong, and + # `helm template` cannot see it because it never sequences. + python3 - <<'DEPS' + import sys, yaml + waves, needs = {}, [] + for d in yaml.safe_load_all(open("/tmp/preview.yaml")): + if not d: + continue + kind, name = d["kind"], d["metadata"]["name"] + w = int((d["metadata"].get("annotations") or {}) + .get("argocd.argoproj.io/sync-wave", 0)) + waves[name] = w + if kind not in ("Job", "Deployment", "StatefulSet"): + continue + spec = d["spec"]["template"]["spec"] + for c in (spec.get("initContainers") or []) + spec["containers"]: + for ref in (c.get("envFrom") or []): + for key in ("configMapRef", "secretRef"): + if key in ref: + needs.append((kind, name, w, ref[key]["name"])) + bad = [] + for kind, name, w, dep in needs: + # Only things this chart renders; anything else is pre-existing. + if dep not in waves: + continue + if waves[dep] > w: + bad.append(f"{kind}/{name} (wave {w}) consumes {dep} (wave {waves[dep]})") + if bad: + print("::error::a resource is sequenced before something it mounts: " + + "; ".join(bad)) + sys.exit(1) + print(f"every envFrom source is created no later than its consumer " + f"({len(needs)} references checked)") + DEPS + # Every studio container (api-0, api-1, worker) must skip migrations: # the PreSync Job is the single writer. Three, not two. # `|| true` — grep exits 1 on zero matches, which under `set -e` would diff --git a/deploy/helm/studio/Chart.yaml b/deploy/helm/studio/Chart.yaml index daeaaf64b3..1fb5c299fa 100644 --- a/deploy/helm/studio/Chart.yaml +++ b/deploy/helm/studio/Chart.yaml @@ -2,6 +2,13 @@ apiVersion: v2 name: chart-deco-studio description: Helm chart for deco Studio — supports inline secrets, external secretName, and AWS Secrets Manager via ExternalSecret type: application +# 0.14.1: the preview ConfigMap had no sync-wave, so it landed at 0 — after the +# migrate Job at -10 that consumes it via envFrom. The Job sat in +# CreateContainerConfigError and the sync stalled on the hook with nothing on +# any object reporting a problem. Found on the first real preview; `helm +# template` cannot see it, so helm-test now asserts that every envFrom source +# is created no later than what mounts it. +# # 0.14.0: per-PR preview releases (preview.enabled, default false). Adds an # HTTPRoute, an ephemeral in-namespace Postgres and a single PreSync migration # Job — the database lives and dies with the namespace, so there is no shared @@ -19,7 +26,7 @@ type: application # when disabled, so existing releases are unaffected. # 0.12.4: chart-managed API/worker dispatch roles now use # STUDIO_DISPATCH_ROLE; legacy overrides are rejected. -version: 0.14.0 +version: 0.14.1 appVersion: "latest" dependencies: diff --git a/deploy/helm/studio/templates/configmap.yaml b/deploy/helm/studio/templates/configmap.yaml index 602d3a36ad..39c043f73e 100644 --- a/deploy/helm/studio/templates/configmap.yaml +++ b/deploy/helm/studio/templates/configmap.yaml @@ -4,6 +4,15 @@ metadata: name: {{ include "chart-deco-studio.fullname" . }}-config labels: {{- include "chart-deco-studio.labels" . | nindent 4 }} + {{- if .Values.preview.enabled }} + annotations: + # Same wave as the ExternalSecret, and for the same reason: the migrate Job + # (-10) and the app Deployments (0) both take this via envFrom, and a + # missing configMapRef leaves the container in CreateContainerConfigError — + # which stalls the whole sync on the hook, with no error on the object + # itself. Only a real sync surfaces this; `helm template` cannot. + argocd.argoproj.io/sync-wave: "-30" + {{- end }} data: {{- toYaml .Values.configMap.meshConfig | nindent 2 }} {{- $autoNatsUrl := "" }} diff --git a/deploy/helm/studio/templates/secret.yaml b/deploy/helm/studio/templates/secret.yaml index 16e26105ed..d266ee425d 100644 --- a/deploy/helm/studio/templates/secret.yaml +++ b/deploy/helm/studio/templates/secret.yaml @@ -5,6 +5,15 @@ metadata: name: {{ include "chart-deco-studio.fullname" . }}-secrets labels: {{- include "chart-deco-studio.labels" . | nindent 4 }} + {{- if .Values.preview.enabled }} + annotations: + # Same wave as the ExternalSecret, and for the same reason: the migrate Job + # (-10) and the app Deployments (0) both take this via envFrom, and a + # missing configMapRef leaves the container in CreateContainerConfigError — + # which stalls the whole sync on the hook, with no error on the object + # itself. Only a real sync surfaces this; `helm template` cannot. + argocd.argoproj.io/sync-wave: "-30" + {{- end }} type: Opaque stringData: BETTER_AUTH_SECRET: {{ .Values.secret.BETTER_AUTH_SECRET | quote }}