mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 12:47:04 +00:00
New ``cli.administration.deploy.swarm`` package wraps the strict
``cli.administration.deploy.dedicated`` runner with a layered closure
so swarm deploys never require operators (or test scripts) to
enumerate every infra role:
effective --id = seed (operator --id, else inventory_role_groups)
∪ dep-walk(seed via applications_if_group_and_all_deps)
∪ default-placement safety net
The dep-walker is the same SPOT
(``utils.roles.applications.in_group_deps.applications_if_group_and_all_deps``)
the runtime constructor consumes via ``lookup('applications_current_play')``,
so the CLI-side closure and the in-play role set stay in sync.
The dedicated CLI keeps strict explicit-only semantics: ``main`` was
split into ``parse_args`` + ``run_from_args`` so the swarm wrapper
can share parser, mode wiring, validator and runner verbatim and
only mutate ``args.id``.
Meta-side: ``svc-db-postgres`` gains ``default_placement: manager``
(semantic symmetry with mariadb). ``default_placement`` is now a
PLACEMENT hint only ("if deployed, run on the manager"); deploy
decisions come from the dep-walk. Apps that declare ``mariadb:``
pull svc-db-mariadb; apps that declare ``postgres:`` pull
svc-db-postgres; apps that declare neither get no central DB.
Shared utils extracted so the four consumers stay aligned:
* ``utils.inventory.groups.inventory_has_group``: YAML + INI static
inventory group probe (extracted from ValidDeployId).
* ``utils.roles.meta_lookup.{get_role_default_placement,
iter_roles_with_default_placement}``: single source of truth for
``default_placement`` discovery (lookup plugin is now a thin wrapper).
Test pipeline (``scripts/tests/deploy/swarm/*``):
* Heredoc-Python shells converted to proper modules under
``utils.tests.swarm.*`` and invoked via ``python -m``:
- extend_inventory.py (was 08_extend_inventory.sh)
- write_extras.py (was 09_write_extras.sh dynamic half)
- derive_includes.py (replaces 06's hardcoded includes array)
- write_hosts_entries.py (replaces 02's hardcoded /etc/hosts heredoc;
derives every canonical + www-redirect domain from APP_ID via
canonical_domains_map + generate_all_domains, so the test
works for any app in the matrix without per-app maintenance)
* 01_create_network.sh deleted (single docker command inlined in workflow).
* 11_deploy.sh deleted (swarm CLI now called directly in workflow).
* 09_write_extras.sh: static half moved to inventories/development/swarm.yml.
* Remaining shell scripts renumbered 01-13 without gaps.
* 08_wait_converge.sh: case block on DB_DEP replaced with single
conditional (container name == DB_DEP).
* 06_provision_inventory.sh: hardcoded includes array replaced with
`mapfile -t includes < <(python -m utils.tests.swarm.derive_includes)`.
* Workflow YAML step references updated to new script names + module
invocations.
New tests (39 covering the closure surface):
* tests/unit/cli/administration/deploy/swarm/test_closure.py (14)
* tests/unit/utils/inventory/test_groups.py (11)
* tests/unit/utils/roles/test_meta_lookup.py (+3 for default_placement)
* tests/unit/utils/roles/validation/test_deploy_id.py (unchanged
8 tests still pass after refactor onto the shared util).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""
|
|
Utility for validating deployment application IDs against defined roles and inventory.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from plugins.filter.get_all_application_ids import get_all_application_ids
|
|
from utils.inventory.groups import inventory_has_group
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
|
|
class ValidDeployId:
|
|
def __init__(self) -> None:
|
|
"""
|
|
Always resolve roles/ from the repository root, independent of CWD.
|
|
"""
|
|
repo_root = PROJECT_ROOT
|
|
roles_dir = repo_root / "roles"
|
|
|
|
if not roles_dir.is_dir():
|
|
raise RuntimeError(
|
|
f"roles directory not found at expected location: {roles_dir}"
|
|
)
|
|
|
|
self.roles_dir = roles_dir
|
|
self.valid_ids = set(get_all_application_ids(str(roles_dir)))
|
|
|
|
def validate(
|
|
self, inventory_path: str, ids: list[str]
|
|
) -> dict[str, dict[str, bool]]:
|
|
"""
|
|
Validate a list of application IDs against both role definitions and inventory.
|
|
|
|
Returns:
|
|
{
|
|
"app1": {"in_roles": False, "in_inventory": True},
|
|
"app2": {"in_roles": True, "in_inventory": False},
|
|
}
|
|
"""
|
|
invalid: dict[str, dict[str, bool]] = {}
|
|
|
|
for app_id in ids:
|
|
in_roles = app_id in self.valid_ids
|
|
in_inventory = inventory_has_group(inventory_path, app_id)
|
|
|
|
if not (in_roles and in_inventory):
|
|
invalid[app_id] = {
|
|
"in_roles": in_roles,
|
|
"in_inventory": in_inventory,
|
|
}
|
|
|
|
return invalid
|