feat: add invoke bootstrap, a stack that loads itself - #27
Open
lancamat1 wants to merge 2 commits into
Open
Conversation
Getting a generated repository into a usable state took `invoke start`, then a wait for the API, then load-schema / load-menu / load-objects — and none of that exercises the mechanism Infrahub actually runs in production, which is reading a Git repository and loading what .infrahub.yml declares. `invoke bootstrap` publishes the current commit as a bare Git origin the containers can reach, starts the stack, registers it with Infrahub, and waits for the import to genuinely finish — polling sync_status and the tracked commit rather than sleeping, so it cannot report success for an instance that loaded nothing. .infrahub.yml declared only `schemas`, whatever features were enabled, so a repository import never picked up a generated repo's objects or menus. It becomes a template and declares `objects` and `menus` when those features are on. The origin is a bare mirror under .origin rather than the working copy that the OpsMill demo repos mount: in a git worktree checkout `.git` is a file pointing outside the mount, so the in-container clone fails, and Git refuses the proposed-change branches Infrahub pushes back to a non-bare repository. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`invoke lint` could not pass on a freshly generated repository, which makes a red lint gate meaningless as a signal for anyone starting from this template. Every fix here is in the template's own scaffolding, not in the bootstrap task. - lint_mypy pointed at `infrahub_sdk`, a directory a generated repository does not have, so mypy failed outright before checking anything. It now targets the paths that exist, since which of lib/, scripts/ and tests/ are present depends on the features chosen. - `preview = true` with `select = ["ALL"]` opts a generated repository into every unstable ruff rule, so its lint gate can go red on a ruff release with no code change. Two such rules were already firing. - lib/example.py annotated `nodes` as `list[InfrahubNode]` while the body called `nodes.keys()`. client.schema.all() returns a mapping keyed by kind, so the annotation was wrong in an example users copy. - Import order, return annotations, a missing tests/integration/__init__.py, a missing trailing newline in objects/example.yml, and ruff formatting. README: the testing section named INFRAHUB_TESTING_IMAGE_VERSION, which is internal to infrahub-testcontainers and always overwritten. The variable it reads from the environment is INFRAHUB_TESTING_IMAGE_VER. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lancamat1
force-pushed
the
feat/invoke-bootstrap
branch
from
August 20, 2026 20:15
9b531fb to
cc1f999
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits, separable:
6a0e37faddsinvoke bootstrap;cc1f999makes a generated repository pass its owninvoke lint. Drop the second if you'd rather take it separately.1.
invoke bootstrapOne command from a freshly generated repository to a populated Infrahub, via the mechanism
Infrahub actually runs in production: it clones the repository and loads what
.infrahub.ymldeclares.It publishes the current commit as a bare Git origin the containers can reach, starts the
stack, registers it with Infrahub as a
CoreRepository, and waits for the import tofinish — polling
sync_statusand the tracked commit rather than sleeping a fixedinterval, so it cannot report success for an instance that loaded nothing.
Why
Getting a generated repo into a usable state took
invoke start, then a wait for the API(
startreturns before the server answers), thenload-schema/load-menu/load-objects. That is a serviceable dev loop, but every step pushes files in from the hostwith
infrahubctl— which is not how the repository will be consumed in production, and itmeans a new user's first experience of Infrahub's Git integration is no experience of it.
bootstrapsits alongside theload-*tasks; they are untouched and remain the faster loopwhile iterating on a schema that has not been committed yet.
.infrahub.ymlbecomes a templateThis is the part worth a close look.
.infrahub.ymldeclared onlyschemas, regardlessof which features were enabled:
So a repository import from a generated repo never picked up its objects or menus, even with
objects: true/menus: true. Generating withgetting_started: trueproduced anobjects/example.ymlthat Infrahub would not load.It is now
.infrahub.yml.jinjaand declares each section when the matching feature is on:.infrahub.ymlschemas,objects,menusgetting_started: trueschemas,objectsschemasThe origin is a bare mirror, not the working copy
infrahub-demo-dcandinfrahub-demo-spmount the working copy into the task worker(
./:/upstream) and register that path. I started there; two things push away from it:.gitis a file pointing at the mainrepository's admin directory, which is outside the mount, so the in-container clone fails
with
not a git repository. Anyone usinggit worktree— increasingly common withagent-driven workflows — cannot bootstrap at all.
CoreRepositoryis read-write and Infrahub pushes proposed-changebranches to the origin. Git refuses a push to the checked-out branch of a non-bare
repository, so a merged proposed change cannot land back in a working-copy origin.
So
bootstrappublishesHEADinto.origin/<name>.git(gitignored) and the newdocker-compose.override.ymlmounts that at/remote— the same mount pointinfrahub_sdk.testing.repository.GitRepoandopsmill/infrahub's owntasks/dev.pyuse.The container path is constant rather than derived from the checkout directory, so renaming
or copying the repo never desyncs it from the location Infrahub recorded.
The cost is one extra gitignored directory and a
git push; the payoff is that it worksfrom a worktree and the merge-back path is not broken by construction.
Two accepted trade-offs, documented in the task docstring and the README
HEAD, so an uncommittededit is invisible to Infrahub.
bootstrapwarns when the working tree is dirty ratherthan letting a missing change look like a broken import.
has no data to migrate and nothing to preview. The README points at the branch +
proposed-change workflow for once there is data.
2. A generated repository now passes its own
invoke lintinvoke lintcould not pass on a fresh generation, in any configuration — which makes a redlint gate meaningless as a signal for someone starting from this template. All of these are
in the template's own scaffolding; none are in the bootstrap task.
lint_mypypointed atinfrahub_sdk, a directory a generated repository does nothave, so mypy failed outright before checking anything:
Cannot read file 'infrahub_sdk'.It now targets the paths that exist — which of
lib/,scripts/andtests/are presentdepends on the features chosen, and mypy errors on a path that is not there.
preview = truealongsideselect = ["ALL"]opts a generated repository into everyunstable ruff rule, so its lint gate can go red on a ruff release with no code change.
Two such rules were already firing (
noqa-comments,write-whole-file). Set tofalse,with a comment saying how to opt back in. The
write-whole-filesuggestion was worthtaking on its own terms, so
download_compose_filenow usesPath.write_text; thenoqa-commentsone wanted# ruff:ignore[...], itself an unstable syntax, so# noqastays.
lib/example.pyhad a wrong annotation:nodes: list[InfrahubNode]while the bodycalled
nodes.keys().client.schema.all()returnsMutableMapping[str, MainSchemaTypesAPI], so the example users copy was misdescribing its own input.tests/integration/__init__.py(INP001on both test files), a missing trailing newlinein
objects/example.yml, andruff formatontest_infrahub.py.scripts/*gets a scopedINP001ignore rather than an__init__.py— those arestandalone entry points, not an importable package.
Also: the README's testing section named
INFRAHUB_TESTING_IMAGE_VERSION. That one isinternal to
infrahub-testcontainersand always overwritten increate_env_file; thevariable actually read from the environment is
INFRAHUB_TESTING_IMAGE_VER(
helpers.py:23). Corrected, and the paragraph now says what the default is.Verified
Rendered all three answer sets and ran the real thing against Infrahub 1.11.0.
invoke lintruff format --checkinvoke bootstrapin-sync/online; exampleBuiltinTag"Yellow" in the graphgetting_started: truein-sync/onlineThe all-features render is the case I was least sure of: a
menus/directory containingnothing but
.gitkeepdoes not break the import, and neither does an emptyschemas/.Re-runs were exercised in the downstream repo this came from: same commit returns in ~2s, a
new commit waits out the once-a-minute scheduled sync (~67s) and confirms the new commit.
The only remaining yamllint output is a
document-startwarning on.copier-answers.yml, which copier generates itself.🤖 Generated with Claude Code