Skip to content

Commit 5921329

Browse files
committed
feat: add e2e test to validate webhook conversion between versions
1 parent 72d4edb commit 5921329

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

docs/book/src/multiversion-tutorial/testdata/project/test/e2e/e2e_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"os"
2626
"os/exec"
2727
"path/filepath"
28+
"strings"
2829
"time"
2930

3031
. "github.com/onsi/ginkgo/v2"
@@ -97,6 +98,12 @@ var _ = Describe("Manager", Ordered, func() {
9798
// After each test, check for failures and collect logs, events,
9899
// and pod descriptions for debugging.
99100
AfterEach(func() {
101+
By("Cleaning up test CronJob resources")
102+
cmd := exec.Command("kubectl", "delete", "-f", "config/samples/batch_v1_cronjob.yaml", "-n", namespace, "--ignore-not-found=true")
103+
_, _ = utils.Run(cmd)
104+
cmd = exec.Command("kubectl", "delete", "-f", "config/samples/batch_v2_cronjob.yaml", "-n", namespace, "--ignore-not-found=true")
105+
_, _ = utils.Run(cmd)
106+
100107
specReport := CurrentSpecReport()
101108
if specReport.Failed() {
102109
By("Fetching controller manager pod logs")
@@ -331,6 +338,73 @@ var _ = Describe("Manager", Ordered, func() {
331338
// fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`,
332339
// strings.ToLower(<Kind>),
333340
// ))
341+
It("should successfully convert between v1 and v2 versions", func() {
342+
By("waiting for the webhook service to be ready")
343+
Eventually(func(g Gomega) {
344+
cmd := exec.Command("kubectl", "get", "endpoints", "-n", namespace,
345+
"-l", "control-plane=controller-manager",
346+
"-o", "jsonpath={.items[0].subsets[0].addresses[0].ip}")
347+
output, err := utils.Run(cmd)
348+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get webhook service endpoints")
349+
g.Expect(strings.TrimSpace(output)).NotTo(BeEmpty(), "Webhook endpoint should have an IP")
350+
}, time.Minute, time.Second).Should(Succeed())
351+
352+
By("creating a v1 CronJob with a specific schedule")
353+
cmd := exec.Command("kubectl", "apply", "-f", "config/samples/batch_v1_cronjob.yaml", "-n", namespace)
354+
_, err := utils.Run(cmd)
355+
Expect(err).NotTo(HaveOccurred(), "Failed to create v1 CronJob")
356+
357+
By("waiting for the v1 CronJob to be created")
358+
Eventually(func(g Gomega) {
359+
cmd := exec.Command("kubectl", "get", "cronjob", "cronjob-sample", "-n", namespace)
360+
_, err := utils.Run(cmd)
361+
g.Expect(err).NotTo(HaveOccurred(), "v1 CronJob should exist")
362+
}, time.Minute, time.Second).Should(Succeed())
363+
364+
By("fetching the v1 CronJob and verifying the schedule format")
365+
cmd = exec.Command("kubectl", "get", "cronjob.v1.batch.tutorial.kubebuilder.io", "cronjob-sample",
366+
"-n", namespace, "-o", "jsonpath={.spec.schedule}")
367+
v1Schedule, err := utils.Run(cmd)
368+
Expect(err).NotTo(HaveOccurred(), "Failed to get v1 CronJob schedule")
369+
Expect(strings.TrimSpace(v1Schedule)).To(Equal("*/1 * * * *"),
370+
"v1 schedule should be in cron format")
371+
372+
By("fetching the same CronJob as v2 and verifying the converted schedule")
373+
Eventually(func(g Gomega) {
374+
cmd := exec.Command("kubectl", "get", "cronjob.v2.batch.tutorial.kubebuilder.io", "cronjob-sample",
375+
"-n", namespace, "-o", "jsonpath={.spec.schedule.minute}")
376+
v2Minute, err := utils.Run(cmd)
377+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get v2 CronJob schedule")
378+
g.Expect(strings.TrimSpace(v2Minute)).To(Equal("*/1"),
379+
"v2 schedule.minute should be converted from v1 schedule")
380+
}, time.Minute, time.Second).Should(Succeed())
381+
382+
By("creating a v2 CronJob with structured schedule fields")
383+
cmd = exec.Command("kubectl", "apply", "-f", "config/samples/batch_v2_cronjob.yaml", "-n", namespace)
384+
_, err = utils.Run(cmd)
385+
Expect(err).NotTo(HaveOccurred(), "Failed to create v2 CronJob")
386+
387+
By("verifying the v2 CronJob has the correct structured schedule")
388+
Eventually(func(g Gomega) {
389+
cmd := exec.Command("kubectl", "get", "cronjob.v2.batch.tutorial.kubebuilder.io", "cronjob-sample",
390+
"-n", namespace, "-o", "jsonpath={.spec.schedule.minute}")
391+
v2Minute, err := utils.Run(cmd)
392+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get v2 CronJob schedule")
393+
g.Expect(strings.TrimSpace(v2Minute)).To(Equal("*/1"),
394+
"v2 CronJob should have minute field set")
395+
}, time.Minute, time.Second).Should(Succeed())
396+
397+
By("fetching the v2 CronJob as v1 and verifying schedule conversion")
398+
Eventually(func(g Gomega) {
399+
cmd := exec.Command("kubectl", "get", "cronjob.v1.batch.tutorial.kubebuilder.io", "cronjob-sample",
400+
"-n", namespace, "-o", "jsonpath={.spec.schedule}")
401+
v1Schedule, err := utils.Run(cmd)
402+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get converted v1 schedule")
403+
// When v2 only has minute field set, it converts to "*/1 * * * *"
404+
g.Expect(strings.TrimSpace(v1Schedule)).To(Equal("*/1 * * * *"),
405+
"v1 schedule should be converted from v2 structured schedule")
406+
}, time.Minute, time.Second).Should(Succeed())
407+
})
334408
})
335409
})
336410

hack/docs/internal/multiversion-tutorial/generate_multiversion.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ func (sp *Sample) UpdateTutorial() {
111111
sp.updateConversionFiles()
112112
sp.updateSampleV2()
113113
sp.updateMain()
114+
sp.updateE2EWebhookConversion()
114115
}
115116

116117
func (sp *Sample) updateCronjobV1DueForce() {
@@ -790,3 +791,113 @@ func (sp *Sample) CodeGen() {
790791
err = sp.ctx.EditHelmPlugin()
791792
hackutils.CheckError("Failed to enable helm plugin", err)
792793
}
794+
795+
const webhookConversionE2ETest = `
796+
It("should successfully convert between v1 and v2 versions", func() {
797+
By("waiting for the webhook service to be ready")
798+
Eventually(func(g Gomega) {
799+
cmd := exec.Command("kubectl", "get", "endpoints", "-n", namespace,
800+
"-l", "control-plane=controller-manager",
801+
"-o", "jsonpath={.items[0].subsets[0].addresses[0].ip}")
802+
output, err := utils.Run(cmd)
803+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get webhook service endpoints")
804+
g.Expect(strings.TrimSpace(output)).NotTo(BeEmpty(), "Webhook endpoint should have an IP")
805+
}, time.Minute, time.Second).Should(Succeed())
806+
807+
By("creating a v1 CronJob with a specific schedule")
808+
cmd := exec.Command("kubectl", "apply", "-f", "config/samples/batch_v1_cronjob.yaml", "-n", namespace)
809+
_, err := utils.Run(cmd)
810+
Expect(err).NotTo(HaveOccurred(), "Failed to create v1 CronJob")
811+
812+
By("waiting for the v1 CronJob to be created")
813+
Eventually(func(g Gomega) {
814+
cmd := exec.Command("kubectl", "get", "cronjob", "cronjob-sample", "-n", namespace)
815+
_, err := utils.Run(cmd)
816+
g.Expect(err).NotTo(HaveOccurred(), "v1 CronJob should exist")
817+
}, time.Minute, time.Second).Should(Succeed())
818+
819+
By("fetching the v1 CronJob and verifying the schedule format")
820+
cmd = exec.Command("kubectl", "get", "cronjob.v1.batch.tutorial.kubebuilder.io", "cronjob-sample",
821+
"-n", namespace, "-o", "jsonpath={.spec.schedule}")
822+
v1Schedule, err := utils.Run(cmd)
823+
Expect(err).NotTo(HaveOccurred(), "Failed to get v1 CronJob schedule")
824+
Expect(strings.TrimSpace(v1Schedule)).To(Equal("*/1 * * * *"),
825+
"v1 schedule should be in cron format")
826+
827+
By("fetching the same CronJob as v2 and verifying the converted schedule")
828+
Eventually(func(g Gomega) {
829+
cmd := exec.Command("kubectl", "get", "cronjob.v2.batch.tutorial.kubebuilder.io", "cronjob-sample",
830+
"-n", namespace, "-o", "jsonpath={.spec.schedule.minute}")
831+
v2Minute, err := utils.Run(cmd)
832+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get v2 CronJob schedule")
833+
g.Expect(strings.TrimSpace(v2Minute)).To(Equal("*/1"),
834+
"v2 schedule.minute should be converted from v1 schedule")
835+
}, time.Minute, time.Second).Should(Succeed())
836+
837+
By("creating a v2 CronJob with structured schedule fields")
838+
cmd = exec.Command("kubectl", "apply", "-f", "config/samples/batch_v2_cronjob.yaml", "-n", namespace)
839+
_, err = utils.Run(cmd)
840+
Expect(err).NotTo(HaveOccurred(), "Failed to create v2 CronJob")
841+
842+
By("verifying the v2 CronJob has the correct structured schedule")
843+
Eventually(func(g Gomega) {
844+
cmd := exec.Command("kubectl", "get", "cronjob.v2.batch.tutorial.kubebuilder.io", "cronjob-sample",
845+
"-n", namespace, "-o", "jsonpath={.spec.schedule.minute}")
846+
v2Minute, err := utils.Run(cmd)
847+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get v2 CronJob schedule")
848+
g.Expect(strings.TrimSpace(v2Minute)).To(Equal("*/1"),
849+
"v2 CronJob should have minute field set")
850+
}, time.Minute, time.Second).Should(Succeed())
851+
852+
By("fetching the v2 CronJob as v1 and verifying schedule conversion")
853+
Eventually(func(g Gomega) {
854+
cmd := exec.Command("kubectl", "get", "cronjob.v1.batch.tutorial.kubebuilder.io", "cronjob-sample",
855+
"-n", namespace, "-o", "jsonpath={.spec.schedule}")
856+
v1Schedule, err := utils.Run(cmd)
857+
g.Expect(err).NotTo(HaveOccurred(), "Failed to get converted v1 schedule")
858+
// When v2 only has minute field set, it converts to "*/1 * * * *"
859+
g.Expect(strings.TrimSpace(v1Schedule)).To(Equal("*/1 * * * *"),
860+
"v1 schedule should be converted from v2 structured schedule")
861+
}, time.Minute, time.Second).Should(Succeed())
862+
})`
863+
864+
func (sp *Sample) updateE2EWebhookConversion() {
865+
cronjobE2ETest := filepath.Join(sp.ctx.Dir, "test", "e2e", "e2e_test.go")
866+
867+
// Add strings import if not already present
868+
err := pluginutil.InsertCodeIfNotExist(cronjobE2ETest,
869+
` "os/exec"
870+
"path/filepath"
871+
"time"`,
872+
`
873+
"strings"`)
874+
hackutils.CheckError("adding strings import for e2e test", err)
875+
876+
// Add CronJob cleanup to the AfterEach block
877+
err = pluginutil.InsertCode(cronjobE2ETest,
878+
` // After each test, check for failures and collect logs, events,
879+
// and pod descriptions for debugging.
880+
AfterEach(func() {`,
881+
`
882+
By("Cleaning up test CronJob resources")
883+
cmd := exec.Command("kubectl", "delete", "-f", "config/samples/batch_v1_cronjob.yaml", "-n", namespace, "--ignore-not-found=true")
884+
_, _ = utils.Run(cmd)
885+
cmd = exec.Command("kubectl", "delete", "-f", "config/samples/batch_v2_cronjob.yaml", "-n", namespace, "--ignore-not-found=true")
886+
_, _ = utils.Run(cmd)
887+
`)
888+
hackutils.CheckError("adding CronJob cleanup to AfterEach", err)
889+
890+
// Add webhook conversion test after the existing TODO comment
891+
err = pluginutil.InsertCode(cronjobE2ETest,
892+
` // TODO: Customize the e2e test suite with scenarios specific to your project.
893+
// Consider applying sample/CR(s) and check their status and/or verifying
894+
// the reconciliation by using the metrics, i.e.:
895+
// metricsOutput, err := getMetricsOutput()
896+
// Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod")
897+
// Expect(metricsOutput).To(ContainSubstring(
898+
// fmt.Sprintf(`+"`"+`controller_runtime_reconcile_total{controller="%s",result="success"} 1`+"`"+`,
899+
// strings.ToLower(<Kind>),
900+
// ))`,
901+
webhookConversionE2ETest)
902+
hackutils.CheckError("adding webhook conversion e2e test", err)
903+
}

0 commit comments

Comments
 (0)