@@ -110,7 +110,9 @@ func (sp *Sample) UpdateTutorial() {
110110
111111 sp .updateConversionFiles ()
112112 sp .updateSampleV2 ()
113+ sp .updateSampleSecurityContext ()
113114 sp .updateMain ()
115+ sp .updateE2EWebhookConversion ()
114116}
115117
116118func (sp * Sample ) updateCronjobV1DueForce () {
@@ -300,6 +302,84 @@ func (sp *Sample) updateSampleV2() {
300302 hackutils .CheckError ("replacing TODO with sampleV2Code in batch_v2_cronjob.yaml" , err )
301303}
302304
305+ func (sp * Sample ) updateSampleSecurityContext () {
306+ // Update batch_v1_cronjob.yaml with security context
307+ pathV1 := filepath .Join (sp .ctx .Dir , "config/samples/batch_v1_cronjob.yaml" )
308+ oldTextV1 := ` template:
309+ spec:
310+ containers:
311+ - name: hello
312+ image: busybox
313+ args:
314+ - /bin/sh
315+ - -c
316+ - date; echo Hello from the Kubernetes cluster
317+ restartPolicy: OnFailure`
318+
319+ newTextV1 := ` template:
320+ spec:
321+ securityContext:
322+ runAsNonRoot: true
323+ runAsUser: 1000
324+ seccompProfile:
325+ type: RuntimeDefault
326+ containers:
327+ - name: hello
328+ image: busybox
329+ args:
330+ - /bin/sh
331+ - -c
332+ - date; echo Hello from the Kubernetes cluster
333+ securityContext:
334+ allowPrivilegeEscalation: false
335+ capabilities:
336+ drop:
337+ - ALL
338+ readOnlyRootFilesystem: false
339+ restartPolicy: OnFailure`
340+
341+ err := pluginutil .ReplaceInFile (pathV1 , oldTextV1 , newTextV1 )
342+ hackutils .CheckError ("updating security context in batch_v1_cronjob.yaml" , err )
343+
344+ // Update batch_v2_cronjob.yaml with security context
345+ pathV2 := filepath .Join (sp .ctx .Dir , "config/samples/batch_v2_cronjob.yaml" )
346+ oldTextV2 := ` template:
347+ spec:
348+ containers:
349+ - name: hello
350+ image: busybox
351+ args:
352+ - /bin/sh
353+ - -c
354+ - date; echo Hello from the Kubernetes cluster
355+ restartPolicy: OnFailure`
356+
357+ newTextV2 := ` template:
358+ spec:
359+ securityContext:
360+ runAsNonRoot: true
361+ runAsUser: 1000
362+ seccompProfile:
363+ type: RuntimeDefault
364+ containers:
365+ - name: hello
366+ image: busybox
367+ args:
368+ - /bin/sh
369+ - -c
370+ - date; echo Hello from the Kubernetes cluster
371+ securityContext:
372+ allowPrivilegeEscalation: false
373+ capabilities:
374+ drop:
375+ - ALL
376+ readOnlyRootFilesystem: false
377+ restartPolicy: OnFailure`
378+
379+ err = pluginutil .ReplaceInFile (pathV2 , oldTextV2 , newTextV2 )
380+ hackutils .CheckError ("updating security context in batch_v2_cronjob.yaml" , err )
381+ }
382+
303383func (sp * Sample ) updateConversionFiles () {
304384 path := filepath .Join (sp .ctx .Dir , "api/v1/cronjob_conversion.go" )
305385
@@ -790,3 +870,119 @@ func (sp *Sample) CodeGen() {
790870 err = sp .ctx .EditHelmPlugin ()
791871 hackutils .CheckError ("Failed to enable helm plugin" , err )
792872}
873+
874+ const webhookConversionE2ETest = `
875+ It("should successfully convert between v1 and v2 versions", func() {
876+ By("waiting for the webhook service to be ready")
877+ Eventually(func(g Gomega) {
878+ cmd := exec.Command("kubectl", "get", "endpoints", "-n", namespace,
879+ "-l", "control-plane=controller-manager",
880+ "-o", "jsonpath={.items[0].subsets[0].addresses[0].ip}")
881+ output, err := utils.Run(cmd)
882+ g.Expect(err).NotTo(HaveOccurred(), "Failed to get webhook service endpoints")
883+ g.Expect(strings.TrimSpace(output)).NotTo(BeEmpty(), "Webhook endpoint should have an IP")
884+ }, time.Minute, time.Second).Should(Succeed())
885+
886+ By("creating a v1 CronJob with a specific schedule")
887+ cmd := exec.Command("kubectl", "apply", "-f", "config/samples/batch_v1_cronjob.yaml", "-n", namespace)
888+ _, err := utils.Run(cmd)
889+ Expect(err).NotTo(HaveOccurred(), "Failed to create v1 CronJob")
890+
891+ By("waiting for the v1 CronJob to be created")
892+ Eventually(func(g Gomega) {
893+ cmd := exec.Command("kubectl", "get", "cronjob.batch.tutorial.kubebuilder.io", "cronjob-sample", "-n", namespace)
894+ output, err := utils.Run(cmd)
895+ if err != nil {
896+ // Log controller logs on failure for debugging
897+ logCmd := exec.Command("kubectl", "logs", "-l", "control-plane=controller-manager", "-n", namespace, "--tail=50")
898+ logs, _ := utils.Run(logCmd)
899+ _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs when CronJob not found:\n%s\n", logs)
900+ }
901+ g.Expect(err).NotTo(HaveOccurred(), "v1 CronJob should exist, output: "+output)
902+ }, time.Minute, time.Second).Should(Succeed())
903+
904+ By("fetching the v1 CronJob and verifying the schedule format")
905+ cmd = exec.Command("kubectl", "get", "cronjob.v1.batch.tutorial.kubebuilder.io", "cronjob-sample",
906+ "-n", namespace, "-o", "jsonpath={.spec.schedule}")
907+ v1Schedule, err := utils.Run(cmd)
908+ Expect(err).NotTo(HaveOccurred(), "Failed to get v1 CronJob schedule")
909+ Expect(strings.TrimSpace(v1Schedule)).To(Equal("*/1 * * * *"),
910+ "v1 schedule should be in cron format")
911+
912+ By("fetching the same CronJob as v2 and verifying the converted schedule")
913+ Eventually(func(g Gomega) {
914+ cmd := exec.Command("kubectl", "get", "cronjob.v2.batch.tutorial.kubebuilder.io", "cronjob-sample",
915+ "-n", namespace, "-o", "jsonpath={.spec.schedule.minute}")
916+ v2Minute, err := utils.Run(cmd)
917+ g.Expect(err).NotTo(HaveOccurred(), "Failed to get v2 CronJob schedule")
918+ g.Expect(strings.TrimSpace(v2Minute)).To(Equal("*/1"),
919+ "v2 schedule.minute should be converted from v1 schedule")
920+ }, time.Minute, time.Second).Should(Succeed())
921+
922+ By("creating a v2 CronJob with structured schedule fields")
923+ cmd = exec.Command("kubectl", "apply", "-f", "config/samples/batch_v2_cronjob.yaml", "-n", namespace)
924+ _, err = utils.Run(cmd)
925+ Expect(err).NotTo(HaveOccurred(), "Failed to create v2 CronJob")
926+
927+ By("verifying the v2 CronJob has the correct structured schedule")
928+ Eventually(func(g Gomega) {
929+ cmd := exec.Command("kubectl", "get", "cronjob.v2.batch.tutorial.kubebuilder.io", "cronjob-sample",
930+ "-n", namespace, "-o", "jsonpath={.spec.schedule.minute}")
931+ v2Minute, err := utils.Run(cmd)
932+ g.Expect(err).NotTo(HaveOccurred(), "Failed to get v2 CronJob schedule")
933+ g.Expect(strings.TrimSpace(v2Minute)).To(Equal("*/1"),
934+ "v2 CronJob should have minute field set")
935+ }, time.Minute, time.Second).Should(Succeed())
936+
937+ By("fetching the v2 CronJob as v1 and verifying schedule conversion")
938+ Eventually(func(g Gomega) {
939+ cmd := exec.Command("kubectl", "get", "cronjob.v1.batch.tutorial.kubebuilder.io", "cronjob-sample",
940+ "-n", namespace, "-o", "jsonpath={.spec.schedule}")
941+ v1Schedule, err := utils.Run(cmd)
942+ g.Expect(err).NotTo(HaveOccurred(), "Failed to get converted v1 schedule")
943+ // When v2 only has minute field set, it converts to "*/1 * * * *"
944+ g.Expect(strings.TrimSpace(v1Schedule)).To(Equal("*/1 * * * *"),
945+ "v1 schedule should be converted from v2 structured schedule")
946+ }, time.Minute, time.Second).Should(Succeed())
947+ })`
948+
949+ func (sp * Sample ) updateE2EWebhookConversion () {
950+ cronjobE2ETest := filepath .Join (sp .ctx .Dir , "test" , "e2e" , "e2e_test.go" )
951+
952+ // Add strings import if not already present
953+ err := pluginutil .InsertCodeIfNotExist (cronjobE2ETest ,
954+ ` "os/exec"
955+ "path/filepath"
956+ "time"` ,
957+ `
958+ "strings"` )
959+ hackutils .CheckError ("adding strings import for e2e test" , err )
960+
961+ // Add CronJob cleanup to the AfterEach block
962+ err = pluginutil .InsertCode (cronjobE2ETest ,
963+ ` // After each test, check for failures and collect logs, events,
964+ // and pod descriptions for debugging.
965+ AfterEach(func() {` ,
966+ `
967+ By("Cleaning up test CronJob resources")
968+ cmd := exec.Command("kubectl", "delete", "-f", "config/samples/batch_v1_cronjob.yaml", "-n", namespace, "--ignore-not-found=true")
969+ _, _ = utils.Run(cmd)
970+ cmd = exec.Command("kubectl", "delete", "-f", "config/samples/batch_v2_cronjob.yaml", "-n", namespace, "--ignore-not-found=true")
971+ _, _ = utils.Run(cmd)
972+ ` )
973+ hackutils .CheckError ("adding CronJob cleanup to AfterEach" , err )
974+
975+ // Add webhook conversion test after the existing TODO comment
976+ err = pluginutil .InsertCode (cronjobE2ETest ,
977+ ` // TODO: Customize the e2e test suite with scenarios specific to your project.
978+ // Consider applying sample/CR(s) and check their status and/or verifying
979+ // the reconciliation by using the metrics, i.e.:
980+ // metricsOutput, err := getMetricsOutput()
981+ // Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod")
982+ // Expect(metricsOutput).To(ContainSubstring(
983+ // fmt.Sprintf(` + "`" + `controller_runtime_reconcile_total{controller="%s",result="success"} 1` + "`" + `,
984+ // strings.ToLower(<Kind>),
985+ // ))` ,
986+ webhookConversionE2ETest )
987+ hackutils .CheckError ("adding webhook conversion e2e test" , err )
988+ }
0 commit comments