mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
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>
102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
"""Unit tests for `utils.roles.entity_apps.apps_for_entity`."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.cache.yaml import _reset_cache_for_tests, dump_yaml
|
|
from utils.roles.entity_apps import apps_for_entity
|
|
from utils.roles.mapping import ROLE_FILE_VARS_MAIN
|
|
|
|
|
|
class TestAppsForEntity(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
_reset_cache_for_tests()
|
|
self.tmp = Path(tempfile.mkdtemp(prefix="apps_for_entity_test_"))
|
|
self.roles_dir = self.tmp / "roles"
|
|
self.roles_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# `get_entity_name` resolves categories.yml relative to cwd or
|
|
# PROJECT_ROOT, so a temp cwd with our roles tree is the simplest
|
|
# way to feed it a minimal categories layout for the test.
|
|
dump_yaml(
|
|
self.roles_dir / "categories.yml",
|
|
{
|
|
"roles": {
|
|
"web": {
|
|
"app": {"title": "Applications", "invokable": True},
|
|
"svc": {"title": "Services", "invokable": True},
|
|
}
|
|
}
|
|
},
|
|
)
|
|
|
|
self._cwd = Path.cwd()
|
|
os.chdir(self.tmp)
|
|
self.addCleanup(lambda: os.chdir(self._cwd))
|
|
self.addCleanup(lambda: shutil.rmtree(self.tmp, ignore_errors=True))
|
|
|
|
def _mk_role(self, name: str, application_id: str | None) -> None:
|
|
rd = self.roles_dir / name
|
|
(rd / "vars").mkdir(parents=True, exist_ok=True)
|
|
payload: dict = {}
|
|
if application_id is not None:
|
|
payload["application_id"] = application_id
|
|
dump_yaml(rd / ROLE_FILE_VARS_MAIN, payload)
|
|
|
|
def test_returns_apps_under_entity(self) -> None:
|
|
self._mk_role("web-app-matomo", "web-app-matomo")
|
|
self._mk_role("web-app-dashboard", "web-app-dashboard")
|
|
self._mk_role("web-svc-cdn", "web-svc-cdn")
|
|
|
|
self.assertEqual(
|
|
apps_for_entity("matomo", roles_dir=self.roles_dir),
|
|
["web-app-matomo"],
|
|
)
|
|
self.assertEqual(
|
|
apps_for_entity("cdn", roles_dir=self.roles_dir),
|
|
["web-svc-cdn"],
|
|
)
|
|
|
|
def test_falls_back_to_role_dir_name_when_application_id_missing(self) -> None:
|
|
# vars/main.yml present but no application_id → role dir name wins.
|
|
self._mk_role("web-app-foo", None)
|
|
self.assertEqual(
|
|
apps_for_entity("foo", roles_dir=self.roles_dir),
|
|
["web-app-foo"],
|
|
)
|
|
|
|
def test_unknown_entity_returns_empty(self) -> None:
|
|
self._mk_role("web-app-matomo", "web-app-matomo")
|
|
self.assertEqual(
|
|
apps_for_entity("does-not-exist", roles_dir=self.roles_dir),
|
|
[],
|
|
)
|
|
|
|
def test_blank_entity_returns_empty(self) -> None:
|
|
self._mk_role("web-app-matomo", "web-app-matomo")
|
|
self.assertEqual(apps_for_entity("", roles_dir=self.roles_dir), [])
|
|
self.assertEqual(apps_for_entity(" ", roles_dir=self.roles_dir), [])
|
|
|
|
def test_missing_roles_dir_returns_empty(self) -> None:
|
|
ghost = self.tmp / "ghost-roles"
|
|
self.assertEqual(apps_for_entity("matomo", roles_dir=ghost), [])
|
|
|
|
def test_result_is_sorted_and_unique(self) -> None:
|
|
# Two roles map to the same entity via the longest-prefix rule.
|
|
self._mk_role("web-app-matomo", "web-app-matomo")
|
|
self._mk_role("web-svc-matomo", "web-svc-matomo")
|
|
|
|
# Different entities — sanity check ordering across calls
|
|
got = apps_for_entity("matomo", roles_dir=self.roles_dir)
|
|
self.assertEqual(got, sorted(got))
|
|
self.assertEqual(len(got), len(set(got)))
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
unittest.main()
|