package main import ( "os" "regexp" "strings" "testing" ) // R-208: `ARG VERSION` / `ARG GIT_COMMIT` declared ABOVE `RUN go mod download` put a per-build value // into that RUN's cache key, so every release re-downloaded the modules (measured: 208 cache records, // every one with usage count 1). The consequence pinned here, in the builder stage: no ARG whose value // changes per build is declared before the module-download step, and both are still declared before // the `go build` that reads them (or the binary would report "dev"/"unknown"). func TestR208_DockerfileVersionArgsSitBelowModuleDownload(t *testing.T) { src, err := os.ReadFile("../../Dockerfile") if err != nil { t.Fatal(err) } lines := strings.Split(string(src), "\n") argRe := regexp.MustCompile(`^\s*ARG\s+(VERSION|GIT_COMMIT|BUILD_TIME)\b`) download, build := -1, -1 args := map[string]int{} for i, l := range lines { if strings.HasPrefix(strings.TrimSpace(l), "FROM ") && i > 0 && download >= 0 && build >= 0 { break // the runtime stage: only the builder matters } if m := argRe.FindStringSubmatch(l); m != nil { if _, seen := args[m[1]]; !seen { args[m[1]] = i // the FIRST declaration: any one above the download is the defect } } if strings.Contains(l, "go mod download") && download < 0 { download = i } if strings.Contains(l, "go build") && build < 0 { build = i } } if download < 0 || build < 0 { t.Fatalf("the builder stage no longer has a `go mod download` (%d) or a `go build` (%d) — this test reads the wrong file", download, build) } for _, name := range []string{"VERSION", "GIT_COMMIT"} { at, ok := args[name] if !ok { t.Errorf("ARG %s is not declared in the builder stage — the binary would be built without it", name) continue } if at < download { t.Errorf("R-208: ARG %s (line %d) is declared above `go mod download` (line %d), so every build with a new value re-downloads the modules", name, at+1, download+1) } if at > build { t.Errorf("ARG %s (line %d) is declared after the `go build` that reads it (line %d)", name, at+1, build+1) } } }