Skip to content

Commit f90b496

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

File tree

30 files changed

+1173
-237
lines changed

30 files changed

+1173
-237
lines changed

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

Lines changed: 98 additions & 37 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,8 @@ 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{}).
4954
Complete()
5055
}
5156

@@ -65,26 +70,42 @@ A webhook will automatically be served that calls this defaulting.
6570
The `Default` method is expected to mutate the receiver, setting the defaults.
6671
*/
6772

68-
var _ webhook.Defaulter = &CronJob{}
73+
type CronJobCustomDefaulter struct{}
6974

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)
75+
var _ webhook.CustomDefaulter = &CronJobCustomDefaulter{}
7376

74-
if r.Spec.ConcurrencyPolicy == "" {
75-
r.Spec.ConcurrencyPolicy = AllowConcurrent
77+
// Default implements webhook.CustomDefaulter so a webhook will be registered for the type
78+
func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
79+
cronjoblog.Info("CustomDefaulter for CronJob")
80+
req, err := admission.RequestFromContext(ctx)
81+
if err != nil {
82+
return fmt.Errorf("expected admission.Request in ctx: %w", err)
7683
}
77-
if r.Spec.Suspend == nil {
78-
r.Spec.Suspend = new(bool)
84+
if req.Kind.Kind != "CronJob" {
85+
return fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind)
86+
}
87+
castedObj, ok := obj.(*CronJob)
88+
if !ok {
89+
return fmt.Errorf("expected an CronJob object but got %T", obj)
90+
}
91+
cronjoblog.Info("default", "name", castedObj.GetName())
92+
93+
if castedObj.Spec.ConcurrencyPolicy == "" {
94+
castedObj.Spec.ConcurrencyPolicy = AllowConcurrent
7995
}
80-
if r.Spec.SuccessfulJobsHistoryLimit == nil {
81-
r.Spec.SuccessfulJobsHistoryLimit = new(int32)
82-
*r.Spec.SuccessfulJobsHistoryLimit = 3
96+
if castedObj.Spec.Suspend == nil {
97+
castedObj.Spec.Suspend = new(bool)
8398
}
84-
if r.Spec.FailedJobsHistoryLimit == nil {
85-
r.Spec.FailedJobsHistoryLimit = new(int32)
86-
*r.Spec.FailedJobsHistoryLimit = 1
99+
if castedObj.Spec.SuccessfulJobsHistoryLimit == nil {
100+
castedObj.Spec.SuccessfulJobsHistoryLimit = new(int32)
101+
*castedObj.Spec.SuccessfulJobsHistoryLimit = 3
87102
}
103+
if castedObj.Spec.FailedJobsHistoryLimit == nil {
104+
castedObj.Spec.FailedJobsHistoryLimit = new(int32)
105+
*castedObj.Spec.FailedJobsHistoryLimit = 1
106+
}
107+
108+
return nil
88109
}
89110

90111
/*
@@ -118,40 +139,80 @@ validate anything on deletion.
118139
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
119140
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
120141

121-
var _ webhook.Validator = &CronJob{}
142+
type CronJobCustomValidator struct{}
122143

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)
144+
var _ webhook.CustomValidator = &CronJobCustomValidator{}
126145

127-
return nil, r.validateCronJob()
146+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type
147+
func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
148+
cronjoblog.Info("Creation Validation for CronJob")
149+
150+
req, err := admission.RequestFromContext(ctx)
151+
if err != nil {
152+
return nil, fmt.Errorf("expected admission.Request in ctx: %w", err)
153+
}
154+
if req.Kind.Kind != "CronJob" {
155+
return nil, fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind)
156+
}
157+
castedObj, ok := obj.(*CronJob)
158+
if !ok {
159+
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
160+
}
161+
cronjoblog.Info("default", "name", castedObj.GetName())
162+
163+
return nil, v.validateCronJob(castedObj)
128164
}
129165

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)
166+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type
167+
func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
168+
cronjoblog.Info("Update Validation for CronJob")
169+
req, err := admission.RequestFromContext(ctx)
170+
if err != nil {
171+
return nil, fmt.Errorf("expected admission.Request in ctx: %w", err)
172+
}
173+
if req.Kind.Kind != "CronJob" {
174+
return nil, fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind)
175+
}
176+
castedObj, ok := newObj.(*CronJob)
177+
if !ok {
178+
return nil, fmt.Errorf("expected a CronJob object but got %T", newObj)
179+
}
180+
cronjoblog.Info("default", "name", castedObj.GetName())
133181

134-
return nil, r.validateCronJob()
182+
return nil, v.validateCronJob(castedObj)
135183
}
136184

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)
185+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type
186+
func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
187+
cronjoblog.Info("Deletion Validation for CronJob")
188+
req, err := admission.RequestFromContext(ctx)
189+
if err != nil {
190+
return nil, fmt.Errorf("expected admission.Request in ctx: %w", err)
191+
}
192+
if req.Kind.Kind != "CronJob" {
193+
return nil, fmt.Errorf("expected Kind CronJob got %q", req.Kind.Kind)
194+
}
195+
castedObj, ok := obj.(*CronJob)
196+
if !ok {
197+
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
198+
}
199+
cronjoblog.Info("default", "name", castedObj.GetName())
140200

141201
// TODO(user): fill in your validation logic upon object deletion.
202+
142203
return nil, nil
143204
}
144205

145206
/*
146207
We validate the name and the spec of the CronJob.
147208
*/
148209

149-
func (r *CronJob) validateCronJob() error {
210+
func (v *CronJobCustomValidator) validateCronJob(castedObj *CronJob) error {
150211
var allErrs field.ErrorList
151-
if err := r.validateCronJobName(); err != nil {
212+
if err := v.validateCronJobName(castedObj); err != nil {
152213
allErrs = append(allErrs, err)
153214
}
154-
if err := r.validateCronJobSpec(); err != nil {
215+
if err := v.validateCronJobSpec(castedObj); err != nil {
155216
allErrs = append(allErrs, err)
156217
}
157218
if len(allErrs) == 0 {
@@ -160,7 +221,7 @@ func (r *CronJob) validateCronJob() error {
160221

161222
return apierrors.NewInvalid(
162223
schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"},
163-
r.Name, allErrs)
224+
castedObj.Name, allErrs)
164225
}
165226

166227
/*
@@ -173,11 +234,11 @@ declaring validation by running `controller-gen crd -w`,
173234
or [here](/reference/markers/crd-validation.md).
174235
*/
175236

176-
func (r *CronJob) validateCronJobSpec() *field.Error {
237+
func (v *CronJobCustomValidator) validateCronJobSpec(castedObj *CronJob) *field.Error {
177238
// The field helpers from the kubernetes API machinery help us return nicely
178239
// structured validation errors.
179240
return validateScheduleFormat(
180-
r.Spec.Schedule,
241+
castedObj.Spec.Schedule,
181242
field.NewPath("spec").Child("schedule"))
182243
}
183244

@@ -202,15 +263,15 @@ the apimachinery repo, so we can't declaratively validate it using
202263
the validation schema.
203264
*/
204265

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
266+
func (v *CronJobCustomValidator) validateCronJobName(castedObj *CronJob) *field.Error {
267+
if len(castedObj.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 {
268+
// The job name length is 63 characters like all Kubernetes objects
208269
// (which must fit in a DNS subdomain). The cronjob controller appends
209270
// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating
210271
// a job. The job name length limit is 63 characters. Therefore cronjob
211272
// names must have length <= 63-11=52. If we don't validate this here,
212273
// then job creation will fail later.
213-
return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters")
274+
return field.Invalid(field.NewPath("metadata").Child("name"), castedObj.Name, "must be no more than 52 characters")
214275
}
215276
return nil
216277
}

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

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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-20240424215950-a892ee059fd6 // 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.19.0 // indirect
2630
golang.org/x/net v0.27.0 // indirect
2731
golang.org/x/sync v0.7.0 // indirect
2832
golang.org/x/sys v0.22.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-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN
1516
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0=
1927
github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA=
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.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
5668
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
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)