diff --git a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go b/docs/book/src/cronjob-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go index 30f97db255f..5f32c3ab478 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/docs/book/src/cronjob-tutorial/testdata/project/cmd/main.go b/docs/book/src/cronjob-tutorial/testdata/project/cmd/main.go index ab7399bf0c4..8204e955c1d 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/cmd/main.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/cmd/main.go @@ -38,6 +38,7 @@ import ( batchv1 "tutorial.kubebuilder.io/project/api/v1" "tutorial.kubebuilder.io/project/internal/controller" + webhooksv1 "tutorial.kubebuilder.io/project/internal/webhooks/v1" // +kubebuilder:scaffold:imports ) @@ -183,7 +184,7 @@ func main() { */ // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&batchv1.CronJob{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksv1.SetupCronJobWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CronJob") os.Exit(1) } diff --git a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook.go similarity index 81% rename from docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go rename to docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook.go index db59768a51a..cecc61a6ff8 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook.go @@ -31,6 +31,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + batchv1 "tutorial.kubebuilder.io/project/api/v1" ) // +kubebuilder:docs-gen:collapse=Go imports @@ -45,13 +47,12 @@ var cronjoblog = logf.Log.WithName("cronjob-resource") Then, we set up the webhook with the manager. */ -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager. +func SetupCronJobWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&batchv1.CronJob{}). WithValidator(&CronJobCustomValidator{}). WithDefaulter(&CronJobCustomDefaulter{ - DefaultConcurrencyPolicy: AllowConcurrent, + DefaultConcurrencyPolicy: batchv1.AllowConcurrent, DefaultSuspend: false, DefaultSuccessfulJobsHistoryLimit: 3, DefaultFailedJobsHistoryLimit: 1, @@ -81,7 +82,7 @@ This marker is responsible for generating a mutation webhook manifest. type CronJobCustomDefaulter struct { // Default values for various CronJob fields - DefaultConcurrencyPolicy ConcurrencyPolicy + DefaultConcurrencyPolicy batchv1.ConcurrencyPolicy DefaultSuspend bool DefaultSuccessfulJobsHistoryLimit int32 DefaultFailedJobsHistoryLimit int32 @@ -98,32 +99,34 @@ The `Default`method is expected to mutate the receiver, setting the defaults. // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob. func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv1.CronJob) + if !ok { return fmt.Errorf("expected an CronJob object but got %T", obj) } cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName()) // Set default values - cronjob.Default() - + d.applyDefaults(cronjob) return nil } -func (r *CronJob) Default() { - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent +// applyDefaults applies default values to CronJob fields. +func (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv1.CronJob) { + if cronJob.Spec.ConcurrencyPolicy == "" { + cronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if cronJob.Spec.Suspend == nil { + cronJob.Spec.Suspend = new(bool) + *cronJob.Spec.Suspend = d.DefaultSuspend } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if cronJob.Spec.SuccessfulJobsHistoryLimit == nil { + cronJob.Spec.SuccessfulJobsHistoryLimit = new(int32) + *cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if cronJob.Spec.FailedJobsHistoryLimit == nil { + cronJob.Spec.FailedJobsHistoryLimit = new(int32) + *cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit } } @@ -168,29 +171,29 @@ var _ webhook.CustomValidator = &CronJobCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv1.CronJob) if !ok { return nil, fmt.Errorf("expected a CronJob object but got %T", obj) } cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName()) - return nil, cronjob.validateCronJob() + return nil, validateCronJob(cronjob) } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - cronjob, ok := newObj.(*CronJob) + cronjob, ok := newObj.(*batchv1.CronJob) if !ok { - return nil, fmt.Errorf("expected a CronJob object but got %T", newObj) + return nil, fmt.Errorf("expected a CronJob object for the newObj but got got %T", newObj) } cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName()) - return nil, cronjob.validateCronJob() + return nil, validateCronJob(cronjob) } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv1.CronJob) if !ok { return nil, fmt.Errorf("expected a CronJob object but got %T", obj) } @@ -205,12 +208,13 @@ func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime We validate the name and the spec of the CronJob. */ -func (r *CronJob) validateCronJob() error { +// validateCronJob validates the fields of a CronJob object. +func validateCronJob(cronjob *batchv1.CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := validateCronJobName(cronjob); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := validateCronJobSpec(cronjob); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { @@ -219,7 +223,7 @@ func (r *CronJob) validateCronJob() error { return apierrors.NewInvalid( schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + cronjob.Name, allErrs) } /* @@ -232,11 +236,11 @@ declaring validation by running `controller-gen crd -w`, or [here](/reference/markers/crd-validation.md). */ -func (r *CronJob) validateCronJobSpec() *field.Error { +func validateCronJobSpec(cronjob *batchv1.CronJob) *field.Error { // The field helpers from the kubernetes API machinery help us return nicely // structured validation errors. return validateScheduleFormat( - r.Spec.Schedule, + cronjob.Spec.Schedule, field.NewPath("spec").Child("schedule")) } @@ -261,15 +265,15 @@ the apimachinery repo, so we can't declaratively validate it using the validation schema. */ -func (r *CronJob) validateCronJobName() *field.Error { - if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { +func validateCronJobName(cronjob *batchv1.CronJob) *field.Error { + if len(cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { // The job name length is 63 characters like all Kubernetes objects // (which must fit in a DNS subdomain). The cronjob controller appends // a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating // a job. The job name length limit is 63 characters. Therefore cronjob // names must have length <= 63-11=52. If we don't validate this here, // then job creation will fail later. - return field.Invalid(field.NewPath("metadata").Child("name"), r.ObjectMeta.Name, "must be no more than 52 characters") + return field.Invalid(field.NewPath("metadata").Child("name"), cronjob.ObjectMeta.Name, "must be no more than 52 characters") } return nil } diff --git a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook_test.go b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook_test.go similarity index 84% rename from docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook_test.go rename to docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook_test.go index bb82eb2cc3d..0f8bfd0ddee 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook_test.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook_test.go @@ -19,21 +19,24 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + batchv1 "tutorial.kubebuilder.io/project/api/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("CronJob Webhook", func() { var ( - obj *CronJob - oldObj *CronJob + obj = &batchv1.CronJob{} + oldObj = &batchv1.CronJob{} validator CronJobCustomValidator + defaulter CronJobCustomDefaulter ) BeforeEach(func() { - obj = &CronJob{ - Spec: CronJobSpec{ + obj = &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ Schedule: "*/5 * * * *", - ConcurrencyPolicy: AllowConcurrent, + ConcurrencyPolicy: batchv1.AllowConcurrent, SuccessfulJobsHistoryLimit: new(int32), FailedJobsHistoryLimit: new(int32), }, @@ -41,10 +44,10 @@ var _ = Describe("CronJob Webhook", func() { *obj.Spec.SuccessfulJobsHistoryLimit = 3 *obj.Spec.FailedJobsHistoryLimit = 1 - oldObj = &CronJob{ - Spec: CronJobSpec{ + oldObj = &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ Schedule: "*/5 * * * *", - ConcurrencyPolicy: AllowConcurrent, + ConcurrencyPolicy: batchv1.AllowConcurrent, SuccessfulJobsHistoryLimit: new(int32), FailedJobsHistoryLimit: new(int32), }, @@ -53,6 +56,12 @@ var _ = Describe("CronJob Webhook", func() { *oldObj.Spec.FailedJobsHistoryLimit = 1 validator = CronJobCustomValidator{} + defaulter = CronJobCustomDefaulter{ + DefaultConcurrencyPolicy: batchv1.AllowConcurrent, + DefaultSuspend: false, + DefaultSuccessfulJobsHistoryLimit: 3, + DefaultFailedJobsHistoryLimit: 1, + } Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") @@ -71,10 +80,10 @@ var _ = Describe("CronJob Webhook", func() { obj.Spec.FailedJobsHistoryLimit = nil // This should default to 1 By("calling the Default method to apply defaults") - obj.Default() + defaulter.Default(ctx, obj) By("checking that the default values are set") - Expect(obj.Spec.ConcurrencyPolicy).To(Equal(AllowConcurrent), "Expected ConcurrencyPolicy to default to AllowConcurrent") + Expect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.AllowConcurrent), "Expected ConcurrencyPolicy to default to AllowConcurrent") Expect(*obj.Spec.Suspend).To(BeFalse(), "Expected Suspend to default to false") Expect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(3)), "Expected SuccessfulJobsHistoryLimit to default to 3") Expect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(1)), "Expected FailedJobsHistoryLimit to default to 1") @@ -82,7 +91,7 @@ var _ = Describe("CronJob Webhook", func() { It("Should not overwrite fields that are already set", func() { By("setting fields that would normally get a default") - obj.Spec.ConcurrencyPolicy = ForbidConcurrent + obj.Spec.ConcurrencyPolicy = batchv1.ForbidConcurrent obj.Spec.Suspend = new(bool) *obj.Spec.Suspend = true obj.Spec.SuccessfulJobsHistoryLimit = new(int32) @@ -91,10 +100,10 @@ var _ = Describe("CronJob Webhook", func() { *obj.Spec.FailedJobsHistoryLimit = 2 By("calling the Default method to apply defaults") - obj.Default() + defaulter.Default(ctx, obj) By("checking that the fields were not overwritten") - Expect(obj.Spec.ConcurrencyPolicy).To(Equal(ForbidConcurrent), "Expected ConcurrencyPolicy to retain its set value") + Expect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.ForbidConcurrent), "Expected ConcurrencyPolicy to retain its set value") Expect(*obj.Spec.Suspend).To(BeTrue(), "Expected Suspend to retain its set value") Expect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(5)), "Expected SuccessfulJobsHistoryLimit to retain its set value") Expect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(2)), "Expected FailedJobsHistoryLimit to retain its set value") diff --git a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/webhook_suite_test.go b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/webhook_suite_test.go similarity index 98% rename from docs/book/src/cronjob-tutorial/testdata/project/api/v1/webhook_suite_test.go rename to docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/webhook_suite_test.go index e10bdd75482..1a3b6b87b1f 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/webhook_suite_test.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/internal/webhooks/v1/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,7 +115,7 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&CronJob{}).SetupWebhookWithManager(mgr) + err = SetupCronJobWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go b/docs/book/src/multiversion-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go index 30f97db255f..5f32c3ab478 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/zz_generated.deepcopy.go b/docs/book/src/multiversion-tutorial/testdata/project/api/v2/zz_generated.deepcopy.go index 384a9df866c..5ea5cddb2d2 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/zz_generated.deepcopy.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/api/v2/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v2 import ( "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/docs/book/src/multiversion-tutorial/testdata/project/cmd/main.go b/docs/book/src/multiversion-tutorial/testdata/project/cmd/main.go index 65f0a6405bc..8e489b87c52 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/cmd/main.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/cmd/main.go @@ -40,6 +40,8 @@ import ( batchv1 "tutorial.kubebuilder.io/project/api/v1" batchv2 "tutorial.kubebuilder.io/project/api/v2" "tutorial.kubebuilder.io/project/internal/controller" + webhooksv1 "tutorial.kubebuilder.io/project/internal/webhooks/v1" + webhooksv2 "tutorial.kubebuilder.io/project/internal/webhooks/v2" // +kubebuilder:scaffold:imports ) @@ -175,14 +177,14 @@ func main() { */ // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&batchv1.CronJob{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksv1.SetupCronJobWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CronJob") os.Exit(1) } } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&batchv2.CronJob{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksv2.SetupCronJobWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "CronJob") os.Exit(1) } diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_conversion.go similarity index 70% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_conversion.go index 36485072ec8..6d7d8cab598 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_conversion.go @@ -22,5 +22,18 @@ method called `Hub()` to serve as a We could also just put this inline in our `cronjob_types.go` file. */ +import ( + batchv1 "tutorial.kubebuilder.io/project/api/v1" // Import CronJob from API package +) + +/* +ConversionCronJob wraps the API's CronJob and implements the Hub interface for conversion. +*/ + +// ConversionCronJob wraps the existing CronJob type from the API. +type ConversionCronJob struct { + batchv1.CronJob // Embed the original CronJob type +} + // Hub marks this type as a conversion hub. -func (*CronJob) Hub() {} +func (*ConversionCronJob) Hub() {} diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_webhook.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook.go similarity index 81% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_webhook.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook.go index 98bdafe18c8..2d1e194f95b 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_webhook.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook.go @@ -31,6 +31,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + batchv1 "tutorial.kubebuilder.io/project/api/v1" ) // +kubebuilder:docs-gen:collapse=Go imports @@ -49,13 +51,12 @@ types implement the interfaces, a conversion webhook will be registered. */ -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager. +func SetupCronJobWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&batchv1.CronJob{}). WithValidator(&CronJobCustomValidator{}). WithDefaulter(&CronJobCustomDefaulter{ - DefaultConcurrencyPolicy: AllowConcurrent, + DefaultConcurrencyPolicy: batchv1.AllowConcurrent, DefaultSuspend: false, DefaultSuccessfulJobsHistoryLimit: 3, DefaultFailedJobsHistoryLimit: 1, @@ -85,7 +86,7 @@ This marker is responsible for generating a mutation webhook manifest. type CronJobCustomDefaulter struct { // Default values for various CronJob fields - DefaultConcurrencyPolicy ConcurrencyPolicy + DefaultConcurrencyPolicy batchv1.ConcurrencyPolicy DefaultSuspend bool DefaultSuccessfulJobsHistoryLimit int32 DefaultFailedJobsHistoryLimit int32 @@ -102,32 +103,34 @@ The `Default`method is expected to mutate the receiver, setting the defaults. // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob. func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv1.CronJob) + if !ok { return fmt.Errorf("expected an CronJob object but got %T", obj) } cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName()) // Set default values - cronjob.Default() - + d.applyDefaults(cronjob) return nil } -func (r *CronJob) Default() { - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent +// applyDefaults applies default values to CronJob fields. +func (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv1.CronJob) { + if cronJob.Spec.ConcurrencyPolicy == "" { + cronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if cronJob.Spec.Suspend == nil { + cronJob.Spec.Suspend = new(bool) + *cronJob.Spec.Suspend = d.DefaultSuspend } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if cronJob.Spec.SuccessfulJobsHistoryLimit == nil { + cronJob.Spec.SuccessfulJobsHistoryLimit = new(int32) + *cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if cronJob.Spec.FailedJobsHistoryLimit == nil { + cronJob.Spec.FailedJobsHistoryLimit = new(int32) + *cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit } } @@ -172,29 +175,29 @@ var _ webhook.CustomValidator = &CronJobCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv1.CronJob) if !ok { return nil, fmt.Errorf("expected a CronJob object but got %T", obj) } cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName()) - return nil, cronjob.validateCronJob() + return nil, validateCronJob(cronjob) } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - cronjob, ok := newObj.(*CronJob) + cronjob, ok := newObj.(*batchv1.CronJob) if !ok { - return nil, fmt.Errorf("expected a CronJob object but got %T", newObj) + return nil, fmt.Errorf("expected a CronJob object for the newObj but got got %T", newObj) } cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName()) - return nil, cronjob.validateCronJob() + return nil, validateCronJob(cronjob) } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv1.CronJob) if !ok { return nil, fmt.Errorf("expected a CronJob object but got %T", obj) } @@ -209,12 +212,13 @@ func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime We validate the name and the spec of the CronJob. */ -func (r *CronJob) validateCronJob() error { +// validateCronJob validates the fields of a CronJob object. +func validateCronJob(cronjob *batchv1.CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := validateCronJobName(cronjob); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := validateCronJobSpec(cronjob); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { @@ -223,7 +227,7 @@ func (r *CronJob) validateCronJob() error { return apierrors.NewInvalid( schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + cronjob.Name, allErrs) } /* @@ -236,11 +240,11 @@ declaring validation by running `controller-gen crd -w`, or [here](/reference/markers/crd-validation.md). */ -func (r *CronJob) validateCronJobSpec() *field.Error { +func validateCronJobSpec(cronjob *batchv1.CronJob) *field.Error { // The field helpers from the kubernetes API machinery help us return nicely // structured validation errors. return validateScheduleFormat( - r.Spec.Schedule, + cronjob.Spec.Schedule, field.NewPath("spec").Child("schedule")) } @@ -265,15 +269,15 @@ the apimachinery repo, so we can't declaratively validate it using the validation schema. */ -func (r *CronJob) validateCronJobName() *field.Error { - if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { +func validateCronJobName(cronjob *batchv1.CronJob) *field.Error { + if len(cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { // The job name length is 63 characters like all Kubernetes objects // (which must fit in a DNS subdomain). The cronjob controller appends // a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating // a job. The job name length limit is 63 characters. Therefore cronjob // names must have length <= 63-11=52. If we don't validate this here, // then job creation will fail later. - return field.Invalid(field.NewPath("metadata").Child("name"), r.ObjectMeta.Name, "must be no more than 52 characters") + return field.Invalid(field.NewPath("metadata").Child("name"), cronjob.ObjectMeta.Name, "must be no more than 52 characters") } return nil } diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_webhook_test.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook_test.go similarity index 84% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_webhook_test.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook_test.go index bb82eb2cc3d..0f8bfd0ddee 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_webhook_test.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/cronjob_webhook_test.go @@ -19,21 +19,24 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + batchv1 "tutorial.kubebuilder.io/project/api/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("CronJob Webhook", func() { var ( - obj *CronJob - oldObj *CronJob + obj = &batchv1.CronJob{} + oldObj = &batchv1.CronJob{} validator CronJobCustomValidator + defaulter CronJobCustomDefaulter ) BeforeEach(func() { - obj = &CronJob{ - Spec: CronJobSpec{ + obj = &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ Schedule: "*/5 * * * *", - ConcurrencyPolicy: AllowConcurrent, + ConcurrencyPolicy: batchv1.AllowConcurrent, SuccessfulJobsHistoryLimit: new(int32), FailedJobsHistoryLimit: new(int32), }, @@ -41,10 +44,10 @@ var _ = Describe("CronJob Webhook", func() { *obj.Spec.SuccessfulJobsHistoryLimit = 3 *obj.Spec.FailedJobsHistoryLimit = 1 - oldObj = &CronJob{ - Spec: CronJobSpec{ + oldObj = &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ Schedule: "*/5 * * * *", - ConcurrencyPolicy: AllowConcurrent, + ConcurrencyPolicy: batchv1.AllowConcurrent, SuccessfulJobsHistoryLimit: new(int32), FailedJobsHistoryLimit: new(int32), }, @@ -53,6 +56,12 @@ var _ = Describe("CronJob Webhook", func() { *oldObj.Spec.FailedJobsHistoryLimit = 1 validator = CronJobCustomValidator{} + defaulter = CronJobCustomDefaulter{ + DefaultConcurrencyPolicy: batchv1.AllowConcurrent, + DefaultSuspend: false, + DefaultSuccessfulJobsHistoryLimit: 3, + DefaultFailedJobsHistoryLimit: 1, + } Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") @@ -71,10 +80,10 @@ var _ = Describe("CronJob Webhook", func() { obj.Spec.FailedJobsHistoryLimit = nil // This should default to 1 By("calling the Default method to apply defaults") - obj.Default() + defaulter.Default(ctx, obj) By("checking that the default values are set") - Expect(obj.Spec.ConcurrencyPolicy).To(Equal(AllowConcurrent), "Expected ConcurrencyPolicy to default to AllowConcurrent") + Expect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.AllowConcurrent), "Expected ConcurrencyPolicy to default to AllowConcurrent") Expect(*obj.Spec.Suspend).To(BeFalse(), "Expected Suspend to default to false") Expect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(3)), "Expected SuccessfulJobsHistoryLimit to default to 3") Expect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(1)), "Expected FailedJobsHistoryLimit to default to 1") @@ -82,7 +91,7 @@ var _ = Describe("CronJob Webhook", func() { It("Should not overwrite fields that are already set", func() { By("setting fields that would normally get a default") - obj.Spec.ConcurrencyPolicy = ForbidConcurrent + obj.Spec.ConcurrencyPolicy = batchv1.ForbidConcurrent obj.Spec.Suspend = new(bool) *obj.Spec.Suspend = true obj.Spec.SuccessfulJobsHistoryLimit = new(int32) @@ -91,10 +100,10 @@ var _ = Describe("CronJob Webhook", func() { *obj.Spec.FailedJobsHistoryLimit = 2 By("calling the Default method to apply defaults") - obj.Default() + defaulter.Default(ctx, obj) By("checking that the fields were not overwritten") - Expect(obj.Spec.ConcurrencyPolicy).To(Equal(ForbidConcurrent), "Expected ConcurrencyPolicy to retain its set value") + Expect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.ForbidConcurrent), "Expected ConcurrencyPolicy to retain its set value") Expect(*obj.Spec.Suspend).To(BeTrue(), "Expected Suspend to retain its set value") Expect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(5)), "Expected SuccessfulJobsHistoryLimit to retain its set value") Expect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(2)), "Expected FailedJobsHistoryLimit to retain its set value") diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/webhook_suite_test.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/webhook_suite_test.go similarity index 98% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v1/webhook_suite_test.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/webhook_suite_test.go index e10bdd75482..1a3b6b87b1f 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v1/webhook_suite_test.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v1/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,7 +115,7 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&CronJob{}).SetupWebhookWithManager(mgr) + err = SetupCronJobWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_conversion.go similarity index 73% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_conversion.go index ac971d8264a..4ce68efd515 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_conversion.go @@ -23,11 +23,12 @@ standard packages. */ import ( "fmt" - "strings" - "sigs.k8s.io/controller-runtime/pkg/conversion" + "strings" - v1 "tutorial.kubebuilder.io/project/api/v1" + batchv1 "tutorial.kubebuilder.io/project/api/v1" // Import the v1 CronJob type from the API + batchv2 "tutorial.kubebuilder.io/project/api/v2" // Import the v2 CronJob type from the API + v1 "tutorial.kubebuilder.io/project/internal/webhooks/v1" // Import the hub version ) // +kubebuilder:docs-gen:collapse=Imports @@ -37,16 +38,25 @@ Our "spoke" versions need to implement the [`Convertible`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible) interface. Namely, they'll need `ConvertTo()` and `ConvertFrom()` methods to convert to/from the hub version. + +Wrap the v2 CronJob from the API to allow conversion to/from the hub (v1). */ /* ConvertTo is expected to modify its argument to contain the converted object. Most of the conversion is straightforward copying, except for converting our changed field. */ -// ConvertTo converts this CronJob to the Hub version (v1). -func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { - dst := dstRaw.(*v1.CronJob) +// ConversionCronJobV2 wraps the API's CronJob and implements the Convertible interface. +type ConversionCronJobV2 struct { + batchv2.CronJob // Embed the original CronJob type +} + +// ConvertTo converts this v2 CronJob to the Hub version (v1.ConversionCronJob). +func (src *ConversionCronJobV2) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*v1.ConversionCronJob) // Use the wrapped ConversionCronJob from v1 + + // Handle the schedule conversion sched := src.Spec.Schedule scheduleParts := []string{"*", "*", "*", "*", "*"} if sched.Minute != nil { @@ -70,11 +80,12 @@ func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { The rest of the conversion is pretty rote. */ // ObjectMeta + dst.ObjectMeta = src.ObjectMeta // Spec dst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds - dst.Spec.ConcurrencyPolicy = v1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) + dst.Spec.ConcurrencyPolicy = batchv1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) // Correct reference dst.Spec.Suspend = src.Spec.Suspend dst.Spec.JobTemplate = src.Spec.JobTemplate dst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit @@ -93,19 +104,19 @@ ConvertFrom is expected to modify its receiver to contain the converted object. Most of the conversion is straightforward copying, except for converting our changed field. */ -// ConvertFrom converts from the Hub version (v1) to this version. -func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { - src := srcRaw.(*v1.CronJob) +// ConvertFrom converts from the Hub version (v1.ConversionCronJob) to this v2 CronJob. +func (dst *ConversionCronJobV2) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*v1.ConversionCronJob) // Use the wrapped ConversionCronJob from v1 schedParts := strings.Split(src.Spec.Schedule, " ") if len(schedParts) != 5 { return fmt.Errorf("invalid schedule: not a standard 5-field schedule") } - partIfNeeded := func(raw string) *CronField { + partIfNeeded := func(raw string) *batchv2.CronField { if raw == "*" { return nil } - part := CronField(raw) + part := batchv2.CronField(raw) return &part } dst.Spec.Schedule.Minute = partIfNeeded(schedParts[0]) @@ -122,7 +133,7 @@ func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { // Spec dst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds - dst.Spec.ConcurrencyPolicy = ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) + dst.Spec.ConcurrencyPolicy = batchv2.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) // Correct reference dst.Spec.Suspend = src.Spec.Suspend dst.Spec.JobTemplate = src.Spec.JobTemplate dst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_webhook.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_webhook.go similarity index 69% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_webhook.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_webhook.go index 556b051a6df..5bfc38a1da0 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_webhook.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_webhook.go @@ -19,31 +19,33 @@ package v2 import ( "context" "fmt" - "github.com/robfig/cron" "strings" + "github.com/robfig/cron" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" validationutils "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" + + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + batchv2 "tutorial.kubebuilder.io/project/api/v2" ) // nolint:unused // log is for logging in this package. var cronjoblog = logf.Log.WithName("cronjob-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager. +func SetupCronJobWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&batchv2.CronJob{}). WithValidator(&CronJobCustomValidator{}). WithDefaulter(&CronJobCustomDefaulter{ - DefaultConcurrencyPolicy: AllowConcurrent, + DefaultConcurrencyPolicy: batchv2.AllowConcurrent, DefaultSuspend: false, DefaultSuccessfulJobsHistoryLimit: 3, DefaultFailedJobsHistoryLimit: 1, @@ -62,9 +64,8 @@ func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { // NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, // as it is used only for temporary operations and does not need to be deeply copied. type CronJobCustomDefaulter struct { - // Default values for various CronJob fields - DefaultConcurrencyPolicy ConcurrencyPolicy + DefaultConcurrencyPolicy batchv2.ConcurrencyPolicy DefaultSuspend bool DefaultSuccessfulJobsHistoryLimit int32 DefaultFailedJobsHistoryLimit int32 @@ -74,16 +75,17 @@ var _ webhook.CustomDefaulter = &CronJobCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob. func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv2.CronJob) + if !ok { return fmt.Errorf("expected an CronJob object but got %T", obj) } cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName()) // Set default values - cronjob.Default() - + d.applyDefaults(cronjob) return nil + } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. @@ -105,29 +107,29 @@ var _ webhook.CustomValidator = &CronJobCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv2.CronJob) if !ok { return nil, fmt.Errorf("expected a CronJob object but got %T", obj) } cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName()) - return nil, cronjob.validateCronJob() + return nil, validateCronJob(cronjob) } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - cronjob, ok := newObj.(*CronJob) + cronjob, ok := newObj.(*batchv2.CronJob) if !ok { - return nil, fmt.Errorf("expected a CronJob object but got %T", newObj) + return nil, fmt.Errorf("expected a CronJob object for the newObj but got got %T", newObj) } cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName()) - return nil, cronjob.validateCronJob() + return nil, validateCronJob(cronjob) } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob. func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cronjob, ok := obj.(*CronJob) + cronjob, ok := obj.(*batchv2.CronJob) if !ok { return nil, fmt.Errorf("expected a CronJob object but got %T", obj) } @@ -138,65 +140,65 @@ func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime return nil, nil } -func (r *CronJob) Default() { - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent +// applyDefaults applies default values to CronJob fields. +func (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv2.CronJob) { + if cronJob.Spec.ConcurrencyPolicy == "" { + cronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if cronJob.Spec.Suspend == nil { + cronJob.Spec.Suspend = new(bool) + *cronJob.Spec.Suspend = d.DefaultSuspend } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if cronJob.Spec.SuccessfulJobsHistoryLimit == nil { + cronJob.Spec.SuccessfulJobsHistoryLimit = new(int32) + *cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if cronJob.Spec.FailedJobsHistoryLimit == nil { + cronJob.Spec.FailedJobsHistoryLimit = new(int32) + *cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit } } -func (r *CronJob) validateCronJob() error { +// validateCronJob validates the fields of a CronJob object. +func validateCronJob(cronjob *batchv2.CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := validateCronJobName(cronjob); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := validateCronJobSpec(cronjob); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { return nil } - - return apierrors.NewInvalid( - schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + return apierrors.NewInvalid(schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, cronjob.Name, allErrs) } -func (r *CronJob) validateCronJobName() *field.Error { - if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { - return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters") +func validateCronJobName(cronjob *batchv2.CronJob) *field.Error { + if len(cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { + return field.Invalid(field.NewPath("metadata").Child("name"), cronjob.ObjectMeta.Name, "must be no more than 52 characters") } return nil } // validateCronJobSpec validates the schedule format of the custom CronSchedule type -func (r *CronJob) validateCronJobSpec() *field.Error { +func validateCronJobSpec(cronjob *batchv2.CronJob) *field.Error { // Build cron expression from the parts parts := []string{"*", "*", "*", "*", "*"} // default parts for minute, hour, day of month, month, day of week - if r.Spec.Schedule.Minute != nil { - parts[0] = string(*r.Spec.Schedule.Minute) // Directly cast CronField (which is an alias of string) to string + if cronjob.Spec.Schedule.Minute != nil { + parts[0] = string(*cronjob.Spec.Schedule.Minute) // Directly cast CronField (which is an alias of string) to string } - if r.Spec.Schedule.Hour != nil { - parts[1] = string(*r.Spec.Schedule.Hour) + if cronjob.Spec.Schedule.Hour != nil { + parts[1] = string(*cronjob.Spec.Schedule.Hour) } - if r.Spec.Schedule.DayOfMonth != nil { - parts[2] = string(*r.Spec.Schedule.DayOfMonth) + if cronjob.Spec.Schedule.DayOfMonth != nil { + parts[2] = string(*cronjob.Spec.Schedule.DayOfMonth) } - if r.Spec.Schedule.Month != nil { - parts[3] = string(*r.Spec.Schedule.Month) + if cronjob.Spec.Schedule.Month != nil { + parts[3] = string(*cronjob.Spec.Schedule.Month) } - if r.Spec.Schedule.DayOfWeek != nil { - parts[4] = string(*r.Spec.Schedule.DayOfWeek) + if cronjob.Spec.Schedule.DayOfWeek != nil { + parts[4] = string(*cronjob.Spec.Schedule.DayOfWeek) } // Join parts to form the full cron expression diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_webhook_test.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_webhook_test.go similarity index 92% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_webhook_test.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_webhook_test.go index 0e6dffdbcab..a95db520fca 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_webhook_test.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/cronjob_webhook_test.go @@ -19,16 +19,18 @@ package v2 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + batchv2 "tutorial.kubebuilder.io/project/api/v2" // TODO (user): Add any additional imports if needed ) var _ = Describe("CronJob Webhook", func() { var ( - obj *CronJob + obj = &batchv2.CronJob{} ) BeforeEach(func() { - obj = &CronJob{} + obj = &batchv2.CronJob{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -66,7 +68,7 @@ var _ = Describe("CronJob Webhook", func() { // // It("Should validate updates correctly", func() { // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} + // oldObj := &batchv2.Captain{SomeRequiredField: "valid_value"} // obj.SomeRequiredField = "updated_value" // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) // }) @@ -76,7 +78,7 @@ var _ = Describe("CronJob Webhook", func() { // TODO (user): Add logic to convert the object to the desired version and verify the conversion // Example: // It("Should convert the object correctly", func() { - // convertedObj := &CronJob{} + // convertedObj := &batchv2.CronJob{} // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) // Expect(convertedObj).ToNot(BeNil()) // }) diff --git a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/webhook_suite_test.go b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/webhook_suite_test.go similarity index 98% rename from docs/book/src/multiversion-tutorial/testdata/project/api/v2/webhook_suite_test.go rename to docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/webhook_suite_test.go index 9d8ad182e4d..b84db19ab35 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/api/v2/webhook_suite_test.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/internal/webhooks/v2/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,7 +115,7 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&CronJob{}).SetupWebhookWithManager(mgr) + err = SetupCronJobWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook diff --git a/hack/docs/internal/cronjob-tutorial/generate_cronjob.go b/hack/docs/internal/cronjob-tutorial/generate_cronjob.go index 63515ab6d63..719c8c2e39f 100644 --- a/hack/docs/internal/cronjob-tutorial/generate_cronjob.go +++ b/hack/docs/internal/cronjob-tutorial/generate_cronjob.go @@ -375,15 +375,16 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust } func (sp *Sample) updateWebhookTests() { - file := filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook_test.go") + file := filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook_test.go") err := pluginutil.InsertCode(file, `var _ = Describe("CronJob Webhook", func() { var ( - obj *CronJob`, + obj = &batchv1.CronJob{}`, ` - oldObj *CronJob - validator CronJobCustomValidator`) + oldObj = &batchv1.CronJob{} + validator CronJobCustomValidator + defaulter CronJobCustomDefaulter`) hackutils.CheckError("insert global vars", err) err = pluginutil.ReplaceInFile(file, @@ -399,36 +400,36 @@ func (sp *Sample) updateWebhookTests() { err = pluginutil.ReplaceInFile(file, webhookTestsBeforeEachOriginal, webhookTestsBeforeEachChanged) - hackutils.CheckError("replace validating defaulting test", err) + hackutils.CheckError("replace before each webhook test ", err) } func (sp *Sample) updateWebhook() { var err error err = pluginutil.InsertCode( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `limitations under the License. */`, ` // +kubebuilder:docs-gen:collapse=Apache License`) hackutils.CheckError("fixing cronjob_webhook.go by adding collapse", err) - err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + err = pluginutil.InsertCode( + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `import ( "context" - "fmt"`, `import ( - "context" - "fmt" + "fmt"`, + ` "github.com/robfig/cron" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" validationutils "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field"`) + "k8s.io/apimachinery/pkg/util/validation/field"`, + ) hackutils.CheckError("add extra imports to cronjob_webhook.go", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), - `"sigs.k8s.io/controller-runtime/pkg/webhook/admission" + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), + `batchv1 "tutorial.kubebuilder.io/project/api/v1" ) // nolint:unused @@ -437,7 +438,7 @@ func (sp *Sample) updateWebhook() { hackutils.CheckError("fixing cronjob_webhook.go", err) err = pluginutil.InsertCode( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `var cronjoblog = logf.Log.WithName("cronjob-resource")`, ` /* @@ -446,31 +447,31 @@ Then, we set up the webhook with the manager. hackutils.CheckError("fixing cronjob_webhook.go by setting webhook with manager comment", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!`, webhooksNoticeMarker) hackutils.CheckError("fixing cronjob_webhook.go by replacing note about path attribute", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.`, explanationValidateCRD) hackutils.CheckError("fixing cronjob_webhook.go by replacing note about path attribute", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.`, "") hackutils.CheckError("fixing cronjob_webhook.go by replace TODO to change verbs", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// TODO(user): Add more fields as needed for defaulting`, fragmentForDefaultFields) hackutils.CheckError("fixing cronjob_webhook.go by replacing TODO in Defaulter", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `WithDefaulter(&CronJobCustomDefaulter{}).`, `WithDefaulter(&CronJobCustomDefaulter{ - DefaultConcurrencyPolicy: AllowConcurrent, + DefaultConcurrencyPolicy: batchv1.AllowConcurrent, DefaultSuspend: false, DefaultSuccessfulJobsHistoryLimit: 3, DefaultFailedJobsHistoryLimit: 1, @@ -478,7 +479,7 @@ Then, we set up the webhook with the manager. hackutils.CheckError("replacing WithDefaulter call in cronjob_webhook.go", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// TODO(user): fill in your defaulting logic. return nil @@ -486,29 +487,29 @@ Then, we set up the webhook with the manager. hackutils.CheckError("fixing cronjob_webhook.go by adding logic", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// TODO(user): fill in your validation logic upon object creation. return nil, nil`, - `return nil, cronjob.validateCronJob()`) + `return nil, validateCronJob(cronjob)`) hackutils.CheckError("fixing cronjob_webhook.go by fill in your validation", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// TODO(user): fill in your validation logic upon object update. return nil, nil`, - `return nil, cronjob.validateCronJob()`) + `return nil, validateCronJob(cronjob)`) hackutils.CheckError("fixing cronjob_webhook.go by adding validation logic upon object update", err) err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), `// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob.`, customInterfaceDefaultInfo) hackutils.CheckError("fixing cronjob_webhook.go by adding validation logic upon object update", err) err = pluginutil.AppendCodeAtTheEnd( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), webhookValidateSpecMethods) hackutils.CheckError("adding validation spec methods at the end", err) } diff --git a/hack/docs/internal/cronjob-tutorial/webhook_implementation.go b/hack/docs/internal/cronjob-tutorial/webhook_implementation.go index 46bde114dea..13cd39885fb 100644 --- a/hack/docs/internal/cronjob-tutorial/webhook_implementation.go +++ b/hack/docs/internal/cronjob-tutorial/webhook_implementation.go @@ -16,7 +16,7 @@ limitations under the License. package cronjob -const webhookIntro = `"sigs.k8s.io/controller-runtime/pkg/webhook/admission" +const webhookIntro = `batchv1 "tutorial.kubebuilder.io/project/api/v1" ) // +kubebuilder:docs-gen:collapse=Go imports @@ -28,25 +28,26 @@ Next, we'll setup a logger for the webhooks. ` const webhookDefaultingSettings = `// Set default values - cronjob.Default() - + d.applyDefaults(cronjob) return nil } -func (r *CronJob) Default() { - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent +// applyDefaults applies default values to CronJob fields. +func (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv1.CronJob) { + if cronJob.Spec.ConcurrencyPolicy == "" { + cronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if cronJob.Spec.Suspend == nil { + cronJob.Spec.Suspend = new(bool) + *cronJob.Spec.Suspend = d.DefaultSuspend } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if cronJob.Spec.SuccessfulJobsHistoryLimit == nil { + cronJob.Spec.SuccessfulJobsHistoryLimit = new(int32) + *cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if cronJob.Spec.FailedJobsHistoryLimit == nil { + cronJob.Spec.FailedJobsHistoryLimit = new(int32) + *cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit } } ` @@ -105,12 +106,13 @@ const webhookValidateSpecMethods = ` We validate the name and the spec of the CronJob. */ -func (r *CronJob) validateCronJob() error { +// validateCronJob validates the fields of a CronJob object. +func validateCronJob(cronjob *batchv1.CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := validateCronJobName(cronjob); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := validateCronJobSpec(cronjob); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { @@ -119,7 +121,7 @@ func (r *CronJob) validateCronJob() error { return apierrors.NewInvalid( schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + cronjob.Name, allErrs) } /* @@ -132,11 +134,11 @@ declaring validation by running ` + "`" + `controller-gen crd -w` + "`" + `, or [here](/reference/markers/crd-validation.md). */ -func (r *CronJob) validateCronJobSpec() *field.Error { +func validateCronJobSpec(cronjob *batchv1.CronJob) *field.Error { // The field helpers from the kubernetes API machinery help us return nicely // structured validation errors. return validateScheduleFormat( - r.Spec.Schedule, + cronjob.Spec.Schedule, field.NewPath("spec").Child("schedule")) } @@ -161,15 +163,15 @@ the apimachinery repo, so we can't declaratively validate it using the validation schema. */ -func (r *CronJob) validateCronJobName() *field.Error { - if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { +func validateCronJobName(cronjob *batchv1.CronJob) *field.Error { + if len(cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { // The job name length is 63 characters like all Kubernetes objects // (which must fit in a DNS subdomain). The cronjob controller appends // a 11-character suffix to the cronjob (` + "`" + `-$TIMESTAMP` + "`" + `) when creating // a job. The job name length limit is 63 characters. Therefore cronjob // names must have length <= 63-11=52. If we don't validate this here, // then job creation will fail later. - return field.Invalid(field.NewPath("metadata").Child("name"), r.ObjectMeta.Name, "must be no more than 52 characters") + return field.Invalid(field.NewPath("metadata").Child("name"), cronjob.ObjectMeta.Name, "must be no more than 52 characters") } return nil } @@ -178,7 +180,7 @@ func (r *CronJob) validateCronJobName() *field.Error { const fragmentForDefaultFields = ` // Default values for various CronJob fields - DefaultConcurrencyPolicy ConcurrencyPolicy + DefaultConcurrencyPolicy batchv1.ConcurrencyPolicy DefaultSuspend bool DefaultSuccessfulJobsHistoryLimit int32 DefaultFailedJobsHistoryLimit int32 @@ -201,10 +203,10 @@ const webhookTestCreateDefaultingReplaceFragment = `It("Should apply defaults wh obj.Spec.FailedJobsHistoryLimit = nil // This should default to 1 By("calling the Default method to apply defaults") - obj.Default() + defaulter.Default(ctx, obj) By("checking that the default values are set") - Expect(obj.Spec.ConcurrencyPolicy).To(Equal(AllowConcurrent), "Expected ConcurrencyPolicy to default to AllowConcurrent") + Expect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.AllowConcurrent), "Expected ConcurrencyPolicy to default to AllowConcurrent") Expect(*obj.Spec.Suspend).To(BeFalse(), "Expected Suspend to default to false") Expect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(3)), "Expected SuccessfulJobsHistoryLimit to default to 3") Expect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(1)), "Expected FailedJobsHistoryLimit to default to 1") @@ -212,7 +214,7 @@ const webhookTestCreateDefaultingReplaceFragment = `It("Should apply defaults wh It("Should not overwrite fields that are already set", func() { By("setting fields that would normally get a default") - obj.Spec.ConcurrencyPolicy = ForbidConcurrent + obj.Spec.ConcurrencyPolicy = batchv1.ForbidConcurrent obj.Spec.Suspend = new(bool) *obj.Spec.Suspend = true obj.Spec.SuccessfulJobsHistoryLimit = new(int32) @@ -221,10 +223,10 @@ const webhookTestCreateDefaultingReplaceFragment = `It("Should apply defaults wh *obj.Spec.FailedJobsHistoryLimit = 2 By("calling the Default method to apply defaults") - obj.Default() + defaulter.Default(ctx, obj) By("checking that the fields were not overwritten") - Expect(obj.Spec.ConcurrencyPolicy).To(Equal(ForbidConcurrent), "Expected ConcurrencyPolicy to retain its set value") + Expect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.ForbidConcurrent), "Expected ConcurrencyPolicy to retain its set value") Expect(*obj.Spec.Suspend).To(BeTrue(), "Expected Suspend to retain its set value") Expect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(5)), "Expected SuccessfulJobsHistoryLimit to retain its set value") Expect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(2)), "Expected FailedJobsHistoryLimit to retain its set value") @@ -246,7 +248,7 @@ const webhookTestingValidatingTodoFragment = `// TODO (user): Add logic for vali // // It("Should validate updates correctly", func() { // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} + // oldObj := &batchv1.Captain{SomeRequiredField: "valid_value"} // obj.SomeRequiredField = "updated_value" // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) // })` @@ -303,15 +305,15 @@ const webhookTestingValidatingExampleFragment = `It("Should deny creation if the "Expected validation to pass for a valid update") })` -const webhookTestsBeforeEachOriginal = `obj = &CronJob{} +const webhookTestsBeforeEachOriginal = `obj = &batchv1.CronJob{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests` -const webhookTestsBeforeEachChanged = `obj = &CronJob{ - Spec: CronJobSpec{ +const webhookTestsBeforeEachChanged = `obj = &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ Schedule: "*/5 * * * *", - ConcurrencyPolicy: AllowConcurrent, + ConcurrencyPolicy: batchv1.AllowConcurrent, SuccessfulJobsHistoryLimit: new(int32), FailedJobsHistoryLimit: new(int32), }, @@ -319,10 +321,10 @@ const webhookTestsBeforeEachChanged = `obj = &CronJob{ *obj.Spec.SuccessfulJobsHistoryLimit = 3 *obj.Spec.FailedJobsHistoryLimit = 1 - oldObj = &CronJob{ - Spec: CronJobSpec{ + oldObj = &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ Schedule: "*/5 * * * *", - ConcurrencyPolicy: AllowConcurrent, + ConcurrencyPolicy: batchv1.AllowConcurrent, SuccessfulJobsHistoryLimit: new(int32), FailedJobsHistoryLimit: new(int32), }, @@ -331,6 +333,12 @@ const webhookTestsBeforeEachChanged = `obj = &CronJob{ *oldObj.Spec.FailedJobsHistoryLimit = 1 validator = CronJobCustomValidator{} + defaulter = CronJobCustomDefaulter{ + DefaultConcurrencyPolicy: batchv1.AllowConcurrent, + DefaultSuspend: false, + DefaultSuccessfulJobsHistoryLimit: 3, + DefaultFailedJobsHistoryLimit: 1, + } Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized")` diff --git a/hack/docs/internal/multiversion-tutorial/generate_multiversion.go b/hack/docs/internal/multiversion-tutorial/generate_multiversion.go index 3ef2e9baa89..e74c7e7be79 100644 --- a/hack/docs/internal/multiversion-tutorial/generate_multiversion.go +++ b/hack/docs/internal/multiversion-tutorial/generate_multiversion.go @@ -84,7 +84,7 @@ func (sp *Sample) UpdateTutorial() { func (sp *Sample) updateWebhookV1() { err := pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), + filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_webhook.go"), "Then, we set up the webhook with the manager.", `This setup doubles as setup for our conversion webhooks: as long as our types implement the @@ -107,7 +107,7 @@ func (sp *Sample) updateSampleV2() { } func (sp *Sample) createHubFiles() { - path := filepath.Join(sp.ctx.Dir, "api/v1/cronjob_conversion.go") + path := filepath.Join(sp.ctx.Dir, "internal/webhooks/v1/cronjob_conversion.go") _, err := os.Create(path) hackutils.CheckError("creating conversion file v1", err) @@ -118,7 +118,7 @@ func (sp *Sample) createHubFiles() { err = pluginutil.AppendCodeAtTheEnd(path, hubV1Code) hackutils.CheckError("appending hubV1Code to cronjob_conversion.go", err) - path = filepath.Join(sp.ctx.Dir, "api/v2/cronjob_conversion.go") + path = filepath.Join(sp.ctx.Dir, "internal/webhooks/v2/cronjob_conversion.go") _, err = os.Create(path) hackutils.CheckError("creating conversion file v2", err) @@ -202,36 +202,21 @@ func (sp *Sample) updateApiV1() { } func (sp *Sample) updateWebhookV2() { - path := "api/v2/cronjob_webhook.go" + path := "internal/webhooks/v2/cronjob_webhook.go" - err := pluginutil.ReplaceInFile( + err := pluginutil.InsertCode( filepath.Join(sp.ctx.Dir, path), `import ( "context" - "fmt" - - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -)`, - `import ( - "context" - "fmt" - "github.com/robfig/cron" + "fmt"`, + ` "strings" - + + "github.com/robfig/cron" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" validationutils "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -)`, + "k8s.io/apimachinery/pkg/util/validation/field"`, ) hackutils.CheckError("replacing imports in v2", err) @@ -244,57 +229,35 @@ func (sp *Sample) updateWebhookV2() { err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, path), - `// TODO(user): fill in your defaulting logic.`, + `// TODO(user): fill in your defaulting logic. + + return nil`, cronJobDefaultingLogic, ) hackutils.CheckError("replacing defaulting logic in v2", err) err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, path), - `// TODO(user): fill in your validation logic upon object creation.`, - `return nil, cronjob.validateCronJob()`, - ) - hackutils.CheckError("replacing validation logic for creation in v2", err) + `// TODO(user): fill in your validation logic upon object creation. - err = pluginutil.ReplaceInFile( - filepath.Join(sp.ctx.Dir, path), - `// TODO(user): fill in your validation logic upon object update.`, - `return nil, cronjob.validateCronJob()`, + return nil, nil`, + `return nil, validateCronJob(cronjob)`, ) - hackutils.CheckError("replacing validation logic for update in v2", err) + hackutils.CheckError("replacing validation logic for creation in v2", err) err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, path), - `return nil, cronjob.validateCronJob() + `// TODO(user): fill in your validation logic upon object update. return nil, nil`, - `return nil, cronjob.validateCronJob()`, + `return nil, validateCronJob(cronjob)`, ) - hackutils.CheckError("fixing ValidateCreate in v2", err) + hackutils.CheckError("replacing validation logic for update in v2", err) err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, path), - `// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - WithValidator(&CronJobCustomValidator{}). - WithDefaulter(&CronJobCustomDefaulter{}). - Complete() -}`, - `// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - WithValidator(&CronJobCustomValidator{}). - WithDefaulter(&CronJobCustomDefaulter{ - DefaultConcurrencyPolicy: AllowConcurrent, - DefaultSuspend: false, - DefaultSuccessfulJobsHistoryLimit: 3, - DefaultFailedJobsHistoryLimit: 1, - }). - Complete() -}`, + originalSetupManager, + replaceSetupManager, ) hackutils.CheckError("replacing SetupWebhookWithManager in v2", err) diff --git a/hack/docs/internal/multiversion-tutorial/hub.go b/hack/docs/internal/multiversion-tutorial/hub.go index e28dd16e131..54e54710f6f 100644 --- a/hack/docs/internal/multiversion-tutorial/hub.go +++ b/hack/docs/internal/multiversion-tutorial/hub.go @@ -41,8 +41,22 @@ method called ` + "`Hub()`" + ` to serve as a We could also just put this inline in our ` + "`cronjob_types.go`" + ` file. */ +import ( + batchv1 "tutorial.kubebuilder.io/project/api/v1" // Import CronJob from API package +) + +/* +ConversionCronJob wraps the API's CronJob and implements the Hub interface for conversion. +*/ + +// ConversionCronJob wraps the existing CronJob type from the API. +type ConversionCronJob struct { + batchv1.CronJob // Embed the original CronJob type +} + // Hub marks this type as a conversion hub. -func (*CronJob) Hub() {}` +func (*ConversionCronJob) Hub() {} +` const hubV2Code = `/* Licensed under the Apache License, Version 2.0 (the "License"); @@ -69,11 +83,12 @@ standard packages. */ import ( "fmt" - "strings" - "sigs.k8s.io/controller-runtime/pkg/conversion" + "strings" - v1 "tutorial.kubebuilder.io/project/api/v1" + batchv1 "tutorial.kubebuilder.io/project/api/v1" // Import the v1 CronJob type from the API + batchv2 "tutorial.kubebuilder.io/project/api/v2" // Import the v2 CronJob type from the API + v1 "tutorial.kubebuilder.io/project/internal/webhooks/v1" // Import the hub version ) // +kubebuilder:docs-gen:collapse=Imports @@ -83,16 +98,25 @@ Our "spoke" versions need to implement the [` + "`" + `Convertible` + "`" + `](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible) interface. Namely, they'll need ` + "`ConvertTo()`" + ` and ` + "`ConvertFrom()`" + ` methods to convert to/from the hub version. + +Wrap the v2 CronJob from the API to allow conversion to/from the hub (v1). */ /* ConvertTo is expected to modify its argument to contain the converted object. Most of the conversion is straightforward copying, except for converting our changed field. */ -// ConvertTo converts this CronJob to the Hub version (v1). -func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { - dst := dstRaw.(*v1.CronJob) +// ConversionCronJobV2 wraps the API's CronJob and implements the Convertible interface. +type ConversionCronJobV2 struct { + batchv2.CronJob // Embed the original CronJob type +} + +// ConvertTo converts this v2 CronJob to the Hub version (v1.ConversionCronJob). +func (src *ConversionCronJobV2) ConvertTo(dstRaw conversion.Hub) error { + dst := dstRaw.(*v1.ConversionCronJob) // Use the wrapped ConversionCronJob from v1 + + // Handle the schedule conversion sched := src.Spec.Schedule scheduleParts := []string{"*", "*", "*", "*", "*"} if sched.Minute != nil { @@ -116,11 +140,12 @@ func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error { The rest of the conversion is pretty rote. */ // ObjectMeta - dst.ObjectMeta = src.ObjectMeta + dst.ObjectMeta = src.ObjectMeta + // Spec dst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds - dst.Spec.ConcurrencyPolicy = v1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) + dst.Spec.ConcurrencyPolicy = batchv1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) // Correct reference dst.Spec.Suspend = src.Spec.Suspend dst.Spec.JobTemplate = src.Spec.JobTemplate dst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit @@ -139,19 +164,19 @@ ConvertFrom is expected to modify its receiver to contain the converted object. Most of the conversion is straightforward copying, except for converting our changed field. */ -// ConvertFrom converts from the Hub version (v1) to this version. -func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { - src := srcRaw.(*v1.CronJob) +// ConvertFrom converts from the Hub version (v1.ConversionCronJob) to this v2 CronJob. +func (dst *ConversionCronJobV2) ConvertFrom(srcRaw conversion.Hub) error { + src := srcRaw.(*v1.ConversionCronJob) // Use the wrapped ConversionCronJob from v1 schedParts := strings.Split(src.Spec.Schedule, " ") if len(schedParts) != 5 { return fmt.Errorf("invalid schedule: not a standard 5-field schedule") } - partIfNeeded := func(raw string) *CronField { + partIfNeeded := func(raw string) *batchv2.CronField { if raw == "*" { return nil } - part := CronField(raw) + part := batchv2.CronField(raw) return &part } dst.Spec.Schedule.Minute = partIfNeeded(schedParts[0]) @@ -168,7 +193,7 @@ func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { // Spec dst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds - dst.Spec.ConcurrencyPolicy = ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) + dst.Spec.ConcurrencyPolicy = batchv2.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy) // Correct reference dst.Spec.Suspend = src.Spec.Suspend dst.Spec.JobTemplate = src.Spec.JobTemplate dst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit @@ -180,4 +205,5 @@ func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error { // +kubebuilder:docs-gen:collapse=rote conversion return nil -}` +} +` diff --git a/hack/docs/internal/multiversion-tutorial/webhook_v2_implementaton.go b/hack/docs/internal/multiversion-tutorial/webhook_v2_implementaton.go index 3329a4eb7c2..0dd5ad24f69 100644 --- a/hack/docs/internal/multiversion-tutorial/webhook_v2_implementaton.go +++ b/hack/docs/internal/multiversion-tutorial/webhook_v2_implementaton.go @@ -16,81 +16,80 @@ limitations under the License. package multiversion -const cronJobFieldsForDefaulting = ` -// Default values for various CronJob fields -DefaultConcurrencyPolicy ConcurrencyPolicy -DefaultSuspend bool -DefaultSuccessfulJobsHistoryLimit int32 -DefaultFailedJobsHistoryLimit int32 +const cronJobFieldsForDefaulting = ` // Default values for various CronJob fields + DefaultConcurrencyPolicy batchv2.ConcurrencyPolicy + DefaultSuspend bool + DefaultSuccessfulJobsHistoryLimit int32 + DefaultFailedJobsHistoryLimit int32 ` -const cronJobDefaultingLogic = ` -// Set default values -cronjob.Default() +const cronJobDefaultingLogic = `// Set default values + d.applyDefaults(cronjob) + return nil ` const cronJobDefaultFunction = ` -func (r *CronJob) Default() { - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent +// applyDefaults applies default values to CronJob fields. +func (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv2.CronJob) { + if cronJob.Spec.ConcurrencyPolicy == "" { + cronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if cronJob.Spec.Suspend == nil { + cronJob.Spec.Suspend = new(bool) + *cronJob.Spec.Suspend = d.DefaultSuspend } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if cronJob.Spec.SuccessfulJobsHistoryLimit == nil { + cronJob.Spec.SuccessfulJobsHistoryLimit = new(int32) + *cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if cronJob.Spec.FailedJobsHistoryLimit == nil { + cronJob.Spec.FailedJobsHistoryLimit = new(int32) + *cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit } } ` const cronJobValidationFunction = ` -func (r *CronJob) validateCronJob() error { +// validateCronJob validates the fields of a CronJob object. +func validateCronJob(cronjob *batchv2.CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := validateCronJobName(cronjob); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := validateCronJobSpec(cronjob); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { return nil } - - return apierrors.NewInvalid( - schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + return apierrors.NewInvalid(schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, cronjob.Name, allErrs) } -func (r *CronJob) validateCronJobName() *field.Error { - if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { - return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters") +func validateCronJobName(cronjob *batchv2.CronJob) *field.Error { + if len(cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { + return field.Invalid(field.NewPath("metadata").Child("name"), cronjob.ObjectMeta.Name, "must be no more than 52 characters") } return nil } // validateCronJobSpec validates the schedule format of the custom CronSchedule type -func (r *CronJob) validateCronJobSpec() *field.Error { +func validateCronJobSpec(cronjob *batchv2.CronJob) *field.Error { // Build cron expression from the parts parts := []string{"*", "*", "*", "*", "*"} // default parts for minute, hour, day of month, month, day of week - if r.Spec.Schedule.Minute != nil { - parts[0] = string(*r.Spec.Schedule.Minute) // Directly cast CronField (which is an alias of string) to string + if cronjob.Spec.Schedule.Minute != nil { + parts[0] = string(*cronjob.Spec.Schedule.Minute) // Directly cast CronField (which is an alias of string) to string } - if r.Spec.Schedule.Hour != nil { - parts[1] = string(*r.Spec.Schedule.Hour) + if cronjob.Spec.Schedule.Hour != nil { + parts[1] = string(*cronjob.Spec.Schedule.Hour) } - if r.Spec.Schedule.DayOfMonth != nil { - parts[2] = string(*r.Spec.Schedule.DayOfMonth) + if cronjob.Spec.Schedule.DayOfMonth != nil { + parts[2] = string(*cronjob.Spec.Schedule.DayOfMonth) } - if r.Spec.Schedule.Month != nil { - parts[3] = string(*r.Spec.Schedule.Month) + if cronjob.Spec.Schedule.Month != nil { + parts[3] = string(*cronjob.Spec.Schedule.Month) } - if r.Spec.Schedule.DayOfWeek != nil { - parts[4] = string(*r.Spec.Schedule.DayOfWeek) + if cronjob.Spec.Schedule.DayOfWeek != nil { + parts[4] = string(*cronjob.Spec.Schedule.DayOfWeek) } // Join parts to form the full cron expression @@ -108,3 +107,24 @@ func validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error { return nil } ` + +const originalSetupManager = `// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager. +func SetupCronJobWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&batchv2.CronJob{}). + WithValidator(&CronJobCustomValidator{}). + WithDefaulter(&CronJobCustomDefaulter{}). + Complete() +}` + +const replaceSetupManager = `// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager. +func SetupCronJobWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&batchv2.CronJob{}). + WithValidator(&CronJobCustomValidator{}). + WithDefaulter(&CronJobCustomDefaulter{ + DefaultConcurrencyPolicy: batchv2.AllowConcurrent, + DefaultSuspend: false, + DefaultSuccessfulJobsHistoryLimit: 3, + DefaultFailedJobsHistoryLimit: 1, + }). + Complete() +}` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go index 031a83e9795..ea93700522c 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go @@ -62,6 +62,12 @@ type MainUpdater struct { //nolint:maligned // Flags to indicate which parts need to be included when updating the file WireResource, WireController, WireWebhook bool + + // Deprecated - The flag should be removed from go/v5 + // IsLegacyPath indicates if webhooks should be scaffolded under the API. + // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback. + // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path. + IsLegacyPath bool } // GetPath implements file.Builder @@ -93,6 +99,10 @@ const ( apiImportCodeFragment = `%s "%s" ` controllerImportCodeFragment = `"%s/internal/controller" +` + webhookImportCodeFragment = `webhooks%s "%s/internal/webhooks/%s" +` + multiGroupWebhookImportCodeFragment = `webhooks%s%s "%s/internal/webhooks/%s/%s" ` multiGroupControllerImportCodeFragment = `%scontroller "%s/internal/controller/%s" ` @@ -114,7 +124,7 @@ const ( os.Exit(1) } ` - webhookSetupCodeFragment = `// nolint:goconst + webhookSetupCodeFragmentLegacy = `// nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "%s") @@ -122,6 +132,15 @@ const ( } } ` + + webhookSetupCodeFragment = `// nolint:goconst + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = %s.Setup%sWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "%s") + os.Exit(1) + } + } +` ) // GetCodeFragments implements file.Inserter @@ -138,6 +157,14 @@ func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { if f.WireResource { imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } + if f.WireWebhook && !f.IsLegacyPath { + if !f.MultiGroup || f.Resource.Group == "" { + imports = append(imports, fmt.Sprintf(webhookImportCodeFragment, f.Resource.Version, f.Repo, f.Resource.Version)) + } else { + imports = append(imports, fmt.Sprintf(multiGroupWebhookImportCodeFragment, + f.Resource.Group, f.Resource.Version, f.Repo, f.Resource.Group, f.Resource.Version)) + } + } if f.WireController { if !f.MultiGroup || f.Resource.Group == "" { @@ -166,8 +193,19 @@ func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { } } if f.WireWebhook { - setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, - f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + if f.IsLegacyPath { + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragmentLegacy, + f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + } else { + importAlias := "webhooks%s%s" + if !f.MultiGroup || f.Resource.Group == "" { + importAlias = fmt.Sprintf(importAlias, "", f.Resource.Version) + } else { + importAlias = fmt.Sprintf(importAlias, f.Resource.Group, f.Resource.Version) + } + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, + importAlias, f.Resource.Kind, f.Resource.Kind)) + } } // Only store code fragments in the map if the slices are non-empty diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go similarity index 77% rename from pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go rename to pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go index 25b6ae1d830..c37cdefcef8 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package webhooks import ( "path/filepath" @@ -41,15 +41,30 @@ type Webhook struct { // nolint:maligned AdmissionReviewVersions string Force bool + + // Deprecated - The flag should be removed from go/v5 + // IsLegacyPath indicates if webhooks should be scaffolded under the API. + // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback. + // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path. + IsLegacyPath bool } // SetTemplateDefaults implements file.Template func (f *Webhook) SetTemplateDefaults() error { if f.Path == "" { - if f.MultiGroup && f.Resource.Group != "" { - f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_webhook.go") + if f.IsLegacyPath { + // Deprecated code - TODO: remove me for go/v5 + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_webhook.go") + } else { + f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook.go") + } } else { - f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook.go") + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("internal", "webhooks", "%[group]", "%[version]", "%[kind]_webhook.go") + } else { + f.Path = filepath.Join("internal", "webhooks", "%[version]", "%[kind]_webhook.go") + } } } @@ -97,12 +112,18 @@ import ( {{- if .Resource.HasValidationWebhook }} "sigs.k8s.io/controller-runtime/pkg/webhook/admission" {{- end }} + {{ if not .IsLegacyPath -}} + {{ if not (isEmptyStr .Resource.Path) -}} + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + {{- end }} + {{- end }} ) // nolint:unused // log is for logging in this package. var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind }}-resource") +{{- if .IsLegacyPath -}} // SetupWebhookWithManager will setup the manager to manage the webhooks. func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). @@ -115,6 +136,24 @@ func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { {{- end }} Complete() } +{{- else }} +// Setup{{ .Resource.Kind }}WebhookWithManager registers the webhook for {{ .Resource.Kind }} in the manager. +func Setup{{ .Resource.Kind }}WebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + {{- if not (isEmptyStr .Resource.ImportAlias) -}} + For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). + {{- else -}} + For(&{{ .Resource.Kind }}{}). + {{- end }} + {{- if .Resource.HasValidationWebhook }} + WithValidator(&{{ .Resource.Kind }}CustomValidator{}). + {{- end }} + {{- if .Resource.HasDefaultingWebhook }} + WithDefaulter(&{{ .Resource.Kind }}CustomDefaulter{}). + {{- end }} + Complete() +} +{{- end }} // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! ` @@ -137,7 +176,12 @@ var _ webhook.CustomDefaulter = &{{ .Resource.Kind }}CustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind {{ .Resource.Kind }}. func (d *{{ .Resource.Kind }}CustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }}) + {{- else }} + {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + {{- end }} + if !ok { return fmt.Errorf("expected an {{ .Resource.Kind }} object but got %T", obj) } @@ -170,7 +214,11 @@ var _ webhook.CustomValidator = &{{ .Resource.Kind }}CustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}. func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }}) + {{- else }} + {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + {{- end }} if !ok { return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", obj) } @@ -183,9 +231,13 @@ func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(ctx context.Context // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}. func (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := newObj.(*{{ .Resource.Kind }}) + {{- else }} + {{ lower .Resource.Kind }}, ok := newObj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + {{- end }} if !ok { - return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", newObj) + return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object for the newObj but got got %T", newObj) } {{ lower .Resource.Kind }}log.Info("Validation for {{ .Resource.Kind }} upon update", "name", {{ lower .Resource.Kind }}.GetName()) @@ -196,7 +248,11 @@ func (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(ctx context.Context // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}. func (v *{{ .Resource.Kind }}CustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }}) + {{- else }} + {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + {{- end }} if !ok { return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", obj) } diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go similarity index 82% rename from pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go rename to pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go index a058a37ef91..91dc15cbec1 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package webhooks import ( "fmt" @@ -44,15 +44,30 @@ type WebhookSuite struct { //nolint:maligned // BaseDirectoryRelativePath define the Path for the base directory when it is multigroup BaseDirectoryRelativePath string + + // Deprecated - The flag should be removed from go/v5 + // IsLegacyPath indicates if webhooks should be scaffolded under the API. + // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback. + // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path. + IsLegacyPath bool } // SetTemplateDefaults implements file.Template func (f *WebhookSuite) SetTemplateDefaults() error { if f.Path == "" { - if f.MultiGroup && f.Resource.Group != "" { - f.Path = filepath.Join("api", "%[group]", "%[version]", "webhook_suite_test.go") + if f.IsLegacyPath { + // Deprecated code - TODO: remove me for go/v5 + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("api", "%[group]", "%[version]", "webhook_suite_test.go") + } else { + f.Path = filepath.Join("api", "%[version]", "webhook_suite_test.go") + } } else { - f.Path = filepath.Join("api", "%[version]", "webhook_suite_test.go") + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("internal", "webhooks", "%[group]", "%[version]", "webhook_suite_test.go") + } else { + f.Path = filepath.Join("internal", "webhooks", "%[version]", "webhook_suite_test.go") + } } } @@ -61,7 +76,7 @@ func (f *WebhookSuite) SetTemplateDefaults() error { f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate, machinery.NewMarkerFor(f.Path, importMarker), - admissionImportAlias, + admissionImportAlias, admissionImportAlias, machinery.NewMarkerFor(f.Path, addSchemeMarker), machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), "%s", @@ -98,7 +113,14 @@ const ( apiImportCodeFragment = `%s "%s" ` - addWebhookManagerCodeFragment = `err = (&%s{}).SetupWebhookWithManager(mgr) + // Deprecated - TODO: remove for go/v5 + // addWebhookManagerCodeFragmentLegacy is for the path under API + addWebhookManagerCodeFragmentLegacy = `err = (&%s{}).SetupWebhookWithManager(mgr) +Expect(err).NotTo(HaveOccurred()) + +` + + addWebhookManagerCodeFragment = `err = Setup%sWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) ` @@ -117,7 +139,11 @@ func (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap { // Generate add webhookManager code fragments addWebhookManager := make([]string, 0) - addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind)) + if f.IsLegacyPath { + addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragmentLegacy, f.Resource.Kind)) + } else { + addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind)) + } // Only store code fragments in the map if the slices are non-empty if len(addWebhookManager) != 0 { @@ -208,7 +234,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = %s.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = %s.AddToScheme(scheme) diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_test_template.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go similarity index 72% rename from pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_test_template.go rename to pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go index 162eaaa9d06..6efb86b1322 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_test_template.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package api +package webhooks import ( "fmt" @@ -36,15 +36,30 @@ type WebhookTest struct { // nolint:maligned machinery.ResourceMixin Force bool + + // Deprecated - The flag should be removed from go/v5 + // IsLegacyPath indicates if webhooks should be scaffolded under the API. + // Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback. + // This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path. + IsLegacyPath bool } // SetTemplateDefaults implements file.Template func (f *WebhookTest) SetTemplateDefaults() error { if f.Path == "" { - if f.MultiGroup && f.Resource.Group != "" { - f.Path = filepath.Join("api", "%[group]", "%[version]", "%[kind]_webhook_test.go") + if f.IsLegacyPath { + // Deprecated code - TODO: remove me for go/v5 + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("api", "%[version]", "%[group]", "%[version]", "%[kind]_webhook_test.go") + } else { + f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook_test.go") + } } else { - f.Path = filepath.Join("api", "%[version]", "%[kind]_webhook_test.go") + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("internal", "webhooks", "%[group]", "%[version]", "%[kind]_webhook_test.go") + } else { + f.Path = filepath.Join("internal", "webhooks", "%[version]", "%[kind]_webhook_test.go") + } } } f.Path = f.Resource.Replacer().Replace(f.Path) @@ -78,16 +93,29 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + {{ if not .IsLegacyPath -}} + {{ if not (isEmptyStr .Resource.Path) -}} + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + {{- end }} + {{- end }} // TODO (user): Add any additional imports if needed ) var _ = Describe("{{ .Resource.Kind }} Webhook", func() { var ( - obj *{{ .Resource.Kind }} + {{- if .IsLegacyPath -}} + obj = &{{ .Resource.Kind }}{} + {{- else }} + obj = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{} + {{- end }} ) BeforeEach(func() { + {{- if .IsLegacyPath -}} obj = &{{ .Resource.Kind }}{} + {{- else }} + obj = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{} + {{- end }} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -106,7 +134,11 @@ Context("When creating {{ .Resource.Kind }} under Conversion Webhook", func() { // TODO (user): Add logic to convert the object to the desired version and verify the conversion // Example: // It("Should convert the object correctly", func() { + {{- if .IsLegacyPath -}} // convertedObj := &{{ .Resource.Kind }}{} + {{- else }} + // convertedObj := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{} + {{- end }} // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) // Expect(convertedObj).ToNot(BeNil()) // }) @@ -131,7 +163,11 @@ Context("When creating or updating {{ .Resource.Kind }} under Validating Webhook // // It("Should validate updates correctly", func() { // By("simulating a valid update scenario") + {{- if .IsLegacyPath -}} // oldObj := &Captain{SomeRequiredField: "valid_value"} + {{- else }} + // oldObj := &{{ .Resource.ImportAlias }}.Captain{SomeRequiredField: "valid_value"} + {{- end }} // obj.SomeRequiredField = "updated_value" // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) // }) diff --git a/pkg/plugins/golang/v4/scaffolds/webhook.go b/pkg/plugins/golang/v4/scaffolds/webhook.go index 20f4ac5953b..256562d1e93 100644 --- a/pkg/plugins/golang/v4/scaffolds/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/webhook.go @@ -27,9 +27,9 @@ import ( "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" "sigs.k8s.io/kubebuilder/v4/pkg/plugins" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/api" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks" ) var _ plugins.Scaffolder = &webhookScaffolder{} @@ -43,14 +43,19 @@ type webhookScaffolder struct { // force indicates whether to scaffold controller files even if it exists or not force bool + + // Deprecated - TODO: remove it for go/v5 + // isLegacy indicates that the resource should be created in the legacy path under the api + isLegacy bool } // NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations -func NewWebhookScaffolder(config config.Config, resource resource.Resource, force bool) plugins.Scaffolder { +func NewWebhookScaffolder(config config.Config, resource resource.Resource, force bool, isLegacy bool) plugins.Scaffolder { return &webhookScaffolder{ config: config, resource: resource, force: force, + isLegacy: isLegacy, } } @@ -86,10 +91,10 @@ func (s *webhookScaffolder) Scaffold() error { } if err := scaffold.Execute( - &api.Webhook{Force: s.force}, + &webhooks.Webhook{Force: s.force, IsLegacyPath: s.isLegacy}, &e2e.WebhookTestUpdater{WireWebhook: true}, - &templates.MainUpdater{WireWebhook: true}, - &api.WebhookTest{Force: s.force}, + &templates.MainUpdater{WireWebhook: true, IsLegacyPath: s.isLegacy}, + &webhooks.WebhookTest{Force: s.force, IsLegacyPath: s.isLegacy}, ); err != nil { return err } @@ -102,7 +107,7 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. if doDefaulting || doValidation { if err := scaffold.Execute( - &api.WebhookSuite{K8SVersion: EnvtestK8SVersion}, + &webhooks.WebhookSuite{K8SVersion: EnvtestK8SVersion, IsLegacyPath: s.isLegacy}, ); err != nil { return err } diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go index 9fe89cb3343..c8d50fb3a30 100644 --- a/pkg/plugins/golang/v4/webhook.go +++ b/pkg/plugins/golang/v4/webhook.go @@ -43,6 +43,10 @@ type createWebhookSubcommand struct { // force indicates that the resource should be created even if it already exists force bool + + // Deprecated - TODO: remove it for go/v5 + // isLegacyPath indicates that the resource should be created in the legacy path under the api + isLegacyPath bool } func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { @@ -73,6 +77,11 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.options.DoConversion, "conversion", false, "if set, scaffold the conversion webhook") + //TODO: remove for go/v5 + fs.BoolVar(&p.isLegacyPath, "legacy", false, + "[DEPRECATED] Attempts to create resource under the API directory (legacy path). "+ + "This option will be removed in future versions.") + fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") } @@ -107,7 +116,7 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { } func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { - scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force) + scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force, p.isLegacyPath) scaffolder.InjectFS(fs) return scaffolder.Scaffold() } diff --git a/test/testdata/generate.sh b/test/testdata/generate.sh index bc16751b992..91741834da4 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -75,7 +75,8 @@ function scaffold_test_project { header_text 'Creating APIs with deploy-image plugin ...' $kb create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:memcached:1.6.26-alpine3.19 --image-container-command="memcached,--memory-limit=64,-o,modern,-v" --image-container-port="11211" --run-as-user="1001" --plugins="deploy-image/v1-alpha" --make=false $kb create api --group example.com --version v1alpha1 --kind Busybox --image=busybox:1.36.1 --plugins="deploy-image/v1-alpha" --make=false - $kb create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation + ## TODO: FIX for move webhooks to internal. It is failing for multigroup with plugins regards imports + ## $kb create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation header_text 'Editing project with Grafana plugin ...' $kb edit --plugins=grafana.kubebuilder.io/v1-alpha fi diff --git a/testdata/project-v4-multigroup/PROJECT b/testdata/project-v4-multigroup/PROJECT index ea5536b0583..69eb5a38aa3 100644 --- a/testdata/project-v4-multigroup/PROJECT +++ b/testdata/project-v4-multigroup/PROJECT @@ -134,9 +134,6 @@ resources: kind: Memcached path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1 version: v1alpha1 - webhooks: - validation: true - webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go index 438b50de573..26925504f6f 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1 import ( - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_webhook.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_webhook.go deleted file mode 100644 index 11f098e7358..00000000000 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_webhook.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2024 The Kubernetes authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// nolint:unused -// log is for logging in this package. -var memcachedlog = logf.Log.WithName("memcached-resource") - -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Memcached) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - WithValidator(&MemcachedCustomValidator{}). - Complete() -} - -// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. -// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. -// +kubebuilder:webhook:path=/validate-example-com-testproject-org-v1alpha1-memcached,mutating=false,failurePolicy=fail,sideEffects=None,groups=example.com.testproject.org,resources=memcacheds,verbs=create;update,versions=v1alpha1,name=vmemcached-v1alpha1.kb.io,admissionReviewVersions=v1 - -// +kubebuilder:object:generate=false -// MemcachedCustomValidator struct is responsible for validating the Memcached resource -// when it is created, updated, or deleted. -// -// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, -// as this struct is used only for temporary operations and does not need to be deeply copied. -type MemcachedCustomValidator struct { - //TODO(user): Add more fields as needed for validation -} - -var _ webhook.CustomValidator = &MemcachedCustomValidator{} - -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - memcached, ok := obj.(*Memcached) - if !ok { - return nil, fmt.Errorf("expected a Memcached object but got %T", obj) - } - memcachedlog.Info("Validation for Memcached upon creation", "name", memcached.GetName()) - - // TODO(user): fill in your validation logic upon object creation. - - return nil, nil -} - -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - memcached, ok := newObj.(*Memcached) - if !ok { - return nil, fmt.Errorf("expected a Memcached object but got %T", newObj) - } - memcachedlog.Info("Validation for Memcached upon update", "name", memcached.GetName()) - - // TODO(user): fill in your validation logic upon object update. - - return nil, nil -} - -// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - memcached, ok := obj.(*Memcached) - if !ok { - return nil, fmt.Errorf("expected a Memcached object but got %T", obj) - } - memcachedlog.Info("Validation for Memcached upon deletion", "name", memcached.GetName()) - - // TODO(user): fill in your validation logic upon object deletion. - - return nil, nil -} diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_webhook_test.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_webhook_test.go deleted file mode 100644 index b966fb2d8da..00000000000 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_webhook_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2024 The Kubernetes authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - // TODO (user): Add any additional imports if needed -) - -var _ = Describe("Memcached Webhook", func() { - var ( - obj *Memcached - ) - - BeforeEach(func() { - obj = &Memcached{} - Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") - - // TODO (user): Add any setup logic common to all tests - }) - - AfterEach(func() { - // TODO (user): Add any teardown logic common to all tests - }) - - Context("When creating or updating Memcached under Validating Webhook", func() { - // TODO (user): Add logic for validating webhooks - // Example: - // It("Should deny creation if a required field is missing", func() { - // By("simulating an invalid creation scenario") - // obj.SomeRequiredField = "" - // Expect(obj.ValidateCreate(ctx)).Error().To(HaveOccurred()) - // }) - // - // It("Should admit creation if all required fields are present", func() { - // By("simulating an invalid creation scenario") - // obj.SomeRequiredField = "valid_value" - // Expect(obj.ValidateCreate(ctx)).To(BeNil()) - // }) - // - // It("Should validate updates correctly", func() { - // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} - // obj.SomeRequiredField = "updated_value" - // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) - // }) - }) - -}) diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/webhook_suite_test.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/webhook_suite_test.go deleted file mode 100644 index 94caeb96601..00000000000 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/webhook_suite_test.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2024 The Kubernetes authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - "context" - "crypto/tls" - "fmt" - "net" - "path/filepath" - "runtime" - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - admissionv1 "k8s.io/api/admission/v1" - // +kubebuilder:scaffold:imports - apimachineryruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var ( - cancel context.CancelFunc - cfg *rest.Config - ctx context.Context - k8sClient client.Client - testEnv *envtest.Environment -) - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Webhook Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - ctx, cancel = context.WithCancel(context.TODO()) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: false, - - // The BinaryAssetsDirectory is only required if you want to run the tests directly - // without call the makefile target test. If not informed it will look for the - // default path defined in controller-runtime which is /usr/local/kubebuilder/. - // Note that you must have the required binaries setup under the bin directory to perform - // the tests directly. When we run make test it will be setup and used automatically. - BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", - fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)), - - WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, - }, - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) - Expect(err).NotTo(HaveOccurred()) - - err = admissionv1.AddToScheme(scheme) - Expect(err).NotTo(HaveOccurred()) - - // +kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - - // start webhook server using Manager. - webhookInstallOptions := &testEnv.WebhookInstallOptions - mgr, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme, - WebhookServer: webhook.NewServer(webhook.Options{ - Host: webhookInstallOptions.LocalServingHost, - Port: webhookInstallOptions.LocalServingPort, - CertDir: webhookInstallOptions.LocalServingCertDir, - }), - LeaderElection: false, - Metrics: metricsserver.Options{BindAddress: "0"}, - }) - Expect(err).NotTo(HaveOccurred()) - - err = (&Memcached{}).SetupWebhookWithManager(mgr) - Expect(err).NotTo(HaveOccurred()) - - // +kubebuilder:scaffold:webhook - - go func() { - defer GinkgoRecover() - err = mgr.Start(ctx) - Expect(err).NotTo(HaveOccurred()) - }() - - // wait for the webhook server to get ready. - dialer := &net.Dialer{Timeout: time.Second} - addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) - Eventually(func() error { - conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) - if err != nil { - return err - } - - return conn.Close() - }).Should(Succeed()) -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - cancel() - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go index a41c7b842d1..6254bdb0507 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go index ca3974a1d81..8931c51a317 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1 import ( - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go index 8f391c1cf27..01af43ca4fe 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v2alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/testdata/project-v4-multigroup/cmd/main.go b/testdata/project-v4-multigroup/cmd/main.go index e7dc7e0a55e..4464ea12a43 100644 --- a/testdata/project-v4-multigroup/cmd/main.go +++ b/testdata/project-v4-multigroup/cmd/main.go @@ -53,6 +53,10 @@ import ( foopolicycontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/foo.policy" seacreaturescontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/sea-creatures" shipcontroller "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/ship" + webhookscrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhooks/crew/v1" + webhooksshipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhooks/ship/v1" + webhooksshipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1" + webhooksshipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1" // +kubebuilder:scaffold:imports ) @@ -178,7 +182,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&crewv1.Captain{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhookscrewv1.SetupCaptainWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Captain") os.Exit(1) } @@ -192,7 +196,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&shipv1beta1.Frigate{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksshipv1beta1.SetupFrigateWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Frigate") os.Exit(1) } @@ -206,7 +210,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&shipv1.Destroyer{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksshipv1.SetupDestroyerWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Destroyer") os.Exit(1) } @@ -220,7 +224,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&shipv2alpha1.Cruiser{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksshipv2alpha1.SetupCruiserWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Cruiser") os.Exit(1) } @@ -283,13 +287,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Busybox") os.Exit(1) } - // nolint:goconst - if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&examplecomv1alpha1.Memcached{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "Memcached") - os.Exit(1) - } - } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/testdata/project-v4-multigroup/config/crd/kustomization.yaml b/testdata/project-v4-multigroup/config/crd/kustomization.yaml index 6b362727dd7..b40276c542c 100644 --- a/testdata/project-v4-multigroup/config/crd/kustomization.yaml +++ b/testdata/project-v4-multigroup/config/crd/kustomization.yaml @@ -22,7 +22,6 @@ patches: - path: patches/webhook_in_ship_frigates.yaml - path: patches/webhook_in_ship_destroyers.yaml - path: patches/webhook_in_ship_cruisers.yaml -- path: patches/webhook_in_example.com_memcacheds.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. diff --git a/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_memcacheds.yaml b/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_memcacheds.yaml deleted file mode 100644 index 5b9e839364d..00000000000 --- a/testdata/project-v4-multigroup/config/crd/patches/cainjection_in_example.com_memcacheds.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME - name: memcacheds.example.com.testproject.org diff --git a/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_memcacheds.yaml b/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_memcacheds.yaml deleted file mode 100644 index 4a56b0f4c69..00000000000 --- a/testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_memcacheds.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: memcacheds.example.com.testproject.org -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/testdata/project-v4-multigroup/config/webhook/manifests.yaml b/testdata/project-v4-multigroup/config/webhook/manifests.yaml index 3f6221647a1..23ebee06334 100644 --- a/testdata/project-v4-multigroup/config/webhook/manifests.yaml +++ b/testdata/project-v4-multigroup/config/webhook/manifests.yaml @@ -70,26 +70,6 @@ webhooks: resources: - captains sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-example-com-testproject-org-v1alpha1-memcached - failurePolicy: Fail - name: vmemcached-v1alpha1.kb.io - rules: - - apiGroups: - - example.com.testproject.org - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - memcacheds - sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/testdata/project-v4-multigroup/dist/install.yaml b/testdata/project-v4-multigroup/dist/install.yaml index 05a31522eeb..a851908f58a 100644 --- a/testdata/project-v4-multigroup/dist/install.yaml +++ b/testdata/project-v4-multigroup/dist/install.yaml @@ -656,16 +656,6 @@ metadata: controller-gen.kubebuilder.io/version: v0.16.1 name: memcacheds.example.com.testproject.org spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: project-v4-multigroup-webhook-service - namespace: project-v4-multigroup-system - path: /convert - conversionReviewVersions: - - v1 group: example.com.testproject.org names: kind: Memcached @@ -1854,26 +1844,6 @@ webhooks: resources: - captains sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: project-v4-multigroup-webhook-service - namespace: project-v4-multigroup-system - path: /validate-example-com-testproject-org-v1alpha1-memcached - failurePolicy: Fail - name: vmemcached-v1alpha1.kb.io - rules: - - apiGroups: - - example.com.testproject.org - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - memcacheds - sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go b/testdata/project-v4-multigroup/internal/webhooks/crew/v1/captain_webhook.go similarity index 89% rename from testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go rename to testdata/project-v4-multigroup/internal/webhooks/crew/v1/captain_webhook.go index 98fc273afc7..8cf9b359573 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhooks/crew/v1/captain_webhook.go @@ -25,16 +25,17 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1" ) // nolint:unused // log is for logging in this package. var captainlog = logf.Log.WithName("captain-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupCaptainWebhookWithManager registers the webhook for Captain in the manager. +func SetupCaptainWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&crewv1.Captain{}). WithValidator(&CaptainCustomValidator{}). WithDefaulter(&CaptainCustomDefaulter{}). Complete() @@ -58,7 +59,8 @@ var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Captain. func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - captain, ok := obj.(*Captain) + captain, ok := obj.(*crewv1.Captain) + if !ok { return fmt.Errorf("expected an Captain object but got %T", obj) } @@ -88,7 +90,7 @@ var _ webhook.CustomValidator = &CaptainCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Captain. func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - captain, ok := obj.(*Captain) + captain, ok := obj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object but got %T", obj) } @@ -101,9 +103,9 @@ func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Captain. func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - captain, ok := newObj.(*Captain) + captain, ok := newObj.(*crewv1.Captain) if !ok { - return nil, fmt.Errorf("expected a Captain object but got %T", newObj) + return nil, fmt.Errorf("expected a Captain object for the newObj but got got %T", newObj) } captainlog.Info("Validation for Captain upon update", "name", captain.GetName()) @@ -114,7 +116,7 @@ func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, new // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Captain. func (v *CaptainCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - captain, ok := obj.(*Captain) + captain, ok := obj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object but got %T", obj) } diff --git a/testdata/project-v4/api/v1/captain_webhook_test.go b/testdata/project-v4-multigroup/internal/webhooks/crew/v1/captain_webhook_test.go similarity index 92% rename from testdata/project-v4/api/v1/captain_webhook_test.go rename to testdata/project-v4-multigroup/internal/webhooks/crew/v1/captain_webhook_test.go index 4c1020c9c56..cbabe98e3c7 100644 --- a/testdata/project-v4/api/v1/captain_webhook_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/crew/v1/captain_webhook_test.go @@ -19,16 +19,18 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("Captain Webhook", func() { var ( - obj *Captain + obj = &crewv1.Captain{} ) BeforeEach(func() { - obj = &Captain{} + obj = &crewv1.Captain{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -66,7 +68,7 @@ var _ = Describe("Captain Webhook", func() { // // It("Should validate updates correctly", func() { // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} + // oldObj := &crewv1.Captain{SomeRequiredField: "valid_value"} // obj.SomeRequiredField = "updated_value" // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) // }) diff --git a/testdata/project-v4-multigroup/api/crew/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/internal/webhooks/crew/v1/webhook_suite_test.go similarity index 98% rename from testdata/project-v4-multigroup/api/crew/v1/webhook_suite_test.go rename to testdata/project-v4-multigroup/internal/webhooks/crew/v1/webhook_suite_test.go index 6614182b4e2..5f26a41f728 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/crew/v1/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,7 +115,7 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&Captain{}).SetupWebhookWithManager(mgr) + err = SetupCaptainWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook diff --git a/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v1/destroyer_webhook.go similarity index 87% rename from testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v1/destroyer_webhook.go index dbc040c9dbc..128306dc9cb 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v1/destroyer_webhook.go @@ -24,16 +24,17 @@ import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" + + shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1" ) // nolint:unused // log is for logging in this package. var destroyerlog = logf.Log.WithName("destroyer-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Destroyer) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupDestroyerWebhookWithManager registers the webhook for Destroyer in the manager. +func SetupDestroyerWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&shipv1.Destroyer{}). WithDefaulter(&DestroyerCustomDefaulter{}). Complete() } @@ -56,7 +57,8 @@ var _ webhook.CustomDefaulter = &DestroyerCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Destroyer. func (d *DestroyerCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - destroyer, ok := obj.(*Destroyer) + destroyer, ok := obj.(*shipv1.Destroyer) + if !ok { return fmt.Errorf("expected an Destroyer object but got %T", obj) } diff --git a/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook_test.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v1/destroyer_webhook_test.go similarity index 91% rename from testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook_test.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v1/destroyer_webhook_test.go index 4cdedb2e959..0d04c778271 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v1/destroyer_webhook_test.go @@ -19,16 +19,18 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("Destroyer Webhook", func() { var ( - obj *Destroyer + obj = &shipv1.Destroyer{} ) BeforeEach(func() { - obj = &Destroyer{} + obj = &shipv1.Destroyer{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests diff --git a/testdata/project-v4-multigroup/api/ship/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v1/webhook_suite_test.go similarity index 98% rename from testdata/project-v4-multigroup/api/ship/v1/webhook_suite_test.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v1/webhook_suite_test.go index 0236bede36b..aac37acadb9 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v1/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,7 +115,7 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&Destroyer{}).SetupWebhookWithManager(mgr) + err = SetupDestroyerWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook diff --git a/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_webhook.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1/frigate_webhook.go similarity index 74% rename from testdata/project-v4-multigroup/api/ship/v1beta1/frigate_webhook.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1/frigate_webhook.go index c699e518551..90b5342fa05 100644 --- a/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1/frigate_webhook.go @@ -19,16 +19,17 @@ package v1beta1 import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" + + shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1" ) // nolint:unused // log is for logging in this package. var frigatelog = logf.Log.WithName("frigate-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Frigate) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupFrigateWebhookWithManager registers the webhook for Frigate in the manager. +func SetupFrigateWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&shipv1beta1.Frigate{}). Complete() } diff --git a/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_webhook_test.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1/frigate_webhook_test.go similarity index 87% rename from testdata/project-v4-multigroup/api/ship/v1beta1/frigate_webhook_test.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1/frigate_webhook_test.go index ceeae183858..8fb1d99f493 100644 --- a/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_webhook_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v1beta1/frigate_webhook_test.go @@ -19,16 +19,18 @@ package v1beta1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1" // TODO (user): Add any additional imports if needed ) var _ = Describe("Frigate Webhook", func() { var ( - obj *Frigate + obj = &shipv1beta1.Frigate{} ) BeforeEach(func() { - obj = &Frigate{} + obj = &shipv1beta1.Frigate{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -42,7 +44,7 @@ var _ = Describe("Frigate Webhook", func() { // TODO (user): Add logic to convert the object to the desired version and verify the conversion // Example: // It("Should convert the object correctly", func() { - // convertedObj := &Frigate{} + // convertedObj := &shipv1beta1.Frigate{} // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) // Expect(convertedObj).ToNot(BeNil()) // }) diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/cruiser_webhook.go similarity index 87% rename from testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/cruiser_webhook.go index 28c1fb1b72b..37a14fbd466 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/cruiser_webhook.go @@ -25,16 +25,17 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1" ) // nolint:unused // log is for logging in this package. var cruiserlog = logf.Log.WithName("cruiser-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Cruiser) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupCruiserWebhookWithManager registers the webhook for Cruiser in the manager. +func SetupCruiserWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&shipv2alpha1.Cruiser{}). WithValidator(&CruiserCustomValidator{}). Complete() } @@ -60,7 +61,7 @@ var _ webhook.CustomValidator = &CruiserCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Cruiser. func (v *CruiserCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cruiser, ok := obj.(*Cruiser) + cruiser, ok := obj.(*shipv2alpha1.Cruiser) if !ok { return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) } @@ -73,9 +74,9 @@ func (v *CruiserCustomValidator) ValidateCreate(ctx context.Context, obj runtime // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Cruiser. func (v *CruiserCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - cruiser, ok := newObj.(*Cruiser) + cruiser, ok := newObj.(*shipv2alpha1.Cruiser) if !ok { - return nil, fmt.Errorf("expected a Cruiser object but got %T", newObj) + return nil, fmt.Errorf("expected a Cruiser object for the newObj but got got %T", newObj) } cruiserlog.Info("Validation for Cruiser upon update", "name", cruiser.GetName()) @@ -86,7 +87,7 @@ func (v *CruiserCustomValidator) ValidateUpdate(ctx context.Context, oldObj, new // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Cruiser. func (v *CruiserCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - cruiser, ok := obj.(*Cruiser) + cruiser, ok := obj.(*shipv2alpha1.Cruiser) if !ok { return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) } diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook_test.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/cruiser_webhook_test.go similarity index 89% rename from testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook_test.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/cruiser_webhook_test.go index e548fad5f57..e66e3622b22 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/cruiser_webhook_test.go @@ -19,16 +19,18 @@ package v2alpha1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1" // TODO (user): Add any additional imports if needed ) var _ = Describe("Cruiser Webhook", func() { var ( - obj *Cruiser + obj = &shipv2alpha1.Cruiser{} ) BeforeEach(func() { - obj = &Cruiser{} + obj = &shipv2alpha1.Cruiser{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -55,7 +57,7 @@ var _ = Describe("Cruiser Webhook", func() { // // It("Should validate updates correctly", func() { // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} + // oldObj := &shipv2alpha1.Captain{SomeRequiredField: "valid_value"} // obj.SomeRequiredField = "updated_value" // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) // }) diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/webhook_suite_test.go b/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/webhook_suite_test.go similarity index 98% rename from testdata/project-v4-multigroup/api/ship/v2alpha1/webhook_suite_test.go rename to testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/webhook_suite_test.go index 031400e44cc..bef675a827d 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/internal/webhooks/ship/v2alpha1/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,7 +115,7 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&Cruiser{}).SetupWebhookWithManager(mgr) + err = SetupCruiserWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook diff --git a/testdata/project-v4-with-plugins/PROJECT b/testdata/project-v4-with-plugins/PROJECT index f006d1cad32..18d3a854b51 100644 --- a/testdata/project-v4-with-plugins/PROJECT +++ b/testdata/project-v4-with-plugins/PROJECT @@ -36,9 +36,6 @@ resources: kind: Memcached path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1 version: v1alpha1 - webhooks: - validation: true - webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/memcached_webhook.go b/testdata/project-v4-with-plugins/api/v1alpha1/memcached_webhook.go deleted file mode 100644 index 11f098e7358..00000000000 --- a/testdata/project-v4-with-plugins/api/v1alpha1/memcached_webhook.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2024 The Kubernetes authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// nolint:unused -// log is for logging in this package. -var memcachedlog = logf.Log.WithName("memcached-resource") - -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Memcached) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - WithValidator(&MemcachedCustomValidator{}). - Complete() -} - -// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. -// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. -// +kubebuilder:webhook:path=/validate-example-com-testproject-org-v1alpha1-memcached,mutating=false,failurePolicy=fail,sideEffects=None,groups=example.com.testproject.org,resources=memcacheds,verbs=create;update,versions=v1alpha1,name=vmemcached-v1alpha1.kb.io,admissionReviewVersions=v1 - -// +kubebuilder:object:generate=false -// MemcachedCustomValidator struct is responsible for validating the Memcached resource -// when it is created, updated, or deleted. -// -// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, -// as this struct is used only for temporary operations and does not need to be deeply copied. -type MemcachedCustomValidator struct { - //TODO(user): Add more fields as needed for validation -} - -var _ webhook.CustomValidator = &MemcachedCustomValidator{} - -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - memcached, ok := obj.(*Memcached) - if !ok { - return nil, fmt.Errorf("expected a Memcached object but got %T", obj) - } - memcachedlog.Info("Validation for Memcached upon creation", "name", memcached.GetName()) - - // TODO(user): fill in your validation logic upon object creation. - - return nil, nil -} - -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - memcached, ok := newObj.(*Memcached) - if !ok { - return nil, fmt.Errorf("expected a Memcached object but got %T", newObj) - } - memcachedlog.Info("Validation for Memcached upon update", "name", memcached.GetName()) - - // TODO(user): fill in your validation logic upon object update. - - return nil, nil -} - -// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - memcached, ok := obj.(*Memcached) - if !ok { - return nil, fmt.Errorf("expected a Memcached object but got %T", obj) - } - memcachedlog.Info("Validation for Memcached upon deletion", "name", memcached.GetName()) - - // TODO(user): fill in your validation logic upon object deletion. - - return nil, nil -} diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/memcached_webhook_test.go b/testdata/project-v4-with-plugins/api/v1alpha1/memcached_webhook_test.go deleted file mode 100644 index b966fb2d8da..00000000000 --- a/testdata/project-v4-with-plugins/api/v1alpha1/memcached_webhook_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2024 The Kubernetes authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - // TODO (user): Add any additional imports if needed -) - -var _ = Describe("Memcached Webhook", func() { - var ( - obj *Memcached - ) - - BeforeEach(func() { - obj = &Memcached{} - Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") - - // TODO (user): Add any setup logic common to all tests - }) - - AfterEach(func() { - // TODO (user): Add any teardown logic common to all tests - }) - - Context("When creating or updating Memcached under Validating Webhook", func() { - // TODO (user): Add logic for validating webhooks - // Example: - // It("Should deny creation if a required field is missing", func() { - // By("simulating an invalid creation scenario") - // obj.SomeRequiredField = "" - // Expect(obj.ValidateCreate(ctx)).Error().To(HaveOccurred()) - // }) - // - // It("Should admit creation if all required fields are present", func() { - // By("simulating an invalid creation scenario") - // obj.SomeRequiredField = "valid_value" - // Expect(obj.ValidateCreate(ctx)).To(BeNil()) - // }) - // - // It("Should validate updates correctly", func() { - // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} - // obj.SomeRequiredField = "updated_value" - // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) - // }) - }) - -}) diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/webhook_suite_test.go b/testdata/project-v4-with-plugins/api/v1alpha1/webhook_suite_test.go deleted file mode 100644 index e70fab04bb0..00000000000 --- a/testdata/project-v4-with-plugins/api/v1alpha1/webhook_suite_test.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2024 The Kubernetes authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - "context" - "crypto/tls" - "fmt" - "net" - "path/filepath" - "runtime" - "testing" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - admissionv1 "k8s.io/api/admission/v1" - // +kubebuilder:scaffold:imports - apimachineryruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var ( - cancel context.CancelFunc - cfg *rest.Config - ctx context.Context - k8sClient client.Client - testEnv *envtest.Environment -) - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Webhook Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - ctx, cancel = context.WithCancel(context.TODO()) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: false, - - // The BinaryAssetsDirectory is only required if you want to run the tests directly - // without call the makefile target test. If not informed it will look for the - // default path defined in controller-runtime which is /usr/local/kubebuilder/. - // Note that you must have the required binaries setup under the bin directory to perform - // the tests directly. When we run make test it will be setup and used automatically. - BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", - fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)), - - WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "config", "webhook")}, - }, - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) - Expect(err).NotTo(HaveOccurred()) - - err = admissionv1.AddToScheme(scheme) - Expect(err).NotTo(HaveOccurred()) - - // +kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - - // start webhook server using Manager. - webhookInstallOptions := &testEnv.WebhookInstallOptions - mgr, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme, - WebhookServer: webhook.NewServer(webhook.Options{ - Host: webhookInstallOptions.LocalServingHost, - Port: webhookInstallOptions.LocalServingPort, - CertDir: webhookInstallOptions.LocalServingCertDir, - }), - LeaderElection: false, - Metrics: metricsserver.Options{BindAddress: "0"}, - }) - Expect(err).NotTo(HaveOccurred()) - - err = (&Memcached{}).SetupWebhookWithManager(mgr) - Expect(err).NotTo(HaveOccurred()) - - // +kubebuilder:scaffold:webhook - - go func() { - defer GinkgoRecover() - err = mgr.Start(ctx) - Expect(err).NotTo(HaveOccurred()) - }() - - // wait for the webhook server to get ready. - dialer := &net.Dialer{Timeout: time.Second} - addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) - Eventually(func() error { - conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) - if err != nil { - return err - } - - return conn.Close() - }).Should(Succeed()) -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - cancel() - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go index a41c7b842d1..6254bdb0507 100644 --- a/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/testdata/project-v4-with-plugins/cmd/main.go b/testdata/project-v4-with-plugins/cmd/main.go index ade191db8f1..24df24b850b 100644 --- a/testdata/project-v4-with-plugins/cmd/main.go +++ b/testdata/project-v4-with-plugins/cmd/main.go @@ -160,13 +160,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Busybox") os.Exit(1) } - // nolint:goconst - if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&examplecomv1alpha1.Memcached{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "Memcached") - os.Exit(1) - } - } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/testdata/project-v4-with-plugins/config/certmanager/certificate.yaml b/testdata/project-v4-with-plugins/config/certmanager/certificate.yaml deleted file mode 100644 index 68214a62d39..00000000000 --- a/testdata/project-v4-with-plugins/config/certmanager/certificate.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# The following manifests contain a self-signed issuer CR and a certificate CR. -# More document can be found at https://docs.cert-manager.io -# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - labels: - app.kubernetes.io/name: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize - name: selfsigned-issuer - namespace: system -spec: - selfSigned: {} ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - labels: - app.kubernetes.io/name: certificate - app.kubernetes.io/instance: serving-cert - app.kubernetes.io/component: certificate - app.kubernetes.io/created-by: project-v4-with-plugins - app.kubernetes.io/part-of: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize - name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml - namespace: system -spec: - # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize - dnsNames: - - SERVICE_NAME.SERVICE_NAMESPACE.svc - - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local - issuerRef: - kind: Issuer - name: selfsigned-issuer - secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize diff --git a/testdata/project-v4-with-plugins/config/certmanager/kustomization.yaml b/testdata/project-v4-with-plugins/config/certmanager/kustomization.yaml deleted file mode 100644 index bebea5a595e..00000000000 --- a/testdata/project-v4-with-plugins/config/certmanager/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -resources: -- certificate.yaml - -configurations: -- kustomizeconfig.yaml diff --git a/testdata/project-v4-with-plugins/config/certmanager/kustomizeconfig.yaml b/testdata/project-v4-with-plugins/config/certmanager/kustomizeconfig.yaml deleted file mode 100644 index cf6f89e8892..00000000000 --- a/testdata/project-v4-with-plugins/config/certmanager/kustomizeconfig.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# This configuration is for teaching kustomize how to update name ref substitution -nameReference: -- kind: Issuer - group: cert-manager.io - fieldSpecs: - - kind: Certificate - group: cert-manager.io - path: spec/issuerRef/name diff --git a/testdata/project-v4-with-plugins/config/crd/kustomization.yaml b/testdata/project-v4-with-plugins/config/crd/kustomization.yaml index 7b2aba4eb3c..8656b08a78a 100644 --- a/testdata/project-v4-with-plugins/config/crd/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/crd/kustomization.yaml @@ -9,7 +9,6 @@ resources: patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD -- path: patches/webhook_in_memcacheds.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -21,5 +20,5 @@ patches: # [WEBHOOK] To enable webhook, uncomment the following section # the following config is for teaching kustomize how to do kustomization for CRDs. -configurations: -- kustomizeconfig.yaml +#configurations: +#- kustomizeconfig.yaml diff --git a/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_memcacheds.yaml b/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_memcacheds.yaml deleted file mode 100644 index 5b9e839364d..00000000000 --- a/testdata/project-v4-with-plugins/config/crd/patches/cainjection_in_memcacheds.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME - name: memcacheds.example.com.testproject.org diff --git a/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_memcacheds.yaml b/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_memcacheds.yaml deleted file mode 100644 index 4a56b0f4c69..00000000000 --- a/testdata/project-v4-with-plugins/config/crd/patches/webhook_in_memcacheds.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# The following patch enables a conversion webhook for the CRD -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: memcacheds.example.com.testproject.org -spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - namespace: system - name: webhook-service - path: /convert - conversionReviewVersions: - - v1 diff --git a/testdata/project-v4-with-plugins/config/default/kustomization.yaml b/testdata/project-v4-with-plugins/config/default/kustomization.yaml index 3f17cef261d..3f72e9aee8e 100644 --- a/testdata/project-v4-with-plugins/config/default/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/default/kustomization.yaml @@ -20,7 +20,7 @@ resources: - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -- ../webhook +#- ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. #- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. @@ -43,7 +43,7 @@ patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -- path: manager_webhook_patch.yaml +#- path: manager_webhook_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. diff --git a/testdata/project-v4-with-plugins/config/default/manager_webhook_patch.yaml b/testdata/project-v4-with-plugins/config/default/manager_webhook_patch.yaml deleted file mode 100644 index 34989fbbeb1..00000000000 --- a/testdata/project-v4-with-plugins/config/default/manager_webhook_patch.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system - labels: - app.kubernetes.io/name: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize -spec: - template: - spec: - containers: - - name: manager - ports: - - containerPort: 9443 - name: webhook-server - protocol: TCP - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: webhook-server-cert diff --git a/testdata/project-v4-with-plugins/config/default/webhookcainjection_patch.yaml b/testdata/project-v4-with-plugins/config/default/webhookcainjection_patch.yaml deleted file mode 100644 index 3afc9037018..00000000000 --- a/testdata/project-v4-with-plugins/config/default/webhookcainjection_patch.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# This patch add annotation to admission webhook config and -# CERTIFICATE_NAMESPACE and CERTIFICATE_NAME will be substituted by kustomize -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - labels: - app.kubernetes.io/name: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize - name: mutating-webhook-configuration - annotations: - cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - labels: - app.kubernetes.io/name: validatingwebhookconfiguration - app.kubernetes.io/instance: validating-webhook-configuration - app.kubernetes.io/component: webhook - app.kubernetes.io/created-by: project-v4-with-plugins - app.kubernetes.io/part-of: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize - name: validating-webhook-configuration - annotations: - cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME diff --git a/testdata/project-v4-with-plugins/config/network-policy/allow-webhook-traffic.yaml b/testdata/project-v4-with-plugins/config/network-policy/allow-webhook-traffic.yaml deleted file mode 100644 index c75092b53e2..00000000000 --- a/testdata/project-v4-with-plugins/config/network-policy/allow-webhook-traffic.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# This NetworkPolicy allows ingress traffic to your webhook server running -# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks -# will only work when applied in namespaces labeled with 'webhook: enabled' -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/name: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize - name: allow-webhook-traffic - namespace: system -spec: - podSelector: - matchLabels: - control-plane: controller-manager - policyTypes: - - Ingress - ingress: - # This allows ingress traffic from any namespace with the label webhook: enabled - - from: - - namespaceSelector: - matchLabels: - webhook: enabled # Only from namespaces with this label - ports: - - port: 443 - protocol: TCP diff --git a/testdata/project-v4-with-plugins/config/network-policy/kustomization.yaml b/testdata/project-v4-with-plugins/config/network-policy/kustomization.yaml index 0872bee124c..ec0fb5e57df 100644 --- a/testdata/project-v4-with-plugins/config/network-policy/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/network-policy/kustomization.yaml @@ -1,3 +1,2 @@ resources: -- allow-webhook-traffic.yaml - allow-metrics-traffic.yaml diff --git a/testdata/project-v4-with-plugins/config/webhook/kustomization.yaml b/testdata/project-v4-with-plugins/config/webhook/kustomization.yaml deleted file mode 100644 index 9cf26134e4d..00000000000 --- a/testdata/project-v4-with-plugins/config/webhook/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -resources: -- manifests.yaml -- service.yaml - -configurations: -- kustomizeconfig.yaml diff --git a/testdata/project-v4-with-plugins/config/webhook/kustomizeconfig.yaml b/testdata/project-v4-with-plugins/config/webhook/kustomizeconfig.yaml deleted file mode 100644 index 206316e54ff..00000000000 --- a/testdata/project-v4-with-plugins/config/webhook/kustomizeconfig.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# the following config is for teaching kustomize where to look at when substituting nameReference. -# It requires kustomize v2.1.0 or newer to work properly. -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: MutatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/name - - kind: ValidatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/name - -namespace: -- kind: MutatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/namespace - create: true -- kind: ValidatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/namespace - create: true diff --git a/testdata/project-v4-with-plugins/config/webhook/manifests.yaml b/testdata/project-v4-with-plugins/config/webhook/manifests.yaml deleted file mode 100644 index 99b797e22f4..00000000000 --- a/testdata/project-v4-with-plugins/config/webhook/manifests.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: validating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-example-com-testproject-org-v1alpha1-memcached - failurePolicy: Fail - name: vmemcached-v1alpha1.kb.io - rules: - - apiGroups: - - example.com.testproject.org - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - memcacheds - sideEffects: None diff --git a/testdata/project-v4-with-plugins/config/webhook/service.yaml b/testdata/project-v4-with-plugins/config/webhook/service.yaml deleted file mode 100644 index a2c259db493..00000000000 --- a/testdata/project-v4-with-plugins/config/webhook/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/name: project-v4-with-plugins - app.kubernetes.io/managed-by: kustomize - name: webhook-service - namespace: system -spec: - ports: - - port: 443 - protocol: TCP - targetPort: 9443 - selector: - control-plane: controller-manager diff --git a/testdata/project-v4-with-plugins/dist/install.yaml b/testdata/project-v4-with-plugins/dist/install.yaml index 0002f8fa761..e8d526be58f 100644 --- a/testdata/project-v4-with-plugins/dist/install.yaml +++ b/testdata/project-v4-with-plugins/dist/install.yaml @@ -130,16 +130,6 @@ metadata: controller-gen.kubebuilder.io/version: v0.16.1 name: memcacheds.example.com.testproject.org spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: project-v4-with-plugins-webhook-service - namespace: project-v4-with-plugins-system - path: /convert - conversionReviewVersions: - - v1 group: example.com.testproject.org names: kind: Memcached @@ -558,22 +548,6 @@ spec: selector: control-plane: controller-manager --- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/name: project-v4-with-plugins - name: project-v4-with-plugins-webhook-service - namespace: project-v4-with-plugins-system -spec: - ports: - - port: 443 - protocol: TCP - targetPort: 9443 - selector: - control-plane: controller-manager ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -615,10 +589,6 @@ spec: initialDelaySeconds: 15 periodSeconds: 20 name: manager - ports: - - containerPort: 9443 - name: webhook-server - protocol: TCP readinessProbe: httpGet: path: /readyz @@ -637,42 +607,7 @@ spec: capabilities: drop: - ALL - volumeMounts: - - mountPath: /tmp/k8s-webhook-server/serving-certs - name: cert - readOnly: true securityContext: runAsNonRoot: true serviceAccountName: project-v4-with-plugins-controller-manager terminationGracePeriodSeconds: 10 - volumes: - - name: cert - secret: - defaultMode: 420 - secretName: webhook-server-cert ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: project-v4-with-plugins-validating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: project-v4-with-plugins-webhook-service - namespace: project-v4-with-plugins-system - path: /validate-example-com-testproject-org-v1alpha1-memcached - failurePolicy: Fail - name: vmemcached-v1alpha1.kb.io - rules: - - apiGroups: - - example.com.testproject.org - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - memcacheds - sideEffects: None diff --git a/testdata/project-v4-with-plugins/test/e2e/e2e_test.go b/testdata/project-v4-with-plugins/test/e2e/e2e_test.go index 5e48266fff7..c5abb31cc99 100644 --- a/testdata/project-v4-with-plugins/test/e2e/e2e_test.go +++ b/testdata/project-v4-with-plugins/test/e2e/e2e_test.go @@ -189,30 +189,6 @@ var _ = Describe("Manager", Ordered, func() { )) }) - It("should provisioned cert-manager", func() { - By("validating that cert-manager has the certificate Secret") - verifyCertManager := func(g Gomega) { - cmd := exec.Command("kubectl", "get", "secrets", "webhook-server-cert", "-n", namespace) - _, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - } - Eventually(verifyCertManager).Should(Succeed()) - }) - - It("should have CA injection for validating webhooks", func() { - By("checking CA injection for validating webhooks") - verifyCAInjection := func(g Gomega) { - cmd := exec.Command("kubectl", "get", - "validatingwebhookconfigurations.admissionregistration.k8s.io", - "project-v4-with-plugins-validating-webhook-configuration", - "-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}") - vwhOutput, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(len(vwhOutput)).To(BeNumerically(">", 10)) - } - Eventually(verifyCAInjection).Should(Succeed()) - }) - // +kubebuilder:scaffold:e2e-webhooks-checks // TODO: Customize the e2e test suite with scenarios specific to your project. diff --git a/testdata/project-v4/api/v1/zz_generated.deepcopy.go b/testdata/project-v4/api/v1/zz_generated.deepcopy.go index 4ec350e23aa..24fb3a25515 100644 --- a/testdata/project-v4/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4/api/v1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1 import ( - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/testdata/project-v4/cmd/main.go b/testdata/project-v4/cmd/main.go index d2a65954c40..c23e28fd21d 100644 --- a/testdata/project-v4/cmd/main.go +++ b/testdata/project-v4/cmd/main.go @@ -37,6 +37,7 @@ import ( crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/controller" + webhooksv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhooks/v1" // +kubebuilder:scaffold:imports ) @@ -153,7 +154,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&crewv1.Captain{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksv1.SetupCaptainWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Captain") os.Exit(1) } @@ -167,7 +168,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&crewv1.FirstMate{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksv1.SetupFirstMateWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "FirstMate") os.Exit(1) } @@ -181,7 +182,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&crewv1.Admiral{}).SetupWebhookWithManager(mgr); err != nil { + if err = webhooksv1.SetupAdmiralWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Admiral") os.Exit(1) } diff --git a/testdata/project-v4/api/v1/admiral_webhook.go b/testdata/project-v4/internal/webhooks/v1/admiral_webhook.go similarity index 87% rename from testdata/project-v4/api/v1/admiral_webhook.go rename to testdata/project-v4/internal/webhooks/v1/admiral_webhook.go index feff9708a4b..8c51732121e 100644 --- a/testdata/project-v4/api/v1/admiral_webhook.go +++ b/testdata/project-v4/internal/webhooks/v1/admiral_webhook.go @@ -24,16 +24,17 @@ import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" ) // nolint:unused // log is for logging in this package. var admirallog = logf.Log.WithName("admiral-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Admiral) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupAdmiralWebhookWithManager registers the webhook for Admiral in the manager. +func SetupAdmiralWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&crewv1.Admiral{}). WithDefaulter(&AdmiralCustomDefaulter{}). Complete() } @@ -56,7 +57,8 @@ var _ webhook.CustomDefaulter = &AdmiralCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Admiral. func (d *AdmiralCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - admiral, ok := obj.(*Admiral) + admiral, ok := obj.(*crewv1.Admiral) + if !ok { return fmt.Errorf("expected an Admiral object but got %T", obj) } diff --git a/testdata/project-v4/api/v1/admiral_webhook_test.go b/testdata/project-v4/internal/webhooks/v1/admiral_webhook_test.go similarity index 92% rename from testdata/project-v4/api/v1/admiral_webhook_test.go rename to testdata/project-v4/internal/webhooks/v1/admiral_webhook_test.go index 01cd6c5e141..2ffcbfd2498 100644 --- a/testdata/project-v4/api/v1/admiral_webhook_test.go +++ b/testdata/project-v4/internal/webhooks/v1/admiral_webhook_test.go @@ -19,16 +19,18 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("Admiral Webhook", func() { var ( - obj *Admiral + obj = &crewv1.Admiral{} ) BeforeEach(func() { - obj = &Admiral{} + obj = &crewv1.Admiral{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests diff --git a/testdata/project-v4/api/v1/captain_webhook.go b/testdata/project-v4/internal/webhooks/v1/captain_webhook.go similarity index 90% rename from testdata/project-v4/api/v1/captain_webhook.go rename to testdata/project-v4/internal/webhooks/v1/captain_webhook.go index 98fc273afc7..f674c625d9e 100644 --- a/testdata/project-v4/api/v1/captain_webhook.go +++ b/testdata/project-v4/internal/webhooks/v1/captain_webhook.go @@ -25,16 +25,17 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" ) // nolint:unused // log is for logging in this package. var captainlog = logf.Log.WithName("captain-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupCaptainWebhookWithManager registers the webhook for Captain in the manager. +func SetupCaptainWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&crewv1.Captain{}). WithValidator(&CaptainCustomValidator{}). WithDefaulter(&CaptainCustomDefaulter{}). Complete() @@ -58,7 +59,8 @@ var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Captain. func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { - captain, ok := obj.(*Captain) + captain, ok := obj.(*crewv1.Captain) + if !ok { return fmt.Errorf("expected an Captain object but got %T", obj) } @@ -88,7 +90,7 @@ var _ webhook.CustomValidator = &CaptainCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Captain. func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - captain, ok := obj.(*Captain) + captain, ok := obj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object but got %T", obj) } @@ -101,9 +103,9 @@ func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Captain. func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - captain, ok := newObj.(*Captain) + captain, ok := newObj.(*crewv1.Captain) if !ok { - return nil, fmt.Errorf("expected a Captain object but got %T", newObj) + return nil, fmt.Errorf("expected a Captain object for the newObj but got got %T", newObj) } captainlog.Info("Validation for Captain upon update", "name", captain.GetName()) @@ -114,7 +116,7 @@ func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, new // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Captain. func (v *CaptainCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - captain, ok := obj.(*Captain) + captain, ok := obj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object but got %T", obj) } diff --git a/testdata/project-v4-multigroup/api/crew/v1/captain_webhook_test.go b/testdata/project-v4/internal/webhooks/v1/captain_webhook_test.go similarity index 92% rename from testdata/project-v4-multigroup/api/crew/v1/captain_webhook_test.go rename to testdata/project-v4/internal/webhooks/v1/captain_webhook_test.go index 4c1020c9c56..73435820ee4 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/captain_webhook_test.go +++ b/testdata/project-v4/internal/webhooks/v1/captain_webhook_test.go @@ -19,16 +19,18 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("Captain Webhook", func() { var ( - obj *Captain + obj = &crewv1.Captain{} ) BeforeEach(func() { - obj = &Captain{} + obj = &crewv1.Captain{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -66,7 +68,7 @@ var _ = Describe("Captain Webhook", func() { // // It("Should validate updates correctly", func() { // By("simulating a valid update scenario") - // oldObj := &Captain{SomeRequiredField: "valid_value"} + // oldObj := &crewv1.Captain{SomeRequiredField: "valid_value"} // obj.SomeRequiredField = "updated_value" // Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil()) // }) diff --git a/testdata/project-v4/api/v1/firstmate_webhook.go b/testdata/project-v4/internal/webhooks/v1/firstmate_webhook.go similarity index 76% rename from testdata/project-v4/api/v1/firstmate_webhook.go rename to testdata/project-v4/internal/webhooks/v1/firstmate_webhook.go index e19ae07ada5..8b009e57c05 100644 --- a/testdata/project-v4/api/v1/firstmate_webhook.go +++ b/testdata/project-v4/internal/webhooks/v1/firstmate_webhook.go @@ -19,16 +19,17 @@ package v1 import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" ) // nolint:unused // log is for logging in this package. var firstmatelog = logf.Log.WithName("firstmate-resource") -// SetupWebhookWithManager will setup the manager to manage the webhooks. -func (r *FirstMate) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). +// SetupFirstMateWebhookWithManager registers the webhook for FirstMate in the manager. +func SetupFirstMateWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&crewv1.FirstMate{}). Complete() } diff --git a/testdata/project-v4/api/v1/firstmate_webhook_test.go b/testdata/project-v4/internal/webhooks/v1/firstmate_webhook_test.go similarity index 89% rename from testdata/project-v4/api/v1/firstmate_webhook_test.go rename to testdata/project-v4/internal/webhooks/v1/firstmate_webhook_test.go index 040e5dc3ee6..cc9fdb97fcc 100644 --- a/testdata/project-v4/api/v1/firstmate_webhook_test.go +++ b/testdata/project-v4/internal/webhooks/v1/firstmate_webhook_test.go @@ -19,16 +19,18 @@ package v1 import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" // TODO (user): Add any additional imports if needed ) var _ = Describe("FirstMate Webhook", func() { var ( - obj *FirstMate + obj = &crewv1.FirstMate{} ) BeforeEach(func() { - obj = &FirstMate{} + obj = &crewv1.FirstMate{} Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") // TODO (user): Add any setup logic common to all tests @@ -42,7 +44,7 @@ var _ = Describe("FirstMate Webhook", func() { // TODO (user): Add logic to convert the object to the desired version and verify the conversion // Example: // It("Should convert the object correctly", func() { - // convertedObj := &FirstMate{} + // convertedObj := &crewv1.FirstMate{} // Expect(obj.ConvertTo(convertedObj)).To(Succeed()) // Expect(convertedObj).ToNot(BeNil()) // }) diff --git a/testdata/project-v4/api/v1/webhook_suite_test.go b/testdata/project-v4/internal/webhooks/v1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4/api/v1/webhook_suite_test.go rename to testdata/project-v4/internal/webhooks/v1/webhook_suite_test.go index 418ca3f9291..5ed0866eefb 100644 --- a/testdata/project-v4/api/v1/webhook_suite_test.go +++ b/testdata/project-v4/internal/webhooks/v1/webhook_suite_test.go @@ -89,7 +89,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := apimachineryruntime.NewScheme() - err = AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1.AddToScheme(scheme) @@ -115,10 +115,10 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&Captain{}).SetupWebhookWithManager(mgr) + err = SetupCaptainWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) - err = (&Admiral{}).SetupWebhookWithManager(mgr) + err = SetupAdmiralWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:webhook