Skip to content

Commit b502672

Browse files
Remove the usage of deprecated functions for webhooks
Motivation: kubernetes-sigs/controller-runtime#2877
1 parent c0ba5ce commit b502672

File tree

22 files changed

+1025
-245
lines changed

22 files changed

+1025
-245
lines changed

docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_webhook.go

Lines changed: 118 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ limitations under the License.
1818
package v1
1919

2020
import (
21+
"context"
22+
"fmt"
2123
"github.com/robfig/cron"
2224
apierrors "k8s.io/apimachinery/pkg/api/errors"
23-
"k8s.io/apimachinery/pkg/runtime"
2425
"k8s.io/apimachinery/pkg/runtime/schema"
2526
validationutils "k8s.io/apimachinery/pkg/util/validation"
2627
"k8s.io/apimachinery/pkg/util/validation/field"
28+
29+
"k8s.io/apimachinery/pkg/runtime"
2730
ctrl "sigs.k8s.io/controller-runtime"
2831
logf "sigs.k8s.io/controller-runtime/pkg/log"
2932
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -46,6 +49,13 @@ Then, we set up the webhook with the manager.
4649
func (r *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error {
4750
return ctrl.NewWebhookManagedBy(mgr).
4851
For(r).
52+
WithValidator(&CronJobCustomValidator{}).
53+
WithDefaulter(&CronJobCustomDefaulter{
54+
DefaultConcurrencyPolicy: AllowConcurrent,
55+
DefaultSuspend: false,
56+
DefaultSuccessfulJobsHistoryLimit: 3,
57+
DefaultFailedJobsHistoryLimit: 1,
58+
}).
4959
Complete()
5060
}
5161

@@ -59,32 +69,61 @@ The meaning of each marker can be found [here](/reference/markers/webhook.md).
5969
// +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
6070

6171
/*
62-
We use the `webhook.Defaulter` interface to set defaults to our CRD.
72+
We use the `webhook.CustomDefaulter` interface to set defaults to our CRD.
6373
A webhook will automatically be served that calls this defaulting.
6474
6575
The `Default` method is expected to mutate the receiver, setting the defaults.
6676
*/
6777

68-
var _ webhook.Defaulter = &CronJob{}
78+
// +kubebuilder:object:generate=false
79+
// CronJobCustomDefaulter struct is responsible for setting default values on the custom resource of the
80+
// Kind CronJob when those are created or updated.
81+
//
82+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
83+
// as it is used only for temporary operations and does not need to be deeply copied.
84+
type CronJobCustomDefaulter struct {
85+
cronjob *CronJob
86+
87+
// Default values for various CronJob fields
88+
DefaultConcurrencyPolicy ConcurrencyPolicy
89+
DefaultSuspend bool
90+
DefaultSuccessfulJobsHistoryLimit int32
91+
DefaultFailedJobsHistoryLimit int32
92+
}
93+
94+
var _ webhook.CustomDefaulter = &CronJobCustomDefaulter{}
6995

70-
// Default implements webhook.Defaulter so a webhook will be registered for the type
71-
func (r *CronJob) Default() {
72-
cronjoblog.Info("default", "name", r.Name)
96+
// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob
97+
func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
98+
cronjoblog.Info("CustomDefaulter for CronJob")
99+
cronjob, ok := obj.(*CronJob)
100+
if !ok {
101+
return fmt.Errorf("expected an CronJob object but got %T", obj)
102+
}
103+
cronjoblog.Info("default", "name", cronjob.GetName())
104+
105+
// Assign the CronJob object to the CronJobCustomDefaulter struct field
106+
// so that it can be used in the defaulting logic
107+
d.cronjob = cronjob
73108

74-
if r.Spec.ConcurrencyPolicy == "" {
75-
r.Spec.ConcurrencyPolicy = AllowConcurrent
109+
// Set default values using the values from the struct
110+
if cronjob.Spec.ConcurrencyPolicy == "" {
111+
cronjob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy
76112
}
77-
if r.Spec.Suspend == nil {
78-
r.Spec.Suspend = new(bool)
113+
if cronjob.Spec.Suspend == nil {
114+
cronjob.Spec.Suspend = new(bool)
115+
*cronjob.Spec.Suspend = d.DefaultSuspend
79116
}
80-
if r.Spec.SuccessfulJobsHistoryLimit == nil {
81-
r.Spec.SuccessfulJobsHistoryLimit = new(int32)
82-
*r.Spec.SuccessfulJobsHistoryLimit = 3
117+
if cronjob.Spec.SuccessfulJobsHistoryLimit == nil {
118+
cronjob.Spec.SuccessfulJobsHistoryLimit = new(int32)
119+
*cronjob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit
83120
}
84-
if r.Spec.FailedJobsHistoryLimit == nil {
85-
r.Spec.FailedJobsHistoryLimit = new(int32)
86-
*r.Spec.FailedJobsHistoryLimit = 1
121+
if cronjob.Spec.FailedJobsHistoryLimit == nil {
122+
cronjob.Spec.FailedJobsHistoryLimit = new(int32)
123+
*cronjob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit
87124
}
125+
126+
return nil
88127
}
89128

90129
/*
@@ -101,7 +140,7 @@ sometimes more advanced use cases call for complex validation.
101140
For instance, we'll see below that we use this to validate a well-formed cron
102141
schedule without making up a long regular expression.
103142
104-
If `webhook.Validator` interface is implemented, a webhook will automatically be
143+
If `webhook.CustomValidator` interface is implemented, a webhook will automatically be
105144
served that calls the validation.
106145
107146
The `ValidateCreate`, `ValidateUpdate` and `ValidateDelete` methods are expected
@@ -118,40 +157,80 @@ validate anything on deletion.
118157
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
119158
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
120159

121-
var _ webhook.Validator = &CronJob{}
160+
// +kubebuilder:object:generate=false
161+
// CronJobCustomValidator struct is responsible for validating the CronJob resource
162+
// when it is created, updated, or deleted.
163+
//
164+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
165+
// as this struct is used only for temporary operations and does not need to be deeply copied.
166+
type CronJobCustomValidator struct {
167+
cronjob *CronJob
122168

123-
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
124-
func (r *CronJob) ValidateCreate() (admission.Warnings, error) {
125-
cronjoblog.Info("validate create", "name", r.Name)
169+
//TODO(user): Add more fields as needed for validation
170+
}
126171

127-
return nil, r.validateCronJob()
172+
var _ webhook.CustomValidator = &CronJobCustomValidator{}
173+
174+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob
175+
func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
176+
cronjoblog.Info("Creation Validation for CronJob")
177+
cronjob, ok := obj.(*CronJob)
178+
if !ok {
179+
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
180+
}
181+
cronjoblog.Info("default", "name", cronjob.GetName())
182+
183+
// Assign the CronJob object to the CronJobCustomValidator struct field
184+
// so that it can be used in the validation logic
185+
v.cronjob = cronjob
186+
187+
return nil, v.validateCronJob()
128188
}
129189

130-
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
131-
func (r *CronJob) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
132-
cronjoblog.Info("validate update", "name", r.Name)
190+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob
191+
func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
192+
cronjoblog.Info("Update Validation for CronJob")
193+
cronjob, ok := newObj.(*CronJob)
194+
if !ok {
195+
return nil, fmt.Errorf("expected a CronJob object but got %T", newObj)
196+
}
197+
cronjoblog.Info("default", "name", cronjob.GetName())
133198

134-
return nil, r.validateCronJob()
199+
// Assign the CronJob object to the CronJobCustomValidator struct field
200+
// so that it can be used in the validation logic
201+
v.cronjob = cronjob
202+
203+
return nil, v.validateCronJob()
135204
}
136205

137-
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
138-
func (r *CronJob) ValidateDelete() (admission.Warnings, error) {
139-
cronjoblog.Info("validate delete", "name", r.Name)
206+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob
207+
func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
208+
cronjoblog.Info("Deletion Validation for CronJob")
209+
cronjob, ok := obj.(*CronJob)
210+
if !ok {
211+
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
212+
}
213+
cronjoblog.Info("default", "name", cronjob.GetName())
214+
215+
// Assign the CronJob object to the CronJobCustomValidator struct field
216+
// so that it can be used in the validation logic
217+
v.cronjob = cronjob
140218

141219
// TODO(user): fill in your validation logic upon object deletion.
220+
142221
return nil, nil
143222
}
144223

145224
/*
146225
We validate the name and the spec of the CronJob.
147226
*/
148227

149-
func (r *CronJob) validateCronJob() error {
228+
func (v *CronJobCustomValidator) validateCronJob() error {
150229
var allErrs field.ErrorList
151-
if err := r.validateCronJobName(); err != nil {
230+
if err := v.validateCronJobName(); err != nil {
152231
allErrs = append(allErrs, err)
153232
}
154-
if err := r.validateCronJobSpec(); err != nil {
233+
if err := v.validateCronJobSpec(); err != nil {
155234
allErrs = append(allErrs, err)
156235
}
157236
if len(allErrs) == 0 {
@@ -160,7 +239,7 @@ func (r *CronJob) validateCronJob() error {
160239

161240
return apierrors.NewInvalid(
162241
schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"},
163-
r.Name, allErrs)
242+
v.cronjob.ObjectMeta.Name, allErrs)
164243
}
165244

166245
/*
@@ -173,11 +252,11 @@ declaring validation by running `controller-gen crd -w`,
173252
or [here](/reference/markers/crd-validation.md).
174253
*/
175254

176-
func (r *CronJob) validateCronJobSpec() *field.Error {
255+
func (v *CronJobCustomValidator) validateCronJobSpec() *field.Error {
177256
// The field helpers from the kubernetes API machinery help us return nicely
178257
// structured validation errors.
179258
return validateScheduleFormat(
180-
r.Spec.Schedule,
259+
v.cronjob.Spec.Schedule,
181260
field.NewPath("spec").Child("schedule"))
182261
}
183262

@@ -202,15 +281,15 @@ the apimachinery repo, so we can't declaratively validate it using
202281
the validation schema.
203282
*/
204283

205-
func (r *CronJob) validateCronJobName() *field.Error {
206-
if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 {
207-
// The job name length is 63 character like all Kubernetes objects
284+
func (v *CronJobCustomValidator) validateCronJobName() *field.Error {
285+
if len(v.cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 {
286+
// The job name length is 63 characters like all Kubernetes objects
208287
// (which must fit in a DNS subdomain). The cronjob controller appends
209288
// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating
210289
// a job. The job name length limit is 63 characters. Therefore cronjob
211290
// names must have length <= 63-11=52. If we don't validate this here,
212291
// then job creation will fail later.
213-
return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters")
292+
return field.Invalid(field.NewPath("metadata").Child("name"), v.cronjob.ObjectMeta.Name, "must be no more than 52 characters")
214293
}
215294
return nil
216295
}

docs/book/src/cronjob-tutorial/webhook-implementation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Implementing defaulting/validating webhooks
22

33
If you want to implement [admission webhooks](../reference/admission-webhook.md)
4-
for your CRD, the only thing you need to do is to implement the `Defaulter`
5-
and (or) the `Validator` interface.
4+
for your CRD, the only thing you need to do is to implement the `CustomDefaulter`
5+
and (or) the `CustomValidator` interface.
66

77
Kubebuilder takes care of the rest for you, such as
88

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module sigs.k8s.io/kubebuilder/v4
22

3-
go 1.22
3+
go 1.22.0
4+
5+
toolchain go1.22.3
46

57
require (
68
github.com/gobuffalo/flect v1.0.2
@@ -21,10 +23,13 @@ require (
2123
github.com/google/go-cmp v0.6.0 // indirect
2224
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
2325
github.com/inconshreveable/mousetrap v1.1.0 // indirect
26+
github.com/kr/pretty v0.3.1 // indirect
27+
github.com/rogpeppe/go-internal v1.10.0 // indirect
2428
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
2529
golang.org/x/mod v0.20.0 // indirect
2630
golang.org/x/net v0.28.0 // indirect
2731
golang.org/x/sync v0.8.0 // indirect
2832
golang.org/x/sys v0.23.0 // indirect
33+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2934
gopkg.in/yaml.v3 v3.0.1 // indirect
3035
)

go.sum

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
23
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
45
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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQu
1516
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
1617
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1718
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
19+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
20+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
21+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
22+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
23+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
24+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
25+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1826
github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
1927
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
2028
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
2129
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
30+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
2231
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2332
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
33+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
34+
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
35+
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
2436
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
2537
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
2638
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -56,8 +68,9 @@ golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
5668
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
5769
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
5870
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
59-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6071
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
72+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
73+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
6174
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
6275
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6376
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)