Files
computer-playbook/utils/roles/deploy.py
kevinveenbirkenbachandClaude Opus 4.8 90b3845998 feat(readme): role README generator with Cosmos diagrams, Quick Setup, and lint
Add cli/build/readme: derives each role README's managed sections from
templates/roles/README.md.j2.tmpl (the Cosmos mermaid diagram from
meta/services.yml, an invokable-gated Quick Setup, and Credits).

The Cosmos diagram shows dependencies, every declared service, and
dependents (roles that consume this one, matched by role name and by
provides provision-name in both directions). Role nodes carry deploy-mode
emoji markers (compose/swarm/host); disabled and shared services get a
stop mark; dependents are capped at 12.

test_readme enforces the Quick Setup body is byte-exact with the template
render. `make readme-generate` and `make cosmos` regenerate; `make cosmos`
also runs inside `make quality`.

Refactor: utils/symbol_glossary.py is the single source of truth for the
word/emoji mapping (unique project-wide), consumed by the complexity
matrix and the Cosmos builder. utils/roles/deploy.py holds role_has_stack
and role_deploy_modes, shared by both so the logic never drifts.

Add `make lint-mermaid` and its CI workflow, rendering every diagram via
mermaid-cli; the render step provisions chrome-headless-shell and unzip
on demand so it works in the minimal container.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:44:33 +02:00

39 lines
1.5 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_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.
A stack role (ships a compose template) is offered ``compose`` and
``swarm``; a host-only role is offered ``host``. The two are mutually
exclusive: swarm needs a stack, host configures the machine instead.
"""
role_dir = Path(role_dir)
modes = ("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
}