Files
computer-playbook/tests/integration/infrastructure/docker/test_compose_templates.py
kevinveenbirkenbachandClaude Opus 4.8 fbe50d10ed fix(svc-runner): make the runner role swarm-compatible
Workers failed 'docker build: no Dockerfile': the build context, compose file, .env and runner binary were staged on the stack host only while every node deploys its own node-local runner stack. Build and push the image once on the manager (RUNNER_IMAGE_REF via the swarm registry, 04_image.yml) and let workers pull it; render compose.yml (template with lstrip_blocks, matching the replaced stack_host_template action), .env and the extracted run.sh on every node. Add a node_local kwarg to the compose_networks/container_networks/compose_restart/compose_only lookups so this node-local compose stack renders a bridge default network (workers cannot create overlays), keeps restart: unless-stopped (ephemeral runners died after one job) and keeps container_name under swarm; default is unchanged for every other role. Treat build/install.stamp as stale when the venv is missing and exclude ./build from the nested-src copy, closing the nested-e2e ruamel failure class. Regression tests for the node_local render paths; the network include tests now accept lookup kwargs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 02:37:26 +02:00

85 lines
3.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
import unittest
from typing import ClassVar
from utils.cache.files import read_text
from utils.roles.mapping import ROLE_FILE_TEMPL_COMPOSE
from . import PROJECT_ROOT
class TestDockerComposeTemplates(unittest.TestCase):
TEMPLATE_PATTERN = f"roles/*/{ROLE_FILE_TEMPL_COMPOSE}"
ALLOWED_BEFORE_BASE: ClassVar[list[re.Pattern]] = [
re.compile(r"^\s*$"),
re.compile(r"^\s*version:.*$"),
re.compile(r"^\s*#.*$"),
re.compile(r"^\s*\{\#.*\#\}\s*$"),
]
BASE_INCLUDE = "{% include 'roles/sys-svc-compose/templates/base.yml.j2' %}"
NET_INCLUDE = "{{ lookup('compose_networks') }}"
NET_INCLUDE_RE: ClassVar[re.Pattern] = re.compile(
r"\{\{\s*lookup\(\s*'compose_networks'[^)]*\)\s*\}\}"
)
HOST_MODE = 'network_mode: "host"'
def test_docker_compose_includes(self):
"""
Verifies for each found compose.yml.j2:
1. BASE_INCLUDE is present exactly once
2. If no host‑mode is set, NET_INCLUDE must appear exactly once
3. BASE_INCLUDE appears before NET_INCLUDE when both are required
4. Only allowed lines appear before BASE_INCLUDE (invalid lines issue warnings)
"""
template_paths = sorted(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 = read_text(str(template_path))
lines = content.splitlines()
count_base = lines.count(self.BASE_INCLUDE)
self.assertEqual(
count_base,
1,
f"{template_path}: '{self.BASE_INCLUDE}' occurs {count_base} times, expected once",
)
host_mode = self.HOST_MODE in content
count_net = sum(1 for line in lines if self.NET_INCLUDE_RE.search(line))
if host_mode:
self.assertLessEqual(
count_net,
1,
f"{template_path}: '{self.NET_INCLUDE}' occurs {count_net} times with host networking, expected 0 or 1",
)
else:
self.assertEqual(
count_net,
1,
f"{template_path}: '{self.NET_INCLUDE}' occurs {count_net} times, expected once",
)
if count_base and count_net:
idx_base = lines.index(self.BASE_INCLUDE)
idx_net = next(
i
for i, line in enumerate(lines)
if self.NET_INCLUDE_RE.search(line)
)
self.assertLess(
idx_base,
idx_net,
f"{template_path}: '{self.BASE_INCLUDE}' must come before '{self.NET_INCLUDE}'",
)
if __name__ == "__main__":
unittest.main()