1
16
17 package builder
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "io"
24 "net/http"
25 "net/http/httptest"
26 "os"
27 "strings"
28
29 "github.com/go-logr/logr"
30 . "github.com/onsi/ginkgo/v2"
31 . "github.com/onsi/gomega"
32 "github.com/onsi/gomega/gbytes"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/runtime"
35 "k8s.io/apimachinery/pkg/runtime/schema"
36
37 "sigs.k8s.io/controller-runtime/pkg/controller"
38 logf "sigs.k8s.io/controller-runtime/pkg/log"
39 "sigs.k8s.io/controller-runtime/pkg/log/zap"
40 "sigs.k8s.io/controller-runtime/pkg/manager"
41 "sigs.k8s.io/controller-runtime/pkg/scheme"
42 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
43 )
44
45 const (
46 admissionReviewGV = `{
47 "kind":"AdmissionReview",
48 "apiVersion":"admission.k8s.io/`
49
50 svcBaseAddr = "http://svc-name.svc-ns.svc"
51 )
52
53 var _ = Describe("webhook", func() {
54 Describe("New", func() {
55 Context("v1 AdmissionReview", func() {
56 runTests("v1")
57 })
58 Context("v1beta1 AdmissionReview", func() {
59 runTests("v1beta1")
60 })
61 })
62 })
63
64 func runTests(admissionReviewVersion string) {
65 var (
66 stop chan struct{}
67 logBuffer *gbytes.Buffer
68 testingLogger logr.Logger
69 )
70
71 BeforeEach(func() {
72 stop = make(chan struct{})
73 newController = controller.New
74 logBuffer = gbytes.NewBuffer()
75 testingLogger = zap.New(zap.JSONEncoder(), zap.WriteTo(io.MultiWriter(logBuffer, GinkgoWriter)))
76 })
77
78 AfterEach(func() {
79 close(stop)
80 })
81
82 It("should scaffold a defaulting webhook if the type implements the Defaulter interface", func() {
83 By("creating a controller manager")
84 m, err := manager.New(cfg, manager.Options{})
85 ExpectWithOffset(1, err).NotTo(HaveOccurred())
86
87 By("registering the type in the Scheme")
88 builder := scheme.Builder{GroupVersion: testDefaulterGVK.GroupVersion()}
89 builder.Register(&TestDefaulter{}, &TestDefaulterList{})
90 err = builder.AddToScheme(m.GetScheme())
91 ExpectWithOffset(1, err).NotTo(HaveOccurred())
92
93 err = WebhookManagedBy(m).
94 For(&TestDefaulter{}).
95 Complete()
96 ExpectWithOffset(1, err).NotTo(HaveOccurred())
97 svr := m.GetWebhookServer()
98 ExpectWithOffset(1, svr).NotTo(BeNil())
99
100 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
101 "request":{
102 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
103 "kind":{
104 "group":"",
105 "version":"v1",
106 "kind":"TestDefaulter"
107 },
108 "resource":{
109 "group":"",
110 "version":"v1",
111 "resource":"testdefaulter"
112 },
113 "namespace":"default",
114 "operation":"CREATE",
115 "object":{
116 "replica":1
117 },
118 "oldObject":null
119 }
120 }`)
121
122 ctx, cancel := context.WithCancel(context.Background())
123 cancel()
124 err = svr.Start(ctx)
125 if err != nil && !os.IsNotExist(err) {
126 ExpectWithOffset(1, err).NotTo(HaveOccurred())
127 }
128
129 By("sending a request to a mutating webhook path")
130 path := generateMutatePath(testDefaulterGVK)
131 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
132 req.Header.Add("Content-Type", "application/json")
133 w := httptest.NewRecorder()
134 svr.WebhookMux().ServeHTTP(w, req)
135 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
136 By("sanity checking the response contains reasonable fields")
137 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":true`))
138 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"patch":`))
139 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":200`))
140
141 By("sending a request to a validating webhook path that doesn't exist")
142 path = generateValidatePath(testDefaulterGVK)
143 _, err = reader.Seek(0, 0)
144 ExpectWithOffset(1, err).NotTo(HaveOccurred())
145 req = httptest.NewRequest("POST", svcBaseAddr+path, reader)
146 req.Header.Add("Content-Type", "application/json")
147 w = httptest.NewRecorder()
148 svr.WebhookMux().ServeHTTP(w, req)
149 ExpectWithOffset(1, w.Code).To(Equal(http.StatusNotFound))
150 })
151
152 It("should scaffold a defaulting webhook which recovers from panics", func() {
153 By("creating a controller manager")
154 m, err := manager.New(cfg, manager.Options{})
155 ExpectWithOffset(1, err).NotTo(HaveOccurred())
156
157 By("registering the type in the Scheme")
158 builder := scheme.Builder{GroupVersion: testDefaulterGVK.GroupVersion()}
159 builder.Register(&TestDefaulter{}, &TestDefaulterList{})
160 err = builder.AddToScheme(m.GetScheme())
161 ExpectWithOffset(1, err).NotTo(HaveOccurred())
162
163 err = WebhookManagedBy(m).
164 For(&TestDefaulter{Panic: true}).
165 RecoverPanic().
166 Complete()
167 ExpectWithOffset(1, err).NotTo(HaveOccurred())
168 svr := m.GetWebhookServer()
169 ExpectWithOffset(1, svr).NotTo(BeNil())
170
171 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
172 "request":{
173 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
174 "kind":{
175 "group":"",
176 "version":"v1",
177 "kind":"TestDefaulter"
178 },
179 "resource":{
180 "group":"",
181 "version":"v1",
182 "resource":"testdefaulter"
183 },
184 "namespace":"default",
185 "operation":"CREATE",
186 "object":{
187 "replica":1,
188 "panic":true
189 },
190 "oldObject":null
191 }
192 }`)
193
194 ctx, cancel := context.WithCancel(context.Background())
195 cancel()
196 err = svr.Start(ctx)
197 if err != nil && !os.IsNotExist(err) {
198 ExpectWithOffset(1, err).NotTo(HaveOccurred())
199 }
200
201 By("sending a request to a mutating webhook path")
202 path := generateMutatePath(testDefaulterGVK)
203 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
204 req.Header.Add("Content-Type", "application/json")
205 w := httptest.NewRecorder()
206 svr.WebhookMux().ServeHTTP(w, req)
207 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
208 By("sanity checking the response contains reasonable fields")
209 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":false`))
210 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":500`))
211 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"message":"panic: fake panic test [recovered]`))
212 })
213
214 It("should scaffold a defaulting webhook with a custom defaulter", func() {
215 By("creating a controller manager")
216 m, err := manager.New(cfg, manager.Options{})
217 ExpectWithOffset(1, err).NotTo(HaveOccurred())
218
219 By("registering the type in the Scheme")
220 builder := scheme.Builder{GroupVersion: testDefaulterGVK.GroupVersion()}
221 builder.Register(&TestDefaulter{}, &TestDefaulterList{})
222 err = builder.AddToScheme(m.GetScheme())
223 ExpectWithOffset(1, err).NotTo(HaveOccurred())
224
225 err = WebhookManagedBy(m).
226 WithDefaulter(&TestCustomDefaulter{}).
227 For(&TestDefaulter{}).
228 WithLogConstructor(func(base logr.Logger, req *admission.Request) logr.Logger {
229 return admission.DefaultLogConstructor(testingLogger, req)
230 }).
231 Complete()
232 ExpectWithOffset(1, err).NotTo(HaveOccurred())
233 svr := m.GetWebhookServer()
234 ExpectWithOffset(1, svr).NotTo(BeNil())
235
236 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
237 "request":{
238 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
239 "kind":{
240 "group":"foo.test.org",
241 "version":"v1",
242 "kind":"TestDefaulter"
243 },
244 "resource":{
245 "group":"foo.test.org",
246 "version":"v1",
247 "resource":"testdefaulter"
248 },
249 "namespace":"default",
250 "name":"foo",
251 "operation":"CREATE",
252 "object":{
253 "replica":1
254 },
255 "oldObject":null
256 }
257 }`)
258
259 ctx, cancel := context.WithCancel(context.Background())
260 cancel()
261 err = svr.Start(ctx)
262 if err != nil && !os.IsNotExist(err) {
263 ExpectWithOffset(1, err).NotTo(HaveOccurred())
264 }
265
266 By("sending a request to a mutating webhook path")
267 path := generateMutatePath(testDefaulterGVK)
268 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
269 req.Header.Add("Content-Type", "application/json")
270 w := httptest.NewRecorder()
271 svr.WebhookMux().ServeHTTP(w, req)
272 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
273 By("sanity checking the response contains reasonable fields")
274 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":true`))
275 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"patch":`))
276 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":200`))
277 EventuallyWithOffset(1, logBuffer).Should(gbytes.Say(`"msg":"Defaulting object","object":{"name":"foo","namespace":"default"},"namespace":"default","name":"foo","resource":{"group":"foo.test.org","version":"v1","resource":"testdefaulter"},"user":"","requestID":"07e52e8d-4513-11e9-a716-42010a800270"`))
278
279 By("sending a request to a validating webhook path that doesn't exist")
280 path = generateValidatePath(testDefaulterGVK)
281 _, err = reader.Seek(0, 0)
282 ExpectWithOffset(1, err).NotTo(HaveOccurred())
283 req = httptest.NewRequest("POST", svcBaseAddr+path, reader)
284 req.Header.Add("Content-Type", "application/json")
285 w = httptest.NewRecorder()
286 svr.WebhookMux().ServeHTTP(w, req)
287 ExpectWithOffset(1, w.Code).To(Equal(http.StatusNotFound))
288 })
289
290 It("should scaffold a validating webhook if the type implements the Validator interface", func() {
291 By("creating a controller manager")
292 m, err := manager.New(cfg, manager.Options{})
293 ExpectWithOffset(1, err).NotTo(HaveOccurred())
294
295 By("registering the type in the Scheme")
296 builder := scheme.Builder{GroupVersion: testValidatorGVK.GroupVersion()}
297 builder.Register(&TestValidator{}, &TestValidatorList{})
298 err = builder.AddToScheme(m.GetScheme())
299 ExpectWithOffset(1, err).NotTo(HaveOccurred())
300
301 err = WebhookManagedBy(m).
302 For(&TestValidator{}).
303 Complete()
304 ExpectWithOffset(1, err).NotTo(HaveOccurred())
305 svr := m.GetWebhookServer()
306 ExpectWithOffset(1, svr).NotTo(BeNil())
307
308 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
309 "request":{
310 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
311 "kind":{
312 "group":"",
313 "version":"v1",
314 "kind":"TestValidator"
315 },
316 "resource":{
317 "group":"",
318 "version":"v1",
319 "resource":"testvalidator"
320 },
321 "namespace":"default",
322 "operation":"UPDATE",
323 "object":{
324 "replica":1
325 },
326 "oldObject":{
327 "replica":2
328 }
329 }
330 }`)
331
332 ctx, cancel := context.WithCancel(context.Background())
333 cancel()
334 err = svr.Start(ctx)
335 if err != nil && !os.IsNotExist(err) {
336 ExpectWithOffset(1, err).NotTo(HaveOccurred())
337 }
338
339 By("sending a request to a mutating webhook path that doesn't exist")
340 path := generateMutatePath(testValidatorGVK)
341 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
342 req.Header.Add("Content-Type", "application/json")
343 w := httptest.NewRecorder()
344 svr.WebhookMux().ServeHTTP(w, req)
345 ExpectWithOffset(1, w.Code).To(Equal(http.StatusNotFound))
346
347 By("sending a request to a validating webhook path")
348 path = generateValidatePath(testValidatorGVK)
349 _, err = reader.Seek(0, 0)
350 ExpectWithOffset(1, err).NotTo(HaveOccurred())
351 req = httptest.NewRequest("POST", svcBaseAddr+path, reader)
352 req.Header.Add("Content-Type", "application/json")
353 w = httptest.NewRecorder()
354 svr.WebhookMux().ServeHTTP(w, req)
355 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
356 By("sanity checking the response contains reasonable field")
357 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":false`))
358 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":403`))
359 })
360
361 It("should scaffold a validating webhook which recovers from panics", func() {
362 By("creating a controller manager")
363 m, err := manager.New(cfg, manager.Options{})
364 ExpectWithOffset(1, err).NotTo(HaveOccurred())
365
366 By("registering the type in the Scheme")
367 builder := scheme.Builder{GroupVersion: testValidatorGVK.GroupVersion()}
368 builder.Register(&TestValidator{}, &TestValidatorList{})
369 err = builder.AddToScheme(m.GetScheme())
370 ExpectWithOffset(1, err).NotTo(HaveOccurred())
371
372 err = WebhookManagedBy(m).
373 For(&TestValidator{Panic: true}).
374 RecoverPanic().
375 Complete()
376 ExpectWithOffset(1, err).NotTo(HaveOccurred())
377 svr := m.GetWebhookServer()
378 ExpectWithOffset(1, svr).NotTo(BeNil())
379
380 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
381 "request":{
382 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
383 "kind":{
384 "group":"",
385 "version":"v1",
386 "kind":"TestValidator"
387 },
388 "resource":{
389 "group":"",
390 "version":"v1",
391 "resource":"testvalidator"
392 },
393 "namespace":"default",
394 "operation":"CREATE",
395 "object":{
396 "replica":2,
397 "panic":true
398 }
399 }
400 }`)
401
402 ctx, cancel := context.WithCancel(context.Background())
403 cancel()
404 err = svr.Start(ctx)
405 if err != nil && !os.IsNotExist(err) {
406 ExpectWithOffset(1, err).NotTo(HaveOccurred())
407 }
408
409 By("sending a request to a validating webhook path")
410 path := generateValidatePath(testValidatorGVK)
411 _, err = reader.Seek(0, 0)
412 ExpectWithOffset(1, err).NotTo(HaveOccurred())
413 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
414 req.Header.Add("Content-Type", "application/json")
415 w := httptest.NewRecorder()
416 svr.WebhookMux().ServeHTTP(w, req)
417 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
418 By("sanity checking the response contains reasonable field")
419 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":false`))
420 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":500`))
421 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"message":"panic: fake panic test [recovered]`))
422 })
423
424 It("should scaffold a validating webhook with a custom validator", func() {
425 By("creating a controller manager")
426 m, err := manager.New(cfg, manager.Options{})
427 ExpectWithOffset(1, err).NotTo(HaveOccurred())
428
429 By("registering the type in the Scheme")
430 builder := scheme.Builder{GroupVersion: testValidatorGVK.GroupVersion()}
431 builder.Register(&TestValidator{}, &TestValidatorList{})
432 err = builder.AddToScheme(m.GetScheme())
433 ExpectWithOffset(1, err).NotTo(HaveOccurred())
434
435 err = WebhookManagedBy(m).
436 WithValidator(&TestCustomValidator{}).
437 For(&TestValidator{}).
438 WithLogConstructor(func(base logr.Logger, req *admission.Request) logr.Logger {
439 return admission.DefaultLogConstructor(testingLogger, req)
440 }).
441 Complete()
442 ExpectWithOffset(1, err).NotTo(HaveOccurred())
443 svr := m.GetWebhookServer()
444 ExpectWithOffset(1, svr).NotTo(BeNil())
445
446 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
447 "request":{
448 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
449 "kind":{
450 "group":"foo.test.org",
451 "version":"v1",
452 "kind":"TestValidator"
453 },
454 "resource":{
455 "group":"foo.test.org",
456 "version":"v1",
457 "resource":"testvalidator"
458 },
459 "namespace":"default",
460 "name":"foo",
461 "operation":"UPDATE",
462 "object":{
463 "replica":1
464 },
465 "oldObject":{
466 "replica":2
467 }
468 }
469 }`)
470
471 ctx, cancel := context.WithCancel(context.Background())
472 cancel()
473 err = svr.Start(ctx)
474 if err != nil && !os.IsNotExist(err) {
475 ExpectWithOffset(1, err).NotTo(HaveOccurred())
476 }
477
478 By("sending a request to a mutating webhook path that doesn't exist")
479 path := generateMutatePath(testValidatorGVK)
480 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
481 req.Header.Add("Content-Type", "application/json")
482 w := httptest.NewRecorder()
483 svr.WebhookMux().ServeHTTP(w, req)
484 ExpectWithOffset(1, w.Code).To(Equal(http.StatusNotFound))
485
486 By("sending a request to a validating webhook path")
487 path = generateValidatePath(testValidatorGVK)
488 _, err = reader.Seek(0, 0)
489 ExpectWithOffset(1, err).NotTo(HaveOccurred())
490 req = httptest.NewRequest("POST", svcBaseAddr+path, reader)
491 req.Header.Add("Content-Type", "application/json")
492 w = httptest.NewRecorder()
493 svr.WebhookMux().ServeHTTP(w, req)
494 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
495 By("sanity checking the response contains reasonable field")
496 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":false`))
497 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":403`))
498 EventuallyWithOffset(1, logBuffer).Should(gbytes.Say(`"msg":"Validating object","object":{"name":"foo","namespace":"default"},"namespace":"default","name":"foo","resource":{"group":"foo.test.org","version":"v1","resource":"testvalidator"},"user":"","requestID":"07e52e8d-4513-11e9-a716-42010a800270"`))
499 })
500
501 It("should scaffold defaulting and validating webhooks if the type implements both Defaulter and Validator interfaces", func() {
502 By("creating a controller manager")
503 m, err := manager.New(cfg, manager.Options{})
504 ExpectWithOffset(1, err).NotTo(HaveOccurred())
505
506 By("registering the type in the Scheme")
507 builder := scheme.Builder{GroupVersion: testDefaultValidatorGVK.GroupVersion()}
508 builder.Register(&TestDefaultValidator{}, &TestDefaultValidatorList{})
509 err = builder.AddToScheme(m.GetScheme())
510 ExpectWithOffset(1, err).NotTo(HaveOccurred())
511
512 err = WebhookManagedBy(m).
513 For(&TestDefaultValidator{}).
514 Complete()
515 ExpectWithOffset(1, err).NotTo(HaveOccurred())
516 svr := m.GetWebhookServer()
517 ExpectWithOffset(1, svr).NotTo(BeNil())
518
519 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
520 "request":{
521 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
522 "kind":{
523 "group":"",
524 "version":"v1",
525 "kind":"TestDefaultValidator"
526 },
527 "resource":{
528 "group":"",
529 "version":"v1",
530 "resource":"testdefaultvalidator"
531 },
532 "namespace":"default",
533 "operation":"CREATE",
534 "object":{
535 "replica":1
536 },
537 "oldObject":null
538 }
539 }`)
540
541 ctx, cancel := context.WithCancel(context.Background())
542 cancel()
543 err = svr.Start(ctx)
544 if err != nil && !os.IsNotExist(err) {
545 ExpectWithOffset(1, err).NotTo(HaveOccurred())
546 }
547
548 By("sending a request to a mutating webhook path")
549 path := generateMutatePath(testDefaultValidatorGVK)
550 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
551 req.Header.Add("Content-Type", "application/json")
552 w := httptest.NewRecorder()
553 svr.WebhookMux().ServeHTTP(w, req)
554 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
555 By("sanity checking the response contains reasonable field")
556 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":true`))
557 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"patch":`))
558 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":200`))
559
560 By("sending a request to a validating webhook path")
561 path = generateValidatePath(testDefaultValidatorGVK)
562 _, err = reader.Seek(0, 0)
563 ExpectWithOffset(1, err).NotTo(HaveOccurred())
564 req = httptest.NewRequest("POST", svcBaseAddr+path, reader)
565 req.Header.Add("Content-Type", "application/json")
566 w = httptest.NewRecorder()
567 svr.WebhookMux().ServeHTTP(w, req)
568 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
569 By("sanity checking the response contains reasonable field")
570 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":true`))
571 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":200`))
572 })
573
574 It("should scaffold a validating webhook if the type implements the Validator interface to validate deletes", func() {
575 By("creating a controller manager")
576 ctx, cancel := context.WithCancel(context.Background())
577
578 m, err := manager.New(cfg, manager.Options{})
579 ExpectWithOffset(1, err).NotTo(HaveOccurred())
580
581 By("registering the type in the Scheme")
582 builder := scheme.Builder{GroupVersion: testValidatorGVK.GroupVersion()}
583 builder.Register(&TestValidator{}, &TestValidatorList{})
584 err = builder.AddToScheme(m.GetScheme())
585 ExpectWithOffset(1, err).NotTo(HaveOccurred())
586
587 err = WebhookManagedBy(m).
588 For(&TestValidator{}).
589 Complete()
590 ExpectWithOffset(1, err).NotTo(HaveOccurred())
591 svr := m.GetWebhookServer()
592 ExpectWithOffset(1, svr).NotTo(BeNil())
593
594 reader := strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
595 "request":{
596 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
597 "kind":{
598 "group":"",
599 "version":"v1",
600 "kind":"TestValidator"
601 },
602 "resource":{
603 "group":"",
604 "version":"v1",
605 "resource":"testvalidator"
606 },
607 "namespace":"default",
608 "operation":"DELETE",
609 "object":null,
610 "oldObject":{
611 "replica":1
612 }
613 }
614 }`)
615
616 cancel()
617 err = svr.Start(ctx)
618 if err != nil && !os.IsNotExist(err) {
619 ExpectWithOffset(1, err).NotTo(HaveOccurred())
620 }
621
622 By("sending a request to a validating webhook path to check for failed delete")
623 path := generateValidatePath(testValidatorGVK)
624 req := httptest.NewRequest("POST", svcBaseAddr+path, reader)
625 req.Header.Add("Content-Type", "application/json")
626 w := httptest.NewRecorder()
627 svr.WebhookMux().ServeHTTP(w, req)
628 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
629 By("sanity checking the response contains reasonable field")
630 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":false`))
631 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":403`))
632
633 reader = strings.NewReader(admissionReviewGV + admissionReviewVersion + `",
634 "request":{
635 "uid":"07e52e8d-4513-11e9-a716-42010a800270",
636 "kind":{
637 "group":"",
638 "version":"v1",
639 "kind":"TestValidator"
640 },
641 "resource":{
642 "group":"",
643 "version":"v1",
644 "resource":"testvalidator"
645 },
646 "namespace":"default",
647 "operation":"DELETE",
648 "object":null,
649 "oldObject":{
650 "replica":0
651 }
652 }
653 }`)
654 By("sending a request to a validating webhook path with correct request")
655 path = generateValidatePath(testValidatorGVK)
656 req = httptest.NewRequest("POST", svcBaseAddr+path, reader)
657 req.Header.Add("Content-Type", "application/json")
658 w = httptest.NewRecorder()
659 svr.WebhookMux().ServeHTTP(w, req)
660 ExpectWithOffset(1, w.Code).To(Equal(http.StatusOK))
661 By("sanity checking the response contains reasonable field")
662 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"allowed":true`))
663 ExpectWithOffset(1, w.Body).To(ContainSubstring(`"code":200`))
664 })
665
666 It("should send an error when trying to register a webhook with more than one For", func() {
667 By("creating a controller manager")
668 m, err := manager.New(cfg, manager.Options{})
669 ExpectWithOffset(1, err).NotTo(HaveOccurred())
670
671 By("registering the type in the Scheme")
672 builder := scheme.Builder{GroupVersion: testDefaulterGVK.GroupVersion()}
673 builder.Register(&TestDefaulter{}, &TestDefaulterList{})
674 err = builder.AddToScheme(m.GetScheme())
675 ExpectWithOffset(1, err).NotTo(HaveOccurred())
676
677 err = WebhookManagedBy(m).
678 For(&TestDefaulter{}).
679 For(&TestDefaulter{}).
680 Complete()
681 Expect(err).To(HaveOccurred())
682 })
683 }
684
685
686 var _ runtime.Object = &TestDefaulter{}
687
688 const testDefaulterKind = "TestDefaulter"
689
690 type TestDefaulter struct {
691 Replica int `json:"replica,omitempty"`
692 Panic bool `json:"panic,omitempty"`
693 }
694
695 var testDefaulterGVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: testDefaulterKind}
696
697 func (d *TestDefaulter) GetObjectKind() schema.ObjectKind { return d }
698 func (d *TestDefaulter) DeepCopyObject() runtime.Object {
699 return &TestDefaulter{
700 Replica: d.Replica,
701 }
702 }
703
704 func (d *TestDefaulter) GroupVersionKind() schema.GroupVersionKind {
705 return testDefaulterGVK
706 }
707
708 func (d *TestDefaulter) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
709
710 var _ runtime.Object = &TestDefaulterList{}
711
712 type TestDefaulterList struct{}
713
714 func (*TestDefaulterList) GetObjectKind() schema.ObjectKind { return nil }
715 func (*TestDefaulterList) DeepCopyObject() runtime.Object { return nil }
716
717 func (d *TestDefaulter) Default() {
718 if d.Panic {
719 panic("fake panic test")
720 }
721 if d.Replica < 2 {
722 d.Replica = 2
723 }
724 }
725
726
727 var _ runtime.Object = &TestValidator{}
728
729 const testValidatorKind = "TestValidator"
730
731 type TestValidator struct {
732 Replica int `json:"replica,omitempty"`
733 Panic bool `json:"panic,omitempty"`
734 }
735
736 var testValidatorGVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: testValidatorKind}
737
738 func (v *TestValidator) GetObjectKind() schema.ObjectKind { return v }
739 func (v *TestValidator) DeepCopyObject() runtime.Object {
740 return &TestValidator{
741 Replica: v.Replica,
742 }
743 }
744
745 func (v *TestValidator) GroupVersionKind() schema.GroupVersionKind {
746 return testValidatorGVK
747 }
748
749 func (v *TestValidator) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
750
751 var _ runtime.Object = &TestValidatorList{}
752
753 type TestValidatorList struct{}
754
755 func (*TestValidatorList) GetObjectKind() schema.ObjectKind { return nil }
756 func (*TestValidatorList) DeepCopyObject() runtime.Object { return nil }
757
758 var _ admission.Validator = &TestValidator{}
759
760 func (v *TestValidator) ValidateCreate() (admission.Warnings, error) {
761 if v.Panic {
762 panic("fake panic test")
763 }
764 if v.Replica < 0 {
765 return nil, errors.New("number of replica should be greater than or equal to 0")
766 }
767 return nil, nil
768 }
769
770 func (v *TestValidator) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
771 if v.Panic {
772 panic("fake panic test")
773 }
774 if v.Replica < 0 {
775 return nil, errors.New("number of replica should be greater than or equal to 0")
776 }
777 if oldObj, ok := old.(*TestValidator); !ok {
778 return nil, fmt.Errorf("the old object is expected to be %T", oldObj)
779 } else if v.Replica < oldObj.Replica {
780 return nil, fmt.Errorf("new replica %v should not be fewer than old replica %v", v.Replica, oldObj.Replica)
781 }
782 return nil, nil
783 }
784
785 func (v *TestValidator) ValidateDelete() (admission.Warnings, error) {
786 if v.Panic {
787 panic("fake panic test")
788 }
789 if v.Replica > 0 {
790 return nil, errors.New("number of replica should be less than or equal to 0 to delete")
791 }
792 return nil, nil
793 }
794
795
796 var _ runtime.Object = &TestDefaultValidator{}
797
798 type TestDefaultValidator struct {
799 metav1.TypeMeta
800 metav1.ObjectMeta
801
802 Replica int `json:"replica,omitempty"`
803 }
804
805 var testDefaultValidatorGVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaultValidator"}
806
807 func (dv *TestDefaultValidator) GetObjectKind() schema.ObjectKind { return dv }
808 func (dv *TestDefaultValidator) DeepCopyObject() runtime.Object {
809 return &TestDefaultValidator{
810 Replica: dv.Replica,
811 }
812 }
813
814 func (dv *TestDefaultValidator) GroupVersionKind() schema.GroupVersionKind {
815 return testDefaultValidatorGVK
816 }
817
818 func (dv *TestDefaultValidator) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
819
820 var _ runtime.Object = &TestDefaultValidatorList{}
821
822 type TestDefaultValidatorList struct{}
823
824 func (*TestDefaultValidatorList) GetObjectKind() schema.ObjectKind { return nil }
825 func (*TestDefaultValidatorList) DeepCopyObject() runtime.Object { return nil }
826
827 func (dv *TestDefaultValidator) Default() {
828 if dv.Replica < 2 {
829 dv.Replica = 2
830 }
831 }
832
833 var _ admission.Validator = &TestDefaultValidator{}
834
835 func (dv *TestDefaultValidator) ValidateCreate() (admission.Warnings, error) {
836 if dv.Replica < 0 {
837 return nil, errors.New("number of replica should be greater than or equal to 0")
838 }
839 return nil, nil
840 }
841
842 func (dv *TestDefaultValidator) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
843 if dv.Replica < 0 {
844 return nil, errors.New("number of replica should be greater than or equal to 0")
845 }
846 return nil, nil
847 }
848
849 func (dv *TestDefaultValidator) ValidateDelete() (admission.Warnings, error) {
850 if dv.Replica > 0 {
851 return nil, errors.New("number of replica should be less than or equal to 0 to delete")
852 }
853 return nil, nil
854 }
855
856
857
858 type TestCustomDefaulter struct{}
859
860 func (*TestCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error {
861 logf.FromContext(ctx).Info("Defaulting object")
862 req, err := admission.RequestFromContext(ctx)
863 if err != nil {
864 return fmt.Errorf("expected admission.Request in ctx: %w", err)
865 }
866 if req.Kind.Kind != testDefaulterKind {
867 return fmt.Errorf("expected Kind TestDefaulter got %q", req.Kind.Kind)
868 }
869
870 d := obj.(*TestDefaulter)
871 if d.Replica < 2 {
872 d.Replica = 2
873 }
874 return nil
875 }
876
877 var _ admission.CustomDefaulter = &TestCustomDefaulter{}
878
879
880
881 type TestCustomValidator struct{}
882
883 func (*TestCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
884 logf.FromContext(ctx).Info("Validating object")
885 req, err := admission.RequestFromContext(ctx)
886 if err != nil {
887 return nil, fmt.Errorf("expected admission.Request in ctx: %w", err)
888 }
889 if req.Kind.Kind != testValidatorKind {
890 return nil, fmt.Errorf("expected Kind TestValidator got %q", req.Kind.Kind)
891 }
892
893 v := obj.(*TestValidator)
894 if v.Replica < 0 {
895 return nil, errors.New("number of replica should be greater than or equal to 0")
896 }
897 return nil, nil
898 }
899
900 func (*TestCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
901 logf.FromContext(ctx).Info("Validating object")
902 req, err := admission.RequestFromContext(ctx)
903 if err != nil {
904 return nil, fmt.Errorf("expected admission.Request in ctx: %w", err)
905 }
906 if req.Kind.Kind != testValidatorKind {
907 return nil, fmt.Errorf("expected Kind TestValidator got %q", req.Kind.Kind)
908 }
909
910 v := newObj.(*TestValidator)
911 old := oldObj.(*TestValidator)
912 if v.Replica < 0 {
913 return nil, errors.New("number of replica should be greater than or equal to 0")
914 }
915 if v.Replica < old.Replica {
916 return nil, fmt.Errorf("new replica %v should not be fewer than old replica %v", v.Replica, old.Replica)
917 }
918 return nil, nil
919 }
920
921 func (*TestCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
922 logf.FromContext(ctx).Info("Validating object")
923 req, err := admission.RequestFromContext(ctx)
924 if err != nil {
925 return nil, fmt.Errorf("expected admission.Request in ctx: %w", err)
926 }
927 if req.Kind.Kind != testValidatorKind {
928 return nil, fmt.Errorf("expected Kind TestValidator got %q", req.Kind.Kind)
929 }
930
931 v := obj.(*TestValidator)
932 if v.Replica > 0 {
933 return nil, errors.New("number of replica should be less than or equal to 0 to delete")
934 }
935 return nil, nil
936 }
937
938 var _ admission.CustomValidator = &TestCustomValidator{}
939
View as plain text