mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-13 06:06:38 +00:00
The autoformat step of the gate wrapped the long assertions that
660ad2ff16 committed unformatted. No behaviour change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
392 lines
14 KiB
Python
392 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.cache.files import PROJECT_ROOT, read_text
|
|
from utils.cache.yaml import dump_yaml_str
|
|
from utils.roles.applications.services.registry import (
|
|
GATED_LOOKUPS,
|
|
GROUP_GATED_CONSTANTS,
|
|
ServiceRegistryError,
|
|
build_service_registry_from_applications,
|
|
detect_service_bucket,
|
|
detect_service_channel,
|
|
is_explicit_truth,
|
|
ordered_primary_service_entries,
|
|
resolve_service_dependency_roles_from_config,
|
|
)
|
|
|
|
|
|
class TestServiceRegistryDiscovery(unittest.TestCase):
|
|
"""Per, the materialised application payload exposes services
|
|
under the bare ``services`` key (no ``compose.services`` wrapper)."""
|
|
|
|
def test_discovery_reads_provides_canonical_shared_and_enabled(self):
|
|
applications = {
|
|
"web-app-keycloak": {
|
|
"services": {
|
|
"keycloak": {
|
|
"enabled": False,
|
|
"shared": True,
|
|
"provides": "oidc",
|
|
}
|
|
}
|
|
},
|
|
"web-svc-cdn": {
|
|
"services": {
|
|
"cdn": {"enabled": False, "shared": True},
|
|
"css": {"enabled": True, "shared": True, "canonical": "cdn"},
|
|
"javascript": {
|
|
"enabled": False,
|
|
"shared": True,
|
|
"canonical": "cdn",
|
|
},
|
|
}
|
|
},
|
|
}
|
|
|
|
registry = build_service_registry_from_applications(applications)
|
|
|
|
self.assertEqual(registry["oidc"]["role"], "web-app-keycloak")
|
|
self.assertEqual(registry["oidc"]["entity_name"], "keycloak")
|
|
self.assertTrue(registry["oidc"]["shared"])
|
|
self.assertFalse(registry["oidc"]["enabled"])
|
|
|
|
self.assertEqual(registry["cdn"]["role"], "web-svc-cdn")
|
|
self.assertNotIn("canonical", registry["cdn"])
|
|
self.assertEqual(registry["css"]["canonical"], "cdn")
|
|
self.assertTrue(registry["css"]["enabled"])
|
|
self.assertTrue(registry["javascript"]["shared"])
|
|
|
|
def test_entity_name_derivation_for_relevant_role_prefixes(self):
|
|
applications = {
|
|
"web-app-dashboard": {
|
|
"services": {"dashboard": {"enabled": False, "shared": True}}
|
|
},
|
|
"web-svc-file": {"services": {"file": {"enabled": False, "shared": True}}},
|
|
"svc-db-mariadb": {
|
|
"services": {"mariadb": {"enabled": False, "shared": True}}
|
|
},
|
|
"svc-ai-ollama": {
|
|
"services": {"ollama": {"enabled": False, "shared": True}}
|
|
},
|
|
}
|
|
|
|
registry = build_service_registry_from_applications(applications)
|
|
|
|
self.assertIn("dashboard", registry)
|
|
self.assertIn("file", registry)
|
|
self.assertIn("mariadb", registry)
|
|
self.assertIn("ollama", registry)
|
|
self.assertEqual(detect_service_channel("web-app-dashboard"), "frontend")
|
|
self.assertEqual(detect_service_channel("svc-db-mariadb"), "backend")
|
|
self.assertEqual(detect_service_bucket("web-app-dashboard"), "web-app")
|
|
self.assertEqual(detect_service_bucket("web-svc-file"), "web-svc")
|
|
self.assertEqual(detect_service_bucket("svc-db-mariadb"), "universal")
|
|
|
|
def test_discovery_passes_overlay_block_through(self):
|
|
applications = {
|
|
"svc-db-mariadb": {
|
|
"services": {"mariadb": {"enabled": True, "shared": True}},
|
|
"networks": {
|
|
"overlay": {
|
|
"modes": ["compose", "swarm"],
|
|
"topology": "shared_net",
|
|
"aliases": ["mariadb"],
|
|
"consumer": {"kind": "database"},
|
|
}
|
|
},
|
|
},
|
|
"svc-prx-openresty": {
|
|
"services": {"openresty": {"enabled": True, "shared": True}},
|
|
"networks": {
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "default_net",
|
|
"aliases": [],
|
|
"consumer": {
|
|
"kind": "services_flags",
|
|
"key": "sso",
|
|
"flags": ["enabled"],
|
|
},
|
|
}
|
|
},
|
|
},
|
|
"web-app-keycloak": {
|
|
"services": {
|
|
"keycloak": {
|
|
"enabled": True,
|
|
"shared": True,
|
|
"provides": "oidc",
|
|
}
|
|
},
|
|
"networks": {
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"proxy_resolvable": True,
|
|
"aliases": ["auth.example"],
|
|
}
|
|
},
|
|
},
|
|
"web-app-plain": {"services": {"plain": {"enabled": True, "shared": True}}},
|
|
}
|
|
|
|
registry = build_service_registry_from_applications(applications)
|
|
|
|
self.assertEqual(registry["mariadb"]["overlay"]["modes"], ["compose", "swarm"])
|
|
self.assertEqual(registry["mariadb"]["overlay"]["topology"], "shared_net")
|
|
self.assertEqual(registry["mariadb"]["overlay"]["consumer"]["kind"], "database")
|
|
self.assertEqual(registry["openresty"]["overlay"]["modes"], ["swarm"])
|
|
self.assertEqual(registry["openresty"]["overlay"]["aliases"], [])
|
|
self.assertTrue(registry["oidc"]["overlay"]["proxy_resolvable"])
|
|
self.assertNotIn("topology", registry["oidc"]["overlay"])
|
|
self.assertEqual(registry["oidc"]["overlay"]["aliases"], ["auth.example"])
|
|
self.assertNotIn("overlay", registry["plain"])
|
|
|
|
def test_direct_service_resolution_uses_role_local_names(self):
|
|
registry = {
|
|
"ldap": {"role": "svc-db-openldap"},
|
|
"mariadb": {"role": "svc-db-mariadb"},
|
|
}
|
|
config = {
|
|
"services": {
|
|
"ldap": {"enabled": True, "shared": True},
|
|
"mariadb": {"enabled": True, "shared": True},
|
|
"ignored": {"enabled": True, "shared": False},
|
|
}
|
|
}
|
|
|
|
self.assertEqual(
|
|
resolve_service_dependency_roles_from_config(config, registry),
|
|
["svc-db-openldap", "svc-db-mariadb"],
|
|
)
|
|
|
|
|
|
class TestServiceRegistryOrdering(unittest.TestCase):
|
|
"""Per, ``run_after`` lives on the role's primary entity at
|
|
``meta/services.yml.<primary_entity>.run_after``."""
|
|
|
|
def _mk_role(
|
|
self,
|
|
root: Path,
|
|
role: str,
|
|
primary_entity: str,
|
|
services_payload: dict,
|
|
*,
|
|
run_after: list[str] | None = None,
|
|
) -> None:
|
|
|
|
role_dir = root / role
|
|
meta_dir = role_dir / "meta"
|
|
meta_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
services = dict(services_payload)
|
|
if run_after is not None:
|
|
primary = dict(services.get(primary_entity, {}) or {})
|
|
if run_after:
|
|
primary["run_after"] = list(run_after)
|
|
services[primary_entity] = primary
|
|
|
|
meta_dir.joinpath("services.yml").write_text(
|
|
dump_yaml_str(services),
|
|
encoding="utf-8",
|
|
)
|
|
meta_dir.joinpath("main.yml").write_text(
|
|
"galaxy_info: {}\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
def test_ordered_entries_follow_bucket_order_and_run_after(self):
|
|
registry = {
|
|
"mariadb": {
|
|
"role": "svc-db-mariadb",
|
|
"bucket": "universal",
|
|
"deploy_type": "universal",
|
|
},
|
|
"file": {
|
|
"role": "web-svc-file",
|
|
"bucket": "web-svc",
|
|
"deploy_type": "server",
|
|
},
|
|
"asset": {
|
|
"role": "web-svc-asset",
|
|
"bucket": "web-svc",
|
|
"deploy_type": "server",
|
|
},
|
|
"matomo": {
|
|
"role": "web-app-matomo",
|
|
"bucket": "web-app",
|
|
"deploy_type": "server",
|
|
},
|
|
"dashboard": {
|
|
"role": "web-app-dashboard",
|
|
"bucket": "web-app",
|
|
"deploy_type": "server",
|
|
},
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as td:
|
|
roles_dir = Path(td)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"svc-db-mariadb",
|
|
"mariadb",
|
|
{"mariadb": {"enabled": False, "shared": True}},
|
|
)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-svc-file",
|
|
"file",
|
|
{"file": {"enabled": False, "shared": True}},
|
|
)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-svc-asset",
|
|
"asset",
|
|
{"asset": {"enabled": False, "shared": True}},
|
|
run_after=["web-svc-file"],
|
|
)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-app-matomo",
|
|
"matomo",
|
|
{"matomo": {"enabled": False, "shared": True}},
|
|
)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-app-dashboard",
|
|
"dashboard",
|
|
{"dashboard": {"enabled": False, "shared": True}},
|
|
run_after=["web-app-matomo"],
|
|
)
|
|
|
|
ordered = ordered_primary_service_entries(registry, roles_dir)
|
|
|
|
self.assertEqual(
|
|
[entry["role"] for entry in ordered],
|
|
[
|
|
"svc-db-mariadb",
|
|
"web-svc-file",
|
|
"web-svc-asset",
|
|
"web-app-matomo",
|
|
"web-app-dashboard",
|
|
],
|
|
)
|
|
|
|
def test_run_after_orders_across_buckets(self):
|
|
registry = {
|
|
"file": {
|
|
"role": "web-svc-file",
|
|
"bucket": "web-svc",
|
|
"deploy_type": "server",
|
|
},
|
|
"mail": {
|
|
"role": "sys-svc-mail",
|
|
"bucket": "universal",
|
|
"deploy_type": "universal",
|
|
},
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as td:
|
|
roles_dir = Path(td)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-svc-file",
|
|
"file",
|
|
{"file": {"enabled": False, "shared": True}},
|
|
run_after=["sys-svc-mail"],
|
|
)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"sys-svc-mail",
|
|
"mail",
|
|
{"mail": {"enabled": False, "shared": True}},
|
|
)
|
|
|
|
ordered = ordered_primary_service_entries(registry, roles_dir)
|
|
|
|
self.assertEqual(
|
|
[entry["role"] for entry in ordered],
|
|
["sys-svc-mail", "web-svc-file"],
|
|
)
|
|
|
|
def test_circular_run_after_fails_hard(self):
|
|
registry = {
|
|
"file": {"role": "web-svc-file", "bucket": "web-svc"},
|
|
"asset": {"role": "web-svc-asset", "bucket": "web-svc"},
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as td:
|
|
roles_dir = Path(td)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-svc-file",
|
|
"file",
|
|
{"file": {"enabled": False, "shared": True}},
|
|
run_after=["web-svc-asset"],
|
|
)
|
|
self._mk_role(
|
|
roles_dir,
|
|
"web-svc-asset",
|
|
"asset",
|
|
{"asset": {"enabled": False, "shared": True}},
|
|
run_after=["web-svc-file"],
|
|
)
|
|
|
|
with self.assertRaises(ServiceRegistryError):
|
|
ordered_primary_service_entries(registry, roles_dir)
|
|
|
|
|
|
class TestExplicitTruth(unittest.TestCase):
|
|
"""A flag may resolve a dependency through a group-gated constant.
|
|
|
|
The substring rule reads the role id out of the flag itself, so a SPOT
|
|
constant hides it. Roles referencing one keep their provider only while
|
|
the constant is registered.
|
|
"""
|
|
|
|
def test_a_literal_true_resolves(self):
|
|
self.assertTrue(is_explicit_truth(True))
|
|
|
|
def test_the_in_group_names_form_resolves(self):
|
|
self.assertTrue(is_explicit_truth("{{ 'web-app-keycloak' in group_names }}"))
|
|
|
|
def test_every_registered_constant_resolves(self):
|
|
for constant in GROUP_GATED_CONSTANTS:
|
|
with self.subTest(constant=constant):
|
|
self.assertTrue(is_explicit_truth("{{ " + constant + " }}"))
|
|
|
|
def test_every_registered_lookup_resolves(self):
|
|
for token in GATED_LOOKUPS:
|
|
with self.subTest(token=token):
|
|
self.assertTrue(is_explicit_truth("{{ " + token + ", 'clearnet') }}"))
|
|
|
|
def test_every_registered_lookup_has_a_plugin(self):
|
|
for token in GATED_LOOKUPS:
|
|
name = token.split("'")[1]
|
|
with self.subTest(lookup=name):
|
|
self.assertTrue(
|
|
(PROJECT_ROOT / "plugins" / "lookup" / f"{name}.py").is_file()
|
|
)
|
|
|
|
def test_an_unregistered_jinja_expression_does_not_resolve(self):
|
|
self.assertFalse(
|
|
is_explicit_truth("{{ lookup('config', 'web-app-gitea', 'mcp.enabled') }}")
|
|
)
|
|
|
|
def test_a_literal_false_does_not_resolve(self):
|
|
self.assertFalse(is_explicit_truth(False))
|
|
|
|
def test_every_registered_constant_is_defined_in_group_vars(self):
|
|
group_vars = PROJECT_ROOT / "group_vars" / "all"
|
|
defined = "".join(read_text(str(path)) for path in group_vars.glob("*.yml"))
|
|
for constant in GROUP_GATED_CONSTANTS:
|
|
with self.subTest(constant=constant):
|
|
self.assertIn(f"{constant}:", defined)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|