Files
computer-playbook/cli/administration/deploy/development/inventory/spec.py
T
kevinveenbirkenbachandClaude Opus 4.7 bd1a3a69ad fix(deploy/development/inventory/planner): split per-round include from union purge_set
`3c1d5d821` solved the cross-round backbone-abriss problem by making
`include` equal the union of every variant's closure for every round.
Side-effect: every matrix round then ALSO deployed the full union, so
`08_deploy_performance.sh` saw `dashboard.infinito.example` still
answering HTTP 200 after a matomo-only `deploy-fresh-purged-apps` —
the deploy step was no longer matomo-only at all.

Split the two concerns instead:

- `include` (PlanEntry[3]) goes back to the per-round variant-closure.
  Both `init`/`builder` (`--include` for the inventory bake) and
  `deploy` (`--id` for ansible) consume it. Each round therefore
  deploys ONLY its own variant-closure again — the pre-`3c1d5d821`
  semantics.
- `purge_set` (PlanEntry[4], new) carries the union across all rounds.
  `deploy.py` uses it for the inter-round wipe instead of the previous
  round's include. Result: between rounds the WHOLE matrix footprint
  is purged, not just the previous round's variant slice — a variant
  pinning `services.<X>.shared: false` cannot leave its bundled
  provider on the host for a sibling round that wires the same service
  shared. The backbone-abriss `3c1d5d821` was hunting stays fixed:
  whichever round needs the backbone re-deploys it immediately after
  the wipe (purge runs at the start of each round, not the end).

`previous_round_include` tracking is gone. The first iteration skips
the purge (nothing to wipe yet), which also covers `total_rounds == 1`
and `--variant N` pinned single-folder modes cleanly.

Unrelated, but staged together because it is part of the same
investigation:

- `roles/dev-nix/templates/nix.conf.j2`: enable `keep-derivations` /
  `keep-outputs`, so `nix-collect-garbage -d` (run by the aggressive
  prune step) does not drop the build closure of pkgmgr's flake; this
  was making every deploy re-fetch the build inputs from
  cache.nixos.org and overshoot pkgmgr's update timeout.
- `roles/pkgmgr/tasks/01_core.yml`: raise the pkgmgr update timeout
  from 600s to 1800s and lower the retry count from 6 to 3, so a slow
  fetch on a single try no longer pushes total wallclock past three
  hours.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:44:47 +02:00

67 lines
2.5 KiB
Python

"""Data types for the development inventory matrix.
`DevInventorySpec` is the value type `build_dev_inventory` consumes —
it carries everything `infinito administration inventory provision`
needs to materialise one development inventory folder. `PlanEntry` is
the per-round tuple `plan_dev_inventory_matrix` emits and the deploy
wrapper iterates.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from collections.abc import Mapping
# (round_index, inventory_dir, round_variants, include, purge_set) — include is the round's variant-closure (what to deploy); purge_set is the union over all rounds (what the inter-round wipe clears).
PlanEntry = tuple[int, str, dict[str, int], tuple[str, ...], tuple[str, ...]]
@dataclass(frozen=True)
class DevInventorySpec:
"""Everything `infinito administration inventory provision` needs to
materialise one development inventory folder.
`extra_vars` wins over the implicit `STORAGE_CONSTRAINED` / `RUNTIME`
overrides so callers (and tests) keep the documented "user-provided
vars always win" behaviour without re-implementing the merge.
`active_variants` is the per-app variant index for the round this
folder represents. Apps absent from the mapping (or with an out-of-
range index) fall back to variant 0. The resolved variant payload
for every app in `include` is baked into the inventory as an
`applications.<app>` override, so the deploy stage no longer needs
a runtime variant selector — the inventory itself is variant-resolved.
"""
inventory_dir: str
include: tuple[str, ...]
storage_constrained: bool
runtime: str
extra_vars: Mapping[str, Any] | None = None
services_disabled: str = ""
active_variants: Mapping[str, int] | None = None
def __post_init__(self) -> None:
if not self.include:
raise ValueError("DevInventorySpec.include must not be empty")
if not isinstance(self.include, tuple):
object.__setattr__(self, "include", tuple(self.include))
def overrides(self) -> dict[str, Any]:
merged: dict[str, Any] = {
"STORAGE_CONSTRAINED": bool(self.storage_constrained),
"RUNTIME": self.runtime,
}
if self.extra_vars:
merged.update(self.extra_vars)
return merged
def variant_selectors(self) -> dict[str, int]:
return dict(self.active_variants or {})
def inventory_root(self) -> str:
return str(self.inventory_dir).rstrip("/")