Skip to content

Commit 3106fe8

Browse files
committed
add --force flag to alpha update command
This commit adds the --force flag to the alpha update command. It also adds e2e tests and documentation for the flag. The --force flag makes it possible to run the alpha update command in CI workflows.
1 parent dbd035c commit 3106fe8

File tree

4 files changed

+136
-15
lines changed

4 files changed

+136
-15
lines changed

docs/book/src/reference/commands/alpha_update.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ kubebuilder alpha update \
6161
--from-branch=main
6262
```
6363

64+
Force update even with merge conflicts:
65+
66+
```sh
67+
kubebuilder alpha update --force
68+
```
69+
6470
<aside class="note warning">
6571
<h1>You might need to upgrade your project first</h1>
6672

@@ -81,8 +87,46 @@ Once updated, you can use `kubebuilder alpha update` for future upgrades.
8187
| `--from-version` | **Required for projects initialized with versions earlier than v4.6.0.** Kubebuilder version your project was created with. If unset, uses the `PROJECT` file. |
8288
| `--to-version` | Version to upgrade to. Defaults to the latest version. |
8389
| `--from-branch` | Git branch that contains your current project code. Defaults to `main`. |
90+
| `--force` | Force the update even if conflicts occur. Conflicted files will include conflict markers, and a commit will be created automatically. Ideal for automation (e.g., cronjobs, CI). |
8491
| `-h, --help` | Show help for this command. |
8592

93+
<h1> Using the `--force` flag </h1>
94+
95+
When using the `--force` flag, merge conflicts will be committed with conflict markers in the `tmp-kb-update-merge` branch.
96+
You should carefully review and resolve these conflicts before merging into your main branch.
97+
98+
After resolving conflicts, run the following command to ensure manifests and generated files are up to date:
99+
```sh
100+
make manifests generate fmt vet lint-fix
101+
```
102+
103+
You might want to run `make all`, ensure that all tests are passing, and thoroughly validate the final result before committing and pushing a pull request to update your project from the `tmp-kb-update-merge` branch.
104+
105+
## Handling Merge Conflicts
106+
107+
If conflicts occur during a merge, Git will stop the process and leave the merge branch in a conflicted state for manual resolution.
108+
109+
To proceed with the merge despite conflicts, you can use the `--force` option. This is useful in automated environments, such as CI pipelines or cron jobs, where you want to create a pull request with the changes - even if conflicts are present.
110+
111+
```
112+
kubebuilder alpha update --force
113+
```
114+
The resulting commit will include conflict markers in the affected files:
115+
116+
```
117+
<<<<<<< HEAD
118+
Your changes
119+
=======
120+
Incoming changes
121+
>>>>>>> branch-name
122+
```
123+
124+
The commit message will indicate that conflicts are present and need to be resolved manually.
125+
126+
<aside>
127+
Note: This approach is typically used in automation workflows where conflict markers are later addressed by a human, or where preserving the conflicting changes is acceptable for follow-up processing.
128+
</aside>
129+
86130
<aside class="note warning">
87131
<h1>Projects generated with </h1>
88132

pkg/cli/alpha/internal/update/update.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type Update struct {
3838
ToVersion string
3939
// FromBranch stores the branch to update from, e.g., "main".
4040
FromBranch string
41+
// Force commits the update changes even with merge conflicts
42+
Force bool
4143

4244
// UpdateBranches
4345
AncestorBranch string
@@ -334,8 +336,15 @@ func (opts *Update) mergeOriginalToUpgrade() error {
334336
var exitErr *exec.ExitError
335337
// If the merge has an error that is not a conflict, return an error 2
336338
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
337-
log.Warn("Merge completed with conflicts. Manual resolution required.")
338339
hasConflicts = true
340+
if !opts.Force {
341+
log.Warn("Merge stopped due to conflicts. Manual resolution is required.")
342+
log.Warn("After resolving the conflicts, run the following command:")
343+
log.Warn(" make manifests generate fmt vet lint-fix")
344+
log.Warn("This ensures manifests and generated files are up to date, and the project layout remains consistent.")
345+
return fmt.Errorf("merge stopped due to conflicts")
346+
}
347+
log.Warn("Merge completed with conflicts. Conflict markers will be committed.")
339348
} else {
340349
return fmt.Errorf("merge failed unexpectedly: %w", err)
341350
}

pkg/cli/alpha/update.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ The process uses Git branches:
4949
- upgrade: scaffold from the target version
5050
- merge: result of the 3-way merge
5151
52-
If conflicts occur during the merge, resolve them manually in the 'merge' branch.
53-
Once resolved, commit and push it as a pull request. This branch will contain the
54-
final upgraded project with the latest Kubebuilder layout and your custom code.
52+
If conflicts occur during the merge, the command will stop and leave the merge branch for manual resolution.
53+
Use --force to commit conflicts with markers instead.
5554
5655
Examples:
5756
# Update from the version specified in the PROJECT file to the latest release
@@ -63,6 +62,9 @@ Examples:
6362
# Update from a specific version to an specific release
6463
kubebuilder alpha update --from-version v4.5.0 --to-version v4.7.0
6564
65+
# Force update even with merge conflicts (commit conflict markers)
66+
kubebuilder alpha update --force
67+
6668
`,
6769

6870
PreRunE: func(_ *cobra.Command, _ []string) error {
@@ -92,5 +94,9 @@ Examples:
9294
updateCmd.Flags().StringVar(&opts.FromBranch, "from-branch", "",
9395
"Git branch to use as current state of the project for the update.")
9496

97+
updateCmd.Flags().BoolVar(&opts.Force, "force", false,
98+
"Force the update even if conflicts occur. Conflicted files will include conflict markers, and a "+
99+
"commit will be created automatically. Ideal for automation (e.g., cronjobs, CI).")
100+
95101
return updateCmd
96102
}

test/e2e/alphaupdate/update_test.go

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ import (
3232
)
3333

3434
const (
35-
fromVersion = "v4.5.2"
36-
toVersion = "v4.6.0"
35+
toVersionConstant = "v4.6.0"
36+
)
3737

38-
// Binary patterns for cleanup
39-
binFromVersionPattern = "/tmp/kubebuilder" + fromVersion + "-*"
40-
binToVersionPattern = "/tmp/kubebuilder" + toVersion + "-*"
38+
var (
39+
fromVersion string
40+
toVersion string
4141
)
4242

4343
var _ = Describe("kubebuilder", func() {
@@ -59,7 +59,7 @@ var _ = Describe("kubebuilder", func() {
5959
mockProjectDir, err = os.MkdirTemp("/tmp", "kubebuilder-mock-project-")
6060
Expect(err).NotTo(HaveOccurred())
6161

62-
By("downloading kubebuilder v4.5.2 binary to isolated /tmp directory")
62+
By("downloading kubebuilder " + fromVersion + " binary to isolated /tmp directory")
6363
binFromVersionPath, err = downloadKubebuilder()
6464
Expect(err).NotTo(HaveOccurred())
6565
})
@@ -72,8 +72,8 @@ var _ = Describe("kubebuilder", func() {
7272

7373
// Clean up kubebuilder alpha update downloaded binaries
7474
binaryPatterns := []string{
75-
binFromVersionPattern,
76-
binToVersionPattern,
75+
"/tmp/kubebuilder" + fromVersion + "-*",
76+
"/tmp/kubebuilder" + toVersion + "-*",
7777
}
7878

7979
for _, pattern := range binaryPatterns {
@@ -90,7 +90,9 @@ var _ = Describe("kubebuilder", func() {
9090
})
9191

9292
It("should update project from v4.5.2 to v4.6.0 preserving custom code", func() {
93-
By("creating mock project with kubebuilder v4.5.2")
93+
fromVersion = "v4.5.2"
94+
toVersion = toVersionConstant
95+
By("creating mock project with kubebuilder " + fromVersion)
9496
createMockProject(mockProjectDir, binFromVersionPath)
9597

9698
By("injecting custom code in API and controller")
@@ -99,18 +101,61 @@ var _ = Describe("kubebuilder", func() {
99101
By("initializing git repository and committing mock project")
100102
initializeGitRepo(mockProjectDir)
101103

102-
By("running alpha update from v4.5.2 to v4.6.0")
104+
By("running alpha update from " + fromVersion + " to " + toVersion)
103105
runAlphaUpdate(mockProjectDir, kbc)
104106

105107
By("validating custom code preservation")
106108
validateCustomCodePreservation(mockProjectDir)
107109
})
110+
111+
It("should stop on merge conflicts when --force is not used", func() {
112+
fromVersion = "v4.5.0"
113+
toVersion = toVersionConstant
114+
By("creating mock project with kubebuilder " + fromVersion)
115+
createMockProject(mockProjectDir, binFromVersionPath)
116+
117+
By("initializing git repository and committing mock project")
118+
initializeGitRepo(mockProjectDir)
119+
120+
By("running alpha update without --force flag")
121+
cmd := exec.Command(kbc.BinaryName, "alpha", "update",
122+
"--from-version", fromVersion, "--to-version", toVersion, "--from-branch", "main")
123+
cmd.Dir = mockProjectDir
124+
output, err := cmd.CombinedOutput()
125+
126+
By("expecting the command to fail due to merge conflicts")
127+
Expect(err).To(HaveOccurred())
128+
Expect(string(output)).To(ContainSubstring("Merge stopped due to conflicts"))
129+
Expect(string(output)).To(ContainSubstring("merge stopped due to conflicts"))
130+
})
131+
132+
It("should commit with conflict markers when --force is used", func() {
133+
fromVersion = "v4.5.0"
134+
toVersion = toVersionConstant
135+
By("creating mock project with kubebuilder " + fromVersion)
136+
createMockProject(mockProjectDir, binFromVersionPath)
137+
138+
By("initializing git repository and committing mock project")
139+
initializeGitRepo(mockProjectDir)
140+
141+
By("running alpha update with --force flag")
142+
cmd := exec.Command(kbc.BinaryName, "alpha", "update",
143+
"--from-version", fromVersion, "--to-version", toVersion, "--from-branch", "main", "--force")
144+
cmd.Dir = mockProjectDir
145+
output, err := cmd.CombinedOutput()
146+
147+
By("expecting the command to succeed despite conflicts")
148+
Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Alpha update with --force failed: %s", string(output)))
149+
150+
By("validating that conflict markers are present in the merged files")
151+
validateConflictMarkers(mockProjectDir)
152+
})
108153
})
109154
})
110155

111156
// downloadKubebuilder downloads the --from-version kubebuilder binary to a temporary directory
112157
func downloadKubebuilder() (string, error) {
113-
binaryDir, err := os.MkdirTemp("", "kubebuilder-v4.5.2-")
158+
binaryDir, err := os.MkdirTemp("", "kubebuilder-"+fromVersion+"-")
114159
if err != nil {
115160
return "", fmt.Errorf("failed to create binary directory: %w", err)
116161
}
@@ -283,3 +328,20 @@ func validateCustomCodePreservation(projectDir string) {
283328
Expect(string(content)).To(ContainSubstring("log.Info(\"Reconciling TestOperator\")"))
284329
Expect(string(content)).To(ContainSubstring("log.Info(\"TestOperator size\", \"size\", testOperator.Spec.Size)"))
285330
}
331+
332+
func validateConflictMarkers(projectDir string) {
333+
// Use git to check for conflict markers across the entire project
334+
cmd := exec.Command("git", "grep", "-l", "^<<<<<<<\\|^=======\\|^>>>>>>>", ".")
335+
cmd.Dir = projectDir
336+
output, err := cmd.CombinedOutput()
337+
338+
// If git grep finds conflict markers, it returns exit code 0
339+
// If no conflict markers are found, it returns exit code 1
340+
if err == nil {
341+
// Conflict markers found - this is expected
342+
Expect(output).ToNot(BeEmpty(), "Expected conflict markers in merged files")
343+
} else {
344+
// No conflict markers found
345+
Expect(false).To(BeTrue(), "Expected conflict markers in merged files but none were found")
346+
}
347+
}

0 commit comments

Comments
 (0)