ADR-0006: A reusable chart owns every test namespace, not --create-namespace¶
Status: accepted Date: 2026-09-12
Context¶
Making the BDD suite safe to run under cucumber-js's --parallel (see
the worker-scoped namespace/fixture-path work this session) surfaced a
real bug: a fresh, never-before-seen namespace is rejected by this
cluster's baseline PodSecurity admission, which blocks the
test-buildkit fixture's privileged container. The immediate fix was an
imperative When I label namespace "<Namespace>" with --overwrite
pod-security.kubernetes.io/enforce=privileged step, added to the 6
.feature files that install test-buildkit. It worked, and matched
this project's own pre-Thomas documented convention
(.agents/skills/disposable-k8s-test-fixtures/SKILL.md's "One namespace
per repo + workspace/user" section already recommended exactly this
imperative kubectl label fix for the old JS suite).
Reviewing it further: every one of the 10 .feature files creates its
own namespace via --create-namespace on whatever Helm release happens
to run first — an ad hoc, implicit mechanism with no single owner,
repeated (or, for the 6 buildkit files, repeated plus a bespoke label
step) in every file. And none of those bare, --create-namespace-created
namespaces are ever deleted by anything — they accumulate forever, one
per real Owner+WorkerId combination.
Decision¶
A new, small, project-owned chart, charts/test-namespace, is the one
thing that creates and labels every test namespace. Its only template is
a Namespace object, named from .Values.targetNamespace and
optionally labeled pod-security.kubernetes.io/enforce: privileged when
.Values.privileged is set.
The chart's own release always lives in the cluster's default
namespace, never in the namespace it creates. This was not the
original design — the first attempt had the chart's release target the
namespace it was also creating (the obvious shape), and that fails
empirically: helm upgrade --install --create-namespace bare-creates
the namespace via its own client-side precondition, then fails applying
the chart's own Namespace manifest for that same name with Error:
... namespaces "..." already exists — confirmed this also fails with
Helm's --take-ownership flag. Helm specifically refuses to let a
release's chart own the Namespace object matching that release's own
namespace, separate from its generic (and more permissive)
resource-adoption rules. Installing the release into default instead
(always exists, so --create-namespace isn't even needed for the
release's own home) and passing the real target as a plain value sidesteps
this entirely — confirmed working, including a full uninstall-then-
reinstall cycle against the same target name.
Every .feature file installs this chart first, in Background, and
every scenario uninstalls it last, as the final step of its own
teardown — real, complete per-scenario namespace deletion, not the
previous forever-lingering bare namespace. Every other Helm release
across all 10 files had its own --create-namespace row removed:
keeping it would silently paper over a skipped/reordered/broken
namespace-chart step with an unlabeled ad hoc namespace instead of
failing loudly.
Consequences¶
- Easier: one real, declarative, Helm-tracked object is the
namespace's whole lifecycle — creation, the PodSecurity label, and
deletion — instead of an ad hoc
--create-namespaceper file plus a side-channel label step some files needed and others didn't. A future file needing a privileged fixture just sets one value (privileged=true), not a bespoke step to remember. - Easier: real, complete namespace cleanup after every scenario, which this suite never had before.
- Harder / a real, accepted cost: every scenario now pays a real
delete-then-recreate namespace cost, not just release
install/uninstall — deliberately accepted for real cleanliness, not an
oversight. An empty namespace's full deletion measured in the low
single-digit seconds on this cluster; a scenario's real, populated
namespace (Deployments, Secrets, ConfigMaps) will take measurably
longer, though still bounded by the same
helm uninstallstep every other release already goes through. - A real, accepted constraint: the namespace-owning release lives in
default, namedtest-namespace-<real-target-namespace>to stay unique there across every file/worker's own release — a real, if unusual, shape forced by Helm's own restriction on a chart owning its release's namespace, not a stylistic choice.
Alternatives considered: folding this into charts/test-registry
instead of a dedicated chart — rejected, couples an unrelated concern
(cluster-wide namespace policy) to a registry fixture, and doesn't
generalize to files that don't install test-registry at all (e.g.
service_settings_file.feature). Tearing the namespace down only once
per file/run (a new AfterAll-style hook) instead of once per scenario —
rejected this round in favor of real, per-scenario cleanliness; worth
revisiting if the added per-scenario namespace-churn cost turns out to
dominate a file's real run time.