Conversation
Binding the first owner is the one step between a started bundle and a
usable instance, and today it needs a local Node toolchain, the published
CLI, and ten flags typed correctly. The CLI already travels inside the api
image for exactly this reason, but nothing in docker-compose.yml reaches
it.
`facility instance bootstrap` now reads each option from its
`FACILITY_<OPTION>` variable — `--org-slug` from `FACILITY_ORG_SLUG` — so
a one-shot container task carries the binding in its environment and needs
no command line at all. A `bootstrap` Compose profile runs it:
docker compose --profile bootstrap run --rm bootstrap
Environment rather than arguments is the point, not a convenience. An
organization name and a GitHub login are operator input, and the shape
that would otherwise fit a Compose service is `sh -c "facility instance
bootstrap --org-name $FACILITY_ORG_NAME ..."`, which interpolates that
input into a command. Passing values as variables to an exec-form command
removes the shell from the path instead of quoting around it.
Precedence and validation are deliberate: an explicit option wins over its
variable, a malformed option fails rather than being rescued by an ambient
one, and a value is validated identically whichever way it arrived — a
variable is not more trusted for having come from the environment. Missing
values are reported under both spellings, because the operator reading
that error in a container log has only the variable.
The profile keeps `bootstrap` out of `docker compose up`, and its
variables use `:-` rather than `:?` on purpose: a required-variable
interpolation is evaluated for the whole file and would fail `up` itself.
The CLI names what is missing.
Covered without Postgres or the network by refusing at the first database
call: an environment-only run reaches it, an explicit option overrides a
variable, a blank option still defers to one, a malformed option does not,
and the missing-value message is pinned in full.
adrian-lorenzo
left a comment
There was a problem hiding this comment.
Thanks for making container-based bootstrap easier.
The environment path works in a local PostgreSQL probe, but the added tests stop before any binding is written. Please extend the integration test to bootstrap from environment variables, assert the stored owner/installation and flag precedence, and cover conflicting bindings.
The added coverage stopped at the first database call. That proved the environment path passes validation and nothing more: the feature is that a container task writes a binding, and no test watched one being written. Three Postgres-backed cases now do, each in its own schema so one cannot read another's binding: - an environment-only run writes the organization, owner, GitHub identity, owner membership and installation rows, normalizes the address the same way the option path does, and re-running it stays one organization with an identical binding; - an option beats its variable in the row that lands, including the second place the installation id is copied to, so precedence is asserted where it is observable rather than only where it is parsed; - a conflicting binding from the environment is refused across each dimension it is made of — identity, person, installation, organization — and every row is compared before and after, so a refusal cannot be a partial write. All five fail with the environment lookup removed. The schema fixture and the row snapshot are shared with the existing option test rather than copied, since this change is what would have duplicated them.
|
Addressed at 5aee440. You were right that proving validation passes is not proving a binding gets written, which is the whole feature. Three Postgres-backed cases now follow it through: an environment-only run writes and re-writes the same org, owner, identity, membership and installation rows; an option beats its variable in the row that lands, including the installation id copied into settings; and a conflicting binding is refused across each dimension with every row compared before and after, so a refusal cannot be a partial write. All five fail with the environment lookup removed. Thanks. |
What changes
facility instance bootstrapreads each option from itsFACILITY_<OPTION>environment variable —
--org-slugfromFACILITY_ORG_SLUG— so a one-shotcontainer task can carry the binding without a command line. A
bootstrapCompose profile runs it:
Why
Binding the first owner is the one step between a started bundle and a usable
instance, and today it needs a local Node toolchain, the published CLI, and ten
flags typed correctly. The CLI already travels inside the api image for exactly
this reason — the
Dockerfilesays so and installs afacilitywrapper on PATH— but nothing in
docker-compose.ymlreached it.Environment rather than arguments is the design decision, not a convenience.
An organization name and a GitHub login are operator input. The shape that
otherwise fits a Compose service is
sh -c "facility instance bootstrap --org-name $FACILITY_ORG_NAME …", whichinterpolates that input into a command. Passing values as variables to an
exec-form command removes the shell from the path instead of quoting around it —
the same lesson as #214 and #186, where a discovered directory name and a git ref
reached a shell unquoted.
Precedence and validation are deliberate: an explicit option wins over its
variable, a malformed option fails rather than being rescued by an ambient
one, and a value is validated identically whichever way it arrived — a variable
is not more trusted for having come from the environment. Missing values are
reported under both spellings, because the operator reading that error in a
container log has only the variable.
The profile keeps
bootstrapout ofdocker compose up, and its variables use:-rather than:?on purpose: a required-variable interpolation is evaluatedfor the whole file and would fail
upitself. The CLI names what is missing.Verification
Five new cases, deterministic without PostgreSQL or the network — the injected
client refuses at the first database call, so a run that reaches it has passed
every validation:
(
--org-slug must be a lowercase URL slug, not accepted);That includes the pre-existing transactional/idempotency test, which normally
skips: I ran it against a real PostgreSQL rather than leaving it skipped, so the
advisory-lock and conflict paths were exercised.
The Compose profile was resolved, not assumed:
pnpm verifypasses locallypnpm verifydoes not pass on this machine and not because of this change:test:devreports 116 tests, 92 pass, 24 fail here, and I measured theidentical 92/24 on a clean
main— Windows noise in the patchedimage-sizecases,
tarfailing to resolveC:, and the registry publication tests. Beyondthe suite, the profile was resolved with real
docker compose configruns, whichis what confirmed that
:?would have brokenupfor everyone.Not done on purpose: the master key is not auto-generated. It looks like the
obvious companion, and it is the wrong feature. A generated key that is not
persisted makes every stored project secret undecryptable on the next start, and
one written to a file is a secret on disk with whatever mode the writer chose.
${SECRET_MASTER_KEY:?set SECRET_MASTER_KEY}is already a good error for a valuethe operator must own.
Related: #19, which asks for the operator bootstrap to be automated in a
cloud-agnostic bundle. This is that step; it does not close the issue.