Skip to content

Commit 63f0184

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

File tree

22 files changed

+1031
-245
lines changed

22 files changed

+1031
-245
lines changed

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

Lines changed: 121 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,31 +69,63 @@ 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
108+
109+
d.setDefault()
110+
111+
return nil
112+
}
73113

74-
if r.Spec.ConcurrencyPolicy == "" {
75-
r.Spec.ConcurrencyPolicy = AllowConcurrent
114+
func (d *CronJobCustomDefaulter) setDefault() {
115+
if d.cronjob.Spec.ConcurrencyPolicy == "" {
116+
d.cronjob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy
76117
}
77-
if r.Spec.Suspend == nil {
78-
r.Spec.Suspend = new(bool)
118+
if d.cronjob.Spec.Suspend == nil {
119+
d.cronjob.Spec.Suspend = new(bool)
120+
*d.cronjob.Spec.Suspend = d.DefaultSuspend
79121
}
80-
if r.Spec.SuccessfulJobsHistoryLimit == nil {
81-
r.Spec.SuccessfulJobsHistoryLimit = new(int32)
82-
*r.Spec.SuccessfulJobsHistoryLimit = 3
122+
if d.cronjob.Spec.SuccessfulJobsHistoryLimit == nil {
123+
d.cronjob.Spec.SuccessfulJobsHistoryLimit = new(int32)
124+
*d.cronjob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit
83125
}
84-
if r.Spec.FailedJobsHistoryLimit == nil {
85-
r.Spec.FailedJobsHistoryLimit = new(int32)
86-
*r.Spec.FailedJobsHistoryLimit = 1
126+
if d.cronjob.Spec.FailedJobsHistoryLimit == nil {
127+
d.cronjob.Spec.FailedJobsHistoryLimit = new(int32)
128+
*d.cronjob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit
87129
}
88130
}
89131

@@ -101,7 +143,7 @@ sometimes more advanced use cases call for complex validation.
101143
For instance, we'll see below that we use this to validate a well-formed cron
102144
schedule without making up a long regular expression.
103145
104-
If `webhook.Validator` interface is implemented, a webhook will automatically be
146+
If `webhook.CustomValidator` interface is implemented, a webhook will automatically be
105147
served that calls the validation.
106148
107149
The `ValidateCreate`, `ValidateUpdate` and `ValidateDelete` methods are expected
@@ -118,40 +160,80 @@ validate anything on deletion.
118160
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
119161
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
120162

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

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

127-
return nil, r.validateCronJob()
190+
return nil, v.validateCronJob()
128191
}
129192

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)
193+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob
194+
func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
195+
cronjoblog.Info("Update Validation for CronJob")
196+
cronjob, ok := newObj.(*CronJob)
197+
if !ok {
198+
return nil, fmt.Errorf("expected a CronJob object but got %T", newObj)
199+
}
200+
cronjoblog.Info("default", "name", cronjob.GetName())
201+
202+
// Assign the CronJob object to the CronJobCustomValidator struct field
203+
// so that it can be used in the validation logic
204+
v.cronjob = cronjob
133205

134-
return nil, r.validateCronJob()
206+
return nil, v.validateCronJob()
135207
}
136208

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)
209+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob
210+
func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
211+
cronjoblog.Info("Deletion Validation for CronJob")
212+
cronjob, ok := obj.(*CronJob)
213+
if !ok {
214+
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
215+
}
216+
cronjoblog.Info("default", "name", cronjob.GetName())
217+
218+
// Assign the CronJob object to the CronJobCustomValidator struct field
219+
// so that it can be used in the validation logic
220+
v.cronjob = cronjob
140221

141222
// TODO(user): fill in your validation logic upon object deletion.
223+
142224
return nil, nil
143225
}
144226

145227
/*
146228
We validate the name and the spec of the CronJob.
147229
*/
148230

149-
func (r *CronJob) validateCronJob() error {
231+
func (v *CronJobCustomValidator) validateCronJob() error {
150232
var allErrs field.ErrorList
151-
if err := r.validateCronJobName(); err != nil {
233+
if err := v.validateCronJobName(); err != nil {
152234
allErrs = append(allErrs, err)
153235
}
154-
if err := r.validateCronJobSpec(); err != nil {
236+
if err := v.validateCronJobSpec(); err != nil {
155237
allErrs = append(allErrs, err)
156238
}
157239
if len(allErrs) == 0 {
@@ -160,7 +242,7 @@ func (r *CronJob) validateCronJob() error {
160242

161243
return apierrors.NewInvalid(
162244
schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"},
163-
r.Name, allErrs)
245+
v.cronjob.ObjectMeta.Name, allErrs)
164246
}
165247

166248
/*
@@ -173,11 +255,11 @@ declaring validation by running `controller-gen crd -w`,
173255
or [here](/reference/markers/crd-validation.md).
174256
*/
175257

176-
func (r *CronJob) validateCronJobSpec() *field.Error {
258+
func (v *CronJobCustomValidator) validateCronJobSpec() *field.Error {
177259
// The field helpers from the kubernetes API machinery help us return nicely
178260
// structured validation errors.
179261
return validateScheduleFormat(
180-
r.Spec.Schedule,
262+
v.cronjob.Spec.Schedule,
181263
field.NewPath("spec").Child("schedule"))
182264
}
183265

@@ -202,15 +284,15 @@ the apimachinery repo, so we can't declaratively validate it using
202284
the validation schema.
203285
*/
204286

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
287+
func (v *CronJobCustomValidator) validateCronJobName() *field.Error {
288+
if len(v.cronjob.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 {
289+
// The job name length is 63 characters like all Kubernetes objects
208290
// (which must fit in a DNS subdomain). The cronjob controller appends
209291
// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating
210292
// a job. The job name length limit is 63 characters. Therefore cronjob
211293
// names must have length <= 63-11=52. If we don't validate this here,
212294
// then job creation will fail later.
213-
return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters")
295+
return field.Invalid(field.NewPath("metadata").Child("name"), v.cronjob.ObjectMeta.Name, "must be no more than 52 characters")
214296
}
215297
return nil
216298
}

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)