mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 12:47:04 +00:00
A round deploys a provider once, so the role that pulls it in is the only place that can say how lean it has to be. A variant entry may now carry any application config topic under a service it depends on: a mapping deep-merges, so naming one key changes that key alone, and null empties the topic. Empty was the case that needed a marker of its own, because merging an empty mapping is a no-op and would leave exactly the services the round was trying to shed. The topic vocabulary and the merge semantics live in utils.roles.applications.topics, which is also where resources.py now gets them rather than spelling the same null-or-merge out a second time. Collecting who demands what stays in the inventory tree, refuses two roles demanding different values for one provider in the same round, and skips a role's own entity entry so the existing services.<own>.users declarations are not read as overrides. The CI set-cover learns the same fact. Its greedy cover retires a role's variant-0 row when an earlier row of another role embeds it, on the stated premise that embedding pulls the provider's variant 0. A variant that dictates that provider's config breaks the premise from the other side: whatever ran, it was not the provider as the provider declares it. Such a pair now covers nothing, so the retired row keeps its job, its artefact and its verdict. Measured on the current tree: 20 override pairs, 41 covered rows before and after, so nothing is revoked today. The rule bites the moment an override lands in a row that covers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
3.4 KiB
Python
91 lines
3.4 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 .nested_overrides import apply_topic, 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
|