Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚠️ : (go/v4): Replace usage of deprecated webhook.Validator and webhook.Defaulter interfaces #4060

Merged
merged 1 commit into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -46,6 +49,13 @@ 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{}).
camilamacedo86 marked this conversation as resolved.
Show resolved Hide resolved
WithDefaulter(&CronJobCustomDefaulter{
DefaultConcurrencyPolicy: AllowConcurrent,
DefaultSuspend: false,
DefaultSuccessfulJobsHistoryLimit: 3,
DefaultFailedJobsHistoryLimit: 1,
}).
Complete()
}

Expand All @@ -59,32 +69,53 @@ The meaning of each marker can be found [here](/reference/markers/webhook.md).
// +kubebuilder:webhook:path=/mutate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=true,failurePolicy=fail,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=mcronjob.kb.io,sideEffects=None,admissionReviewVersions=v1

/*
We use the `webhook.Defaulter` interface to set defaults to our CRD.
We use the `webhook.CustomDefaulter` interface to set defaults to our CRD.
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{}
// +kubebuilder:object:generate=false
// CronJobCustomDefaulter struct is responsible for setting default values on the custom resource of the
// Kind CronJob when those are created or updated.
//
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
// as it is used only for temporary operations and does not need to be deeply copied.
type CronJobCustomDefaulter struct {

// Default values for various CronJob fields
DefaultConcurrencyPolicy ConcurrencyPolicy
DefaultSuspend bool
DefaultSuccessfulJobsHistoryLimit int32
DefaultFailedJobsHistoryLimit int32
}

// 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 Kind CronJob
func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
cronjob, ok := obj.(*CronJob)
if !ok {
return fmt.Errorf("expected an CronJob object but got %T", obj)
}
if r.Spec.Suspend == nil {
r.Spec.Suspend = new(bool)
cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName())

if cronjob.Spec.ConcurrencyPolicy == "" {
cronjob.Spec.ConcurrencyPolicy = AllowConcurrent
}
if cronjob.Spec.Suspend == nil {
cronjob.Spec.Suspend = new(bool)
}
if r.Spec.SuccessfulJobsHistoryLimit == nil {
r.Spec.SuccessfulJobsHistoryLimit = new(int32)
*r.Spec.SuccessfulJobsHistoryLimit = 3
if cronjob.Spec.SuccessfulJobsHistoryLimit == nil {
cronjob.Spec.SuccessfulJobsHistoryLimit = new(int32)
*cronjob.Spec.SuccessfulJobsHistoryLimit = 3
}
if r.Spec.FailedJobsHistoryLimit == nil {
r.Spec.FailedJobsHistoryLimit = new(int32)
*r.Spec.FailedJobsHistoryLimit = 1
if cronjob.Spec.FailedJobsHistoryLimit == nil {
cronjob.Spec.FailedJobsHistoryLimit = new(int32)
*cronjob.Spec.FailedJobsHistoryLimit = 1
}

return nil
}

/*
Expand All @@ -101,7 +132,7 @@ sometimes more advanced use cases call for complex validation.
For instance, we'll see below that we use this to validate a well-formed cron
schedule without making up a long regular expression.

If `webhook.Validator` interface is implemented, a webhook will automatically be
If `webhook.CustomValidator` interface is implemented, a webhook will automatically be
served that calls the validation.

The `ValidateCreate`, `ValidateUpdate` and `ValidateDelete` methods are expected
Expand All @@ -118,27 +149,50 @@ 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{}
// +kubebuilder:object:generate=false
// CronJobCustomValidator struct is responsible for validating the CronJob resource
// when it is created, updated, or deleted.
//
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
// as this struct is used only for temporary operations and does not need to be deeply copied.
type CronJobCustomValidator struct {
//TODO(user): Add more fields as needed for validation
}

var _ webhook.CustomValidator = &CronJobCustomValidator{}

// 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)
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob
func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
cronjob, ok := obj.(*CronJob)
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
}
cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName())

return nil, r.validateCronJob()
return nil, cronjob.validateCronJob()
}

// 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 CronJob
func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
cronjob, ok := newObj.(*CronJob)
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", newObj)
}
cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName())

return nil, r.validateCronJob()
return nil, cronjob.validateCronJob()
}

// 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 CronJob
func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
cronjob, ok := obj.(*CronJob)
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
}
cronjoblog.Info("Validation for CronJob upon deletion", "name", cronjob.GetName())

// TODO(user): fill in your validation logic upon object deletion.

return nil, nil
}

Expand Down Expand Up @@ -204,13 +258,13 @@ 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
// 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"), r.ObjectMeta.Name, "must be no more than 52 characters")
}
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions docs/book/src/cronjob-tutorial/webhook-implementation.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,10 +23,13 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // 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.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
15 changes: 14 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand All @@ -15,12 +16,23 @@ github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQu
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
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.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
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=
Expand Down Expand Up @@ -56,8 +68,9 @@ golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
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=
Expand Down
61 changes: 45 additions & 16 deletions hack/docs/internal/cronjob-tutorial/generate_cronjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,20 @@ func (sp *Sample) updateWebhook() {
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"`)
hackutils.CheckError("add extra imports to cronjob_webhook.go", err)

err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
`"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// nolint:unused
Expand Down Expand Up @@ -428,45 +437,65 @@ 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): Add more fields as needed for defaulting`, fragmentForDefaultFields)
hackutils.CheckError("fixing cronjob_webhook.go by replacing TODO in Defaulter", err)

// TODO(user): fill in your defaulting logic.
`, WebhookValidate)
err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
`WithDefaulter(&CronJobCustomDefaulter{}).`,
`WithDefaulter(&CronJobCustomDefaulter{
DefaultConcurrencyPolicy: AllowConcurrent,
DefaultSuspend: false,
DefaultSuccessfulJobsHistoryLimit: 3,
DefaultFailedJobsHistoryLimit: 1,
}).`)
hackutils.CheckError("replacing WithDefaulter call in cronjob_webhook.go", err)

err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
`// TODO(user): fill in your defaulting logic.
`, WebhookDefaultingSettings)
hackutils.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, cronjob.validateCronJob()`)
hackutils.CheckError("fixing cronjob_webhook.go by fill in your validation", err)

err = pluginutil.ReplaceInFile(
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
`// TODO(user): fill in your validation logic upon object update.

return nil, nil`,
`
return nil, r.validateCronJob()`)
return nil, cronjob.validateCronJob()`)
hackutils.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)
hackutils.CheckError("fixing cronjob_webhook.go", err)

hackutils.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.
*/`)
hackutils.CheckError("fixing cronjob_webhook.go by adding comments to validate on deletion", err)
*/

`)
hackutils.CheckError("fixing cronjob_webhook.go by removing wrong return nil", err)

}

func (sp *Sample) updateSuiteTest() {
Expand Down
Loading
Loading