@@ -24,6 +24,7 @@ const (
2424 goModFilePerms = fs .FileMode (0600 )
2525 minGoListVersionFields = 2
2626 minPatchNumberToDecrementFrom = 1 // We can only decrement patch if it's 1 or greater (to get 0 or greater)
27+ k8sVersionEnvVar = "K8S_IO_K8S_VERSION"
2728)
2829
2930//nolint:gochecknoglobals
@@ -42,6 +43,42 @@ func readAndParseGoMod(filename string) (*modfile.File, error) {
4243 return modF , nil
4344}
4445
46+ // getK8sVersionFromEnv processes the version specified via environment variable.
47+ // It validates the version and runs `go get` to update the dependency.
48+ func getK8sVersionFromEnv (targetK8sVer string ) (string , error ) {
49+ log .Printf ("Found target %s version override from env var %s: %s" , k8sRepo , k8sVersionEnvVar , targetK8sVer )
50+ if ! semver .IsValid (targetK8sVer ) {
51+ return "" , fmt .Errorf ("invalid semver specified in %s: %s" , k8sVersionEnvVar , targetK8sVer )
52+ }
53+ // Update the go.mod file first
54+ log .Printf ("Running 'go get %s@%s' to update the main dependency..." , k8sRepo , targetK8sVer )
55+ getArgs := fmt .Sprintf ("%s@%s" , k8sRepo , targetK8sVer )
56+ if _ , err := runGoCommand ("get" , getArgs ); err != nil {
57+ return "" , fmt .Errorf ("error running 'go get %s': %w" , getArgs , err )
58+ }
59+ return targetK8sVer , nil // Return the validated version
60+ }
61+
62+ // getK8sVersionFromMod reads the go.mod file to find the current version of k8s.io/kubernetes.
63+ // It returns the version string if found, or an empty string (and nil error) if not found.
64+ func getK8sVersionFromMod () (string , error ) {
65+ modF , err := readAndParseGoMod (goModFilename )
66+ if err != nil {
67+ return "" , err // Propagate error from reading/parsing
68+ }
69+
70+ // Find k8s.io/kubernetes version
71+ for _ , req := range modF .Require {
72+ if req .Mod .Path == k8sRepo {
73+ log .Printf ("Found existing %s version in %s: %s" , k8sRepo , goModFilename , req .Mod .Version )
74+ return req .Mod .Version , nil // Return found version
75+ }
76+ }
77+ // Not found case - return empty string, no error (as per original logic)
78+ log .Printf ("INFO: %s not found in %s require block. Nothing to do." , k8sRepo , goModFilename )
79+ return "" , nil
80+ }
81+
4582func main () {
4683 log .SetFlags (0 )
4784 if os .Getenv ("GOEXE" ) != "" {
@@ -61,23 +98,27 @@ func main() {
6198 }
6299 log .Printf ("Running in module root: %s" , modRoot )
63100
64- modF , err := readAndParseGoMod (goModFilename )
65- if err != nil {
66- log .Fatal (err ) // Error already formatted by helper function
67- }
101+ var k8sVer string
68102
69- // Find k8s.io/kubernetes version
70- k8sVer := ""
71- for _ , req := range modF .Require {
72- if req .Mod .Path == k8sRepo {
73- k8sVer = req .Mod .Version
74- break
103+ // Determine the target k8s version using helper functions
104+ targetK8sVerEnv := os .Getenv (k8sVersionEnvVar )
105+ if targetK8sVerEnv != "" {
106+ // Process version from environment variable
107+ k8sVer , err = getK8sVersionFromEnv (targetK8sVerEnv )
108+ if err != nil {
109+ log .Fatalf ("Failed to process k8s version from environment variable %s: %v" , k8sVersionEnvVar , err )
110+ }
111+ } else {
112+ // Process version from go.mod file
113+ k8sVer , err = getK8sVersionFromMod ()
114+ if err != nil {
115+ log .Fatalf ("Failed to get k8s version from %s: %v" , goModFilename , err )
116+ }
117+ // Handle the "not found" case where getK8sVersionFromMod returns "", nil
118+ if k8sVer == "" {
119+ os .Exit (0 ) // Exit gracefully as requested
75120 }
76121 }
77- if k8sVer == "" {
78- log .Fatalf ("Could not find %s in %s require block" , k8sRepo , goModFilename )
79- }
80- log .Printf ("Found %s version: %s" , k8sRepo , k8sVer )
81122
82123 // Calculate target staging version
83124 if ! semver .IsValid (k8sVer ) {
@@ -92,7 +133,7 @@ func main() {
92133 // targetStagingVer becomes "v0" + ".32" + "." + "3" => "v0.32.3"
93134 targetStagingVer := "v0" + strings .TrimPrefix (majorMinor , "v1" ) + "." + patch
94135 if ! semver .IsValid (targetStagingVer ) {
95- log .Fatalf ("Calculated invalid staging semver: %s" , targetStagingVer )
136+ log .Fatalf ("Calculated invalid staging semver: %s from k8s version %s " , targetStagingVer , k8sVer )
96137 }
97138 log .Printf ("Target staging version calculated: %s" , targetStagingVer )
98139
@@ -158,7 +199,7 @@ func main() {
158199 }
159200
160201 if determinedVer != targetStagingVer {
161- log .Printf ("WARNING : Target version %s not found for %s. Using existing version %s." , targetStagingVer , effectivePath , determinedVer )
202+ log .Printf ("INFO : Target version %s not found for %s. Using existing predecessor version %s." , targetStagingVer , effectivePath , determinedVer )
162203 }
163204
164205 // map the original module path (as seen in the dependency graph) to the desired version for the 'replace' directive
@@ -169,18 +210,18 @@ func main() {
169210 pins [k8sRepo ] = k8sVer
170211 log .Printf ("Identified %d k8s.io/* modules to manage." , len (pins ))
171212
172- // Parse go.mod again (needed in case `go list` modified it)
173- modF , err = readAndParseGoMod (goModFilename )
213+ // Parse go.mod again (needed in case `go list` or `go get` modified it)
214+ modF , err : = readAndParseGoMod (goModFilename )
174215 if err != nil {
175216 log .Fatal (err ) // Error already formatted by helper function
176217 }
177218
178- // Remove all existing k8s.io/* replaces
179- log .Println ("Removing existing k8s.io/* replace directives..." )
219+ // Remove all existing k8s.io/* replaces that target other modules (not local paths)
220+ log .Println ("Removing existing k8s.io/* module replace directives..." )
180221 var replacesToRemove []string
181222 for _ , rep := range modF .Replace {
182223 // Only remove replaces targeting k8s.io/* modules (not local replacements like ../staging)
183- // assumes standard module paths contain '.'
224+ // Check that the old path starts with k8s.io/ and the new path looks like a module path (contains '.')
184225 if strings .HasPrefix (rep .Old .Path , "k8s.io/" ) && strings .Contains (rep .New .Path , "." ) {
185226 replacesToRemove = append (replacesToRemove , rep .Old .Path )
186227 } else if strings .HasPrefix (rep .Old .Path , "k8s.io/" ) {
@@ -274,8 +315,12 @@ func runGoCommand(args ...string) ([]byte, error) {
274315 var stdout , stderr bytes.Buffer
275316 cmd .Stdout = & stdout
276317 cmd .Stderr = & stderr
318+ log .Printf ("Executing: %s %s" , goExe , strings .Join (args , " " ))
277319 if err := cmd .Run (); err != nil {
278- return nil , fmt .Errorf ("command '%s %s' failed: %v\n Stderr:\n %s" , goExe , strings .Join (args , " " ), err , stderr .String ())
320+ if stderr .Len () > 0 {
321+ log .Printf ("Stderr:\n %s" , stderr .String ())
322+ }
323+ return nil , fmt .Errorf ("command '%s %s' failed: %w" , goExe , strings .Join (args , " " ), err )
279324 }
280325 return stdout .Bytes (), nil
281326}
@@ -286,11 +331,14 @@ func getModuleVersions(modulePath string) ([]string, error) {
286331 // Combine output and error message for checking because 'go list' sometimes writes errors to stdout
287332 combinedOutput := string (output )
288333 if err != nil {
289- combinedOutput += err .Error ()
334+ if ! strings .Contains (combinedOutput , err .Error ()) {
335+ combinedOutput += err .Error ()
336+ }
290337 }
291338
292339 // Check if the error/output indicates "no matching versions" - this is not a fatal error for our logic
293- if strings .Contains (combinedOutput , "no matching versions" ) {
340+ if strings .Contains (combinedOutput , "no matching versions" ) || strings .Contains (combinedOutput , "no required module provides package" ) {
341+ log .Printf ("INFO: No versions found for module %s via 'go list'." , modulePath )
294342 return []string {}, nil // Return empty list, not an error
295343 }
296344 // If there was an actual error beyond "no matching versions"
@@ -300,6 +348,7 @@ func getModuleVersions(modulePath string) ([]string, error) {
300348
301349 fields := strings .Fields (string (output ))
302350 if len (fields ) < minGoListVersionFields {
351+ log .Printf ("INFO: No versions listed for module %s (output: '%s')" , modulePath , string (output ))
303352 return []string {}, nil // No versions listed (e.g., just the module path)
304353 }
305354 return fields [1 :], nil // First field is the module path
0 commit comments