diff --git a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go b/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go index abb025276bc..9e447ec3f92 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go @@ -18,12 +18,15 @@ limitations under the License. package v1 import ( + "context" + "fmt" "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" @@ -46,6 +49,8 @@ Then, we set up the webhook with the manager. func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&CronJobCustomValidator{}). + WithDefaulter(&CronJobCustomDefaulter{}). Complete() } @@ -65,26 +70,42 @@ A webhook will automatically be served that calls this defaulting. The `Default` method is expected to mutate the receiver, setting the defaults. */ -var _ webhook.Defaulter = &CronJob{} +type CronJobCustomDefaulter struct{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *CronJob) Default() { - cronjoblog.Info("default", "name", r.Name) +var _ webhook.CustomDefaulter = &CronJobCustomDefaulter{} - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + cronjoblog.Info("CustomDefaulter for CronJob") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if req.Kind.Kind != "CronJob" { + return fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*CronJob) + if !ok { + return fmt.Errorf("expected an CronJob object but got %T", obj) + } + cronjoblog.Info("default", "name", castedObj.GetName()) + + if castedObj.Spec.ConcurrencyPolicy == "" { + castedObj.Spec.ConcurrencyPolicy = AllowConcurrent } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if castedObj.Spec.Suspend == nil { + castedObj.Spec.Suspend = new(bool) } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if castedObj.Spec.SuccessfulJobsHistoryLimit == nil { + castedObj.Spec.SuccessfulJobsHistoryLimit = new(int32) + *castedObj.Spec.SuccessfulJobsHistoryLimit = 3 } + if castedObj.Spec.FailedJobsHistoryLimit == nil { + castedObj.Spec.FailedJobsHistoryLimit = new(int32) + *castedObj.Spec.FailedJobsHistoryLimit = 1 + } + + return nil } /* @@ -118,27 +139,67 @@ validate anything on deletion. // 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. -var _ webhook.Validator = &CronJob{} +type CronJobCustomValidator struct{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *CronJob) ValidateCreate() (admission.Warnings, error) { - cronjoblog.Info("validate create", "name", r.Name) +var _ webhook.CustomValidator = &CronJobCustomValidator{} - return nil, r.validateCronJob() +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cronjoblog.Info("Creation Validation for CronJob") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "CronJob" { + return nil, fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*CronJob) + if !ok { + return nil, fmt.Errorf("expected a CronJob object but got %T", obj) + } + cronjoblog.Info("default", "name", castedObj.GetName()) + + return nil, v.validateCronJob(castedObj) } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *CronJob) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - cronjoblog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + cronjoblog.Info("Update Validation for CronJob") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "CronJob" { + return nil, fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*CronJob) + if !ok { + return nil, fmt.Errorf("expected a CronJob object but got %T", newObj) + } + cronjoblog.Info("default", "name", castedObj.GetName()) - return nil, r.validateCronJob() + return nil, v.validateCronJob(castedObj) } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *CronJob) ValidateDelete() (admission.Warnings, error) { - cronjoblog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cronjoblog.Info("Deletion Validation for CronJob") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "CronJob" { + return nil, fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*CronJob) + if !ok { + return nil, fmt.Errorf("expected a CronJob object but got %T", obj) + } + cronjoblog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } @@ -146,12 +207,12 @@ func (r *CronJob) ValidateDelete() (admission.Warnings, error) { We validate the name and the spec of the CronJob. */ -func (r *CronJob) validateCronJob() error { +func (v *CronJobCustomValidator) validateCronJob(castedObj *CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := v.validateCronJobName(castedObj); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := v.validateCronJobSpec(castedObj); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { @@ -160,7 +221,7 @@ func (r *CronJob) validateCronJob() error { return apierrors.NewInvalid( schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + castedObj.Name, allErrs) } /* @@ -173,11 +234,11 @@ declaring validation by running `controller-gen crd -w`, or [here](/reference/markers/crd-validation.md). */ -func (r *CronJob) validateCronJobSpec() *field.Error { +func (v *CronJobCustomValidator) validateCronJobSpec(castedObj *CronJob) *field.Error { // The field helpers from the kubernetes API machinery help us return nicely // structured validation errors. return validateScheduleFormat( - r.Spec.Schedule, + castedObj.Spec.Schedule, field.NewPath("spec").Child("schedule")) } @@ -202,15 +263,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 { - // The job name length is 63 character like all Kubernetes objects +func (v *CronJobCustomValidator) validateCronJobName(castedObj *CronJob) *field.Error { + if len(castedObj.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.Name, "must be no more than 52 characters") + return field.Invalid(field.NewPath("metadata").Child("name"), castedObj.Name, "must be no more than 52 characters") } return nil } 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..d80f0264d5f 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 @@ -52,6 +52,36 @@ func (in *CronJob) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CronJobCustomDefaulter) DeepCopyInto(out *CronJobCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobCustomDefaulter. +func (in *CronJobCustomDefaulter) DeepCopy() *CronJobCustomDefaulter { + if in == nil { + return nil + } + out := new(CronJobCustomDefaulter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CronJobCustomValidator) DeepCopyInto(out *CronJobCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobCustomValidator. +func (in *CronJobCustomValidator) DeepCopy() *CronJobCustomValidator { + if in == nil { + return nil + } + out := new(CronJobCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJobList) DeepCopyInto(out *CronJobList) { *out = *in diff --git a/docs/book/src/cronjob-tutorial/webhook-implementation.md b/docs/book/src/cronjob-tutorial/webhook-implementation.md index 756bbb1474b..38fc8f56f04 100644 --- a/docs/book/src/cronjob-tutorial/webhook-implementation.md +++ b/docs/book/src/cronjob-tutorial/webhook-implementation.md @@ -1,8 +1,8 @@ # Implementing defaulting/validating webhooks If you want to implement [admission webhooks](../reference/admission-webhook.md) -for your CRD, the only thing you need to do is to implement the `Defaulter` -and (or) the `Validator` interface. +for your CRD, the only thing you need to do is to implement the `CustomDefaulter` +and (or) the `CustomValidator` interface. Kubebuilder takes care of the rest for you, such as diff --git a/go.mod b/go.mod index 65489f669d9..06dc18d7b33 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module sigs.k8s.io/kubebuilder/v4 -go 1.22 +go 1.22.0 + +toolchain go1.22.3 require ( github.com/gobuffalo/flect v1.0.2 @@ -21,10 +23,13 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 927ef1460a0..3662f20bd58 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,12 +16,23 @@ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0= github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -56,8 +68,9 @@ golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hack/docs/internal/cronjob-tutorial/generate_cronjob.go b/hack/docs/internal/cronjob-tutorial/generate_cronjob.go index f59bd88dbb1..147bccf17aa 100644 --- a/hack/docs/internal/cronjob-tutorial/generate_cronjob.go +++ b/hack/docs/internal/cronjob-tutorial/generate_cronjob.go @@ -379,15 +379,23 @@ func updateWebhook(sp *Sample) { err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), `import ( - "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" + "context" + "fmt"`, `import ( + "context" + "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"`) + 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" ) -// log is for logging in this package. -`, WebhookIntro) +// log is for logging in this package.`, WebhookIntro) CheckError("fixing cronjob_webhook.go", err) err = pluginutil.InsertCode( @@ -421,45 +429,48 @@ Then, we set up the webhook with the manager. err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), - `cronjoblog.Info("default", "name", r.Name) - - // TODO(user): fill in your defaulting logic. + `// TODO(user): fill in your defaulting logic. `, WebhookValidate) CheckError("fixing cronjob_webhook.go by adding logic", err) err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), `// TODO(user): fill in your validation logic upon object creation. + return nil, nil`, ` - return nil, r.validateCronJob()`) + return nil, v.validateCronJob(castedObj)`) CheckError("fixing cronjob_webhook.go by fill in your validation", err) err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), `// TODO(user): fill in your validation logic upon object update. + return nil, nil`, ` - return nil, r.validateCronJob()`) + return nil, v.validateCronJob(castedObj)`) CheckError("fixing cronjob_webhook.go by adding validation logic upon object update", err) err = pluginutil.InsertCode( filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), - `func (r *CronJob) ValidateDelete() (admission.Warnings, error) { - cronjoblog.Info("validate delete", "name", r.Name) + `// TODO(user): fill in your validation logic upon object deletion. - // TODO(user): fill in your validation logic upon object deletion. return nil, nil }`, WebhookValidateSpec) - CheckError("fixing cronjob_webhook.go", err) + + CheckError("fixing cronjob_webhook.go upon object deletion", err) err = pluginutil.ReplaceInFile( filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"), `validate anything on deletion. */ + + return nil }`, `validate anything on deletion. -*/`) - CheckError("fixing cronjob_webhook.go by adding comments to validate on deletion", err) +*/ + +`) + CheckError("fixing cronjob_webhook.go by removing wrong return nil", err) } func updateSuiteTest(sp *Sample) { diff --git a/hack/docs/internal/cronjob-tutorial/webhook_implementation.go b/hack/docs/internal/cronjob-tutorial/webhook_implementation.go index 05269a81755..012049296b0 100644 --- a/hack/docs/internal/cronjob-tutorial/webhook_implementation.go +++ b/hack/docs/internal/cronjob-tutorial/webhook_implementation.go @@ -16,17 +16,7 @@ limitations under the License. package cronjob -const WebhookIntro = `import ( - "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" +const WebhookIntro = `"sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) // +kubebuilder:docs-gen:collapse=Go imports @@ -54,22 +44,24 @@ The` + " `" + `Default` + "`" + ` method is expected to mutate the receiver, set */ ` -const WebhookValidate = ` cronjoblog.Info("default", "name", r.Name) +const WebhookValidate = ` - if r.Spec.ConcurrencyPolicy == "" { - r.Spec.ConcurrencyPolicy = AllowConcurrent + if castedObj.Spec.ConcurrencyPolicy == "" { + castedObj.Spec.ConcurrencyPolicy = AllowConcurrent } - if r.Spec.Suspend == nil { - r.Spec.Suspend = new(bool) + if castedObj.Spec.Suspend == nil { + castedObj.Spec.Suspend = new(bool) } - if r.Spec.SuccessfulJobsHistoryLimit == nil { - r.Spec.SuccessfulJobsHistoryLimit = new(int32) - *r.Spec.SuccessfulJobsHistoryLimit = 3 + if castedObj.Spec.SuccessfulJobsHistoryLimit == nil { + castedObj.Spec.SuccessfulJobsHistoryLimit = new(int32) + *castedObj.Spec.SuccessfulJobsHistoryLimit = 3 } - if r.Spec.FailedJobsHistoryLimit == nil { - r.Spec.FailedJobsHistoryLimit = new(int32) - *r.Spec.FailedJobsHistoryLimit = 1 + if castedObj.Spec.FailedJobsHistoryLimit == nil { + castedObj.Spec.FailedJobsHistoryLimit = new(int32) + *castedObj.Spec.FailedJobsHistoryLimit = 1 } + + return nil } /* @@ -100,18 +92,17 @@ Here, however, we just use the same shared validation for` + " `" + `ValidateCre validate anything on deletion. */ ` - const WebhookValidateSpec = ` /* We validate the name and the spec of the CronJob. */ -func (r *CronJob) validateCronJob() error { +func (v *CronJobCustomValidator) validateCronJob(castedObj *CronJob) error { var allErrs field.ErrorList - if err := r.validateCronJobName(); err != nil { + if err := v.validateCronJobName(castedObj); err != nil { allErrs = append(allErrs, err) } - if err := r.validateCronJobSpec(); err != nil { + if err := v.validateCronJobSpec(castedObj); err != nil { allErrs = append(allErrs, err) } if len(allErrs) == 0 { @@ -120,24 +111,24 @@ func (r *CronJob) validateCronJob() error { return apierrors.NewInvalid( schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, - r.Name, allErrs) + castedObj.Name, allErrs) } /* Some fields are declaratively validated by OpenAPI schema. You can find kubebuilder validation markers (prefixed -with` + " `" + `// +kubebuilder:validation` + "`" + `) in the +with ` + "`" + `// +kubebuilder:validation` + "`" + `) in the [Designing an API](api-design.md) section. You can find all of the kubebuilder supported markers for -declaring validation by running` + " `" + `controller-gen crd -w` + "`" + `, +declaring validation by running ` + "`" + `controller-gen crd -w` + "`" + `, or [here](/reference/markers/crd-validation.md). */ -func (r *CronJob) validateCronJobSpec() *field.Error { +func (v *CronJobCustomValidator) validateCronJobSpec(castedObj *CronJob) *field.Error { // The field helpers from the kubernetes API machinery help us return nicely // structured validation errors. return validateScheduleFormat( - r.Spec.Schedule, + castedObj.Spec.Schedule, field.NewPath("spec").Child("schedule")) } @@ -157,20 +148,20 @@ func validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error { Validating the length of a string field can be done declaratively by the validation schema. -But the` + " `" + `ObjectMeta.Name` + "`" + ` field is defined in a shared package under +But the ` + "`" + `ObjectMeta.Name` + "`" + ` field is defined in a shared package under 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 { - // The job name length is 63 character like all Kubernetes objects +func (v *CronJobCustomValidator) validateCronJobName(castedObj *CronJob) *field.Error { + if len(castedObj.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.Name, "must be no more than 52 characters") + return field.Invalid(field.NewPath("metadata").Child("name"), castedObj.Name, "must be no more than 52 characters") } return nil } diff --git a/pkg/plugin/util/util.go b/pkg/plugin/util/util.go index 407e3258407..0e94d653ee9 100644 --- a/pkg/plugin/util/util.go +++ b/pkg/plugin/util/util.go @@ -241,8 +241,8 @@ func ImplementWebhooks(filename string) error { str, err = EnsureExistAndReplace( str, "// TODO(user): fill in your defaulting logic.", - `if r.Spec.Count == 0 { - r.Spec.Count = 5 + `if castedObj.Spec.Count == 0 { + castedObj.Spec.Count = 5 }`) if err != nil { return err @@ -252,7 +252,7 @@ func ImplementWebhooks(filename string) error { str, err = EnsureExistAndReplace( str, "// TODO(user): fill in your validation logic upon object creation.", - `if r.Spec.Count < 0 { + `if castedObj.Spec.Count < 0 { return nil, errors.New(".spec.count must >= 0") }`) if err != nil { @@ -261,7 +261,7 @@ func ImplementWebhooks(filename string) error { str, err = EnsureExistAndReplace( str, "// TODO(user): fill in your validation logic upon object update.", - `if r.Spec.Count < 0 { + `if castedObj.Spec.Count < 0 { return nil, errors.New(".spec.count must >= 0") }`) if err != nil { diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go index 5dee1272f66..3cfe9a5b767 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go @@ -83,16 +83,18 @@ const ( package {{ .Resource.Version }} import ( + {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} + "context" + "fmt" + {{- end }} + ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" - {{- if .Resource.HasValidationWebhook }} + {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - {{- end }} - {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} "sigs.k8s.io/controller-runtime/pkg/webhook" {{- end }} - ) // log is for logging in this package. @@ -102,6 +104,12 @@ var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind } func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + {{- if .Resource.HasValidationWebhook }} + WithValidator(&{{ .Resource.Kind }}CustomValidator{}). + {{- end }} + {{- if .Resource.HasDefaultingWebhook }} + WithDefaulter(&{{ .Resource.Kind }}CustomDefaulter{}). + {{- end }} Complete() } @@ -112,13 +120,29 @@ func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { defaultingWebhookTemplate = ` // +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }} -var _ webhook.Defaulter = &{{ .Resource.Kind }}{} +type {{ .Resource.Kind }}CustomDefaulter struct{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *{{ .Resource.Kind }}) Default() { - {{ lower .Resource.Kind }}log.Info("default", "name", r.Name) +var _ webhook.CustomDefaulter = &{{ .Resource.Kind }}CustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *{{ .Resource.Kind }}CustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + {{ lower .Resource.Kind }}log.Info("CustomDefaulter for {{ .Resource.Kind }}") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "{{ .Resource.Kind }}" { + return fmt.Errorf("expected Kind {{ .Resource.Kind }} got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*{{ .Resource.Kind }}) + if !ok { + return fmt.Errorf("expected an {{ .Resource.Kind }} object but got %T", obj) + } + {{ lower .Resource.Kind }}log.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } ` @@ -129,30 +153,72 @@ func (r *{{ .Resource.Kind }}) Default() { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }} -var _ webhook.Validator = &{{ .Resource.Kind }}{} +type {{ .Resource.Kind }}CustomValidator struct{} + +var _ webhook.CustomValidator = &{{ .Resource.Kind }}CustomValidator{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *{{ .Resource.Kind }}) ValidateCreate() (admission.Warnings, error) { - {{ lower .Resource.Kind }}log.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + {{ lower .Resource.Kind }}log.Info("Creation Validation for {{ .Resource.Kind }}") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "{{ .Resource.Kind }}" { + return nil, fmt.Errorf("expected Kind {{ .Resource.Kind }} got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*{{ .Resource.Kind }}) + if !ok { + return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", obj) + } + {{ lower .Resource.Kind }}log.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *{{ .Resource.Kind }}) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - {{ lower .Resource.Kind }}log.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + {{ lower .Resource.Kind }}log.Info("Update Validation for {{ .Resource.Kind }}") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "{{ .Resource.Kind }}" { + return nil, fmt.Errorf("expected Kind {{ .Resource.Kind }} got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*{{ .Resource.Kind }}) + if !ok { + return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", newObj) + } + {{ lower .Resource.Kind }}log.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *{{ .Resource.Kind }}) ValidateDelete() (admission.Warnings, error) { - {{ lower .Resource.Kind }}log.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *{{ .Resource.Kind }}CustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + {{ lower .Resource.Kind }}log.Info("Deletion Validation for {{ .Resource.Kind }}") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "{{ .Resource.Kind }}" { + return nil, fmt.Errorf("expected Kind {{ .Resource.Kind }} got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*{{ .Resource.Kind }}) + if !ok { + return nil, fmt.Errorf("expected a {{ .Resource.Kind }} object but got %T", obj) + } + {{ lower .Resource.Kind }}log.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. - return nil,nil + + return nil, nil } ` ) diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/captain_webhook.go b/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/captain_webhook.go index b5cba3110d5..a35e466cfdf 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/captain_webhook.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/captain_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,8 @@ var captainlog = logf.Log.WithName("captain-resource") func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&CaptainCustomValidator{}). + WithDefaulter(&CaptainCustomDefaulter{}). Complete() } @@ -38,13 +43,29 @@ func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-captain,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=mcaptain.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Captain{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Captain) Default() { - captainlog.Info("default", "name", r.Name) +type CaptainCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + captainlog.Info("CustomDefaulter for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return fmt.Errorf("expected an Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. @@ -52,28 +73,70 @@ func (r *Captain) Default() { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-crew-testproject-org-v1-captain,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=vcaptain.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Captain{} +type CaptainCustomValidator struct{} + +var _ webhook.CustomValidator = &CaptainCustomValidator{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateCreate() (admission.Warnings, error) { - captainlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Creation Validation for Captain") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - captainlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Update Validation for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", newObj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateDelete() (admission.Warnings, error) { - captainlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Deletion Validation for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/zz_generated.deepcopy.go index 438b50de573..be64ab60474 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/crew/v1/zz_generated.deepcopy.go @@ -51,6 +51,36 @@ func (in *Captain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainCustomDefaulter) DeepCopyInto(out *CaptainCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainCustomDefaulter. +func (in *CaptainCustomDefaulter) DeepCopy() *CaptainCustomDefaulter { + if in == nil { + return nil + } + out := new(CaptainCustomDefaulter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainCustomValidator) DeepCopyInto(out *CaptainCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainCustomValidator. +func (in *CaptainCustomValidator) DeepCopy() *CaptainCustomValidator { + if in == nil { + return nil + } + out := new(CaptainCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CaptainList) DeepCopyInto(out *CaptainList) { *out = *in diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/destroyer_webhook.go b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/destroyer_webhook.go index 3ae740aad03..f5ef1bdd3cd 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/destroyer_webhook.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/destroyer_webhook.go @@ -17,9 +17,14 @@ limitations under the License. package v1 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" ) // log is for logging in this package. @@ -29,6 +34,7 @@ var destroyerlog = logf.Log.WithName("destroyer-resource") func (r *Destroyer) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(&DestroyerCustomDefaulter{}). Complete() } @@ -36,11 +42,27 @@ func (r *Destroyer) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-ship-testproject-org-v1-destroyer,mutating=true,failurePolicy=fail,sideEffects=None,groups=ship.testproject.org,resources=destroyers,verbs=create;update,versions=v1,name=mdestroyer.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Destroyer{} +type DestroyerCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &DestroyerCustomDefaulter{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Destroyer) Default() { - destroyerlog.Info("default", "name", r.Name) +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *DestroyerCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + destroyerlog.Info("CustomDefaulter for Destroyer") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Destroyer" { + return fmt.Errorf("expected Kind Destroyer got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Destroyer) + if !ok { + return fmt.Errorf("expected an Destroyer object but got %T", obj) + } + destroyerlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/zz_generated.deepcopy.go index 8931c51a317..9ddea5b4b23 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1 import ( - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -51,6 +51,21 @@ func (in *Destroyer) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DestroyerCustomDefaulter) DeepCopyInto(out *DestroyerCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestroyerCustomDefaulter. +func (in *DestroyerCustomDefaulter) DeepCopy() *DestroyerCustomDefaulter { + if in == nil { + return nil + } + out := new(DestroyerCustomDefaulter) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestroyerList) DeepCopyInto(out *DestroyerList) { *out = *in diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/cruiser_webhook.go b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/cruiser_webhook.go index 0a1596f4934..78084ddbd3b 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/cruiser_webhook.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/cruiser_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v2alpha1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,7 @@ var cruiserlog = logf.Log.WithName("cruiser-resource") func (r *Cruiser) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&CruiserCustomValidator{}). Complete() } @@ -41,28 +45,70 @@ func (r *Cruiser) SetupWebhookWithManager(mgr ctrl.Manager) error { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-ship-testproject-org-v2alpha1-cruiser,mutating=false,failurePolicy=fail,sideEffects=None,groups=ship.testproject.org,resources=cruisers,verbs=create;update,versions=v2alpha1,name=vcruiser.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Cruiser{} +type CruiserCustomValidator struct{} + +var _ webhook.CustomValidator = &CruiserCustomValidator{} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CruiserCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cruiserlog.Info("Creation Validation for Cruiser") -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Cruiser) ValidateCreate() (admission.Warnings, error) { - cruiserlog.Info("validate create", "name", r.Name) + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Cruiser" { + return nil, fmt.Errorf("expected Kind Cruiser got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Cruiser) + if !ok { + return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) + } + cruiserlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Cruiser) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - cruiserlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CruiserCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + cruiserlog.Info("Update Validation for Cruiser") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Cruiser" { + return nil, fmt.Errorf("expected Kind Cruiser got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Cruiser) + if !ok { + return nil, fmt.Errorf("expected a Cruiser object but got %T", newObj) + } + cruiserlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Cruiser) ValidateDelete() (admission.Warnings, error) { - cruiserlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CruiserCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cruiserlog.Info("Deletion Validation for Cruiser") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Cruiser" { + return nil, fmt.Errorf("expected Kind Cruiser got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Cruiser) + if !ok { + return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) + } + cruiserlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/zz_generated.deepcopy.go index 8f391c1cf27..534d76c75a3 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/ship/v2alpha1/zz_generated.deepcopy.go @@ -51,6 +51,21 @@ func (in *Cruiser) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CruiserCustomValidator) DeepCopyInto(out *CruiserCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CruiserCustomValidator. +func (in *CruiserCustomValidator) DeepCopy() *CruiserCustomValidator { + if in == nil { + return nil + } + out := new(CruiserCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CruiserList) DeepCopyInto(out *CruiserList) { *out = *in diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/v1/lakers_webhook.go b/testdata/project-v4-multigroup-with-deploy-image/api/v1/lakers_webhook.go index fbe8e42180c..87297e54699 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/v1/lakers_webhook.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/v1/lakers_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,8 @@ var lakerslog = logf.Log.WithName("lakers-resource") func (r *Lakers) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&LakersCustomValidator{}). + WithDefaulter(&LakersCustomDefaulter{}). Complete() } @@ -38,13 +43,29 @@ func (r *Lakers) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-testproject-org-v1-lakers,mutating=true,failurePolicy=fail,sideEffects=None,groups=testproject.org,resources=lakers,verbs=create;update,versions=v1,name=mlakers.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Lakers{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Lakers) Default() { - lakerslog.Info("default", "name", r.Name) +type LakersCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &LakersCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *LakersCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + lakerslog.Info("CustomDefaulter for Lakers") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Lakers) + if !ok { + return fmt.Errorf("expected an Lakers object but got %T", obj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. @@ -52,28 +73,70 @@ func (r *Lakers) Default() { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-testproject-org-v1-lakers,mutating=false,failurePolicy=fail,sideEffects=None,groups=testproject.org,resources=lakers,verbs=create;update,versions=v1,name=vlakers.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Lakers{} +type LakersCustomValidator struct{} + +var _ webhook.CustomValidator = &LakersCustomValidator{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Lakers) ValidateCreate() (admission.Warnings, error) { - lakerslog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *LakersCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + lakerslog.Info("Creation Validation for Lakers") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return nil, fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Lakers) + if !ok { + return nil, fmt.Errorf("expected a Lakers object but got %T", obj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Lakers) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - lakerslog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *LakersCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + lakerslog.Info("Update Validation for Lakers") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return nil, fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Lakers) + if !ok { + return nil, fmt.Errorf("expected a Lakers object but got %T", newObj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Lakers) ValidateDelete() (admission.Warnings, error) { - lakerslog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *LakersCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + lakerslog.Info("Deletion Validation for Lakers") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return nil, fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Lakers) + if !ok { + return nil, fmt.Errorf("expected a Lakers object but got %T", obj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } diff --git a/testdata/project-v4-multigroup-with-deploy-image/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup-with-deploy-image/api/v1/zz_generated.deepcopy.go index 64e471813a8..591a89ab556 100644 --- a/testdata/project-v4-multigroup-with-deploy-image/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup-with-deploy-image/api/v1/zz_generated.deepcopy.go @@ -51,6 +51,36 @@ func (in *Lakers) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LakersCustomDefaulter) DeepCopyInto(out *LakersCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LakersCustomDefaulter. +func (in *LakersCustomDefaulter) DeepCopy() *LakersCustomDefaulter { + if in == nil { + return nil + } + out := new(LakersCustomDefaulter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LakersCustomValidator) DeepCopyInto(out *LakersCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LakersCustomValidator. +func (in *LakersCustomValidator) DeepCopy() *LakersCustomValidator { + if in == nil { + return nil + } + out := new(LakersCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LakersList) DeepCopyInto(out *LakersList) { *out = *in diff --git a/testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go b/testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go index b5cba3110d5..a35e466cfdf 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go +++ b/testdata/project-v4-multigroup/api/crew/v1/captain_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,8 @@ var captainlog = logf.Log.WithName("captain-resource") func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&CaptainCustomValidator{}). + WithDefaulter(&CaptainCustomDefaulter{}). Complete() } @@ -38,13 +43,29 @@ func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-captain,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=mcaptain.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Captain{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Captain) Default() { - captainlog.Info("default", "name", r.Name) +type CaptainCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + captainlog.Info("CustomDefaulter for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return fmt.Errorf("expected an Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. @@ -52,28 +73,70 @@ func (r *Captain) Default() { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-crew-testproject-org-v1-captain,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=vcaptain.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Captain{} +type CaptainCustomValidator struct{} + +var _ webhook.CustomValidator = &CaptainCustomValidator{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateCreate() (admission.Warnings, error) { - captainlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Creation Validation for Captain") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - captainlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Update Validation for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", newObj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateDelete() (admission.Warnings, error) { - captainlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Deletion Validation for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } 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..be64ab60474 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 @@ -51,6 +51,36 @@ func (in *Captain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainCustomDefaulter) DeepCopyInto(out *CaptainCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainCustomDefaulter. +func (in *CaptainCustomDefaulter) DeepCopy() *CaptainCustomDefaulter { + if in == nil { + return nil + } + out := new(CaptainCustomDefaulter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainCustomValidator) DeepCopyInto(out *CaptainCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainCustomValidator. +func (in *CaptainCustomValidator) DeepCopy() *CaptainCustomValidator { + if in == nil { + return nil + } + out := new(CaptainCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CaptainList) DeepCopyInto(out *CaptainList) { *out = *in diff --git a/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go b/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go index 3ae740aad03..f5ef1bdd3cd 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go +++ b/testdata/project-v4-multigroup/api/ship/v1/destroyer_webhook.go @@ -17,9 +17,14 @@ limitations under the License. package v1 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" ) // log is for logging in this package. @@ -29,6 +34,7 @@ var destroyerlog = logf.Log.WithName("destroyer-resource") func (r *Destroyer) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(&DestroyerCustomDefaulter{}). Complete() } @@ -36,11 +42,27 @@ func (r *Destroyer) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-ship-testproject-org-v1-destroyer,mutating=true,failurePolicy=fail,sideEffects=None,groups=ship.testproject.org,resources=destroyers,verbs=create;update,versions=v1,name=mdestroyer.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Destroyer{} +type DestroyerCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &DestroyerCustomDefaulter{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Destroyer) Default() { - destroyerlog.Info("default", "name", r.Name) +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *DestroyerCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + destroyerlog.Info("CustomDefaulter for Destroyer") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Destroyer" { + return fmt.Errorf("expected Kind Destroyer got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Destroyer) + if !ok { + return fmt.Errorf("expected an Destroyer object but got %T", obj) + } + destroyerlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return 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 8931c51a317..9ddea5b4b23 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 ( - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -51,6 +51,21 @@ func (in *Destroyer) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DestroyerCustomDefaulter) DeepCopyInto(out *DestroyerCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestroyerCustomDefaulter. +func (in *DestroyerCustomDefaulter) DeepCopy() *DestroyerCustomDefaulter { + if in == nil { + return nil + } + out := new(DestroyerCustomDefaulter) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestroyerList) DeepCopyInto(out *DestroyerList) { *out = *in diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go b/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go index 0a1596f4934..78084ddbd3b 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go +++ b/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v2alpha1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,7 @@ var cruiserlog = logf.Log.WithName("cruiser-resource") func (r *Cruiser) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&CruiserCustomValidator{}). Complete() } @@ -41,28 +45,70 @@ func (r *Cruiser) SetupWebhookWithManager(mgr ctrl.Manager) error { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-ship-testproject-org-v2alpha1-cruiser,mutating=false,failurePolicy=fail,sideEffects=None,groups=ship.testproject.org,resources=cruisers,verbs=create;update,versions=v2alpha1,name=vcruiser.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Cruiser{} +type CruiserCustomValidator struct{} + +var _ webhook.CustomValidator = &CruiserCustomValidator{} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CruiserCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cruiserlog.Info("Creation Validation for Cruiser") -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Cruiser) ValidateCreate() (admission.Warnings, error) { - cruiserlog.Info("validate create", "name", r.Name) + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Cruiser" { + return nil, fmt.Errorf("expected Kind Cruiser got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Cruiser) + if !ok { + return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) + } + cruiserlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Cruiser) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - cruiserlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CruiserCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + cruiserlog.Info("Update Validation for Cruiser") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Cruiser" { + return nil, fmt.Errorf("expected Kind Cruiser got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Cruiser) + if !ok { + return nil, fmt.Errorf("expected a Cruiser object but got %T", newObj) + } + cruiserlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Cruiser) ValidateDelete() (admission.Warnings, error) { - cruiserlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CruiserCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cruiserlog.Info("Deletion Validation for Cruiser") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Cruiser" { + return nil, fmt.Errorf("expected Kind Cruiser got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Cruiser) + if !ok { + return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) + } + cruiserlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, 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..534d76c75a3 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 @@ -51,6 +51,21 @@ func (in *Cruiser) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CruiserCustomValidator) DeepCopyInto(out *CruiserCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CruiserCustomValidator. +func (in *CruiserCustomValidator) DeepCopy() *CruiserCustomValidator { + if in == nil { + return nil + } + out := new(CruiserCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CruiserList) DeepCopyInto(out *CruiserList) { *out = *in diff --git a/testdata/project-v4-multigroup/api/v1/lakers_webhook.go b/testdata/project-v4-multigroup/api/v1/lakers_webhook.go index fbe8e42180c..87297e54699 100644 --- a/testdata/project-v4-multigroup/api/v1/lakers_webhook.go +++ b/testdata/project-v4-multigroup/api/v1/lakers_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,8 @@ var lakerslog = logf.Log.WithName("lakers-resource") func (r *Lakers) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&LakersCustomValidator{}). + WithDefaulter(&LakersCustomDefaulter{}). Complete() } @@ -38,13 +43,29 @@ func (r *Lakers) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-testproject-org-v1-lakers,mutating=true,failurePolicy=fail,sideEffects=None,groups=testproject.org,resources=lakers,verbs=create;update,versions=v1,name=mlakers.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Lakers{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Lakers) Default() { - lakerslog.Info("default", "name", r.Name) +type LakersCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &LakersCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *LakersCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + lakerslog.Info("CustomDefaulter for Lakers") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Lakers) + if !ok { + return fmt.Errorf("expected an Lakers object but got %T", obj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. @@ -52,28 +73,70 @@ func (r *Lakers) Default() { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-testproject-org-v1-lakers,mutating=false,failurePolicy=fail,sideEffects=None,groups=testproject.org,resources=lakers,verbs=create;update,versions=v1,name=vlakers.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Lakers{} +type LakersCustomValidator struct{} + +var _ webhook.CustomValidator = &LakersCustomValidator{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Lakers) ValidateCreate() (admission.Warnings, error) { - lakerslog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *LakersCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + lakerslog.Info("Creation Validation for Lakers") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return nil, fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Lakers) + if !ok { + return nil, fmt.Errorf("expected a Lakers object but got %T", obj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Lakers) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - lakerslog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *LakersCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + lakerslog.Info("Update Validation for Lakers") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return nil, fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Lakers) + if !ok { + return nil, fmt.Errorf("expected a Lakers object but got %T", newObj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Lakers) ValidateDelete() (admission.Warnings, error) { - lakerslog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *LakersCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + lakerslog.Info("Deletion Validation for Lakers") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Lakers" { + return nil, fmt.Errorf("expected Kind Lakers got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Lakers) + if !ok { + return nil, fmt.Errorf("expected a Lakers object but got %T", obj) + } + lakerslog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } diff --git a/testdata/project-v4-multigroup/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/v1/zz_generated.deepcopy.go index 64e471813a8..591a89ab556 100644 --- a/testdata/project-v4-multigroup/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/v1/zz_generated.deepcopy.go @@ -51,6 +51,36 @@ func (in *Lakers) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LakersCustomDefaulter) DeepCopyInto(out *LakersCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LakersCustomDefaulter. +func (in *LakersCustomDefaulter) DeepCopy() *LakersCustomDefaulter { + if in == nil { + return nil + } + out := new(LakersCustomDefaulter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LakersCustomValidator) DeepCopyInto(out *LakersCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LakersCustomValidator. +func (in *LakersCustomValidator) DeepCopy() *LakersCustomValidator { + if in == nil { + return nil + } + out := new(LakersCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LakersList) DeepCopyInto(out *LakersList) { *out = *in diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/memcached_webhook.go b/testdata/project-v4-with-deploy-image/api/v1alpha1/memcached_webhook.go index c5009402948..54b4a82c427 100644 --- a/testdata/project-v4-with-deploy-image/api/v1alpha1/memcached_webhook.go +++ b/testdata/project-v4-with-deploy-image/api/v1alpha1/memcached_webhook.go @@ -17,6 +17,9 @@ 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" @@ -31,6 +34,7 @@ var memcachedlog = logf.Log.WithName("memcached-resource") func (r *Memcached) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&MemcachedCustomValidator{}). Complete() } @@ -41,28 +45,70 @@ func (r *Memcached) SetupWebhookWithManager(mgr ctrl.Manager) error { // 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.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Memcached{} +type MemcachedCustomValidator struct{} + +var _ webhook.CustomValidator = &MemcachedCustomValidator{} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + memcachedlog.Info("Creation Validation for Memcached") -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Memcached) ValidateCreate() (admission.Warnings, error) { - memcachedlog.Info("validate create", "name", r.Name) + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Memcached" { + return nil, fmt.Errorf("expected Kind Memcached got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Memcached) + if !ok { + return nil, fmt.Errorf("expected a Memcached object but got %T", obj) + } + memcachedlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Memcached) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - memcachedlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *MemcachedCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + memcachedlog.Info("Update Validation for Memcached") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Memcached" { + return nil, fmt.Errorf("expected Kind Memcached got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Memcached) + if !ok { + return nil, fmt.Errorf("expected a Memcached object but got %T", newObj) + } + memcachedlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Memcached) ValidateDelete() (admission.Warnings, error) { - memcachedlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *MemcachedCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + memcachedlog.Info("Deletion Validation for Memcached") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Memcached" { + return nil, fmt.Errorf("expected Kind Memcached got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Memcached) + if !ok { + return nil, fmt.Errorf("expected a Memcached object but got %T", obj) + } + memcachedlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go index a41c7b842d1..f58df5b81bd 100644 --- a/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go @@ -148,6 +148,21 @@ func (in *Memcached) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedCustomValidator) DeepCopyInto(out *MemcachedCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedCustomValidator. +func (in *MemcachedCustomValidator) DeepCopy() *MemcachedCustomValidator { + if in == nil { + return nil + } + out := new(MemcachedCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MemcachedList) DeepCopyInto(out *MemcachedList) { *out = *in diff --git a/testdata/project-v4/api/v1/admiral_webhook.go b/testdata/project-v4/api/v1/admiral_webhook.go index 12c1d7d42bd..efee81ab4ab 100644 --- a/testdata/project-v4/api/v1/admiral_webhook.go +++ b/testdata/project-v4/api/v1/admiral_webhook.go @@ -17,9 +17,14 @@ limitations under the License. package v1 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" ) // log is for logging in this package. @@ -29,6 +34,7 @@ var admirallog = logf.Log.WithName("admiral-resource") func (r *Admiral) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(&AdmiralCustomDefaulter{}). Complete() } @@ -36,11 +42,27 @@ func (r *Admiral) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-admiral,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=admirales,verbs=create;update,versions=v1,name=madmiral.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Admiral{} +type AdmiralCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &AdmiralCustomDefaulter{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Admiral) Default() { - admirallog.Info("default", "name", r.Name) +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *AdmiralCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + admirallog.Info("CustomDefaulter for Admiral") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Admiral" { + return fmt.Errorf("expected Kind Admiral got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Admiral) + if !ok { + return fmt.Errorf("expected an Admiral object but got %T", obj) + } + admirallog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } diff --git a/testdata/project-v4/api/v1/captain_webhook.go b/testdata/project-v4/api/v1/captain_webhook.go index b5cba3110d5..a35e466cfdf 100644 --- a/testdata/project-v4/api/v1/captain_webhook.go +++ b/testdata/project-v4/api/v1/captain_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v1 import ( + "context" + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -31,6 +34,8 @@ var captainlog = logf.Log.WithName("captain-resource") func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&CaptainCustomValidator{}). + WithDefaulter(&CaptainCustomDefaulter{}). Complete() } @@ -38,13 +43,29 @@ func (r *Captain) SetupWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-captain,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=mcaptain.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &Captain{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Captain) Default() { - captainlog.Info("default", "name", r.Name) +type CaptainCustomDefaulter struct{} + +var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { + captainlog.Info("CustomDefaulter for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return fmt.Errorf("expected an Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your defaulting logic. + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. @@ -52,28 +73,70 @@ func (r *Captain) Default() { // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. // +kubebuilder:webhook:path=/validate-crew-testproject-org-v1-captain,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=vcaptain.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &Captain{} +type CaptainCustomValidator struct{} + +var _ webhook.CustomValidator = &CaptainCustomValidator{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateCreate() (admission.Warnings, error) { - captainlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Creation Validation for Captain") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object creation. + return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - captainlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Update Validation for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := newObj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", newObj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object update. + return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Captain) ValidateDelete() (admission.Warnings, error) { - captainlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (v *CaptainCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + captainlog.Info("Deletion Validation for Captain") + req, err := admission.RequestFromContext(ctx) + if err != nil { + return nil, fmt.Errorf("expected admission.Request in ctx: %w", err) + } + if req.Kind.Kind != "Captain" { + return nil, fmt.Errorf("expected Kind Captain got %q", req.Kind.Kind) + } + castedObj, ok := obj.(*Captain) + if !ok { + return nil, fmt.Errorf("expected a Captain object but got %T", obj) + } + captainlog.Info("default", "name", castedObj.GetName()) // TODO(user): fill in your validation logic upon object deletion. + return nil, nil } diff --git a/testdata/project-v4/api/v1/zz_generated.deepcopy.go b/testdata/project-v4/api/v1/zz_generated.deepcopy.go index 4ec350e23aa..164009dc4ec 100644 --- a/testdata/project-v4/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4/api/v1/zz_generated.deepcopy.go @@ -51,6 +51,21 @@ func (in *Admiral) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmiralCustomDefaulter) DeepCopyInto(out *AdmiralCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralCustomDefaulter. +func (in *AdmiralCustomDefaulter) DeepCopy() *AdmiralCustomDefaulter { + if in == nil { + return nil + } + out := new(AdmiralCustomDefaulter) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdmiralList) DeepCopyInto(out *AdmiralList) { *out = *in @@ -140,6 +155,36 @@ func (in *Captain) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainCustomDefaulter) DeepCopyInto(out *CaptainCustomDefaulter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainCustomDefaulter. +func (in *CaptainCustomDefaulter) DeepCopy() *CaptainCustomDefaulter { + if in == nil { + return nil + } + out := new(CaptainCustomDefaulter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainCustomValidator) DeepCopyInto(out *CaptainCustomValidator) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainCustomValidator. +func (in *CaptainCustomValidator) DeepCopy() *CaptainCustomValidator { + if in == nil { + return nil + } + out := new(CaptainCustomValidator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CaptainList) DeepCopyInto(out *CaptainList) { *out = *in