sigs.k8s.io/kubebuilder/v3@v3.14.0/hack/docs/internal/cronjob-tutorial/webhook_implementation.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cronjob 18 19 const WebhookIntro = `import ( 20 "github.com/robfig/cron" 21 apierrors "k8s.io/apimachinery/pkg/api/errors" 22 "k8s.io/apimachinery/pkg/runtime" 23 "k8s.io/apimachinery/pkg/runtime/schema" 24 validationutils "k8s.io/apimachinery/pkg/util/validation" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 ctrl "sigs.k8s.io/controller-runtime" 27 logf "sigs.k8s.io/controller-runtime/pkg/log" 28 "sigs.k8s.io/controller-runtime/pkg/webhook" 29 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 30 ) 31 32 // +kubebuilder:docs-gen:collapse=Go imports 33 34 /* 35 Next, we'll setup a logger for the webhooks. 36 */ 37 38 ` 39 40 const WebhookMarker = `/* 41 Notice that we use kubebuilder markers to generate webhook manifests. 42 This marker is responsible for generating a mutating webhook manifest. 43 44 The meaning of each marker can be found [here](/reference/markers/webhook.md). 45 */ 46 47 //+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 48 49 /* 50 We use the` + " `" + `webhook.Defaulter` + "`" + ` interface to set defaults to our CRD. 51 A webhook will automatically be served that calls this defaulting. 52 53 The` + " `" + `Default` + "`" + ` method is expected to mutate the receiver, setting the defaults. 54 */ 55 ` 56 57 const WebhookValidate = ` cronjoblog.Info("default", "name", r.Name) 58 59 if r.Spec.ConcurrencyPolicy == "" { 60 r.Spec.ConcurrencyPolicy = AllowConcurrent 61 } 62 if r.Spec.Suspend == nil { 63 r.Spec.Suspend = new(bool) 64 } 65 if r.Spec.SuccessfulJobsHistoryLimit == nil { 66 r.Spec.SuccessfulJobsHistoryLimit = new(int32) 67 *r.Spec.SuccessfulJobsHistoryLimit = 3 68 } 69 if r.Spec.FailedJobsHistoryLimit == nil { 70 r.Spec.FailedJobsHistoryLimit = new(int32) 71 *r.Spec.FailedJobsHistoryLimit = 1 72 } 73 } 74 75 /* 76 This marker is responsible for generating a validating webhook manifest. 77 */ 78 79 //+kubebuilder:webhook:verbs=create;update;delete,path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,versions=v1,name=vcronjob.kb.io,sideEffects=None,admissionReviewVersions=v1 80 81 /* 82 We can validate our CRD beyond what's possible with declarative 83 validation. Generally, declarative validation should be sufficient, but 84 sometimes more advanced use cases call for complex validation. 85 86 For instance, we'll see below that we use this to validate a well-formed cron 87 schedule without making up a long regular expression. 88 89 If` + " `" + `webhook.Validator` + "`" + ` interface is implemented, a webhook will automatically be 90 served that calls the validation. 91 92 The` + " `" + `ValidateCreate` + "`" + `, ` + "`" + `ValidateUpdate` + "`" + ` and` + " `" + `ValidateDelete` + "`" + ` methods are expected 93 to validate its receiver upon creation, update and deletion respectively. 94 We separate out ValidateCreate from ValidateUpdate to allow behavior like making 95 certain fields immutable, so that they can only be set on creation. 96 ValidateDelete is also separated from ValidateUpdate to allow different 97 validation behavior on deletion. 98 Here, however, we just use the same shared validation for` + " `" + `ValidateCreate` + "`" + ` and 99 ` + "`" + `ValidateUpdate` + "`" + `. And we do nothing in` + " `" + `ValidateDelete` + "`" + `, since we don't need to 100 validate anything on deletion. 101 */ 102 ` 103 104 const WebhookValidateSpec = ` 105 /* 106 We validate the name and the spec of the CronJob. 107 */ 108 109 func (r *CronJob) validateCronJob() error { 110 var allErrs field.ErrorList 111 if err := r.validateCronJobName(); err != nil { 112 allErrs = append(allErrs, err) 113 } 114 if err := r.validateCronJobSpec(); err != nil { 115 allErrs = append(allErrs, err) 116 } 117 if len(allErrs) == 0 { 118 return nil 119 } 120 121 return apierrors.NewInvalid( 122 schema.GroupKind{Group: "batch.tutorial.kubebuilder.io", Kind: "CronJob"}, 123 r.Name, allErrs) 124 } 125 126 /* 127 Some fields are declaratively validated by OpenAPI schema. 128 You can find kubebuilder validation markers (prefixed 129 with` + " `" + `// +kubebuilder:validation` + "`" + `) in the 130 [Designing an API](api-design.md) section. 131 You can find all of the kubebuilder supported markers for 132 declaring validation by running` + " `" + `controller-gen crd -w` + "`" + `, 133 or [here](/reference/markers/crd-validation.md). 134 */ 135 136 func (r *CronJob) validateCronJobSpec() *field.Error { 137 // The field helpers from the kubernetes API machinery help us return nicely 138 // structured validation errors. 139 return validateScheduleFormat( 140 r.Spec.Schedule, 141 field.NewPath("spec").Child("schedule")) 142 } 143 144 /* 145 We'll need to validate the [cron](https://en.wikipedia.org/wiki/Cron) schedule 146 is well-formatted. 147 */ 148 149 func validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error { 150 if _, err := cron.ParseStandard(schedule); err != nil { 151 return field.Invalid(fldPath, schedule, err.Error()) 152 } 153 return nil 154 } 155 156 /* 157 Validating the length of a string field can be done declaratively by 158 the validation schema. 159 160 But the` + " `" + `ObjectMeta.Name` + "`" + ` field is defined in a shared package under 161 the apimachinery repo, so we can't declaratively validate it using 162 the validation schema. 163 */ 164 165 func (r *CronJob) validateCronJobName() *field.Error { 166 if len(r.ObjectMeta.Name) > validationutils.DNS1035LabelMaxLength-11 { 167 // The job name length is 63 character like all Kubernetes objects 168 // (which must fit in a DNS subdomain). The cronjob controller appends 169 // a 11-character suffix to the cronjob (` + "`" + `-$TIMESTAMP` + "`" + `) when creating 170 // a job. The job name length limit is 63 characters. Therefore cronjob 171 // names must have length <= 63-11=52. If we don't validate this here, 172 // then job creation will fail later. 173 return field.Invalid(field.NewPath("metadata").Child("name"), r.Name, "must be no more than 52 characters") 174 } 175 return nil 176 } 177 178 // +kubebuilder:docs-gen:collapse=Validate object name`