@@ -6,9 +6,11 @@ import subprocess
66import re
77import argparse
88
9- # The file paths to update .
9+ # Root marker used to verify script execution location .
1010# Assumes the script is run from the root of the repository.
11- WORKSPACE_PATH = "WORKSPACE"
11+ ROOT_MARKER_PATH = "go.work"
12+ GO_RUNNER_TAGS_URL = "https://registry.k8s.io/v2/build-image/go-runner/tags/list"
13+ GO_RUNNER_GO_SEGMENT_RE = re .compile (r"-go\d+(?:\.\d+){1,2}-" )
1214
1315def find_go_files ():
1416 """Finds all go.mod and go.work files in the repository."""
@@ -46,6 +48,37 @@ def get_latest_go_version():
4648 return version
4749 raise RuntimeError ("Could not find a stable Go release." )
4850
51+ def get_go_runner_tags ():
52+ """Fetches go-runner tags from the registry API."""
53+ print (f"Fetching go-runner tags from { GO_RUNNER_TAGS_URL } ..." )
54+ with urllib .request .urlopen (GO_RUNNER_TAGS_URL ) as response :
55+ if response .status != 200 :
56+ raise RuntimeError (
57+ f"Failed to fetch go-runner tags: HTTP { response .status } "
58+ )
59+ payload = json .loads (response .read ().decode ())
60+ tags = payload .get ("tags" , [])
61+ if not tags :
62+ raise RuntimeError ("No tags returned from go-runner registry API." )
63+
64+ return tags
65+
66+ def resolve_go_runner_tag (current_tag , go_version ):
67+ """Resolves the go-runner tag for the target Go version."""
68+ target_tag , replacements = GO_RUNNER_GO_SEGMENT_RE .subn (
69+ f"-go{ go_version } -" , current_tag , count = 1
70+ )
71+ if replacements != 1 :
72+ raise RuntimeError (f"Unsupported go-runner tag format: { current_tag } " )
73+
74+ if target_tag not in get_go_runner_tags ():
75+ raise RuntimeError (
76+ f"go-runner image tag not found for Go { go_version } : { target_tag } "
77+ )
78+
79+ print (f"Using go-runner tag { target_tag } for Go { go_version } " )
80+ return target_tag
81+
4982def _replace_file_contents (file_path , fn ):
5083 """Replaces the contents of a file with the result of a function, applied line-by-line."""
5184 if not os .path .exists (file_path ):
@@ -72,11 +105,19 @@ def _replace_file_contents(file_path, fn):
72105
73106def update_go_file (file_path , new_version ):
74107 """Updates the go and toolchain directives in a go.mod or go.work file."""
108+ parts = new_version .split ("." )
109+ if len (parts ) < 2 :
110+ raise ValueError (f"Invalid Go version '{ new_version } '. Expected at least major.minor." )
111+ # The `go` directive should express the minimum supported Go line,
112+ # so we keep it at `major.minor.0` for modules that depend on this repo.
113+ # Exact patch pinning belongs in `toolchain` and runtime/build image config.
114+ go_directive_version = f"{ parts [0 ]} .{ parts [1 ]} .0"
115+
75116 def fn (line ):
76117 stripped = line .lstrip ()
77118 # Replace lines like `go 1.22.3`
78119 if stripped .startswith ("go " ):
79- return f"go { new_version } \n "
120+ return f"go { go_directive_version } \n "
80121 # Replace lines like `toolchain go1.22.3`
81122 if stripped .startswith ("toolchain go" ):
82123 return f"toolchain go{ new_version } \n "
@@ -92,6 +133,15 @@ def update_dockerfile(file_path, go_version):
92133 # Replace lines like `FROM golang:1.24.1` or `FROM --platform=... golang:1.24.1 AS builder`
93134 if line .lstrip ().startswith ("FROM " ) and "golang:" in line :
94135 return re .sub (r"golang:[0-9.]+" , f"golang:{ go_version } " , line )
136+ # Replace runtime image tags like:
137+ # `FROM registry.k8s.io/build-image/go-runner:v2.4.0-go1.25.6-bookworm.0`
138+ if line .lstrip ().startswith ("FROM " ) and "build-image/go-runner:" in line :
139+ image_match = re .search (r"build-image/go-runner:(?P<tag>[^ \t\n]+)" , line )
140+ if not image_match :
141+ return line
142+ target_tag = resolve_go_runner_tag (image_match .group ("tag" ), go_version )
143+ tag_start , tag_end = image_match .span ("tag" )
144+ return f"{ line [:tag_start ]} { target_tag } { line [tag_end :]} "
95145 return line
96146
97147 _replace_file_contents (file_path , fn )
@@ -103,8 +153,7 @@ def main():
103153 1. Fetches (or receives) the Go version to update to.
104154 2. Updates all go.mod and go.work files.
105155 3. Updates all Dockerfiles.
106- 4. Updates the WORKSPACE file.
107- 5. Verifies the updates with 'go mod tidy'.
156+ 4. Verifies the updates with 'go mod tidy'.
108157 """
109158 parser = argparse .ArgumentParser (description = "Update Go version across the repository." )
110159 parser .add_argument ("--version" , help = "The Go version to update to (e.g., 1.25.7). If not specified, fetches the latest stable version." )
@@ -139,7 +188,7 @@ def main():
139188
140189if __name__ == "__main__" :
141190 # Ensure the script is run from the repository root
142- if not os .path .exists (WORKSPACE_PATH ):
143- print (f"Error: This script must be run from the repository root directory containing the WORKSPACE file." )
191+ if not os .path .exists (ROOT_MARKER_PATH ):
192+ print (f"Error: This script must be run from the repository root directory containing the { ROOT_MARKER_PATH } file." )
144193 else :
145194 main ()
0 commit comments