• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / kubebuilder / 16231804143

11 Jul 2025 11:47PM UTC coverage: 58.994%. Remained the same
16231804143

Pull #4922

github

web-flow
:seedling: Bump golang.org/x/tools from 0.34.0 to 0.35.0

Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.34.0 to 0.35.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4922: :seedling: Bump golang.org/x/tools from 0.34.0 to 0.35.0

2404 of 4075 relevant lines covered (58.99%)

13.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/cli/alpha/internal/update/update.go
1
/*
2
Copyright 2025 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package update
18

19
import (
20
        "errors"
21
        "fmt"
22
        "io"
23
        "net/http"
24
        "os"
25
        "os/exec"
26
        "time"
27

28
        log "github.com/sirupsen/logrus"
29
        "github.com/spf13/afero"
30
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
31
)
32

33
// Update contains configuration for the update operation
34
type Update struct {
35
        // FromVersion stores the version to update from, e.g., "v4.5.0".
36
        FromVersion string
37
        // ToVersion stores the version to update to, e.g., "v4.6.0".
38
        ToVersion string
39
        // FromBranch stores the branch to update from, e.g., "main".
40
        FromBranch string
41

42
        // UpdateBranches
43
        AncestorBranch string
44
        OriginalBranch string
45
        UpgradeBranch  string
46
        MergeBranch    string
47
}
48

49
// Update a project using a default three-way Git merge.
50
// This helps apply new scaffolding changes while preserving custom code.
51
func (opts *Update) Update() error {
×
52
        log.Infof("Checking out base branch: %s", opts.FromBranch)
×
53
        checkoutCmd := exec.Command("git", "checkout", opts.FromBranch)
×
54
        if err := checkoutCmd.Run(); err != nil {
×
55
                return fmt.Errorf("failed to checkout base branch %s: %w", opts.FromBranch, err)
×
56
        }
×
57

58
        suffix := time.Now().Format("02-01-06-15-04")
×
59

×
60
        opts.AncestorBranch = "tmp-ancestor-" + suffix
×
61
        opts.OriginalBranch = "tmp-original-" + suffix
×
62
        opts.UpgradeBranch = "tmp-upgrade-" + suffix
×
63
        opts.MergeBranch = "tmp-merge-" + suffix
×
64

×
65
        log.Infof("Using branch names:")
×
66
        log.Infof("  Ancestor: %s", opts.AncestorBranch)
×
67
        log.Infof("  Original:  %s", opts.OriginalBranch)
×
68
        log.Infof("  Upgrade:  %s", opts.UpgradeBranch)
×
69
        log.Infof("  Merge:    %s", opts.MergeBranch)
×
70

×
71
        // 1. Creates an ancestor branch based on base branch
×
72
        // 2. Deletes everything except .git and PROJECT
×
73
        // 3. Installs old release
×
74
        // 4. Runs alpha generate with old release binary
×
75
        // 5. Commits the result
×
76
        log.Infof("Preparing Ancestor branch with name %s", opts.AncestorBranch)
×
77
        if err := opts.prepareAncestorBranch(); err != nil {
×
78
                return fmt.Errorf("failed to prepare ancestor branch: %w", err)
×
79
        }
×
80
        // 1. Creates original branch
81
        // 2. Ensure that original branch is == Based on user’s current base branch content with
82
        // git checkout "main" -- .
83
        // 3. Commits this state
84
        log.Infof("Preparing Original branch with name %s", opts.OriginalBranch)
×
85
        if err := opts.prepareOriginalBranch(); err != nil {
×
86
                return fmt.Errorf("failed to checkout current off ancestor: %w", err)
×
87
        }
×
88
        // 1. Creates upgrade branch from ancestor
89
        // 2. Cleans up the branch by removing all files except .git and PROJECT
90
        // 2. Regenerates scaffold using alpha generate with new version
91
        // 3. Commits the result
92
        log.Infof("Preparing Upgrade branch with name %s", opts.UpgradeBranch)
×
93
        if err := opts.prepareUpgradeBranch(); err != nil {
×
94
                return fmt.Errorf("failed to checkout upgrade off ancestor: %w", err)
×
95
        }
×
96

97
        // 1. Creates merge branch from upgrade
98
        // 2. Merges in original (user code)
99
        // 3. If conflicts occur, it will warn the user and leave the merge branch for manual resolution
100
        // 4. If merge is clean, it stages the changes and commits the result
101
        log.Infof("Preparing Merge branch with name %s and performing merge", opts.MergeBranch)
×
102
        if err := opts.mergeOriginalToUpgrade(); err != nil {
×
103
                return fmt.Errorf("failed to merge upgrade into merge branch: %w", err)
×
104
        }
×
105
        return nil
×
106
}
107

108
// regenerateProjectWithVersion downloads the release binary for the specified version,
109
// and runs the `alpha generate` command to re-scaffold the project
110
func regenerateProjectWithVersion(version string) error {
×
111
        tempDir, err := binaryWithVersion(version)
×
112
        if err != nil {
×
113
                return fmt.Errorf("failed to download release %s binary: %w", version, err)
×
114
        }
×
115
        if err := runAlphaGenerate(tempDir, version); err != nil {
×
116
                return fmt.Errorf("failed to run alpha generate on ancestor branch: %w", err)
×
117
        }
×
118
        return nil
×
119
}
120

121
// prepareAncestorBranch prepares the ancestor branch by checking it out,
122
// cleaning up the project files, and regenerating the project with the specified version.
123
func (opts *Update) prepareAncestorBranch() error {
×
124
        gitCmd := exec.Command("git", "checkout", "-b", opts.AncestorBranch, opts.FromBranch)
×
125
        if err := gitCmd.Run(); err != nil {
×
126
                return fmt.Errorf("failed to create %s from %s: %w", opts.AncestorBranch, opts.FromBranch, err)
×
127
        }
×
128
        checkoutCmd := exec.Command("git", "checkout", opts.AncestorBranch)
×
129
        if err := checkoutCmd.Run(); err != nil {
×
130
                return fmt.Errorf("failed to checkout base branch %s: %w", opts.AncestorBranch, err)
×
131
        }
×
132
        if err := cleanupBranch(); err != nil {
×
133
                return fmt.Errorf("failed to cleanup the %s : %w", opts.AncestorBranch, err)
×
134
        }
×
135
        if err := regenerateProjectWithVersion(opts.FromVersion); err != nil {
×
136
                return fmt.Errorf("failed to regenerate project with fromVersion %s: %w", opts.FromVersion, err)
×
137
        }
×
138
        gitCmd = exec.Command("git", "add", "--all")
×
139
        if err := gitCmd.Run(); err != nil {
×
140
                return fmt.Errorf("failed to stage changes in %s: %w", opts.AncestorBranch, err)
×
141
        }
×
142
        commitMessage := "Clean scaffold from release version:" + opts.FromVersion
×
143
        _ = exec.Command("git", "commit", "-m", commitMessage).Run()
×
144
        return nil
×
145
}
146

147
// binaryWithVersion downloads the specified released version from GitHub releases and saves it
148
// to a temporary directory with executable permissions.
149
// Returns the temporary directory path containing the binary.
150
func binaryWithVersion(version string) (string, error) {
×
151
        url := buildReleaseURL(version)
×
152

×
153
        fs := afero.NewOsFs()
×
154
        tempDir, err := afero.TempDir(fs, "", "kubebuilder"+version+"-")
×
155
        if err != nil {
×
156
                return "", fmt.Errorf("failed to create temporary directory: %w", err)
×
157
        }
×
158

159
        binaryPath := tempDir + "/kubebuilder"
×
160
        file, err := os.Create(binaryPath)
×
161
        if err != nil {
×
162
                return "", fmt.Errorf("failed to create the binary file: %w", err)
×
163
        }
×
164
        defer func() {
×
165
                if err = file.Close(); err != nil {
×
166
                        log.Errorf("failed to close the file: %v", err)
×
167
                }
×
168
        }()
169

170
        response, err := http.Get(url)
×
171
        if err != nil {
×
172
                return "", fmt.Errorf("failed to download the binary: %w", err)
×
173
        }
×
174
        defer func() {
×
175
                if err = response.Body.Close(); err != nil {
×
176
                        log.Errorf("failed to close the connection: %v", err)
×
177
                }
×
178
        }()
179

180
        if response.StatusCode != http.StatusOK {
×
181
                return "", fmt.Errorf("failed to download the binary: HTTP %d", response.StatusCode)
×
182
        }
×
183

184
        _, err = io.Copy(file, response.Body)
×
185
        if err != nil {
×
186
                return "", fmt.Errorf("failed to write the binary content to file: %w", err)
×
187
        }
×
188

189
        if err := os.Chmod(binaryPath, 0o755); err != nil {
×
190
                return "", fmt.Errorf("failed to make binary executable: %w", err)
×
191
        }
×
192
        return tempDir, nil
×
193
}
194

195
// cleanupBranch removes all files and folders in the current directory
196
// except for the .git directory and the PROJECT file.
197
// This is necessary to ensure the ancestor branch starts with a clean slate
198
// TODO: Analise if this command is still needed in the future.
199
// It is required because the alpha generate command in versions prior to v4.7.0 do not properly
200
// handle the removal of files in the ancestor branch.
201
func cleanupBranch() error {
×
202
        cmd := exec.Command("sh", "-c", "find . -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {} +")
×
203

×
204
        if err := cmd.Run(); err != nil {
×
205
                return fmt.Errorf("failed to clean up files: %w", err)
×
206
        }
×
207
        return nil
×
208
}
209

210
// runMakeTargets is a helper function to run make with the targets necessary
211
// to ensure all the necessary components are generated, formatted and linted.
212
func runMakeTargets() {
×
213
        targets := []string{"manifests", "generate", "fmt", "vet", "lint-fix"}
×
214
        for _, target := range targets {
×
215
                err := util.RunCmd(fmt.Sprintf("Running make %s", target), "make", target)
×
216
                if err != nil {
×
217
                        log.Warnf("make %s failed: %v", target, err)
×
218
                }
×
219
        }
220
}
221

222
// runAlphaGenerate executes the old Kubebuilder version's 'alpha generate' command
223
// to create clean scaffolding in the ancestor branch. This uses the downloaded
224
// binary with the original PROJECT file to recreate the project's initial state.
225
func runAlphaGenerate(tempDir, version string) error {
×
226
        log.Infof("Generating project with version %s", version)
×
227
        // Temporarily modify PATH to use the downloaded Kubebuilder binary
×
228
        tempBinaryPath := tempDir + "/kubebuilder"
×
229
        originalPath := os.Getenv("PATH")
×
230
        tempEnvPath := tempDir + ":" + originalPath
×
231

×
232
        if err := os.Setenv("PATH", tempEnvPath); err != nil {
×
233
                return fmt.Errorf("failed to set temporary PATH: %w", err)
×
234
        }
×
235

236
        defer func() {
×
237
                if err := os.Setenv("PATH", originalPath); err != nil {
×
238
                        log.Errorf("failed to restore original PATH: %v", err)
×
239
                }
×
240
        }()
241

242
        // TODO: we need improve the implementation from utils to allow us
243
        // to pass the path of the binary and use it to run the alpha generate command.
244
        cmd := exec.Command(tempBinaryPath, "alpha", "generate")
×
245
        cmd.Stdout = os.Stdout
×
246
        cmd.Stderr = os.Stderr
×
247
        cmd.Env = os.Environ()
×
248

×
249
        if err := cmd.Run(); err != nil {
×
250
                return fmt.Errorf("failed to run alpha generate: %w", err)
×
251
        }
×
252
        log.Info("Successfully ran alpha generate", version)
×
253

×
254
        // TODO: Analyse if this command is still needed in the future.
×
255
        // It was added because the alpha generate command in versions prior to v4.7.0 does
×
256
        // not run those commands automatically which will not allow we properly ensure
×
257
        // that all manifests, code generation, formatting, and linting are applied to
×
258
        // properly do the 3-way merge.
×
259
        runMakeTargets()
×
260
        return nil
×
261
}
262

263
// prepareOriginalBranch creates the 'original' branch from ancestor and
264
// populates it with the user's actual project content from the default branch.
265
// This represents the current state of the user's project.
266
func (opts *Update) prepareOriginalBranch() error {
×
267
        gitCmd := exec.Command("git", "checkout", "-b", opts.OriginalBranch)
×
268
        if err := gitCmd.Run(); err != nil {
×
269
                return fmt.Errorf("failed to checkout branch %s: %w", opts.OriginalBranch, err)
×
270
        }
×
271

272
        gitCmd = exec.Command("git", "checkout", opts.FromBranch, "--", ".")
×
273
        if err := gitCmd.Run(); err != nil {
×
274
                return fmt.Errorf("failed to checkout content from %s branch onto %s: %w", opts.FromBranch, opts.OriginalBranch, err)
×
275
        }
×
276

277
        gitCmd = exec.Command("git", "add", "--all")
×
278
        if err := gitCmd.Run(); err != nil {
×
279
                return fmt.Errorf("failed to stage all changes in current: %w", err)
×
280
        }
×
281

282
        _ = exec.Command("git", "commit", "-m",
×
283
                fmt.Sprintf("Add code from %s into %s",
×
284
                        opts.FromBranch, opts.OriginalBranch)).Run()
×
285
        return nil
×
286
}
287

288
// prepareUpgradeBranch creates the 'upgrade' branch from ancestor and
289
// generates fresh scaffolding using the current (latest) CLI version.
290
// This represents what the project should look like with the new version.
291
func (opts *Update) prepareUpgradeBranch() error {
×
292
        gitCmd := exec.Command("git", "checkout", "-b", opts.UpgradeBranch, opts.AncestorBranch)
×
293
        if err := gitCmd.Run(); err != nil {
×
294
                return fmt.Errorf("failed to checkout %s branch off %s: %w",
×
295
                        opts.UpgradeBranch, opts.AncestorBranch, err)
×
296
        }
×
297

298
        checkoutCmd := exec.Command("git", "checkout", opts.UpgradeBranch)
×
299
        if err := checkoutCmd.Run(); err != nil {
×
300
                return fmt.Errorf("failed to checkout base branch %s: %w", opts.UpgradeBranch, err)
×
301
        }
×
302

303
        if err := cleanupBranch(); err != nil {
×
304
                return fmt.Errorf("failed to cleanup the %s branch: %w", opts.UpgradeBranch, err)
×
305
        }
×
306
        if err := regenerateProjectWithVersion(opts.ToVersion); err != nil {
×
307
                return fmt.Errorf("failed to regenerate project with version %s: %w", opts.ToVersion, err)
×
308
        }
×
309
        gitCmd = exec.Command("git", "add", "--all")
×
310
        if err := gitCmd.Run(); err != nil {
×
311
                return fmt.Errorf("failed to stage changes in %s: %w", opts.UpgradeBranch, err)
×
312
        }
×
313

314
        _ = exec.Command("git", "commit", "-m", "Clean scaffolding from version "+opts.ToVersion).Run()
×
315
        return nil
×
316
}
317

318
// mergeOriginalToUpgrade attempts to merge the upgrade branch
319
func (opts *Update) mergeOriginalToUpgrade() error {
×
320
        if err := exec.Command("git", "checkout", "-b", opts.MergeBranch, opts.UpgradeBranch).Run(); err != nil {
×
321
                return fmt.Errorf("failed to create merge branch %s from %s: %w", opts.MergeBranch, opts.OriginalBranch, err)
×
322
        }
×
323

324
        checkoutCmd := exec.Command("git", "checkout", opts.MergeBranch)
×
325
        if err := checkoutCmd.Run(); err != nil {
×
326
                return fmt.Errorf("failed to checkout base branch %s: %w", opts.MergeBranch, err)
×
327
        }
×
328

329
        mergeCmd := exec.Command("git", "merge", "--no-edit", "--no-commit", opts.OriginalBranch)
×
330
        err := mergeCmd.Run()
×
331

×
332
        hasConflicts := false
×
333
        if err != nil {
×
334
                var exitErr *exec.ExitError
×
335
                // If the merge has an error that is not a conflict, return an error 2
×
336
                if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
×
337
                        log.Warn("Merge completed with conflicts. Manual resolution required.")
×
338
                        hasConflicts = true
×
339
                } else {
×
340
                        return fmt.Errorf("merge failed unexpectedly: %w", err)
×
341
                }
×
342
        }
343

344
        if !hasConflicts {
×
345
                log.Info("Merge happened without conflicts.")
×
346
        }
×
347

348
        // Best effort to run make targets to ensure the project is in a good state
349
        runMakeTargets()
×
350

×
351
        // Step 4: Stage and commit
×
352
        if err := exec.Command("git", "add", "--all").Run(); err != nil {
×
353
                return fmt.Errorf("failed to stage merge results: %w", err)
×
354
        }
×
355

356
        message := fmt.Sprintf("Merge from %s to %s.", opts.FromVersion, opts.ToVersion)
×
357
        if hasConflicts {
×
358
                message += " With conflicts - manual resolution required."
×
359
        } else {
×
360
                message += " Merge happened without conflicts."
×
361
        }
×
362

363
        _ = exec.Command("git", "commit", "-m", message).Run()
×
364

×
365
        return nil
×
366
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc