mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
An image bump in a provider redeployed every variant of every consumer, including the variants that switch that provider off and therefore never deploy the bumped image at all. The diff-derived whitelist now narrows each affected role to the variants whose own round closure still reaches a seed, emitted as `role#0,2` selection tokens. That closure is walked on the same variant-merged services maps the matrix planner builds its rounds from, so the narrowing follows a parent's variant choice across every generation of the chain. It covers every auto-derived whitelist: the weekly update PRs, Dependabot, and any other diff-driven run. The narrowing never shrinks the matrix on partial information. A seed role keeps every variant, because its own files changed. A role reached only through a run_after edge keeps every variant too, because the round closure does not follow that edge and a role whose hits are empty is therefore unnarrowed rather than dropped. A pinned token naming a role the discovery query does not return now warns instead of aborting the run. The diff-derived whitelist legitimately names roles the lifecycle envelope filters out, and once it pins variants such a token would abort every chunk; role names are still checked against roles/ before the run starts, so a typo is caught one layer earlier. The round's variant merge moves out of the deploy inventory package into utils/roles/applications/variants.py. The CI selection has to build the same maps, and importing the deploy package for them pulls in a module that reads INFINITO_SRC_DIR at import time, which the runner host does not set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
"""Variant payload baking for the development inventory.
|
|
|
|
Two helpers, both pure: `_resolve_variant_payloads` picks the variant
|
|
payload for each app in a round's include set, and `_bake_overrides`
|
|
merges those payloads under `applications:` inside the `--vars` JSON
|
|
the inventory provision step consumes. Caller-supplied `extra_vars`
|
|
always win over the variant bake — that's the documented "user vars
|
|
always win" rule.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
from typing import Any
|
|
|
|
from utils.cache.applications import get_variant_overrides_only
|
|
from utils.roles.applications.topics import apply_topic
|
|
from utils.roles.applications.variants import collect_provider_overrides
|
|
|
|
|
|
def _resolve_variant_payloads(
|
|
*,
|
|
roles_dir: str,
|
|
include: Sequence[str],
|
|
active_variants: Mapping[str, int],
|
|
) -> dict[str, Any]:
|
|
"""Return ``{app_id: variant_override}`` for the requested round.
|
|
|
|
Each value is the raw `meta/variants.yml` entry — NOT the
|
|
deep-merge of variants.yml on top of `meta/services.yml`. Baking
|
|
only the override keeps host_vars sparse so downstream passes
|
|
(notably `apply_mirror_overrides`) can populate fields like
|
|
`image`/`version` that the role declares in `meta/services.yml`.
|
|
|
|
Apps without `meta/variants.yml` collapse to a single empty
|
|
override `{}`. Out-of-range indices clamp to 0.
|
|
|
|
A config topic a variant declares under a service it depends on is
|
|
finally moved onto that service's provider, so the role pulling a
|
|
dependency in is what decides how lean the dependency runs.
|
|
"""
|
|
overrides_per_app = get_variant_overrides_only(roles_dir=roles_dir)
|
|
resolved: dict[str, Any] = {}
|
|
for app_id in include:
|
|
variant_list = overrides_per_app.get(app_id) or [{}]
|
|
if not variant_list:
|
|
continue
|
|
index = active_variants.get(app_id, 0)
|
|
if not 0 <= index < len(variant_list):
|
|
index = 0
|
|
resolved[app_id] = variant_list[index]
|
|
for provider, topics in collect_provider_overrides(
|
|
resolved, roles_dir=roles_dir
|
|
).items():
|
|
if provider not in resolved:
|
|
continue
|
|
payload = resolved[provider]
|
|
payload = dict(payload) if isinstance(payload, Mapping) else {}
|
|
for topic, value in topics.items():
|
|
payload[topic] = apply_topic(payload.get(topic), value)
|
|
resolved[provider] = payload
|
|
return resolved
|
|
|
|
|
|
def _bake_overrides(
|
|
*,
|
|
base_overrides: Mapping[str, Any],
|
|
variant_payloads: Mapping[str, Any],
|
|
) -> dict[str, Any]:
|
|
"""Merge the per-app variant payloads under `applications:` into the
|
|
`--vars` JSON the init step will hand to
|
|
`infinito administration inventory provision`.
|
|
Caller-supplied `extra_vars` (already inside `base_overrides`) win
|
|
over the variant bake when the same key is set on both sides — that
|
|
matches the existing "user vars always win" rule."""
|
|
merged: dict[str, Any] = dict(base_overrides)
|
|
if not variant_payloads:
|
|
return merged
|
|
existing_apps = merged.get("applications")
|
|
if not isinstance(existing_apps, Mapping):
|
|
existing_apps = {}
|
|
apps: dict[str, Any] = dict(variant_payloads.items())
|
|
for app_id, override in existing_apps.items():
|
|
base_payload = apps.get(app_id)
|
|
if isinstance(base_payload, Mapping) and isinstance(override, Mapping):
|
|
apps[app_id] = {**base_payload, **override}
|
|
else:
|
|
apps[app_id] = override
|
|
merged["applications"] = apps
|
|
return merged
|