Files
computer-playbook/tests/lint/docker/dockerfile/test_templates.py
T
Environment TestandClaude Sonnet 4.6 9ea2ad6ff1 refactor(docker): introduce utils/docker/image_discovery as single SPOT for image enumeration
- Move iter_role_images + all helpers + ImageRef from cli/mirror/util.py and
  cli/mirror/model.py into utils/docker/image_discovery.py; add source_file field
  to ImageRef to distinguish config/main.yml vs vars/main.yml entries
- Delete cli/mirror/util.py; update all callers to import from utils.docker.image_discovery
- Move utils into subpackages: utils/docker/ (image_ref, paths_utils, hadolint) and
  utils/roles/ (dependency_resolver, resource_validation); update all imports
- Reorganise tests into subpackages: tests/lint/docker/dockerfile/, tests/integration/docker/,
  tests/unit/utils/docker/, tests/unit/utils/roles/; fix parents[N] offsets after relocation
- tests/lint/docker/test_image_versions.py now uses iter_role_images (same SPOT as mirror)
- Add vars/main.yml images.<name>.{image,version} support for non-application roles
  (test-e2e-playwright, sys-ctl-hlth-csp); eliminate hardcoded duplicates in defaults/tasks
- Add web-app-matrix matrix-chatgpt-bot and web-app-mini-qr mini-qr entries in config/main.yml;
  replace hardcoded image strings in vars/templates with lookup('config', ...) references
- Fix sync/__main__.py log line to use correct registry prefix for non-Docker-Hub images
- Update docs/contributing/code/role/dockerfile.md with image declaration rules and SPOT reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 19:32:31 +02:00

97 lines
3.5 KiB
Python

"""Warn about templated Dockerfiles that should become plain files.
Roles that still ship ``templates/Dockerfile.j2`` are reported as warnings so
that maintainers can migrate them to ``files/Dockerfile`` over time without
blocking CI.
Two distinct warnings are emitted depending on the file content:
- **No Jinja2 logic** (only ``{{ variable }}`` substitutions or plain text):
The file can be migrated to a plain ``files/Dockerfile`` by replacing
variables with Docker ``ARG`` declarations and passing them via the compose
build ``args`` block.
- **Jinja2 control-flow logic** (``{% if %}``, ``{% for %}``, etc.):
The conditional logic prevents a direct one-to-one migration. The warning
remains because maintainers should still consider whether the logic can be
eliminated or split into separate Dockerfiles.
"""
from __future__ import annotations
import re
import unittest
from pathlib import Path
from utils.annotations.message import warning
_REPO_ROOT = Path(__file__).resolve().parents[4]
_ROLES_ROOT = _REPO_ROOT / "roles"
# Matches any Jinja2 control-flow or structural tag: {% ... %}
_J2_LOGIC_RE = re.compile(r"\{%-?\s*\w")
def _has_j2_logic(dockerfile_j2: Path) -> bool:
"""Return True if the file contains at least one Jinja2 block tag."""
return bool(_J2_LOGIC_RE.search(dockerfile_j2.read_text(encoding="utf-8")))
def _collect_templated_dockerfiles() -> list[Path]:
templated: list[Path] = []
for dockerfile_j2 in sorted(_ROLES_ROOT.glob("*/templates/Dockerfile.j2")):
plain_dockerfile = dockerfile_j2.parents[1] / "files" / "Dockerfile"
if plain_dockerfile.is_file():
continue
templated.append(dockerfile_j2)
return templated
def _warning_message(relative_path: str, has_logic: bool) -> str:
if has_logic:
return (
f"{relative_path} uses Dockerfile.j2 with Jinja2 control-flow logic. "
"Consider whether the conditional logic can be eliminated or split into "
"separate Dockerfiles to allow migration to a plain files/Dockerfile."
)
return (
f"{relative_path} uses Dockerfile.j2 instead of files/Dockerfile. "
"Prefer a plain Dockerfile because Dockerfiles are easier to test and "
"maintain and provide better separation of concerns. "
"Variables can be passed as Docker ARG declarations via the compose build args block."
)
def _emit_warning(dockerfile_j2: Path) -> None:
relative_path = dockerfile_j2.relative_to(_REPO_ROOT).as_posix()
has_logic = _has_j2_logic(dockerfile_j2)
message = _warning_message(relative_path, has_logic)
warning(message, title="Templated Dockerfile", file=relative_path)
class TestDockerfileTemplates(unittest.TestCase):
"""Warn when roles still use templates/Dockerfile.j2."""
def test_dockerfile_j2_warns_only(self) -> None:
self.assertTrue(
_ROLES_ROOT.is_dir(), f"'roles' directory not found at: {_ROLES_ROOT}"
)
templated = _collect_templated_dockerfiles()
failures: list[Path] = []
for dockerfile_j2 in templated:
_emit_warning(dockerfile_j2)
if not _has_j2_logic(dockerfile_j2):
failures.append(dockerfile_j2)
self.assertFalse(
failures,
"The following Dockerfile.j2 files contain no Jinja2 control-flow logic "
"and MUST be migrated to plain files/Dockerfile:\n"
+ "\n".join(f" {p.relative_to(_REPO_ROOT).as_posix()}" for p in failures),
)
if __name__ == "__main__":
unittest.main()