Files
computer-playbook/tests/lint/docker/dockerfile/test_no_syntax_directive.py
T
kevinveenbirkenbachandClaude Opus 5 41754343b6 ci(dockerfile): ban syntax parser directive
Drop the `# syntax=docker/dockerfile:1` line from the root Dockerfile and
add a lint test that keeps it out of every Dockerfile in the repo.

The directive makes BuildKit pull an external frontend image from Docker
Hub at build time. That tag floats to the newest 1.x release, Podman and
Buildah have no frontend dispatch and ignore it entirely, and the pull
turns every build into a network dependency that fails whenever Hub is
unreachable or rate-limits the runner.

The root Dockerfile used no frontend-only feature, so the build behaviour
is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 23:25:50 +02:00

75 lines
2.8 KiB
Python

"""Lint guard: no Dockerfile may carry a ``# syntax=`` parser directive.
The directive tells BuildKit to fetch an external frontend image (e.g.
``docker/dockerfile:1``) from Docker Hub and hand the Dockerfile to it for
parsing. Three reasons this project refuses it:
* **Not future-proof.** ``docker/dockerfile:1`` floats to the newest 1.x
frontend, so a Hub-side release can change parse semantics of an unchanged
Dockerfile. Pinning a patch version instead only trades that for a stale
pin nobody bumps.
* **Podman incompatible.** Podman builds through Buildah, which has no
frontend dispatch at all, so the directive is silently discarded. Any
Dockerfile relying on a frontend-only feature builds under Docker and
breaks under Podman, and the directive hides that asymmetry.
* **Flaky CI.** The frontend pull is a build-time network dependency on
docker.io. When Hub is down, rate-limits the runner, or the runner is
air-gapped, the build fails before the first instruction runs.
Dockerfiles must therefore stay within what the local builder understands,
Docker and Buildah alike.
"""
from __future__ import annotations
import re
import unittest
from pathlib import Path
from utils.cache.files import iter_project_files, read_text
from . import PROJECT_ROOT
_SYNTAX_DIRECTIVE_RE = re.compile(r"^\s*#\s*syntax\s*=", re.IGNORECASE)
def _is_dockerfile(path: str) -> bool:
name = Path(path).name.lower()
return name.startswith("dockerfile") or ".dockerfile" in name
def _collect_violations() -> list[str]:
violations: list[str] = []
for path in sorted(iter_project_files()):
if not _is_dockerfile(path):
continue
relative = Path(path).relative_to(PROJECT_ROOT).as_posix()
violations.extend(
f"{relative}:{lineno}: {line.strip()}"
for lineno, line in enumerate(read_text(path).splitlines(), start=1)
if _SYNTAX_DIRECTIVE_RE.match(line)
)
return violations
class TestDockerfileNoSyntaxDirective(unittest.TestCase):
"""Fail when a Dockerfile pins an external BuildKit frontend."""
def test_no_dockerfile_declares_a_syntax_frontend(self) -> None:
violations = _collect_violations()
self.assertFalse(
violations,
"The following Dockerfiles declare a '# syntax=' parser directive.\n"
"It pins an external BuildKit frontend, which is not future-proof "
"(the tag floats), is ignored by Podman/Buildah, and turns every "
"build into a docker.io network dependency that breaks CI whenever "
"Docker Hub is unavailable.\n"
"Remove the directive and keep the Dockerfile within the feature set "
"of the local builder:\n\n" + "\n".join(f" {v}" for v in violations),
)
if __name__ == "__main__":
unittest.main()