Files
computer-playbook/utils/roles/deploy.py
T
kevinveenbirkenbachandClaude Opus 5 8ec6f79471 fix(ci): offer the deploy modes a role declares, not the ones its shape implies
role_deploy_modes derived the offered modes from the container shape alone and read modes.<mode>.enabled only for what the shape had already decided. A role could therefore write compose: enabled: true and never be read. The declaration now wins; only a role that declares nothing falls back on its shape.

The complexity matrix hand-rolled the same logic and drifted: host and swarm were gated on the declaration, compose on nothing at all. Every stackless invokable role was handed compose, rotating per sweep, which is why the desktop tier showed up under a whale. Both call sites now read the one helper.

web-svc-legal is the first role to state its own modes. It ships no compose template of its own and reaches compose and swarm through the html server it pulls, which the shape heuristic cannot see. host stays declared as a bool because the host lint requires one on a stackless role.

Measured against the plan: the deployed role#variant set is unchanged, and three mode cells move.

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

45 lines
1.8 KiB
Python

"""Deploy-shape helpers shared by the complexity matrix and the Cosmos builder.
Both need the same facts about a role: whether it ships its own container
stack, and which deploy modes it supports. Keeping the logic here means the two
callers never drift.
"""
from __future__ import annotations
from pathlib import Path
from utils.roles.meta_lookup import get_role_declared_modes, get_role_mode_enabled
def role_has_stack(role_dir: Path) -> bool:
"""True iff the role renders its own container stack, i.e. ships a
``templates/*compose*.yml.j2``. Host-only roles (backup cron, wireguard,
swapfile) and pure service-injectors carry no compose template and return
False. Presence (not an ``image`` key) is the signal so build-from-source
stacks that pull no registry image still count as a stack."""
templates = Path(role_dir) / "templates"
return templates.is_dir() and any(templates.rglob("*compose*.yml.j2"))
def role_deploy_modes(role_dir: Path, role_name: str | None = None) -> dict[str, bool]:
"""Return the deploy modes a role supports, keyed by mode with its
``modes.<mode>.enabled`` SPOT value.
What the role declares wins. A role that names its modes gets exactly
those, which is how an injector without a stack of its own can still be
deployed into compose and swarm alongside the partner that carries one.
Only a role that declares nothing falls back on its shape: a stack role
(ships a compose template) is offered ``compose`` and ``swarm``, anything
else is offered ``host``.
"""
role_dir = Path(role_dir)
modes = get_role_declared_modes(role_dir, role_name=role_name) or (
("compose", "swarm") if role_has_stack(role_dir) else ("host",)
)
return {
mode: get_role_mode_enabled(role_dir, mode=mode, role_name=role_name)
for mode in modes
}