Skip to content

Commit e8f1a7a

Browse files
authored
fix(helm): expand CLICKHOUSE_PASSWORD in webapp CLICKHOUSE_URL via kubelet (#3449)
## Summary When the official Helm chart is deployed with an external ClickHouse and `clickhouse.external.existingSecret` set — the documented path for not committing secrets to `values.yaml` — the webapp pod crash-loops on startup: ``` goose run: parse "http://default:${CLICKHOUSE_PASSWORD}@<host>:8123?secure=false": net/url: invalid userinfo ``` Context in vouch request #3443. Re-opening in draft status per bot policy (previous attempt was #3445, closed by automation because it wasn't draft; no changes to the patch). ## Root cause Two pieces interact: 1. `hosting/k8s/helm/templates/_helpers.tpl` renders `CLICKHOUSE_URL` (and `RUN_REPLICATION_CLICKHOUSE_URL`) with a shell-style literal `${CLICKHOUSE_PASSWORD}` expecting bash expansion at container start. 2. `docker/scripts/entrypoint.sh` does `export GOOSE_DBSTRING="$CLICKHOUSE_URL"` — single-pass POSIX sh substitution, so the inner `${...}` survives as literal text and goose rejects it. Reproduces against the latest published chart (`oci://ghcr.io/triggerdotdev/charts/trigger:4.0.5`) and `main`. ## Fix Switch the two helpers (external + `existingSecret` branch) from shell-style `${CLICKHOUSE_PASSWORD}` to Kubernetes' `$(CLICKHOUSE_PASSWORD)`. Kubelet substitutes `$(VAR)` at pod-creation time from earlier env entries, and the chart already declares `CLICKHOUSE_PASSWORD` from the Secret immediately before `CLICKHOUSE_URL`, so the URL reaches the entrypoint with the real password already inlined. No entrypoint change, no image change. The plain-password branch (no `existingSecret`) is unchanged. Operator caveat added as template comments: `CLICKHOUSE_PASSWORD` must be URL-userinfo-safe since kubelet substitutes verbatim without percent-encoding. Hex-encoded passwords (e.g. `openssl rand -hex 32`) are safe by construction. ## Verification - `helm template` against `external.existingSecret` now renders `value: "http://default:$(CLICKHOUSE_PASSWORD)@<host>:8123?secure=false"` (was `${CLICKHOUSE_PASSWORD}`). - `helm template` against the plain-password branch is byte-identical to before. - Deployed end-to-end on a staging EKS cluster (Meistrari platform): webapp container reaches `goose: successfully migrated database to version: 6`, Node.js ClickHouse client connects at runtime. ## Alternatives considered - **Change `entrypoint.sh`** to `eval` / `envsubst` the URL — larger surface, touches every deployment mode (Docker Compose + k8s) and every container image. - **Mirror the Postgres pattern** (chart reads the full URL via `valueFrom.secretKeyRef`, as in `trigger-v4.postgres.useSecretUrl`) — cleaner long-term but requires a new `values.yaml` field and a migration path for existing users. Happy to follow up with that as a separate PR if the minimal fix here isn't the preferred direction. ## Changeset None added — the Helm chart isn't versioned through `@changesets/cli` (docs/chart-only PRs historically merge without a changeset, e.g. #2671). Happy to add one if the policy changed. Closes #3443.
1 parent 4dced14 commit e8f1a7a

1 file changed

Lines changed: 18 additions & 2 deletions

File tree

hosting/k8s/helm/templates/_helpers.tpl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,19 @@ ClickHouse hostname
400400

401401
{{/*
402402
ClickHouse URL for application (with secure parameter)
403+
404+
Note on the external+existingSecret branch: the password is expanded via
405+
Kubernetes' `$(VAR)` syntax, not shell `${VAR}`. Kubelet substitutes
406+
`$(CLICKHOUSE_PASSWORD)` at container-creation time from the
407+
CLICKHOUSE_PASSWORD env var declared just before CLICKHOUSE_URL in
408+
webapp.yaml. Shell-style `${...}` does not work here because
409+
`docker/scripts/entrypoint.sh` assigns CLICKHOUSE_URL to GOOSE_DBSTRING
410+
with a single-pass expansion (`export GOOSE_DBSTRING="$CLICKHOUSE_URL"`),
411+
so any inner `${...}` reaches goose verbatim and fails URL parsing.
412+
413+
CLICKHOUSE_PASSWORD must contain only URL-userinfo-safe characters — the
414+
value is substituted verbatim, so `@ : / ? # [ ] %` break the URL. Use a
415+
hex-encoded password or percent-encode before storing in the Secret.
403416
*/}}
404417
{{- define "trigger-v4.clickhouse.url" -}}
405418
{{- if .Values.clickhouse.deploy -}}
@@ -410,7 +423,7 @@ ClickHouse URL for application (with secure parameter)
410423
{{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}}
411424
{{- $secure := ternary "true" "false" .Values.clickhouse.external.secure -}}
412425
{{- if .Values.clickhouse.external.existingSecret -}}
413-
{{ $protocol }}://{{ .Values.clickhouse.external.username }}:${CLICKHOUSE_PASSWORD}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }}
426+
{{ $protocol }}://{{ .Values.clickhouse.external.username }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }}
414427
{{- else -}}
415428
{{ $protocol }}://{{ .Values.clickhouse.external.username }}:{{ .Values.clickhouse.external.password }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}?secure={{ $secure }}
416429
{{- end -}}
@@ -419,6 +432,9 @@ ClickHouse URL for application (with secure parameter)
419432

420433
{{/*
421434
ClickHouse URL for replication (without secure parameter)
435+
436+
See the note on clickhouse.url above — same `$(VAR)` vs `${VAR}` rationale
437+
applies to the replication URL.
422438
*/}}
423439
{{- define "trigger-v4.clickhouse.replication.url" -}}
424440
{{- if .Values.clickhouse.deploy -}}
@@ -427,7 +443,7 @@ ClickHouse URL for replication (without secure parameter)
427443
{{- else if .Values.clickhouse.external.host -}}
428444
{{- $protocol := ternary "https" "http" .Values.clickhouse.external.secure -}}
429445
{{- if .Values.clickhouse.external.existingSecret -}}
430-
{{ $protocol }}://{{ .Values.clickhouse.external.username }}:${CLICKHOUSE_PASSWORD}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}
446+
{{ $protocol }}://{{ .Values.clickhouse.external.username }}:$(CLICKHOUSE_PASSWORD)@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}
431447
{{- else -}}
432448
{{ $protocol }}://{{ .Values.clickhouse.external.username }}:{{ .Values.clickhouse.external.password }}@{{ .Values.clickhouse.external.host }}:{{ .Values.clickhouse.external.httpPort | default 8123 }}
433449
{{- end -}}

0 commit comments

Comments
 (0)