mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-12 05:36:40 +00:00
- Add 'email' key (role: web-app-mailu, type: frontend) to SERVICE_REGISTRY - Declare compose.services.email with enabled: true and shared: true in all 33 app roles that reference web-app-mailu or use SYSTEM_EMAIL_* variables - Add integration test test_mailu_service_dependency.py that enforces this constraint for any future role additions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
import re
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
_MAILU_ROLE = "web-app-mailu"
|
|
_EMAIL_SERVICE_KEY = "email"
|
|
|
|
# Patterns that indicate an email dependency on Mailu
|
|
_MAILU_REF_RE = re.compile(r"web-app-mailu")
|
|
_SYSTEM_EMAIL_RE = re.compile(r"SYSTEM_EMAIL_")
|
|
|
|
# File extensions to scan within a role
|
|
_SCAN_EXTENSIONS = {".yml", ".yaml", ".j2", ".py", ".sh", ".conf", ".env"}
|
|
|
|
|
|
def _scan_role(role_path: Path) -> tuple[bool, bool]:
|
|
"""Return (refs_mailu, refs_system_email) for all scannable files in *role_path*."""
|
|
refs_mailu = False
|
|
refs_email = False
|
|
for path in role_path.rglob("*"):
|
|
if not path.is_file() or path.suffix not in _SCAN_EXTENSIONS:
|
|
continue
|
|
try:
|
|
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
except OSError:
|
|
continue
|
|
if not refs_mailu and _MAILU_REF_RE.search(text):
|
|
refs_mailu = True
|
|
if not refs_email and _SYSTEM_EMAIL_RE.search(text):
|
|
refs_email = True
|
|
if refs_mailu and refs_email:
|
|
break
|
|
return refs_mailu, refs_email
|
|
|
|
|
|
class TestMailuServiceDependency(unittest.TestCase):
|
|
"""Every role that references 'web-app-mailu' or uses SYSTEM_EMAIL_* variables
|
|
must declare compose.services.email with enabled: true and shared: true
|
|
in its config/main.yml."""
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
def setUp(self):
|
|
self.roles_root = self.PROJECT_ROOT / "roles"
|
|
self.assertTrue(
|
|
self.roles_root.is_dir(),
|
|
f"Roles directory not found: {self.roles_root}",
|
|
)
|
|
|
|
def _email_service_conf(self, config_path: Path) -> dict:
|
|
if not config_path.is_file():
|
|
return {}
|
|
content = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
|
return (
|
|
content.get("compose", {}).get("services", {}).get(_EMAIL_SERVICE_KEY, {})
|
|
)
|
|
|
|
def test_mailu_dependents_declare_email_service(self):
|
|
errors = []
|
|
for role_path in sorted(self.roles_root.iterdir()):
|
|
if not role_path.is_dir():
|
|
continue
|
|
role_name = role_path.name
|
|
if role_name == _MAILU_ROLE:
|
|
continue
|
|
|
|
config_path = role_path / "config" / "main.yml"
|
|
if not config_path.is_file():
|
|
continue
|
|
|
|
refs_mailu, refs_system_email = _scan_role(role_path)
|
|
if not refs_mailu and not refs_system_email:
|
|
continue
|
|
|
|
reasons = []
|
|
if refs_mailu:
|
|
reasons.append("references 'web-app-mailu'")
|
|
if refs_system_email:
|
|
reasons.append("uses SYSTEM_EMAIL_* variables")
|
|
reason_str = " and ".join(reasons)
|
|
|
|
email_svc = self._email_service_conf(config_path)
|
|
rel = config_path.relative_to(self.PROJECT_ROOT)
|
|
|
|
if not email_svc.get("enabled"):
|
|
errors.append(
|
|
f"[{role_name}] {reason_str} but "
|
|
f"compose.services.email.enabled is not true in {rel}"
|
|
)
|
|
if not email_svc.get("shared"):
|
|
errors.append(
|
|
f"[{role_name}] {reason_str} but "
|
|
f"compose.services.email.shared is not true in {rel}"
|
|
)
|
|
|
|
if errors:
|
|
self.fail(
|
|
"Roles that depend on Mailu must declare "
|
|
"compose.services.email with enabled: true and shared: true:\n\n"
|
|
+ "\n".join(errors)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|