Files
felhom.eu/scripts/test_dockerfile_arg_order.py
T

55 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""R-208: in hub/Dockerfile no per-build ARG (VERSION, BUILD_TIME, GIT_COMMIT) is declared above the
module-download RUN of the same stage.
WHY. An ARG in scope is part of every later RUN's cache key. Declared above `RUN go mod download`, a fresh
--build-arg VERSION invalidates the download layer on every build — measured: 208 download records, each used
once, ~440 MB of dead cache per build. Declared below it, the download is CACHED until go.mod/go.sum change.
Pure text read; runs on the BusyBox CI runner. Run: python3 scripts/test_dockerfile_arg_order.py"""
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PER_BUILD = {"VERSION", "BUILD_TIME", "GIT_COMMIT"}
def violations(text):
"""Per stage: a per-build ARG that appears before that stage's `go mod download` RUN."""
out, stage, seen_args, downloaded = [], 0, [], False
for n, raw in enumerate(text.splitlines(), 1):
line = raw.strip()
if re.match(r"(?i)^FROM\s", line):
stage, seen_args, downloaded = stage + 1, [], False
continue
m = re.match(r"(?i)^ARG\s+([A-Za-z_][A-Za-z0-9_]*)", line)
if m and m.group(1) in PER_BUILD and not downloaded:
seen_args.append((n, m.group(1)))
if re.match(r"(?i)^RUN\s.*\bgo mod download\b", line):
downloaded = True
out.extend("line %d: ARG %s above the module download (stage %d)" % (n2, a, stage) for n2, a in seen_args)
return out
def main():
# Decoy first: the pre-fix shape must convict, or this test checks nothing.
decoy = "FROM golang AS b\nARG VERSION=dev\nCOPY go.mod ./\nRUN go mod download || true\nRUN go build\n"
if not violations(decoy):
print("FAIL: the decoy (ARG VERSION above go mod download) was not convicted")
return 1
path = os.path.join(ROOT, "hub", "Dockerfile")
text = open(path, encoding="utf-8").read()
if not re.search(r"(?m)^RUN\s.*\bgo mod download\b", text):
print("FAIL: hub/Dockerfile has no `go mod download` RUN — update this test with the new layout")
return 1
bad = violations(text)
if bad:
print("FAIL: hub/Dockerfile (R-208):\n " + "\n ".join(bad))
return 1
print("OK: hub/Dockerfile declares its per-build ARGs below the module download")
return 0
if __name__ == "__main__":
sys.exit(main())