Skip to content

Commit e2f3136

Browse files
fix: devcontainers go version stops drifting (#13523)
Signed-off-by: David L. Chandler <david.chandler@solo.io>
1 parent 0ddde33 commit e2f3136

4 files changed

Lines changed: 47 additions & 78 deletions

File tree

.github/workflows/unit.yaml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,19 @@ jobs:
3737
# The make will error if test coverage drops below a certain threshold
3838
# We intentionally ignore the errors while we build out our test coverage, to establish a good baseline
3939
# However, we should strive to establish a baseline, and then make it required on PRs
40-
run: make --always-make --ignore-errors validate-test-coverage
40+
run: make --always-make --ignore-errors validate-test-coverage
41+
42+
# Catches version drift between go.mod, Dockerfile, and Makefile
43+
version-drift:
44+
name: Version Drift Check
45+
runs-on: ubuntu-22.04
46+
timeout-minutes: 5
47+
steps:
48+
- uses: actions/checkout@v4
49+
- name: Setup Go
50+
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
51+
with:
52+
go-version-file: tools/go.mod
53+
- name: Run drift tests
54+
working-directory: tools
55+
run: go test -v -count=1 ./build-tools/

tools/build-tools/Dockerfile

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
# Based on Istio's build-tools image: https://github.com/istio/tools/tree/228d21452fd640bf7389d7d41fecaa715ce73249/docker/build-tools
2-
ARG GO_VERSION=1.25.7
3-
ARG GO_BASE_IMAGE=golang:${GO_VERSION}-bookworm
42
ARG UBUNTU_BASE_IMAGE=ubuntu:noble
53

6-
FROM ${GO_BASE_IMAGE} AS go_context
74
FROM ${UBUNTU_BASE_IMAGE} AS build_tools
85

96
ARG TARGETARCH
@@ -14,10 +11,9 @@ LABEL "io.kgateway.version"="${VERSION}"
1411

1512
ENV DEBIAN_FRONTEND=noninteractive
1613

17-
# Pinned tool versions (keep aligned with `go.mod` and repo tooling where possible)
14+
# Pinned tool versions (not tracked in go.mod)
1815
ENV RUST_VERSION=1.86.0
1916
ENV KUBECTL_VERSION=1.35.0
20-
ENV HELM_VERSION=v3.19.2
2117
ENV PROTOC_VERSION=33.2
2218
ENV BUF_VERSION=v1.62.1
2319
ENV YQ_VERSION=4.50.1
@@ -87,8 +83,15 @@ RUN --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
8783
docker-buildx-plugin \
8884
&& rm -rf /var/lib/apt/lists/*
8985

90-
# Go toolchain (copy from official golang image to match go.mod)
91-
COPY --from=go_context /usr/local/go /usr/local/go
86+
# go.mod is the single source of truth for Go, Helm, and Kind versions.
87+
# Tool install steps below extract their version from it at build time.
88+
COPY go.mod /tmp/go.mod
89+
90+
# Go toolchain (version from go.mod)
91+
RUN set -eux; \
92+
GO_VERSION=$(grep '^go ' /tmp/go.mod | awk '{print $2}'); \
93+
test -n "$GO_VERSION" || { echo "Failed to extract Go version from go.mod"; exit 1; }; \
94+
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" | tar -C /usr/local -xz
9295

9396
# Install protoc
9497
RUN set -eux; \
@@ -112,8 +115,10 @@ RUN set -eux; \
112115
COPY tools/build-tools/kind /usr/local/bin/kind
113116
RUN chmod 555 /usr/local/bin/kind
114117

115-
# helm
118+
# helm (version from go.mod helm.sh/helm/v3)
116119
RUN set -eux; \
120+
HELM_VERSION=$(grep 'helm.sh/helm/v3' /tmp/go.mod | awk '{print $2}'); \
121+
test -n "$HELM_VERSION" || { echo "Failed to extract Helm version from go.mod"; exit 1; }; \
117122
curl -fsSL -o /tmp/helm.tgz "https://get.helm.sh/helm-${HELM_VERSION}-linux-${TARGETARCH}.tar.gz"; \
118123
mkdir -p /tmp/helm; \
119124
tar -xzf /tmp/helm.tgz -C /tmp/helm; \

tools/build-tools/version_drift_test.go

Lines changed: 17 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
"regexp"
77
"runtime"
88
"testing"
9-
10-
"golang.org/x/mod/modfile"
119
)
1210

1311
func TestDockerfileVersionsMatchGoMod(t *testing.T) {
@@ -22,48 +20,15 @@ func TestDockerfileVersionsMatchGoMod(t *testing.T) {
2220
}
2321
dockerfile := string(dockerfileBytes)
2422

25-
gotGoVersion := mustMatch1(t, dockerfile, `(?m)^ARG GO_VERSION=([^\s]+)\s*$`, "Dockerfile ARG GO_VERSION")
26-
gotHelmVersion := mustMatch1(t, dockerfile, `(?m)^ENV HELM_VERSION=([^\s]+)\s*$`, "Dockerfile ENV HELM_VERSION")
27-
28-
goModPath := filepath.Join(rootDir, "go.mod")
29-
goModBytes, err := os.ReadFile(goModPath)
30-
if err != nil {
31-
t.Fatalf("read go.mod: %v", err)
32-
}
33-
34-
parsed, err := modfile.Parse(goModPath, goModBytes, nil)
35-
if err != nil {
36-
t.Fatalf("parse go.mod: %v", err)
23+
// Go and Helm versions are extracted directly from go.mod at Docker build
24+
// time, so there are no hardcoded values to drift. Verify the Dockerfile
25+
// does NOT contain stale hardcoded versions.
26+
if regexp.MustCompile(`(?m)^ARG GO_VERSION=`).FindStringIndex(dockerfile) != nil {
27+
t.Fatalf("Dockerfile should not hardcode ARG GO_VERSION; the Go version is derived from go.mod at build time")
3728
}
38-
if parsed.Go == nil || parsed.Go.Version == "" {
39-
t.Fatalf("go.mod is missing a go version directive")
40-
}
41-
42-
wantGoVersion := parsed.Go.Version
43-
wantHelmVersion := requireVersion(t, parsed, "helm.sh/helm/v3")
44-
wantKindVersion := requireVersion(t, parsed, "sigs.k8s.io/kind")
45-
46-
makefilePath := filepath.Join(rootDir, "Makefile")
47-
makefileBytes, err := os.ReadFile(makefilePath)
48-
if err != nil {
49-
t.Fatalf("read Makefile: %v", err)
29+
if regexp.MustCompile(`(?m)^ENV HELM_VERSION=`).FindStringIndex(dockerfile) != nil {
30+
t.Fatalf("Dockerfile should not hardcode ENV HELM_VERSION; the Helm version is derived from go.mod at build time")
5031
}
51-
makefile := string(makefileBytes)
52-
gotKindVersion := mustMatch1(t, makefile, `(?m)^KIND_VERSION\s*\?=\s*([^\s]+)\s*$`, "Makefile KIND_VERSION")
53-
54-
t.Run("go", func(t *testing.T) {
55-
t.Parallel()
56-
if gotGoVersion != wantGoVersion {
57-
t.Fatalf("GO_VERSION drift detected: Dockerfile has %q, go.mod has %q", gotGoVersion, wantGoVersion)
58-
}
59-
})
60-
61-
t.Run("helm", func(t *testing.T) {
62-
t.Parallel()
63-
if gotHelmVersion != wantHelmVersion {
64-
t.Fatalf("HELM_VERSION drift detected: Dockerfile has %q, go.mod helm.sh/helm/v3 is %q", gotHelmVersion, wantHelmVersion)
65-
}
66-
})
6732

6833
t.Run("kind", func(t *testing.T) {
6934
t.Parallel()
@@ -77,8 +42,16 @@ func TestDockerfileVersionsMatchGoMod(t *testing.T) {
7742
t.Fatalf("KIND_VERSION drift risk detected: Dockerfile should not download kind via curl")
7843
}
7944

80-
if gotKindVersion != wantKindVersion {
81-
t.Fatalf("KIND_VERSION drift detected: Makefile has %q, go.mod sigs.k8s.io/kind is %q", gotKindVersion, wantKindVersion)
45+
// KIND_VERSION in the Makefile is derived from go.mod at make time,
46+
// so there is no hardcoded literal to drift. Verify it stays dynamic.
47+
makefilePath := filepath.Join(rootDir, "Makefile")
48+
makefileBytes, err := os.ReadFile(makefilePath)
49+
if err != nil {
50+
t.Fatalf("read Makefile: %v", err)
51+
}
52+
makefile := string(makefileBytes)
53+
if regexp.MustCompile(`(?m)^KIND_VERSION\s*\?=\s*v[\d.]+\s*$`).FindStringIndex(makefile) != nil {
54+
t.Fatalf("KIND_VERSION drift risk detected: Makefile should derive KIND_VERSION from go.mod, not hardcode it")
8255
}
8356
})
8457
}
@@ -113,27 +86,3 @@ func fileExists(path string) bool {
11386
_, err := os.Stat(path)
11487
return err == nil
11588
}
116-
117-
func mustMatch1(t *testing.T, content, pattern, label string) string {
118-
t.Helper()
119-
120-
re := regexp.MustCompile(pattern)
121-
m := re.FindStringSubmatch(content)
122-
if len(m) != 2 {
123-
t.Fatalf("%s not found (pattern %q)", label, pattern)
124-
}
125-
return m[1]
126-
}
127-
128-
func requireVersion(t *testing.T, mf *modfile.File, modulePath string) string {
129-
t.Helper()
130-
131-
for _, req := range mf.Require {
132-
if req.Mod.Path == modulePath {
133-
return req.Mod.Version
134-
}
135-
}
136-
137-
t.Fatalf("go.mod is missing required module %q", modulePath)
138-
return ""
139-
}

tools/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ tool (
77
sigs.k8s.io/controller-runtime/tools/setup-envtest
88
)
99

10-
require golang.org/x/mod v0.32.0
10+
require golang.org/x/mod v0.32.0 // indirect
1111

1212
require (
1313
al.essio.dev/pkg/shellescape v1.6.0 // indirect

0 commit comments

Comments
 (0)