Files
computer-playbook/cli/administration/deploy/development/inventory/builder.py
T
kevinveenbirkenbachandClaude Opus 5.5 6314938133 feat(ci): replace the tor axis with a network axis of node modes
Every deploy row now takes a node network mode, clearnet, tor or multi,
instead of an onion yes or no. The workflow input `network`
(auto|clearnet|tor|multi) replaces `tor`, and INFINITO_NETWORK replaces
INFINITO_TOR.

utils/github/variant/network.py decides which modes a row can take: clearnet
only without a Tor bond or when the variant pins Tor off, no multi for a
single_mode role, no clearnet for the Tor provider, whose rows are therefore no
longer dropped when the rotation lands on clearnet. Priority rows cover every
mode and network combination, regular rows rotate the network on sweep // 2,
and clearnet rows carry disable=tor. Selection tokens pin the axis with
+clearnet, +tor or +multi, and multi rows wear the new glyph.

A row hands its mode to the deploy as the variable `network`, which
provisioning writes into host_vars as NETWORK_MODE. Without it the node keeps
deriving its mode from the deployed providers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 22:40:20 +02:00

147 lines
4.6 KiB
Python

"""Inventory folder materialisation.
`build_dev_inventory` is the SPOT that turns a `DevInventorySpec` into a
fully baked inventory folder via the `infinito administration inventory
provision` CLI. `build_dev_inventory_matrix` is the orchestrator: it
runs the planner and builds every round's folder.
"""
from __future__ import annotations
import json
import os
from typing import TYPE_CHECKING, Any
from cli.administration.deploy.development.common import DEV_INVENTORY_VARS_FILE
from cli.administration.deploy.development.mirrors import (
generate_ci_mirrors_file,
should_use_mirrors,
)
from .payload import _bake_overrides, _resolve_variant_payloads
from .planner import plan_dev_inventory_matrix
from .spec import DevInventorySpec, PlanEntry
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from cli.administration.deploy.development.compose import Compose
def build_dev_inventory(compose: Compose, spec: DevInventorySpec) -> None:
"""Build the inventory folder described by `spec` and ensure its
vault password file exists. Two side-effects, no return value: after
this call, `spec.inventory_dir` contains a complete, variant-resolved
inventory.
"""
inv_root = spec.inventory_root()
variant_payloads = _resolve_variant_payloads(
roles_dir=str(compose.repo_root / "roles"),
include=spec.include,
active_variants=spec.variant_selectors(),
)
vars_payload = _bake_overrides(
base_overrides=spec.overrides(),
variant_payloads=variant_payloads,
)
cmd = [
"infinito",
"administration",
"inventory",
"provision",
inv_root,
"--host",
"localhost",
"--vars-file",
DEV_INVENTORY_VARS_FILE,
"--vars",
json.dumps(vars_payload, sort_keys=True),
"--include",
",".join(spec.include),
]
variant_selectors = spec.variant_selectors()
if variant_selectors:
cmd += ["--app-variants", json.dumps(variant_selectors, sort_keys=True)]
if should_use_mirrors():
mirrors_file = generate_ci_mirrors_file(compose, inventory_dir=inv_root)
cmd += ["--mirror", mirrors_file]
extra_env: dict[str, str] = {}
if spec.services_disabled:
extra_env["disable"] = spec.services_disabled
if spec.network_mode:
extra_env["network"] = spec.network_mode
compose.exec(
cmd,
check=True,
workdir=os.environ["INFINITO_SRC_DIR"],
extra_env=extra_env or None,
)
_ensure_vault_password_file(compose, inventory_dir=inv_root)
def build_dev_inventory_matrix(
compose: Compose,
*,
base_inventory_dir: str,
primary_apps: Sequence[str],
storage_constrained: bool,
runtime: str,
extra_vars: Mapping[str, Any] | None = None,
services_disabled: str = "",
include_filter: Sequence[str] | None = None,
) -> list[PlanEntry]:
"""Build every folder in the matrix plan and return the plan.
`include_filter`, when provided, is the set of role names a caller
has already filtered (e.g. by `disable` removal of provider
roles). Each round's include is intersected with this set before
being baked, so the inventory and `--include` flag stay aligned
with whatever the deploy step will actually deploy.
"""
plan = plan_dev_inventory_matrix(
roles_dir=str(compose.repo_root / "roles"),
primary_apps=primary_apps,
base_inventory_dir=base_inventory_dir,
)
allow: set[str] | None = set(include_filter) if include_filter is not None else None
for _round_index, inv_dir, round_variants, raw_include, _purge_set in plan:
round_include = (
tuple(role for role in raw_include if role in allow)
if allow is not None
else raw_include
)
spec = DevInventorySpec(
inventory_dir=inv_dir,
include=round_include,
storage_constrained=storage_constrained,
runtime=runtime,
extra_vars=extra_vars,
services_disabled=services_disabled,
active_variants=round_variants,
)
build_dev_inventory(compose, spec)
return plan
def _ensure_vault_password_file(compose: Compose, *, inventory_dir: str) -> None:
inv_root = str(inventory_dir).rstrip("/")
pw_file = f"{inv_root}/.password"
compose.exec(
[
"sh",
"-lc",
(
f"mkdir -p {inv_root} && "
f"[ -f {pw_file} ] || "
f"printf '%s\n' 'ci-vault-password' > {pw_file}"
),
],
check=True,
)