Skip to content

Commit 72ab3fe

Browse files
authored
Merge pull request #525 from awesomenix/supportdelete
⚠️ support delete validation in validator interface
2 parents 86ffaff + ad9b00b commit 72ab3fe

File tree

4 files changed

+168
-26
lines changed

4 files changed

+168
-26
lines changed

examples/crd/pkg/resource.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ func (c *ChaosPod) ValidateUpdate(old runtime.Object) error {
9393
return nil
9494
}
9595

96+
// ValidateDelete implements webhookutil.validator so a webhook will be registered for the type
97+
func (c *ChaosPod) ValidateDelete() error {
98+
log.Info("validate delete", "name", c.Name)
99+
100+
if c.Spec.NextStop.Before(&metav1.Time{Time: time.Now()}) {
101+
return fmt.Errorf(".spec.nextStop must be later than current time")
102+
}
103+
return nil
104+
}
105+
96106
// +kubebuilder:webhook:path=/mutate-chaosapps-metamagical-io-v1-chaospod,mutating=true,failurePolicy=fail,groups=chaosapps.metamagical.io,resources=chaospods,verbs=create;update,versions=v1,name=mchaospod.kb.io
97107

98108
var _ webhook.Defaulter = &ChaosPod{}

pkg/builder/webhook.go

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -96,37 +96,43 @@ func (blder *WebhookBuilder) registerWebhooks() error {
9696

9797
// registerDefaultingWebhook registers a defaulting webhook if th
9898
func (blder *WebhookBuilder) registerDefaultingWebhook() {
99-
if defaulter, isDefaulter := blder.apiType.(admission.Defaulter); isDefaulter {
100-
mwh := admission.DefaultingWebhookFor(defaulter)
101-
if mwh != nil {
102-
path := generateMutatePath(blder.gvk)
103-
104-
// Checking if the path is already registered.
105-
// If so, just skip it.
106-
if !blder.isAlreadyHandled(path) {
107-
log.Info("Registering a mutating webhook",
108-
"GVK", blder.gvk,
109-
"path", path)
110-
blder.mgr.GetWebhookServer().Register(path, mwh)
111-
}
99+
defaulter, isDefaulter := blder.apiType.(admission.Defaulter)
100+
if !isDefaulter {
101+
log.Info("skip registering a mutating webhook, admission.Defaulter interface is not implemented", "GVK", blder.gvk)
102+
return
103+
}
104+
mwh := admission.DefaultingWebhookFor(defaulter)
105+
if mwh != nil {
106+
path := generateMutatePath(blder.gvk)
107+
108+
// Checking if the path is already registered.
109+
// If so, just skip it.
110+
if !blder.isAlreadyHandled(path) {
111+
log.Info("Registering a mutating webhook",
112+
"GVK", blder.gvk,
113+
"path", path)
114+
blder.mgr.GetWebhookServer().Register(path, mwh)
112115
}
113116
}
114117
}
115118

116119
func (blder *WebhookBuilder) registerValidatingWebhook() {
117-
if validator, isValidator := blder.apiType.(admission.Validator); isValidator {
118-
vwh := admission.ValidatingWebhookFor(validator)
119-
if vwh != nil {
120-
path := generateValidatePath(blder.gvk)
121-
122-
// Checking if the path is already registered.
123-
// If so, just skip it.
124-
if !blder.isAlreadyHandled(path) {
125-
log.Info("Registering a validating webhook",
126-
"GVK", blder.gvk,
127-
"path", path)
128-
blder.mgr.GetWebhookServer().Register(path, vwh)
129-
}
120+
validator, isValidator := blder.apiType.(admission.Validator)
121+
if !isValidator {
122+
log.Info("skip registering a validating webhook, admission.Validator interface is not implemented", "GVK", blder.gvk)
123+
return
124+
}
125+
vwh := admission.ValidatingWebhookFor(validator)
126+
if vwh != nil {
127+
path := generateValidatePath(blder.gvk)
128+
129+
// Checking if the path is already registered.
130+
// If so, just skip it.
131+
if !blder.isAlreadyHandled(path) {
132+
log.Info("Registering a validating webhook",
133+
"GVK", blder.gvk,
134+
"path", path)
135+
blder.mgr.GetWebhookServer().Register(path, vwh)
130136
}
131137
}
132138
}

pkg/builder/webhook_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,103 @@ var _ = Describe("application", func() {
269269
Expect(w.Body).To(ContainSubstring(`"allowed":true`))
270270
Expect(w.Body).To(ContainSubstring(`"code":200`))
271271
})
272+
273+
It("should scaffold a validating webhook if the type implements the Validator interface to validate deletes", func() {
274+
By("creating a controller manager")
275+
m, err := manager.New(cfg, manager.Options{})
276+
Expect(err).NotTo(HaveOccurred())
277+
278+
By("registering the type in the Scheme")
279+
builder := scheme.Builder{GroupVersion: testValidatorGVK.GroupVersion()}
280+
builder.Register(&TestValidator{}, &TestValidatorList{})
281+
err = builder.AddToScheme(m.GetScheme())
282+
Expect(err).NotTo(HaveOccurred())
283+
284+
err = WebhookManagedBy(m).
285+
For(&TestValidator{}).
286+
Complete()
287+
Expect(err).NotTo(HaveOccurred())
288+
svr := m.GetWebhookServer()
289+
Expect(svr).NotTo(BeNil())
290+
291+
reader := strings.NewReader(`{
292+
"kind":"AdmissionReview",
293+
"apiVersion":"admission.k8s.io/v1beta1",
294+
"request":{
295+
"uid":"07e52e8d-4513-11e9-a716-42010a800270",
296+
"kind":{
297+
"group":"",
298+
"version":"v1",
299+
"kind":"TestValidator"
300+
},
301+
"resource":{
302+
"group":"",
303+
"version":"v1",
304+
"resource":"testvalidator"
305+
},
306+
"namespace":"default",
307+
"operation":"DELETE",
308+
"object":null,
309+
"oldObject":{
310+
"replica":1
311+
}
312+
}
313+
}`)
314+
stopCh := make(chan struct{})
315+
close(stopCh)
316+
// TODO: we may want to improve it to make it be able to inject dependencies,
317+
// but not always try to load certs and return not found error.
318+
err = svr.Start(stopCh)
319+
if err != nil && !os.IsNotExist(err) {
320+
Expect(err).NotTo(HaveOccurred())
321+
}
322+
323+
By("sending a request to a validating webhook path to check for failed delete")
324+
path := generateValidatePath(testValidatorGVK)
325+
req := httptest.NewRequest("POST", "http://svc-name.svc-ns.svc"+path, reader)
326+
req.Header.Add(http.CanonicalHeaderKey("Content-Type"), "application/json")
327+
w := httptest.NewRecorder()
328+
svr.WebhookMux.ServeHTTP(w, req)
329+
Expect(w.Code).To(Equal(http.StatusOK))
330+
By("sanity checking the response contains reasonable field")
331+
Expect(w.Body).To(ContainSubstring(`"allowed":false`))
332+
Expect(w.Body).To(ContainSubstring(`"code":403`))
333+
334+
reader = strings.NewReader(`{
335+
"kind":"AdmissionReview",
336+
"apiVersion":"admission.k8s.io/v1beta1",
337+
"request":{
338+
"uid":"07e52e8d-4513-11e9-a716-42010a800270",
339+
"kind":{
340+
"group":"",
341+
"version":"v1",
342+
"kind":"TestValidator"
343+
},
344+
"resource":{
345+
"group":"",
346+
"version":"v1",
347+
"resource":"testvalidator"
348+
},
349+
"namespace":"default",
350+
"operation":"DELETE",
351+
"object":null,
352+
"oldObject":{
353+
"replica":0
354+
}
355+
}
356+
}`)
357+
By("sending a request to a validating webhook path with correct request")
358+
path = generateValidatePath(testValidatorGVK)
359+
req = httptest.NewRequest("POST", "http://svc-name.svc-ns.svc"+path, reader)
360+
req.Header.Add(http.CanonicalHeaderKey("Content-Type"), "application/json")
361+
w = httptest.NewRecorder()
362+
svr.WebhookMux.ServeHTTP(w, req)
363+
Expect(w.Code).To(Equal(http.StatusOK))
364+
By("sanity checking the response contains reasonable field")
365+
Expect(w.Body).To(ContainSubstring(`"allowed":true`))
366+
Expect(w.Body).To(ContainSubstring(`"code":200`))
367+
368+
})
272369
})
273370
})
274371

@@ -357,6 +454,13 @@ func (v *TestValidator) ValidateUpdate(old runtime.Object) error {
357454
return nil
358455
}
359456

457+
func (v *TestValidator) ValidateDelete() error {
458+
if v.Replica > 0 {
459+
return errors.New("number of replica should be less than or equal to 0 to delete")
460+
}
461+
return nil
462+
}
463+
360464
// TestDefaultValidator
361465
var _ runtime.Object = &TestDefaultValidator{}
362466

@@ -407,3 +511,10 @@ func (dv *TestDefaultValidator) ValidateUpdate(old runtime.Object) error {
407511
}
408512
return nil
409513
}
514+
515+
func (dv *TestDefaultValidator) ValidateDelete() error {
516+
if dv.Replica > 0 {
517+
return errors.New("number of replica should be less than or equal to 0 to delete")
518+
}
519+
return nil
520+
}

pkg/webhook/admission/validator.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Validator interface {
2929
runtime.Object
3030
ValidateCreate() error
3131
ValidateUpdate(old runtime.Object) error
32+
ValidateDelete() error
3233
}
3334

3435
// ValidatingWebhookFor creates a new Webhook for validating the provided type.
@@ -89,5 +90,19 @@ func (h *validatingHandler) Handle(ctx context.Context, req Request) Response {
8990
}
9091
}
9192

93+
if req.Operation == v1beta1.Delete {
94+
// In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346
95+
// OldObject contains the object being deleted
96+
err := h.decoder.DecodeRaw(req.OldObject, obj)
97+
if err != nil {
98+
return Errored(http.StatusBadRequest, err)
99+
}
100+
101+
err = obj.ValidateDelete()
102+
if err != nil {
103+
return Denied(err.Error())
104+
}
105+
}
106+
92107
return Allowed("")
93108
}

0 commit comments

Comments
 (0)