Files
computer-playbook/utils/roles/entity_apps.py
T
kevinveenbirkenbachandClaude Opus 4.7 820fb0a222 fix(purge): clear stale nginx vhosts between matrix-deploy rounds
Matrix-deploy variant transitions left per-domain vhost files behind:
when a service dropped out of a round's include set, sys-svc-compose's
docker prune removed its container but the vhost under
<NGINX_DIR>/conf.d/servers/{http,https}/<domain>.conf survived, making
the next round's HTTPS probe see 502 instead of TLS-error 000 (5 failed
test-development jobs on run 25705903504).

The inter-round purge in cli.administration.deploy.development.deploy
already invokes scripts/tests/deploy/local/purge/entity.sh with the
previous round's full include — the missing piece was an entity-keyed
nginx primitive next to db/compose/dir. Add it:

- utils.roles.entity_apps.apps_for_entity: reverse of get_entity_name,
  walks roles/*/vars/main.yml to map entity -> [application_id]
- utils.cleanup.nginx_vhosts: resolves entity -> apps -> canonical+alias
  domains (via build_applications_from_roles + iter_app_domains so it
  stays ansible-free) and removes the matching vhost files
- scripts/container/purge/entity/nginx.sh: thin bash wrapper,
  symmetric to db/compose/dir
- scripts/container/purge/apps.sh: drive nginx.sh inside the entity
  loop, alongside db/compose/dir

Tests: 14 new unit tests covering the reverse-lookup edge cases and
the purge iterator (existing-only / aliases / multi-entity / no-op CLI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 02:17:26 +02:00

60 lines
2.0 KiB
Python

"""Reverse entity -> application_id resolver.
`utils.roles.entity_name.get_entity_name` maps a role / application_id to
its compose entity name (the longest matching category prefix stripped).
The purge orchestrators need the inverse: given an entity name, which
application_ids belong to it? This helper walks the roles directory and
returns that mapping.
Used by `utils.cleanup.nginx_vhosts` so an entity-keyed purge can
enumerate the per-app domains whose nginx vhost files belong to the
stack being torn down.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from utils.cache.yaml import load_yaml_any
from utils.roles.entity_name import get_entity_name
from utils.roles.mapping import ROLE_FILE_VARS_MAIN
if TYPE_CHECKING:
from pathlib import Path
def _role_app_id(role_dir: Path) -> str:
"""Return the role's application_id (vars/main.yml.application_id),
falling back to the directory name when the field is missing or the
file does not exist. Mirrors `utils.roles.validation.invokable._role_to_app_id`.
"""
vars_file = role_dir / ROLE_FILE_VARS_MAIN
if not vars_file.is_file():
return role_dir.name
data = load_yaml_any(str(vars_file), default_if_missing={}) or {}
if not isinstance(data, dict):
return role_dir.name
app_id = data.get("application_id")
return str(app_id) if app_id else role_dir.name
def apps_for_entity(entity: str, *, roles_dir: Path) -> list[str]:
"""Return sorted, deduplicated application_ids whose role belongs to
*entity* under `get_entity_name`.
Returns an empty list when *entity* is blank, *roles_dir* is not a
directory, or no role matches.
"""
target = entity.strip()
if not target or not roles_dir.is_dir():
return []
out: set[str] = set()
for role_dir in sorted(p for p in roles_dir.iterdir() if p.is_dir()):
app_id = _role_app_id(role_dir)
if not app_id:
continue
if get_entity_name(app_id) == target:
out.add(app_id)
return sorted(out)