@@ -24,6 +24,7 @@ import (
2424 "os/exec"
2525 "path/filepath"
2626 "runtime"
27+ "strings"
2728
2829 . "github.com/onsi/ginkgo/v2"
2930 . "github.com/onsi/gomega"
@@ -33,12 +34,14 @@ import (
3334)
3435
3536const (
36- fromVersion = "v4.5.2"
37- toVersion = "v4.6.0"
37+ fromVersion = "v4.5.2"
38+ toVersion = "v4.6.0"
39+ toVersionWithConflict = "v4.7.0"
3840
3941 // Binary patterns for cleanup
40- binFromVersionPath = "/tmp/kubebuilder" + fromVersion + "-*"
41- pathBinToVersion = "/tmp/kubebuilder" + toVersion + "-*"
42+ binFromVersionPath = "/tmp/kubebuilder" + fromVersion + "-*"
43+ pathBinToVersion = "/tmp/kubebuilder" + toVersion + "-*"
44+ pathBinToVersionWithConflict = "/tmp/kubebuilder" + toVersionWithConflict + "-*"
4245
4346 controllerImplementation = `// Fetch the TestOperator instance
4447 testOperator := &webappv1.TestOperator{}
@@ -88,6 +91,7 @@ var _ = Describe("kubebuilder", func() {
8891 binaryPatterns := []string {
8992 pathBinFromVersion ,
9093 pathBinToVersion ,
94+ pathBinToVersionWithConflict ,
9195 }
9296
9397 for _ , pattern := range binaryPatterns {
@@ -116,33 +120,78 @@ var _ = Describe("kubebuilder", func() {
116120
117121 By ("validating custom code preservation" )
118122 validateCustomCodePreservation (mockProjectDir )
123+
124+ By ("validating no conflict markers are present" )
125+ validateConflictMarkers (mockProjectDir , false )
126+ })
127+
128+ It ("should update project from v4.5.2 to v4.7.0 with --force flag and create conflict markers" , func () {
129+ By ("creating mock project with kubebuilder v4.5.2" )
130+ createMockProject (mockProjectDir , pathBinFromVersion )
131+
132+ By ("adding custom code in API and controller" )
133+ updateAPI (mockProjectDir )
134+ updateController (mockProjectDir )
135+
136+ By ("initializing git repository and committing mock project" )
137+ initializeGitRepo (mockProjectDir )
138+
139+ By ("running alpha update from v4.5.2 to v4.7.0 with --force flag" )
140+ runAlphaUpdateWithForce (mockProjectDir , kbc )
141+
142+ By ("validating conflict markers are present" )
143+ validateConflictMarkers (mockProjectDir , true )
144+ })
145+
146+ It ("should stop when updating the project from v4.5.2 to v4.7.0 without the flag force " +
147+ "to allow manual conflicts resolution" , func () {
148+ By ("creating mock project with kubebuilder v4.5.2" )
149+ createMockProject (mockProjectDir , pathBinFromVersion )
150+
151+ By ("adding custom code in API and controller" )
152+ updateAPI (mockProjectDir )
153+ updateController (mockProjectDir )
154+
155+ By ("initializing git repository and committing mock project" )
156+ initializeGitRepo (mockProjectDir )
157+
158+ By ("running alpha update from v4.5.2 to v4.7.0 without --force flag" )
159+ runAlphaUpdateWithoutForce (mockProjectDir , kbc )
160+
161+ By ("validating merge stopped in conflict state for manual resolution" )
162+ validateConflictState (mockProjectDir )
119163 })
120164 })
121165})
122166
123167// downloadKubebuilder downloads the --from-version kubebuilder binary to a temporary directory
124168func downloadKubebuilder () (string , error ) {
125- binaryDir , err := os .MkdirTemp ("" , "kubebuilder-v4.5.2-" )
169+ return downloadKubebuilderVersion (fromVersion )
170+ }
171+
172+ // downloadKubebuilderVersion downloads a specific kubebuilder version binary to a temporary directory
173+ func downloadKubebuilderVersion (version string ) (string , error ) {
174+ binaryDir , err := os .MkdirTemp ("" , "kubebuilder-" + version + "-" )
126175 if err != nil {
127176 return "" , fmt .Errorf ("failed to create binary directory: %w" , err )
128177 }
129178
130179 url := fmt .Sprintf (
131180 "https://github.com/kubernetes-sigs/kubebuilder/releases/download/%s/kubebuilder_%s_%s" ,
132- fromVersion ,
181+ version ,
133182 runtime .GOOS ,
134183 runtime .GOARCH ,
135184 )
136185 binaryPath := filepath .Join (binaryDir , "kubebuilder" )
137186
138187 resp , err := http .Get (url )
139188 if err != nil {
140- return "" , fmt .Errorf ("failed to download kubebuilder %s: %w" , fromVersion , err )
189+ return "" , fmt .Errorf ("failed to download kubebuilder %s: %w" , version , err )
141190 }
142191 defer func () { _ = resp .Body .Close () }()
143192
144193 if resp .StatusCode != http .StatusOK {
145- return "" , fmt .Errorf ("failed to download kubebuilder %s: HTTP %d" , fromVersion , resp .StatusCode )
194+ return "" , fmt .Errorf ("failed to download kubebuilder %s: HTTP %d" , version , resp .StatusCode )
146195 }
147196
148197 file , err := os .Create (binaryPath )
@@ -165,13 +214,10 @@ func downloadKubebuilder() (string, error) {
165214}
166215
167216func createMockProject (projectDir , binaryPath string ) {
168- err := os .Chdir (projectDir )
169- Expect (err ).NotTo (HaveOccurred ())
170-
171217 By ("running kubebuilder init" )
172218 cmd := exec .Command (binaryPath , "init" , "--domain" , "example.com" , "--repo" , "github.com/example/test-operator" )
173219 cmd .Dir = projectDir
174- _ , err = cmd .CombinedOutput ()
220+ _ , err : = cmd .CombinedOutput ()
175221 Expect (err ).NotTo (HaveOccurred ())
176222
177223 By ("running kubebuilder create api" )
@@ -268,17 +314,30 @@ func initializeGitRepo(projectDir string) {
268314}
269315
270316func runAlphaUpdate (projectDir string , kbc * utils.TestContext ) {
271- err := os .Chdir (projectDir )
272- Expect (err ).NotTo (HaveOccurred ())
273-
274- // Use TestContext to run alpha update command
275317 cmd := exec .Command (kbc .BinaryName , "alpha" , "update" ,
276318 "--from-version" , fromVersion , "--to-version" , toVersion , "--from-branch" , "main" )
277319 cmd .Dir = projectDir
278320 output , err := cmd .CombinedOutput ()
279321 Expect (err ).NotTo (HaveOccurred (), fmt .Sprintf ("Alpha update failed: %s" , string (output )))
280322}
281323
324+ func runAlphaUpdateWithForce (projectDir string , kbc * utils.TestContext ) {
325+ cmd := exec .Command (kbc .BinaryName , "alpha" , "update" , "--from-version" , fromVersion ,
326+ "--to-version" , toVersionWithConflict , "--from-branch" , "main" , "--force" )
327+ cmd .Dir = projectDir
328+ output , err := cmd .CombinedOutput ()
329+ Expect (err ).NotTo (HaveOccurred (), fmt .Sprintf ("Alpha update with force failed: %s" , string (output )))
330+ }
331+
332+ func runAlphaUpdateWithoutForce (projectDir string , kbc * utils.TestContext ) {
333+ cmd := exec .Command (kbc .BinaryName , "alpha" , "update" ,
334+ "--from-version" , fromVersion , "--to-version" , toVersionWithConflict , "--from-branch" , "main" )
335+ cmd .Dir = projectDir
336+ output , err := cmd .CombinedOutput ()
337+ Expect (err ).To (HaveOccurred ())
338+ Expect (string (output )).To (ContainSubstring ("merge stopped due to conflicts" ))
339+ }
340+
282341func validateCustomCodePreservation (projectDir string ) {
283342 By ("validating the API" )
284343 typesFile := filepath .Join (projectDir , "api" , "v1" , "testoperator_types.go" )
@@ -295,3 +354,48 @@ func validateCustomCodePreservation(projectDir string) {
295354 Expect (err ).NotTo (HaveOccurred ())
296355 Expect (string (content )).To (ContainSubstring (controllerImplementation ))
297356}
357+
358+ func validateConflictMarkers (projectDir string , expectMarkers bool ) {
359+ if expectMarkers {
360+ By ("validating conflict markers are present" )
361+ } else {
362+ By ("validating no conflict markers are present" )
363+ }
364+
365+ filesToCheck := []string {
366+ filepath .Join (projectDir , "api" , "v1" , "testoperator_types.go" ),
367+ filepath .Join (projectDir , "internal" , "controller" , "testoperator_controller.go" ),
368+ }
369+
370+ conflictMarkersFound := false
371+ for _ , file := range filesToCheck {
372+ content , err := os .ReadFile (file )
373+ if err != nil {
374+ continue
375+ }
376+ fileContent := string (content )
377+ if strings .Contains (fileContent , "<<<<<<<" ) && strings .Contains (fileContent , "=======" ) &&
378+ strings .Contains (fileContent , ">>>>>>>" ) {
379+ conflictMarkersFound = true
380+ break
381+ }
382+ }
383+
384+ if expectMarkers {
385+ Expect (conflictMarkersFound ).To (BeTrue (), "Expected to find conflict markers in at least one file" )
386+ } else {
387+ Expect (conflictMarkersFound ).To (BeFalse (), "Expected no conflict markers, but found them in files" )
388+ }
389+ }
390+
391+ func validateConflictState (projectDir string ) {
392+ By ("validating merge stopped with conflicts requiring manual resolution" )
393+ cmd := exec .Command ("git" , "status" , "--porcelain" )
394+ cmd .Dir = projectDir
395+ output , err := cmd .CombinedOutput ()
396+ Expect (err ).NotTo (HaveOccurred ())
397+ statusOutput := strings .TrimSpace (string (output ))
398+ Expect (statusOutput ).NotTo (BeEmpty (), "Working directory should have uncommitted changes from merge conflict" )
399+
400+ validateConflictMarkers (projectDir , true )
401+ }
0 commit comments