@@ -25,6 +25,7 @@ import (
2525 "os"
2626 "os/exec"
2727 "path/filepath"
28+ "strings"
2829 "time"
2930
3031 . "github.com/onsi/ginkgo/v2"
@@ -319,7 +320,147 @@ var _ = Describe("Manager", Ordered, func() {
319320
320321 // +kubebuilder:scaffold:e2e-webhooks-checks
321322
322- // TODO: Customize the e2e test suite with scenarios specific to your project.
323+ It ("should validate webhook conversion between v1 and v2 CronJob versions" , func () {
324+ By ("creating a CronJob in v1 format" )
325+
326+ // Create a CronJob using v1 API with traditional cron schedule
327+ cronJobV1 := `
328+ apiVersion: batch.tutorial.kubebuilder.io/v1
329+ kind: CronJob
330+ metadata:
331+ name: test-cronjob-v1
332+ namespace: ` + namespace + `
333+ spec:
334+ schedule: "*/5 * * * *"
335+ jobTemplate:
336+ spec:
337+ template:
338+ spec:
339+ containers:
340+ - name: hello
341+ image: busybox:1.28
342+ imagePullPolicy: IfNotPresent
343+ command:
344+ - /bin/sh
345+ - -c
346+ - date; echo Hello from the Kubernetes cluster
347+ restartPolicy: OnFailure
348+ `
349+
350+ // Apply the v1 CronJob
351+ cmd := exec .Command ("kubectl" , "apply" , "-f" , "-" )
352+ cmd .Stdin = strings .NewReader (cronJobV1 )
353+ _ , err := utils .Run (cmd )
354+ Expect (err ).NotTo (HaveOccurred (), "Failed to create v1 CronJob" )
355+
356+ // Verify the CronJob was created and is accessible
357+ By ("verifying the v1 CronJob was created successfully" )
358+ Eventually (func (g Gomega ) {
359+ cmd := exec .Command ("kubectl" , "get" , "cronjob.batch.tutorial.kubebuilder.io" , "test-cronjob-v1" , "-n" , namespace , "-o" , "json" )
360+ output , err := utils .Run (cmd )
361+ g .Expect (err ).NotTo (HaveOccurred ())
362+
363+ var cronJobData map [string ]interface {}
364+ err = json .Unmarshal ([]byte (output ), & cronJobData )
365+ g .Expect (err ).NotTo (HaveOccurred ())
366+
367+ // Verify this is indeed a resource with string schedule
368+ spec , ok := cronJobData ["spec" ].(map [string ]interface {})
369+ g .Expect (ok ).To (BeTrue (), "Failed to get spec from CronJob" )
370+
371+ schedule , ok := spec ["schedule" ].(string )
372+ g .Expect (ok ).To (BeTrue (), "Schedule should be a string in stored format" )
373+ g .Expect (schedule ).To (Equal ("*/5 * * * *" ), "Schedule should match the applied format" )
374+ }).Should (Succeed ())
375+
376+ By ("creating a CronJob in v2 format to test structured schedule" )
377+
378+ // Create a CronJob using v2 API with structured schedule
379+ cronJobV2 := `
380+ apiVersion: batch.tutorial.kubebuilder.io/v2
381+ kind: CronJob
382+ metadata:
383+ name: test-cronjob-v2
384+ namespace: ` + namespace + `
385+ spec:
386+ schedule:
387+ minute: "0"
388+ hour: "6"
389+ dayOfMonth: "1"
390+ jobTemplate:
391+ spec:
392+ template:
393+ spec:
394+ containers:
395+ - name: hello
396+ image: busybox:1.28
397+ imagePullPolicy: IfNotPresent
398+ command:
399+ - /bin/sh
400+ - -c
401+ - date; echo Hello from v2 CronJob
402+ restartPolicy: OnFailure
403+ `
404+
405+ // Apply the v2 CronJob
406+ cmd = exec .Command ("kubectl" , "apply" , "-f" , "-" )
407+ cmd .Stdin = strings .NewReader (cronJobV2 )
408+ _ , err = utils .Run (cmd )
409+ Expect (err ).NotTo (HaveOccurred (), "Failed to create v2 CronJob" )
410+
411+ // Verify the v2 CronJob was created and stored correctly
412+ By ("verifying the v2 CronJob was created successfully" )
413+ Eventually (func (g Gomega ) {
414+ cmd := exec .Command ("kubectl" , "get" , "cronjob.batch.tutorial.kubebuilder.io" , "test-cronjob-v2" , "-n" , namespace , "-o" , "json" )
415+ output , err := utils .Run (cmd )
416+ g .Expect (err ).NotTo (HaveOccurred ())
417+
418+ var cronJobData map [string ]interface {}
419+ err = json .Unmarshal ([]byte (output ), & cronJobData )
420+ g .Expect (err ).NotTo (HaveOccurred ())
421+
422+ spec , ok := cronJobData ["spec" ].(map [string ]interface {})
423+ g .Expect (ok ).To (BeTrue (), "Failed to get spec from CronJob" )
424+
425+ // The schedule should be stored as string in the hub version (v1) due to conversion
426+ schedule , ok := spec ["schedule" ].(string )
427+ g .Expect (ok ).To (BeTrue (), "Schedule should be stored as string due to hub conversion" )
428+ // This should be the converted cron string: minute=0, hour=6, dayOfMonth=1, month=*, dayOfWeek=*
429+ g .Expect (schedule ).To (Equal ("0 6 1 * *" ), "Schedule should be converted to cron format" )
430+ }).Should (Succeed ())
431+
432+ By ("testing conversion by applying patch to trigger webhook conversion" )
433+ // Apply a patch to trigger conversion webhook calls
434+ patchData := `{"metadata":{"labels":{"test":"conversion"}}}`
435+ cmd = exec .Command ("kubectl" , "patch" , "cronjob.batch.tutorial.kubebuilder.io" , "test-cronjob-v1" , "-n" , namespace ,
436+ "--type" , "merge" , "-p" , patchData )
437+ _ , err = utils .Run (cmd )
438+ Expect (err ).NotTo (HaveOccurred (), "Failed to patch v1 CronJob" )
439+
440+ cmd = exec .Command ("kubectl" , "patch" , "cronjob.batch.tutorial.kubebuilder.io" , "test-cronjob-v2" , "-n" , namespace ,
441+ "--type" , "merge" , "-p" , patchData )
442+ _ , err = utils .Run (cmd )
443+ Expect (err ).NotTo (HaveOccurred (), "Failed to patch v2 CronJob" )
444+
445+ By ("verifying conversion webhook was called by checking controller logs" )
446+ // Check the controller logs for conversion messages
447+ Eventually (func (g Gomega ) {
448+ cmd := exec .Command ("kubectl" , "logs" , controllerPodName , "-n" , namespace )
449+ logs , err := utils .Run (cmd )
450+ g .Expect (err ).NotTo (HaveOccurred ())
451+
452+ // Look for conversion log messages that we added in the conversion functions
453+ g .Expect (logs ).To (ContainSubstring ("ConvertTo: Converting CronJob from Spoke version v2 to Hub version v1" ),
454+ "Should see v2->v1 conversion in logs" )
455+ g .Expect (logs ).To (ContainSubstring ("ConvertFrom: Converting CronJob from Hub version v1 to Spoke version v2" ),
456+ "Should see v1->v2 conversion in logs" )
457+ }, 30 * time .Second , 2 * time .Second ).Should (Succeed ())
458+
459+ // Cleanup the test CronJobs
460+ By ("cleaning up test CronJobs" )
461+ cmd = exec .Command ("kubectl" , "delete" , "cronjob.batch.tutorial.kubebuilder.io" , "test-cronjob-v1" , "test-cronjob-v2" , "-n" , namespace , "--ignore-not-found" )
462+ _ , _ = utils .Run (cmd ) // Use _ for cleanup as it might fail if resources don't exist
463+ }) // TODO: Customize the e2e test suite with scenarios specific to your project.
323464 // Consider applying sample/CR(s) and check their status and/or verifying
324465 // the reconciliation by using the metrics, i.e.:
325466 // metricsOutput := getMetricsOutput()
0 commit comments