Skip to content

Commit 0f6d5f6

Browse files
committed
chore: add a script to auto-bump go runtime version
1 parent 313aace commit 0f6d5f6

2 files changed

Lines changed: 381 additions & 0 deletions

File tree

hack/update-go-version.sh

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2019 The Kubernetes Authors.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
set -euo pipefail
6+
7+
usage() {
8+
echo "usage: $0 <go patch version: 1.N.M> [--check]"
9+
}
10+
11+
patch=""
12+
check_only=false
13+
14+
while [[ $# -gt 0 ]]; do
15+
case "$1" in
16+
--check)
17+
check_only=true
18+
shift
19+
;;
20+
-h|--help)
21+
usage
22+
exit 0
23+
;;
24+
-*)
25+
echo "unknown option: $1" >&2
26+
usage >&2
27+
exit 2
28+
;;
29+
*)
30+
if [[ -n "${patch}" ]]; then
31+
echo "unexpected argument: $1" >&2
32+
usage >&2
33+
exit 2
34+
fi
35+
patch="$1"
36+
shift
37+
;;
38+
esac
39+
done
40+
41+
if [[ -z "${patch}" ]]; then
42+
usage >&2
43+
exit 2
44+
fi
45+
46+
if [[ ! "${patch}" =~ ^1\.[0-9]+\.[0-9]+$ ]]; then
47+
echo "patch must look like 1.N.M, e.g. 1.25.10" >&2
48+
exit 2
49+
fi
50+
51+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
52+
repo_root="$(cd "${script_dir}/.." && pwd -P)"
53+
cd "${repo_root}"
54+
55+
if [[ ! -f go.work ]]; then
56+
echo "could not find go.work at repository root: ${repo_root}" >&2
57+
exit 1
58+
fi
59+
60+
for tool in git go awk mktemp cp mv; do
61+
if ! command -v "${tool}" >/dev/null 2>&1; then
62+
echo "missing required command: ${tool}" >&2
63+
exit 1
64+
fi
65+
done
66+
67+
minor="${patch%.*}"
68+
go_directive="${minor}.0"
69+
toolchain="go${patch}"
70+
71+
echo "go directive : ${go_directive}"
72+
echo "toolchain / Docker : ${patch}"
73+
74+
module_files() {
75+
git ls-files -z -- \
76+
'go.mod' \
77+
'go.mod.src' \
78+
':(glob)**/go.mod' \
79+
':(glob)**/go.mod.src'
80+
}
81+
82+
image_files() {
83+
local status
84+
85+
git --no-pager grep -zlE 'golang:1\.[0-9]+\.[0-9]+' -- \
86+
'*Dockerfile' \
87+
'*.Dockerfile' \
88+
'*.go' \
89+
'*.md' \
90+
'*.yaml' \
91+
'*.yml' || {
92+
status=$?
93+
[[ "${status}" -eq 1 ]] || return "${status}"
94+
}
95+
}
96+
97+
go_edit() {
98+
GOWORK=off go "$@"
99+
}
100+
101+
replace_golang_image_versions() {
102+
local file="$1"
103+
local tmp
104+
105+
tmp="$(mktemp "${file}.XXXXXX")"
106+
cp -p "${file}" "${tmp}"
107+
if ! awk -v patch="${patch}" '{
108+
gsub(/golang:1\.[0-9]+\.[0-9]+/, "golang:" patch)
109+
print
110+
}' "${file}" >"${tmp}"; then
111+
rm -f "${tmp}"
112+
return 1
113+
fi
114+
mv "${tmp}" "${file}"
115+
}
116+
117+
check_versions() {
118+
local actual
119+
local failed=0
120+
local found_images=false
121+
local found_modules=false
122+
local image_ref
123+
local modfile
124+
125+
while IFS= read -r -d '' modfile; do
126+
found_modules=true
127+
actual="$(awk '$1 == "go" { print $2; exit }' "${modfile}")"
128+
if [[ "${actual}" != "${go_directive}" ]]; then
129+
echo "${modfile}: expected 'go ${go_directive}', found 'go ${actual:-<missing>}'" >&2
130+
failed=1
131+
fi
132+
done < <(module_files)
133+
134+
if [[ "${found_modules}" != "true" ]]; then
135+
echo "no tracked go.mod or go.mod.src files found" >&2
136+
failed=1
137+
fi
138+
139+
actual="$(awk '$1 == "go" { print $2; exit }' go.work)"
140+
if [[ "${actual}" != "${go_directive}" ]]; then
141+
echo "go.work: expected 'go ${go_directive}', found 'go ${actual:-<missing>}'" >&2
142+
failed=1
143+
fi
144+
145+
actual="$(awk '$1 == "toolchain" { print $2; exit }' go.work)"
146+
if [[ "${actual}" != "${toolchain}" ]]; then
147+
echo "go.work: expected 'toolchain ${toolchain}', found 'toolchain ${actual:-<missing>}'" >&2
148+
failed=1
149+
fi
150+
151+
while IFS= read -r image_ref; do
152+
[[ -n "${image_ref}" ]] || continue
153+
found_images=true
154+
if [[ "${image_ref}" != "golang:${patch}" ]]; then
155+
echo "expected 'golang:${patch}', found '${image_ref}'" >&2
156+
failed=1
157+
fi
158+
done < <(
159+
git --no-pager grep -hoE 'golang:1\.[0-9]+\.[0-9]+' -- \
160+
'*Dockerfile' \
161+
'*.Dockerfile' \
162+
'*.go' \
163+
'*.md' \
164+
'*.yaml' \
165+
'*.yml' || true
166+
)
167+
168+
if [[ "${found_images}" != "true" ]]; then
169+
echo "no tracked golang image references found" >&2
170+
failed=1
171+
fi
172+
173+
if [[ "${failed}" -ne 0 ]]; then
174+
return 1
175+
fi
176+
177+
echo "All managed Go version references match ${patch}."
178+
}
179+
180+
if [[ "${check_only}" == "true" ]]; then
181+
check_versions
182+
exit
183+
fi
184+
185+
# 1. Keep all go.mod files and go.mod templates at the downstream-visible minimum version.
186+
while IFS= read -r -d '' modfile; do
187+
go_edit mod edit -go="${go_directive}" "${modfile}"
188+
go_edit mod edit -fmt "${modfile}"
189+
done < <(module_files)
190+
191+
# 2. Keep go.work at the minimum version, plus the actual toolchain version.
192+
go_edit work edit -go="${go_directive}" -toolchain="${toolchain}" go.work
193+
go_edit work edit -fmt go.work
194+
195+
# 3. Update golang:1.x.y references in Dockerfiles, docs, workflows, and generated source.
196+
while IFS= read -r -d '' file; do
197+
replace_golang_image_versions "${file}"
198+
done < <(image_files)
199+
200+
check_versions
201+
202+
echo
203+
echo "Go version references after update:"
204+
git --no-pager grep -nE \
205+
'(^go 1\.[0-9]+(\.[0-9]+)?$|toolchain go1\.[0-9]+\.[0-9]+|golang:1\.[0-9]+\.[0-9]+|go-version:)' \
206+
-- ':!:vendor/**' ':!:hack/update-go-version.sh' || true
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2026 The Kubernetes Authors.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
set -euo pipefail
6+
7+
usage() {
8+
cat <<EOF
9+
usage: $0 <go patch version: 1.N.M> [options]
10+
11+
Options:
12+
--checkout-dir DIR test-infra checkout path, relative to the kustomize root (default: ../test-infra)
13+
--branch BRANCH branch name to create (default: kustomize-update-golang-1-N-M)
14+
EOF
15+
}
16+
17+
patch=""
18+
checkout_dir="${TEST_INFRA_CHECKOUT_DIR:-../test-infra}"
19+
branch=""
20+
21+
while [[ $# -gt 0 ]]; do
22+
case "$1" in
23+
--checkout-dir)
24+
checkout_dir="${2:?--checkout-dir requires a value}"
25+
shift 2
26+
;;
27+
--branch)
28+
branch="${2:?--branch requires a value}"
29+
shift 2
30+
;;
31+
-h|--help)
32+
usage
33+
exit 0
34+
;;
35+
-*)
36+
echo "unknown option: $1" >&2
37+
usage >&2
38+
exit 2
39+
;;
40+
*)
41+
if [[ -n "${patch}" ]]; then
42+
echo "unexpected argument: $1" >&2
43+
usage >&2
44+
exit 2
45+
fi
46+
patch="$1"
47+
shift
48+
;;
49+
esac
50+
done
51+
52+
if [[ -z "${patch}" ]]; then
53+
usage >&2
54+
exit 2
55+
fi
56+
57+
if [[ ! "${patch}" =~ ^1\.[0-9]+\.[0-9]+$ ]]; then
58+
echo "patch must look like 1.N.M, e.g. 1.25.10" >&2
59+
exit 2
60+
fi
61+
62+
for tool in gh git awk mktemp cp mv; do
63+
if ! command -v "${tool}" >/dev/null 2>&1; then
64+
echo "missing required command: ${tool}" >&2
65+
exit 1
66+
fi
67+
done
68+
69+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
70+
repo_root="$(cd "${script_dir}/.." && pwd -P)"
71+
cd "${repo_root}"
72+
73+
repo="kubernetes/test-infra"
74+
target_file="config/jobs/kubernetes-sigs/kustomize/kustomize-presubmit-master.yaml"
75+
image="public.ecr.aws/docker/library/golang:${patch}"
76+
branch="${branch:-kustomize-update-golang-${patch//./-}}"
77+
commit_message="Update go version for ${patch} in kustomize"
78+
pr_body="Update golang version in prow that is used in tests for https://github.com/kubernetes-sigs/kustomize"
79+
80+
replace_presubmit_image() {
81+
local file="$1"
82+
local tmp
83+
84+
tmp="$(mktemp "${file}.XXXXXX")"
85+
cp -p "${file}" "${tmp}"
86+
if ! awk -v image="${image}" '
87+
/image: public\.ecr\.aws\/docker\/library\/golang:1\.[0-9]+\.[0-9]+([-[:alnum:].]+)?$/ {
88+
sub(/image: public\.ecr\.aws\/docker\/library\/golang:1\.[0-9]+\.[0-9]+([-[:alnum:].]+)?$/, "image: " image)
89+
}
90+
{ print }
91+
' "${file}" >"${tmp}"; then
92+
rm -f "${tmp}"
93+
return 1
94+
fi
95+
mv "${tmp}" "${file}"
96+
}
97+
98+
is_generated_image_change() {
99+
[[ "$(git branch --show-current)" == kustomize-update-golang-* ]] || return 1
100+
[[ "$(git status --porcelain --untracked-files=all)" == " M ${target_file}" ]] || return 1
101+
git diff --cached --quiet -- "${target_file}" || return 1
102+
103+
git diff --unified=0 -- "${target_file}" | awk '
104+
/^diff --git / || /^index / || /^--- / || /^\+\+\+ / || /^@@ / { next }
105+
/^-/ {
106+
removed++
107+
if ($0 !~ /^-[[:space:]]+- image: public\.ecr\.aws\/docker\/library\/golang:1\.[0-9]+\.[0-9]+([-[:alnum:].]+)?$/) {
108+
invalid = 1
109+
}
110+
next
111+
}
112+
/^\+/ {
113+
added++
114+
if ($0 !~ /^\+[[:space:]]+- image: public\.ecr\.aws\/docker\/library\/golang:1\.[0-9]+\.[0-9]+([-[:alnum:].]+)?$/) {
115+
invalid = 1
116+
}
117+
next
118+
}
119+
NF { invalid = 1 }
120+
END { exit !(removed == 1 && added == 1 && !invalid) }
121+
'
122+
}
123+
124+
if [[ ! -d "${checkout_dir}/.git" ]]; then
125+
mkdir -p "$(dirname "${checkout_dir}")"
126+
gh repo clone "${repo}" "${checkout_dir}" -- --depth=1 --branch master
127+
fi
128+
129+
cd "${checkout_dir}"
130+
checkout_dir="$(pwd -P)"
131+
132+
git fetch --depth=1 origin master
133+
134+
if [[ -n "$(git status --porcelain)" ]]; then
135+
if is_generated_image_change; then
136+
echo "Discarding the Go image change generated by the previous run."
137+
git restore --source=origin/master --worktree -- "${target_file}"
138+
else
139+
echo "${checkout_dir} has unrelated uncommitted changes; please commit or stash them first." >&2
140+
exit 1
141+
fi
142+
fi
143+
144+
git switch -C "${branch}" origin/master
145+
146+
if [[ ! -f "${target_file}" ]]; then
147+
echo "missing expected file: ${target_file}" >&2
148+
exit 1
149+
fi
150+
151+
replace_presubmit_image "${target_file}"
152+
153+
if git diff --quiet -- "${target_file}"; then
154+
echo "${target_file} already uses ${image}"
155+
exit 0
156+
fi
157+
158+
git diff --check -- "${target_file}"
159+
git diff -- "${target_file}"
160+
161+
github_user="$(gh api user --jq .login 2>/dev/null || echo '<your-github-user>')"
162+
fork_repo="${github_user}/test-infra"
163+
fork_remote="kustomize-go-bump"
164+
165+
echo
166+
echo "Updated ${target_file} to ${image}"
167+
echo
168+
echo "Next steps:"
169+
echo " cd ${checkout_dir}"
170+
echo " gh repo view ${fork_repo} >/dev/null 2>&1 || gh repo fork ${repo} --clone=false"
171+
echo " git remote remove ${fork_remote} >/dev/null 2>&1 || true"
172+
echo " git remote add ${fork_remote} https://github.com/${fork_repo}.git"
173+
echo " git commit -am '${commit_message}'"
174+
echo " git push -u ${fork_remote} ${branch}"
175+
echo " gh pr create --repo ${repo} --base master --head ${github_user}:${branch} --title '${commit_message}' --body '${pr_body}'"

0 commit comments

Comments
 (0)