#!/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())