Refactored docker-compose roles

This commit is contained in:
2025-07-07 03:24:54 +02:00
parent 2ea7a606b6
commit 38ed1e94e8
102 changed files with 681 additions and 507 deletions

View File

@@ -11,7 +11,7 @@ class TestApplicationIdConsistency(unittest.TestCase):
failed_roles = []
for role_path in ROLES_DIR.iterdir():
if role_path.name in ["docker-compose", "docker-central-database", "docker-repository-setup"]:
if role_path.name in ["docker-container","docker-compose", "docker-central-database", "docker-repository-setup"]:
continue
if role_path.is_dir() and role_path.name.startswith("docker-"):

View File

@@ -0,0 +1,43 @@
import unittest
from pathlib import Path
import yaml
class TestConfigurationDatabaseDependency(unittest.TestCase):
# Define project root and glob pattern for configuration files
PROJECT_ROOT = Path(__file__).resolve().parents[2]
CONFIG_PATTERN = 'roles/*/vars/configuration.yml'
def test_central_database_implies_database_service_enabled(self):
"""
For each roles/*/vars/configuration.yml:
If features.central_database is true,
then docker.services.database.enabled must be true.
"""
config_paths = sorted(self.PROJECT_ROOT.glob(self.CONFIG_PATTERN))
self.assertTrue(config_paths, f"No configuration files found for pattern {self.CONFIG_PATTERN}")
for config_path in config_paths:
with self.subTest(configuration=config_path):
content = yaml.safe_load(config_path.read_text(encoding='utf-8')) or {}
# Read central_database flag
features = content.get('features', {})
central_db = features.get('central_database', False)
# Read database enabled flag
docker = content.get('docker', {})
services = docker.get('services', {})
database = services.get('database', {})
db_enabled = database.get('enabled', False)
if central_db:
self.assertTrue(
db_enabled,
f"{config_path}: features.central_database is true but docker.services.database.enabled is not true"
)
else:
# No requirement when central_database is false or absent
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,67 @@
import re
import warnings
import unittest
from pathlib import Path
class TestDockerComposeTemplates(unittest.TestCase):
# Search for all roles/*/templates/docker-compose.yml.j2
PROJECT_ROOT = Path(__file__).resolve().parents[2]
TEMPLATE_PATTERN = 'roles/*/templates/docker-compose.yml.j2'
# Allowed lines before BASE_INCLUDE
ALLOWED_BEFORE_BASE = [
re.compile(r'^\s*$'), # empty line
re.compile(r'^\s*version:.*$'), # version: ...
re.compile(r'^\s*#.*$'), # YAML comment
re.compile(r'^\s*\{\#.*\#\}\s*$'), # Jinja comment {# ... #}
]
BASE_INCLUDE = "{% include 'roles/docker-compose/templates/base.yml.j2' %}"
NET_INCLUDE = "{% include 'templates/docker/compose/networks.yml.j2' %}"
def test_docker_compose_includes(self):
"""
Verifies for each found docker-compose.yml.j2:
1. BASE_INCLUDE and NET_INCLUDE are present
2. BASE_INCLUDE appears before NET_INCLUDE
3. Only allowed lines appear before BASE_INCLUDE (invalid lines issue warnings)
"""
template_paths = sorted(
self.PROJECT_ROOT.glob(self.TEMPLATE_PATTERN)
)
self.assertTrue(template_paths, f"No templates found for pattern {self.TEMPLATE_PATTERN}")
for template_path in template_paths:
with self.subTest(template=template_path):
content = template_path.read_text(encoding='utf-8')
lines = content.splitlines()
# Find BASE_INCLUDE
try:
idx_base = lines.index(self.BASE_INCLUDE)
except ValueError:
self.fail(f"{template_path}: '{self.BASE_INCLUDE}' not found")
# Find NET_INCLUDE
try:
idx_net = lines.index(self.NET_INCLUDE)
except ValueError:
self.fail(f"{template_path}: '{self.NET_INCLUDE}' not found")
# Check order
self.assertLess(
idx_base,
idx_net,
f"{template_path}: '{self.BASE_INCLUDE}' must come before '{self.NET_INCLUDE}'"
)
# Warn on invalid lines before BASE_INCLUDE
for i, line in enumerate(lines[:idx_base]):
if not any(pat.match(line) for pat in self.ALLOWED_BEFORE_BASE):
warnings.warn(
f"{template_path}: Invalid line before {self.BASE_INCLUDE} (line {i+1}): {line!r}",
category=RuntimeWarning
)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,48 @@
import re
import unittest
from pathlib import Path
class TestJinjaIncludePaths(unittest.TestCase):
"""
Verifies that in all .j2 files in the project (root + subfolders):
- Every {% include 'string/path' %} or {% include "string/path" %} refers to an existing file.
- Any include using a variable or concatenation is ignored.
"""
PROJECT_ROOT = Path(__file__).resolve().parents[2]
# Fängt jede include-Direktive ein (den gesamten Ausdruck zwischen include und %})
INCLUDE_STMT_RE = re.compile(r"{%\s*include\s+(.+?)\s*%}")
# Erlaubt nur ein einzelnes String-Literal (Gänse- oder einfache Anführungszeichen)
LITERAL_PATH_RE = re.compile(r"^['\"]([^'\"]+)['\"]$")
def test_all_jinja_includes_exist(self):
template_paths = list(self.PROJECT_ROOT.glob("**/*.j2"))
self.assertTrue(
template_paths,
"No .j2 templates found anywhere in the project"
)
missing = []
for tpl in template_paths:
text = tpl.read_text(encoding="utf-8")
for stmt in self.INCLUDE_STMT_RE.finditer(text):
expr = stmt.group(1).strip()
m = self.LITERAL_PATH_RE.match(expr)
if not m:
continue # Variable-based includes ignorieren
include_path = m.group(1)
abs_target = self.PROJECT_ROOT / include_path
rel_target = tpl.parent / include_path
if not (abs_target.exists() or rel_target.exists()):
rel_tpl = tpl.relative_to(self.PROJECT_ROOT)
missing.append(
f"{rel_tpl}: included file '{include_path}' not found "
f"(neither in PROJECT_ROOT nor in {tpl.parent.relative_to(self.PROJECT_ROOT)})"
)
if missing:
self.fail("Broken {% include %} references:\n" + "\n".join(missing))
if __name__ == "__main__":
unittest.main()