557629d2bf
gates / gates (push) Successful in 2m3s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
55 lines
2.4 KiB
Python
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())
|