Files
turnstone/deploy
Patrick Buckley 9334cf0cef fix(helm): make the bundled-PostgreSQL default installable (#949)
* fix(helm): render the chart Secret for every inline credential

Setting llm.existingSecret suppressed the chart's whole Secret, not just
the LLM API key it replaces. POSTGRES_PASSWORD and TURNSTONE_JWT_SECRET
went unrendered with it while server, console and the migrate Job went on
referencing them, so every pod stalled in CreateContainerConfigError.
Supplying an LLM Secret is a supported, documented configuration, and it
took the install down on both the bundled and external database paths.

turnstone.db.secretName compounded it by falling back to
turnstone.llm.secretName, pointing the password lookup at the operator's
LLM Secret — which has no reason to carry a database password.

Both now derive from one predicate. turnstone.db.inlinePassword returns
the password when the chart stores it itself and empty when an operator
supplies it, so secret.yaml renders on exactly the condition under which
turnstone.db.secretName resolves to <fullname>-secrets. The two cannot
disagree about where the password lives, which is what the earlier
llm.secretName fallback was working around. Each key keeps its own
condition, so an existingSecret still suppresses the value it replaces
and nothing else.

Verified by rendering nine values permutations against both this and the
previous templates and diffing every secretKeyRef against the Secrets
each tree creates: three permutations fixed, six byte-identical, none
regressed. helm lint passes on all nine.

The bundled-PostgreSQL default is unaffected and still broken: the
subchart generates its password into <fullname>-postgresql, which the
chart never reads. It is separately blocked by the migrate hook running
before the database exists, so it needs the design decision called for
in #932 rather than a secret-name change.

* fix(helm): default the inline password so an unset key cannot become one

turnstone.db.inlinePassword is reached through include, which captures
rendered text rather than a value. A key that is unset rather than empty
— "password:" with nothing after it, or --set database.external.password=null
— renders as the literal "<no value>", and a ten-character string is
truthy, so it satisfied the gate in templates/secret.yaml and landed
base64-encoded in POSTGRES_PASSWORD. Workloads then authenticated with
the string "<no value>".

Reaching the values through default "" keeps unset and empty equivalent,
which is what the previous templates got for free by testing the value
directly instead of the rendered text. Introduced by the commit before
this one; caught in review.

The two null spellings are now permanent cases in the render matrix.
Across eleven permutations, three are fixed relative to main, eight are
byte-identical, none regress, and the inline password still round-trips
byte-exact. helm lint passes on all eleven.

* docs(helm): narrow the inlinePassword guarantee to what it holds

The comment claimed secret.yaml and turnstone.db.secretName cannot
disagree about where the password lives. That holds wherever the chart
or the operator supplies the password, but not where the bundled
subchart generates its own — that lands in the subchart's Secret, which
neither helper reads. State the two guarantees that do hold instead.

* fix(helm): make the bundled-PostgreSQL default installable

The default values have never produced a working install. Two faults,
and the first is why the second could not be fixed on its own.

The migrate Job ran as a pre-install hook, and Helm creates ordinary
resources only once hooks have finished. On a first install that means
none of what the migration needs exists yet: not the ConfigMap, not the
Secret, and — because the subchart is an ordinary resource — not the
database either. #932 worked around the first two by dropping the Job's
ServiceAccount reference and inlining its environment, but nothing can
work around the third: no reference to the subchart's Secret, however
derived, is readable by a hook that runs before the subchart exists.

So the Job moves to post-install, and to pre-upgrade rather than
post-upgrade: on an upgrade everything is already running, and
migrations belong before the new code rolls out rather than after. Helm
does not wait for readiness before post-install hooks, so the Job's own
retry is what waits for a cold database, and backoffLimit rises to cover
an image pull and cluster initialisation.

That in turn unwinds the workarounds. The Job takes the chart's
ServiceAccount back, and templates/secret.yaml drops the hook
annotations it was given so the pre-install Job could read it — those
made it a hook resource, untracked by the release, so the credentials
survived helm uninstall and were skipped by helm rollback.

With ordering fixed the password resolves properly. When the subchart
generates its own, turnstone.db.secretName now points at the subchart's
Secret instead of at <fullname>-secrets, which never carried the key.
The naming is mirrored rather than delegated, since the subchart's
helpers expect a context this chart cannot hand them, and it is derived
from the release name: a fullnameOverride here renames this chart's
resources and leaves the subchart's alone, so "<fullname>-postgresql"
would name a Secret that does not exist.

Verified across fifteen values permutations against origin/main: nine
fixed, six byte-identical, none regressed, helm lint clean on all
fifteen. The permutations cover both fullnameOverride spellings, a
subchart existingSecret with a renamed key, and the superuser key rule.

An external database with no password and no existingSecret is unchanged
and still fails at pod start. Passwordless authentication is not
something the chart models — the URL always references a password — so
that stays as it was rather than becoming a template-time error.
2026-08-02 14:52:31 -07:00
..