Skip to content

Commit 7875dcd

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

File tree

22 files changed

+766
-229
lines changed

22 files changed

+766
-229
lines changed

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

Lines changed: 85 additions & 31 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,53 @@ 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+
86+
// Default values for various CronJob fields
87+
DefaultConcurrencyPolicy ConcurrencyPolicy
88+
DefaultSuspend bool
89+
DefaultSuccessfulJobsHistoryLimit int32
90+
DefaultFailedJobsHistoryLimit int32
91+
}
6992

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

74-
if r.Spec.ConcurrencyPolicy == "" {
75-
r.Spec.ConcurrencyPolicy = AllowConcurrent
95+
// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob
96+
func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
97+
cronjob, ok := obj.(*CronJob)
98+
if !ok {
99+
return fmt.Errorf("expected an CronJob object but got %T", obj)
76100
}
77-
if r.Spec.Suspend == nil {
78-
r.Spec.Suspend = new(bool)
101+
cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName())
102+
103+
if cronjob.Spec.ConcurrencyPolicy == "" {
104+
cronjob.Spec.ConcurrencyPolicy = AllowConcurrent
105+
}
106+
if cronjob.Spec.Suspend == nil {
107+
cronjob.Spec.Suspend = new(bool)
79108
}
80-
if r.Spec.SuccessfulJobsHistoryLimit == nil {
81-
r.Spec.SuccessfulJobsHistoryLimit = new(int32)
82-
*r.Spec.SuccessfulJobsHistoryLimit = 3
109+
if cronjob.Spec.SuccessfulJobsHistoryLimit == nil {
110+
cronjob.Spec.SuccessfulJobsHistoryLimit = new(int32)
111+
*cronjob.Spec.SuccessfulJobsHistoryLimit = 3
83112
}
84-
if r.Spec.FailedJobsHistoryLimit == nil {
85-
r.Spec.FailedJobsHistoryLimit = new(int32)
86-
*r.Spec.FailedJobsHistoryLimit = 1
113+
if cronjob.Spec.FailedJobsHistoryLimit == nil {
114+
cronjob.Spec.FailedJobsHistoryLimit = new(int32)
115+
*cronjob.Spec.FailedJobsHistoryLimit = 1
87116
}
117+
118+
return nil
88119
}
89120

90121
/*
@@ -101,7 +132,7 @@ sometimes more advanced use cases call for complex validation.
101132
For instance, we'll see below that we use this to validate a well-formed cron
102133
schedule without making up a long regular expression.
103134
104-
If `webhook.Validator` interface is implemented, a webhook will automatically be
135+
If `webhook.CustomValidator` interface is implemented, a webhook will automatically be
105136
served that calls the validation.
106137
107138
The `ValidateCreate`, `ValidateUpdate` and `ValidateDelete` methods are expected
@@ -118,27 +149,50 @@ validate anything on deletion.
118149
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
119150
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
120151

121-
var _ webhook.Validator = &CronJob{}
152+
// +kubebuilder:object:generate=false
153+
// CronJobCustomValidator struct is responsible for validating the CronJob resource
154+
// when it is created, updated, or deleted.
155+
//
156+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
157+
// as this struct is used only for temporary operations and does not need to be deeply copied.
158+
type CronJobCustomValidator struct {
159+
//TODO(user): Add more fields as needed for validation
160+
}
161+
162+
var _ webhook.CustomValidator = &CronJobCustomValidator{}
122163

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

127-
return nil, r.validateCronJob()
172+
return nil, cronjob.validateCronJob()
128173
}
129174

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)
175+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob
176+
func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
177+
cronjob, ok := newObj.(*CronJob)
178+
if !ok {
179+
return nil, fmt.Errorf("expected a CronJob object but got %T", newObj)
180+
}
181+
cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName())
133182

134-
return nil, r.validateCronJob()
183+
return nil, cronjob.validateCronJob()
135184
}
136185

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

141194
// TODO(user): fill in your validation logic upon object deletion.
195+
142196
return nil, nil
143197
}
144198

@@ -204,13 +258,13 @@ the validation schema.
204258

205259
func (r *CronJob) validateCronJobName() *field.Error {
206260
if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 {
207-
// The job name length is 63 character like all Kubernetes objects
261+
// The job name length is 63 characters like all Kubernetes objects
208262
// (which must fit in a DNS subdomain). The cronjob controller appends
209263
// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating
210264
// a job. The job name length limit is 63 characters. Therefore cronjob
211265
// names must have length <= 63-11=52. If we don't validate this here,
212266
// then job creation will fail later.
213-
return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters")
267+
return field.Invalid(field.NewPath("metadata").Child("name"), r.ObjectMeta.Name, "must be no more than 52 characters")
214268
}
215269
return nil
216270
}

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=

hack/docs/internal/cronjob-tutorial/generate_cronjob.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,20 @@ func (sp *Sample) updateWebhook() {
385385
err = pluginutil.ReplaceInFile(
386386
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
387387
`import (
388-
"k8s.io/apimachinery/pkg/runtime"
389-
ctrl "sigs.k8s.io/controller-runtime"
390-
logf "sigs.k8s.io/controller-runtime/pkg/log"
391-
"sigs.k8s.io/controller-runtime/pkg/webhook"
392-
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
388+
"context"
389+
"fmt"`, `import (
390+
"context"
391+
"fmt"
392+
"github.com/robfig/cron"
393+
apierrors "k8s.io/apimachinery/pkg/api/errors"
394+
"k8s.io/apimachinery/pkg/runtime/schema"
395+
validationutils "k8s.io/apimachinery/pkg/util/validation"
396+
"k8s.io/apimachinery/pkg/util/validation/field"`)
397+
hackutils.CheckError("add extra imports to cronjob_webhook.go", err)
398+
399+
err = pluginutil.ReplaceInFile(
400+
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
401+
`"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
393402
)
394403
395404
// nolint:unused
@@ -428,45 +437,65 @@ Then, we set up the webhook with the manager.
428437

429438
err = pluginutil.ReplaceInFile(
430439
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
431-
`cronjoblog.Info("default", "name", r.Name)
440+
`// TODO(user): Add more fields as needed for defaulting`, fragmentForDefaultFields)
441+
hackutils.CheckError("fixing cronjob_webhook.go by replacing TODO in Defaulter", err)
432442

433-
// TODO(user): fill in your defaulting logic.
434-
`, WebhookValidate)
443+
err = pluginutil.ReplaceInFile(
444+
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
445+
`WithDefaulter(&CronJobCustomDefaulter{}).`,
446+
`WithDefaulter(&CronJobCustomDefaulter{
447+
DefaultConcurrencyPolicy: AllowConcurrent,
448+
DefaultSuspend: false,
449+
DefaultSuccessfulJobsHistoryLimit: 3,
450+
DefaultFailedJobsHistoryLimit: 1,
451+
}).`)
452+
hackutils.CheckError("replacing WithDefaulter call in cronjob_webhook.go", err)
453+
454+
err = pluginutil.ReplaceInFile(
455+
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
456+
`// TODO(user): fill in your defaulting logic.
457+
`, WebhookDefaultingSettings)
435458
hackutils.CheckError("fixing cronjob_webhook.go by adding logic", err)
436459

437460
err = pluginutil.ReplaceInFile(
438461
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
439462
`// TODO(user): fill in your validation logic upon object creation.
463+
440464
return nil, nil`,
441465
`
442-
return nil, r.validateCronJob()`)
466+
return nil, cronjob.validateCronJob()`)
443467
hackutils.CheckError("fixing cronjob_webhook.go by fill in your validation", err)
444468

445469
err = pluginutil.ReplaceInFile(
446470
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
447471
`// TODO(user): fill in your validation logic upon object update.
472+
448473
return nil, nil`,
449474
`
450-
return nil, r.validateCronJob()`)
475+
return nil, cronjob.validateCronJob()`)
451476
hackutils.CheckError("fixing cronjob_webhook.go by adding validation logic upon object update", err)
452477

453478
err = pluginutil.InsertCode(
454479
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
455-
`func (r *CronJob) ValidateDelete() (admission.Warnings, error) {
456-
cronjoblog.Info("validate delete", "name", r.Name)
480+
`// TODO(user): fill in your validation logic upon object deletion.
457481
458-
// TODO(user): fill in your validation logic upon object deletion.
459482
return nil, nil
460483
}`, WebhookValidateSpec)
461-
hackutils.CheckError("fixing cronjob_webhook.go", err)
484+
485+
hackutils.CheckError("fixing cronjob_webhook.go upon object deletion", err)
462486

463487
err = pluginutil.ReplaceInFile(
464488
filepath.Join(sp.ctx.Dir, "api/v1/cronjob_webhook.go"),
465489
`validate anything on deletion.
466490
*/
491+
492+
return nil
467493
}`, `validate anything on deletion.
468-
*/`)
469-
hackutils.CheckError("fixing cronjob_webhook.go by adding comments to validate on deletion", err)
494+
*/
495+
496+
`)
497+
hackutils.CheckError("fixing cronjob_webhook.go by removing wrong return nil", err)
498+
470499
}
471500

472501
func (sp *Sample) updateSuiteTest() {

0 commit comments

Comments
 (0)