Skip to content

Commit b5094f5

Browse files
committed
Improvements to k8smaintainer code
Signed-off-by: Brett Tofel <[email protected]>
1 parent a1904d8 commit b5094f5

File tree

1 file changed

+86
-51
lines changed

1 file changed

+86
-51
lines changed

hack/tools/k8smaintainer/main.go

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,30 @@ import (
1818
)
1919

2020
const (
21-
k8sRepo = "k8s.io/kubernetes"
22-
expectedMajorMinorParts = 2
23-
goModFilePerms = fs.FileMode(0600)
24-
minGoListVersionFields = 2
25-
minValidPatchNumber = 1
21+
k8sRepo = "k8s.io/kubernetes"
22+
expectedMajorMinorParts = 2
23+
goModFilename = "go.mod"
24+
goModFilePerms = fs.FileMode(0600)
25+
minGoListVersionFields = 2
26+
minPatchNumberToDecrementFrom = 1 // We can only decrement patch if it's 1 or greater (to get 0 or greater)
2627
)
2728

2829
//nolint:gochecknoglobals
2930
var goExe = "go"
3031

32+
// readAndParseGoMod reads and parses the go.mod file.
33+
func readAndParseGoMod(filename string) ([]byte, *modfile.File, error) {
34+
modBytes, err := os.ReadFile(filename)
35+
if err != nil {
36+
return nil, nil, fmt.Errorf("error reading %s: %w", filename, err)
37+
}
38+
modF, err := modfile.Parse(filename, modBytes, nil)
39+
if err != nil {
40+
return nil, nil, fmt.Errorf("error parsing %s: %w", filename, err)
41+
}
42+
return modBytes, modF, nil
43+
}
44+
3145
func main() {
3246
log.SetFlags(0)
3347
if os.Getenv("GOEXE") != "" {
@@ -40,21 +54,16 @@ func main() {
4054
}
4155
modRoot := findModRoot(wd)
4256
if modRoot == "" {
43-
log.Fatalf("Failed to find go.mod in %s or parent directories", wd)
57+
log.Fatalf("Failed to find %s in %s or parent directories", goModFilename, wd)
4458
}
4559
if err := os.Chdir(modRoot); err != nil {
4660
log.Fatalf("Error changing directory to %s: %v", modRoot, err)
4761
}
4862
log.Printf("Running in module root: %s", modRoot)
4963

50-
modBytes, err := os.ReadFile("go.mod")
64+
_, modF, err := readAndParseGoMod(goModFilename)
5165
if err != nil {
52-
log.Fatalf("Error reading go.mod: %v", err)
53-
}
54-
55-
modF, err := modfile.Parse("go.mod", modBytes, nil)
56-
if err != nil {
57-
log.Fatalf("Error parsing go.mod: %v", err)
66+
log.Fatal(err) // Error already formatted by helper function
5867
}
5968

6069
// Find k8s.io/kubernetes version
@@ -66,20 +75,22 @@ func main() {
6675
}
6776
}
6877
if k8sVer == "" {
69-
log.Fatalf("Could not find %s in go.mod require block", k8sRepo)
78+
log.Fatalf("Could not find %s in %s require block", k8sRepo, goModFilename)
7079
}
7180
log.Printf("Found %s version: %s", k8sRepo, k8sVer)
7281

7382
// Calculate target staging version
7483
if !semver.IsValid(k8sVer) {
7584
log.Fatalf("Invalid semver for %s: %s", k8sRepo, k8sVer)
7685
}
86+
// Example: k8sVer = v1.32.3
7787
majorMinor := semver.MajorMinor(k8sVer) // e.g., "v1.32"
7888
patch := strings.TrimPrefix(k8sVer, majorMinor+".") // e.g., "3"
7989
if len(strings.Split(majorMinor, ".")) != expectedMajorMinorParts {
8090
log.Fatalf("Unexpected format for MajorMinor: %s", majorMinor)
8191
}
82-
targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch // e.g., "v0.32.3"
92+
// targetStagingVer becomes "v0" + ".32" + "." + "3" => "v0.32.3"
93+
targetStagingVer := "v0" + strings.TrimPrefix(majorMinor, "v1") + "." + patch
8394
if !semver.IsValid(targetStagingVer) {
8495
log.Fatalf("Calculated invalid staging semver: %s", targetStagingVer)
8596
}
@@ -96,7 +107,7 @@ func main() {
96107
output, err := runGoCommand("list", "-m", "-json", "all")
97108
if err != nil {
98109
// Try downloading first if list fails
99-
log.Println("go list failed, trying go mod download...")
110+
log.Println("'go list' failed, trying 'go mod download'...")
100111
if _, downloadErr := runGoCommand("mod", "download"); downloadErr != nil {
101112
log.Fatalf("Error running 'go mod download' after list failed: %v", downloadErr)
102113
}
@@ -121,15 +132,17 @@ func main() {
121132
continue
122133
}
123134

124-
// Use replacement path if it exists
135+
// Use replacement path if it exists, but skip local file replacements
125136
effectivePath := mod.Path
126137
if mod.Replace != nil {
127-
effectivePath = mod.Replace.Path
128-
// Skip local file replacements
129-
if !strings.Contains(effectivePath, ".") { // Basic check if it looks like a module path vs local path
130-
log.Printf("Skipping local replace: %s => %s", mod.Path, effectivePath)
138+
// Heuristic: Assume module paths have a domain-like structure (e.g., 'xxx.yyy/zzz') in the first segment.
139+
// Local paths usually don't (e.g., '../othermod', './local').
140+
parts := strings.SplitN(mod.Replace.Path, "/", 2)
141+
if len(parts) > 0 && !strings.Contains(parts[0], ".") {
142+
log.Printf("Skipping local replace: %s => %s", mod.Path, mod.Replace.Path)
131143
continue
132144
}
145+
effectivePath = mod.Replace.Path
133146
}
134147

135148
// Check existence of target version, fallback to previous patch if needed
@@ -152,33 +165,39 @@ func main() {
152165
pins[mod.Path] = determinedVer
153166
}
154167

155-
// Add k8s.io/kubernetes itself to the pins map
168+
// Add k8s.io/kubernetes itself to the pins map (ensures it's covered by the replace logic)
156169
pins[k8sRepo] = k8sVer
157170
log.Printf("Identified %d k8s.io/* modules to manage.", len(pins))
158171

159-
// 7. Parse go.mod again (to have a fresh modfile object)
160-
modBytes, err = os.ReadFile("go.mod")
161-
if err != nil {
162-
log.Fatalf("Error reading go.mod again: %v", err)
163-
}
164-
modF, err = modfile.Parse("go.mod", modBytes, nil)
172+
// Parse go.mod again (needed in case `go list` modified it)
173+
_, modF, err = readAndParseGoMod(goModFilename)
165174
if err != nil {
166-
log.Fatalf("Error parsing go.mod again: %v", err)
175+
log.Fatal(err) // Error already formatted by helper function
167176
}
168177

169178
// Remove all existing k8s.io/* replaces
170179
log.Println("Removing existing k8s.io/* replace directives...")
171180
var replacesToRemove []string
172181
for _, rep := range modF.Replace {
173-
if strings.HasPrefix(rep.Old.Path, "k8s.io/") {
182+
// Only remove replaces targeting k8s.io/* modules (not local replacements like ../staging)
183+
// assumes standard module paths contain '.'
184+
if strings.HasPrefix(rep.Old.Path, "k8s.io/") && strings.Contains(rep.New.Path, ".") {
174185
replacesToRemove = append(replacesToRemove, rep.Old.Path)
186+
} else if strings.HasPrefix(rep.Old.Path, "k8s.io/") {
187+
log.Printf("Note: Found existing non-module replace for %s, leaving untouched: %s => %s %s", rep.Old.Path, rep.Old.Path, rep.New.Path, rep.New.Version)
175188
}
176189
}
177-
for _, path := range replacesToRemove {
178-
if err := modF.DropReplace(path, ""); err != nil {
179-
// Tolerate errors if the replace was already somehow removed
180-
log.Printf("Note: Error dropping replace for %s (might be benign): %v", path, err)
190+
if len(replacesToRemove) > 0 {
191+
for _, path := range replacesToRemove {
192+
log.Printf("Removing replace for: %s", path)
193+
// Drop replace expects oldPath and oldVersion. Version is empty for path-only replaces.
194+
if err := modF.DropReplace(path, ""); err != nil {
195+
// Tolerate errors if the replace was already somehow removed or structure changed
196+
log.Printf("Note: Error dropping replace for %s (might be benign): %v", path, err)
197+
}
181198
}
199+
} else {
200+
log.Println("No existing k8s.io/* module replaces found to remove.")
182201
}
183202

184203
// Add new replace directives
@@ -193,7 +212,6 @@ func main() {
193212
for _, path := range sortedPaths {
194213
version := pins[path]
195214
// Add replace for the module path itself (e.g., k8s.io/api => k8s.io/api v0.32.3)
196-
// This handles cases where the effective path from `go list` might differ due to other replaces
197215
if err := modF.AddReplace(path, "", path, version); err != nil {
198216
log.Fatalf("Error adding replace for %s => %s %s: %v", path, path, version, err)
199217
}
@@ -202,30 +220,31 @@ func main() {
202220

203221
// Write go.mod
204222
log.Println("Writing updated go.mod...")
205-
modF.Cleanup() // Sort blocks, etc.
223+
modF.Cleanup() // Sort blocks, remove redundant directives etc.
206224
newModBytes, err := modF.Format()
207225
if err != nil {
208226
log.Fatalf("Error formatting go.mod: %v", err)
209227
}
210-
if err := os.WriteFile("go.mod", newModBytes, goModFilePerms); err != nil {
211-
log.Fatalf("Error writing go.mod: %v", err)
228+
if err := os.WriteFile(goModFilename, newModBytes, goModFilePerms); err != nil {
229+
log.Fatalf("Error writing %s: %v", goModFilename, err)
212230
}
213231

214232
// Run `go mod tidy`
215-
goVer := modF.Go.Version
233+
goVer := ""
234+
if modF.Go != nil { // Ensure Go directive exists before accessing Version
235+
goVer = modF.Go.Version
236+
}
216237
tidyArgs := []string{"mod", "tidy"}
217238
if goVer != "" {
218239
tidyArgs = append(tidyArgs, fmt.Sprintf("-go=%s", goVer))
219-
log.Printf("Running 'go mod tidy -go=%s'...", goVer)
220-
} else {
221-
log.Println("Running 'go mod tidy'...")
222240
}
241+
log.Printf("Running '%s %s'...", goExe, strings.Join(tidyArgs, " "))
223242
if _, err := runGoCommand(tidyArgs...); err != nil {
224243
log.Fatalf("Error running 'go mod tidy': %v", err)
225244
}
226245

227246
// Run `go mod download k8s.io/kubernetes`
228-
log.Printf("Running 'go mod download %s'...", k8sRepo)
247+
log.Printf("Running '%s mod download %s'...", goExe, k8sRepo)
229248
if _, err := runGoCommand("mod", "download", k8sRepo); err != nil {
230249
// This might not be fatal, could be network issues, but log it prominently
231250
log.Printf("WARNING: Error running 'go mod download %s': %v", k8sRepo, err)
@@ -237,7 +256,7 @@ func main() {
237256
// findModRoot searches for go.mod in dir and parent directories
238257
func findModRoot(dir string) string {
239258
for {
240-
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
259+
if _, err := os.Stat(filepath.Join(dir, goModFilename)); err == nil {
241260
return dir
242261
}
243262
parent := filepath.Dir(dir)
@@ -264,16 +283,24 @@ func runGoCommand(args ...string) ([]byte, error) {
264283
// getModuleVersions retrieves the list of available versions for a module
265284
func getModuleVersions(modulePath string) ([]string, error) {
266285
output, err := runGoCommand("list", "-m", "-versions", modulePath)
286+
// Combine output and error message for checking because 'go list' sometimes writes errors to stdout
287+
combinedOutput := string(output)
288+
if err != nil {
289+
combinedOutput += err.Error()
290+
}
291+
292+
// 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") {
294+
return []string{}, nil // Return empty list, not an error
295+
}
296+
// If there was an actual error beyond "no matching versions"
267297
if err != nil {
268-
// Check if the error is "no matching versions" - this is not a fatal error for our logic
269-
if strings.Contains(string(output)+err.Error(), "no matching versions") {
270-
return []string{}, nil // Return empty list, not an error
271-
}
272298
return nil, fmt.Errorf("error listing versions for %s: %w", modulePath, err)
273299
}
300+
274301
fields := strings.Fields(string(output))
275302
if len(fields) < minGoListVersionFields {
276-
return []string{}, nil // No versions listed
303+
return []string{}, nil // No versions listed (e.g., just the module path)
277304
}
278305
return fields[1:], nil // First field is the module path
279306
}
@@ -301,10 +328,18 @@ func getLatestExistingVersion(modulePath, targetVer string) (string, error) {
301328
majorMinor := semver.MajorMinor(targetVer) // e.g., v0.32
302329
patchStr := strings.TrimPrefix(targetVer, majorMinor+".") // e.g., 3
303330
var patch int
304-
if _, err := fmt.Sscan(patchStr, &patch); err != nil || patch < minValidPatchNumber {
305-
log.Printf("Could not parse patch version or patch < %d for %s, cannot determine predecessor.", minValidPatchNumber, targetVer)
331+
// Use Sscan to parse the integer patch number
332+
if _, err := fmt.Sscan(patchStr, &patch); err != nil {
333+
log.Printf("Could not parse patch version from %s for module %s: %v. Cannot determine predecessor.", targetVer, modulePath, err)
306334
return "", nil // Cannot determine predecessor
307335
}
336+
337+
// Only try to decrement if the patch number is >= the minimum required to do so
338+
if patch < minPatchNumberToDecrementFrom {
339+
log.Printf("Patch version %d is less than %d for %s, cannot determine predecessor.", patch, minPatchNumberToDecrementFrom, targetVer)
340+
return "", nil // Cannot determine predecessor (e.g., target was v0.32.0)
341+
}
342+
308343
prevPatchVer := fmt.Sprintf("%s.%d", majorMinor, patch-1) // e.g., v0.32.2
309344

310345
foundPrev := false

0 commit comments

Comments
 (0)