Skip to content

feat(iaci): environment axis — env-scoped overlay orthogonal to profile (#60)#61

Merged
Zorlin merged 3 commits into
mainfrom
feat/environment-axis
Jun 25, 2026
Merged

feat(iaci): environment axis — env-scoped overlay orthogonal to profile (#60)#61
Zorlin merged 3 commits into
mainfrom
feat/environment-axis

Conversation

@Zorlin

@Zorlin Zorlin commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Implements #60.

What

A native environment axis, orthogonal to profile:

  • profile = location/site/context → selects inventory + secrets_inventory (unchanged).
  • environment = env-within (prod/test/staging) → layers an env-scoped secrets overlay on top of whatever the profile selected, selected independently.
version: 1
defaults:
  playbook: playbooks/k3s/main.yml
  inventory: [labs/london]
  secrets_inventory: [../infra-secrets/london]
  environment: prod          # default env
environments:
  test:
    secrets_inventory: [../infra-secrets/london-test]
jetpack apply                       # prod (default env, no overlay)
jetpack apply --environment test    # london inv+secrets + london-test overlay
jetpack apply --profile nyc --environment test   # orthogonal

Selecting an env appends its overlay to the secrets load list (loaded last → later-wins), so an env can pin any per-env var without touching the site inventory. --no-secrets skips it; an unknown env is a clear error.

How (mirrors --profile end-to-end)

  • Schema (config_file.rs): defaults.environment + environments: map + JetpackEnvironment { secrets_inventory } (Some(vec![]) clears, None inherits), surfaced in EffectiveDefaults.
  • CLI (parser.rs): --environment/-E, fields + summary row.
  • Application (parser.rs): env overlay appended after profile resolution; --no-secrets skips it; unknown-env errors.

Tests & verification

  • 9 new tests (schema parse, overlay-appended-last, -E alias, default-env, orthogonality with --profile, unknown-env error, --no-secrets skip, empty-vec-vs-absent). 389 total green; cargo clippy clean for the new code.
  • Real binary: -v shows sec/base alone without --environment; sec/base, sec/envtest + an Environment | test row with -E test.

Deferred (with reasons)

  • Overlay-merge integration test (env var shadows base): relies on loading.rs's existing later-wins multi-path behavior, which the appended-overlay test already exercises at the parser level.
  • Templated play.groups e2e from the issue (groups: ["{{ k3s_group_prefix }}-bootstrap"]): depends on Template play.groups — parameterize a playbook's target groups from vars #52 (unmerged) — lands with that integration.

🤖 Generated with Claude Code

Zorlin and others added 3 commits June 25, 2026 22:39
Schema foundation for an env-scoped overlay orthogonal to `profile`:

- `defaults.environment` (default env name) + `environments:` map of named
  overlays on `JetpackFileConfig`, paralleling `profiles:`.
- New `JetpackEnvironment { secrets_inventory }` — `Some(vec![])` clears,
  `None` inherits, mirroring `JetpackProfile`.
- `EffectiveDefaults.environment` surfaced by `effective()`.

Parse-only this commit: selection (`--environment`) and the overlay-append
application land in the parser next. Three schema tests cover the default-env
field, the environments map, and the empty-vec-vs-absent distinction.

Co-Authored-By: Claude <noreply@anthropic.com>
The selection + application half of the environment axis, orthogonal to profile:

- CLI: `--environment` / `-E` flag (fields `environment`/`active_environment`,
  mirroring `--profile`), surfaced in the verbose resolution summary.
- Application: a selected environment APPENDS its `secrets_inventory` to the load
  list (loaded last → later-wins) on top of whatever profile/defaults selected.
  Precedence is CLI `--environment` > `defaults.environment`. It affects secrets
  only, so `--no-secrets` skips it entirely; an unknown environment is a clear
  error.

Six tests cover: overlay-appended-last, `-E` alias, default-env, orthogonality
with `--profile`, unknown-env error, and `--no-secrets` skip. Verified on the
real binary: `-v` shows `sec/base` alone without `--environment`, and
`sec/base, sec/envtest` + an `Environment | test` row with `-E test`.

The overlay-merge (env var shadows base) relies on loading.rs's existing
later-wins multi-path behavior; the templated-`play.groups` e2e from the issue
depends on #52 (unmerged), so lands with that integration.

Co-Authored-By: Claude <noreply@anthropic.com>
…60)

Stitches the real layers the runtime chains — `load_inventory` →
`resolve_target_groups` → `get_play_hosts` — with no mocks: an
environment's `secrets_inventory` overlay (loaded last → later-wins)
pins `target` in `group_vars/all`, where templated `play.groups`
reads it, so one playbook fans out to the env-specific cluster. A
causality test confirms the var genuinely comes from the overlay
(Strict templating errors without it). This owns the previously-
untested seam between the environment axis (#60) and templated
groups (#52); the `--environment`→append step is covered by the
parser's own tests.

Co-Authored-By: Claude <noreply@anthropic.com>
@Zorlin Zorlin merged commit caa60b6 into main Jun 25, 2026
8 of 9 checks passed
@Zorlin Zorlin deleted the feat/environment-axis branch June 25, 2026 22:25
Zorlin added a commit that referenced this pull request Jun 26, 2026
style(playbooks): cargo fmt — unbreak main's Format check (#61)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant