1
16
17 package admission
18
19 import (
20 "context"
21 "errors"
22 "net/http"
23
24 . "github.com/onsi/ginkgo/v2"
25 . "github.com/onsi/gomega"
26 admissionv1 "k8s.io/api/admission/v1"
27 apierrors "k8s.io/apimachinery/pkg/api/errors"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/client-go/kubernetes/scheme"
32 )
33
34 var fakeValidatorVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "fakeValidator"}
35
36 var _ = Describe("validatingHandler", func() {
37
38 decoder := NewDecoder(scheme.Scheme)
39
40 Context("when dealing with successful results without warning", func() {
41 f := &fakeValidator{ErrorToReturn: nil, GVKToReturn: fakeValidatorVK, WarningsToReturn: nil}
42 handler := validatingHandler{validator: f, decoder: decoder}
43
44 It("should return 200 in response when create succeeds", func() {
45
46 response := handler.Handle(context.TODO(), Request{
47 AdmissionRequest: admissionv1.AdmissionRequest{
48 Operation: admissionv1.Create,
49 Object: runtime.RawExtension{
50 Raw: []byte("{}"),
51 Object: handler.validator,
52 },
53 },
54 })
55
56 Expect(response.Allowed).Should(BeTrue())
57 Expect(response.Result.Code).Should(Equal(int32(http.StatusOK)))
58 })
59
60 It("should return 200 in response when update succeeds", func() {
61
62 response := handler.Handle(context.TODO(), Request{
63 AdmissionRequest: admissionv1.AdmissionRequest{
64 Operation: admissionv1.Update,
65 Object: runtime.RawExtension{
66 Raw: []byte("{}"),
67 Object: handler.validator,
68 },
69 OldObject: runtime.RawExtension{
70 Raw: []byte("{}"),
71 Object: handler.validator,
72 },
73 },
74 })
75 Expect(response.Allowed).Should(BeTrue())
76 Expect(response.Result.Code).Should(Equal(int32(http.StatusOK)))
77 })
78
79 It("should return 200 in response when delete succeeds", func() {
80
81 response := handler.Handle(context.TODO(), Request{
82 AdmissionRequest: admissionv1.AdmissionRequest{
83 Operation: admissionv1.Delete,
84 OldObject: runtime.RawExtension{
85 Raw: []byte("{}"),
86 Object: handler.validator,
87 },
88 },
89 })
90 Expect(response.Allowed).Should(BeTrue())
91 Expect(response.Result.Code).Should(Equal(int32(http.StatusOK)))
92 })
93 })
94
95 const warningMessage = "warning message"
96 const anotherWarningMessage = "another warning message"
97 Context("when dealing with successful results with warning", func() {
98 f := &fakeValidator{ErrorToReturn: nil, GVKToReturn: fakeValidatorVK, WarningsToReturn: []string{
99 warningMessage,
100 anotherWarningMessage,
101 }}
102 handler := validatingHandler{validator: f, decoder: decoder}
103
104 It("should return 200 in response when create succeeds, with warning messages", func() {
105 response := handler.Handle(context.TODO(), Request{
106 AdmissionRequest: admissionv1.AdmissionRequest{
107 Operation: admissionv1.Create,
108 Object: runtime.RawExtension{
109 Raw: []byte("{}"),
110 Object: handler.validator,
111 },
112 },
113 })
114
115 Expect(response.Allowed).Should(BeTrue())
116 Expect(response.Result.Code).Should(Equal(int32(http.StatusOK)))
117 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(warningMessage))
118 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(anotherWarningMessage))
119 })
120
121 It("should return 200 in response when update succeeds, with warning messages", func() {
122
123 response := handler.Handle(context.TODO(), Request{
124 AdmissionRequest: admissionv1.AdmissionRequest{
125 Operation: admissionv1.Update,
126 Object: runtime.RawExtension{
127 Raw: []byte("{}"),
128 Object: handler.validator,
129 },
130 OldObject: runtime.RawExtension{
131 Raw: []byte("{}"),
132 Object: handler.validator,
133 },
134 },
135 })
136 Expect(response.Allowed).Should(BeTrue())
137 Expect(response.Result.Code).Should(Equal(int32(http.StatusOK)))
138 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(warningMessage))
139 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(anotherWarningMessage))
140 })
141
142 It("should return 200 in response when delete succeeds, with warning messages", func() {
143
144 response := handler.Handle(context.TODO(), Request{
145 AdmissionRequest: admissionv1.AdmissionRequest{
146 Operation: admissionv1.Delete,
147 OldObject: runtime.RawExtension{
148 Raw: []byte("{}"),
149 Object: handler.validator,
150 },
151 },
152 })
153 Expect(response.Allowed).Should(BeTrue())
154 Expect(response.Result.Code).Should(Equal(int32(http.StatusOK)))
155 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(warningMessage))
156 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(anotherWarningMessage))
157 })
158 })
159
160 Context("when dealing with Status errors, with warning messages", func() {
161
162 expectedError := &apierrors.StatusError{
163 ErrStatus: metav1.Status{
164 Message: "some message",
165 Code: http.StatusUnprocessableEntity,
166 },
167 }
168 f := &fakeValidator{ErrorToReturn: expectedError, GVKToReturn: fakeValidatorVK, WarningsToReturn: []string{warningMessage, anotherWarningMessage}}
169 handler := validatingHandler{validator: f, decoder: decoder}
170
171 It("should propagate the Status from ValidateCreate's return value to the HTTP response", func() {
172
173 response := handler.Handle(context.TODO(), Request{
174 AdmissionRequest: admissionv1.AdmissionRequest{
175 Operation: admissionv1.Create,
176 Object: runtime.RawExtension{
177 Raw: []byte("{}"),
178 Object: handler.validator,
179 },
180 },
181 })
182
183 Expect(response.Allowed).Should(BeFalse())
184 Expect(response.Result.Code).Should(Equal(expectedError.Status().Code))
185 Expect(*response.Result).Should(Equal(expectedError.Status()))
186 Expect(response.AdmissionResponse.Warnings).Should(ContainElements(warningMessage))
187 Expect(response.AdmissionResponse.Warnings).Should(ContainElements(anotherWarningMessage))
188
189 })
190
191 It("should propagate the Status from ValidateUpdate's return value to the HTTP response", func() {
192
193 response := handler.Handle(context.TODO(), Request{
194 AdmissionRequest: admissionv1.AdmissionRequest{
195 Operation: admissionv1.Update,
196 Object: runtime.RawExtension{
197 Raw: []byte("{}"),
198 Object: handler.validator,
199 },
200 OldObject: runtime.RawExtension{
201 Raw: []byte("{}"),
202 Object: handler.validator,
203 },
204 },
205 })
206
207 Expect(response.Allowed).Should(BeFalse())
208 Expect(response.Result.Code).Should(Equal(expectedError.Status().Code))
209 Expect(*response.Result).Should(Equal(expectedError.Status()))
210 Expect(response.AdmissionResponse.Warnings).Should(ContainElements(warningMessage))
211 Expect(response.AdmissionResponse.Warnings).Should(ContainElements(anotherWarningMessage))
212
213 })
214
215 It("should propagate the Status from ValidateDelete's return value to the HTTP response", func() {
216
217 response := handler.Handle(context.TODO(), Request{
218 AdmissionRequest: admissionv1.AdmissionRequest{
219 Operation: admissionv1.Delete,
220 OldObject: runtime.RawExtension{
221 Raw: []byte("{}"),
222 Object: handler.validator,
223 },
224 },
225 })
226
227 Expect(response.Allowed).Should(BeFalse())
228 Expect(response.Result.Code).Should(Equal(expectedError.Status().Code))
229 Expect(*response.Result).Should(Equal(expectedError.Status()))
230 Expect(response.AdmissionResponse.Warnings).Should(ContainElements(warningMessage))
231 Expect(response.AdmissionResponse.Warnings).Should(ContainElements(anotherWarningMessage))
232
233 })
234
235 })
236
237 Context("when dealing with Status errors, without warning messages", func() {
238
239 expectedError := &apierrors.StatusError{
240 ErrStatus: metav1.Status{
241 Message: "some message",
242 Code: http.StatusUnprocessableEntity,
243 },
244 }
245 f := &fakeValidator{ErrorToReturn: expectedError, GVKToReturn: fakeValidatorVK, WarningsToReturn: nil}
246 handler := validatingHandler{validator: f, decoder: decoder}
247
248 It("should propagate the Status from ValidateCreate's return value to the HTTP response", func() {
249
250 response := handler.Handle(context.TODO(), Request{
251 AdmissionRequest: admissionv1.AdmissionRequest{
252 Operation: admissionv1.Create,
253 Object: runtime.RawExtension{
254 Raw: []byte("{}"),
255 Object: handler.validator,
256 },
257 },
258 })
259
260 Expect(response.Allowed).Should(BeFalse())
261 Expect(response.Result.Code).Should(Equal(expectedError.Status().Code))
262 Expect(*response.Result).Should(Equal(expectedError.Status()))
263
264 })
265
266 It("should propagate the Status from ValidateUpdate's return value to the HTTP response", func() {
267
268 response := handler.Handle(context.TODO(), Request{
269 AdmissionRequest: admissionv1.AdmissionRequest{
270 Operation: admissionv1.Update,
271 Object: runtime.RawExtension{
272 Raw: []byte("{}"),
273 Object: handler.validator,
274 },
275 OldObject: runtime.RawExtension{
276 Raw: []byte("{}"),
277 Object: handler.validator,
278 },
279 },
280 })
281
282 Expect(response.Allowed).Should(BeFalse())
283 Expect(response.Result.Code).Should(Equal(expectedError.Status().Code))
284 Expect(*response.Result).Should(Equal(expectedError.Status()))
285
286 })
287
288 It("should propagate the Status from ValidateDelete's return value to the HTTP response", func() {
289
290 response := handler.Handle(context.TODO(), Request{
291 AdmissionRequest: admissionv1.AdmissionRequest{
292 Operation: admissionv1.Delete,
293 OldObject: runtime.RawExtension{
294 Raw: []byte("{}"),
295 Object: handler.validator,
296 },
297 },
298 })
299
300 Expect(response.Allowed).Should(BeFalse())
301 Expect(response.Result.Code).Should(Equal(expectedError.Status().Code))
302 Expect(*response.Result).Should(Equal(expectedError.Status()))
303
304 })
305
306 })
307
308 Context("when dealing with non-status errors, without warning messages", func() {
309
310 expectedError := errors.New("some error")
311 f := &fakeValidator{ErrorToReturn: expectedError, GVKToReturn: fakeValidatorVK}
312 handler := validatingHandler{validator: f, decoder: decoder}
313
314 It("should return 403 response when ValidateCreate with error message embedded", func() {
315
316 response := handler.Handle(context.TODO(), Request{
317 AdmissionRequest: admissionv1.AdmissionRequest{
318 Operation: admissionv1.Create,
319 Object: runtime.RawExtension{
320 Raw: []byte("{}"),
321 Object: handler.validator,
322 },
323 },
324 })
325 Expect(response.Allowed).Should(BeFalse())
326 Expect(response.Result.Code).Should(Equal(int32(http.StatusForbidden)))
327 Expect(response.Result.Reason).Should(Equal(metav1.StatusReasonForbidden))
328 Expect(response.Result.Message).Should(Equal(expectedError.Error()))
329
330 })
331
332 It("should return 403 response when ValidateUpdate returns non-APIStatus error", func() {
333
334 response := handler.Handle(context.TODO(), Request{
335 AdmissionRequest: admissionv1.AdmissionRequest{
336 Operation: admissionv1.Update,
337 Object: runtime.RawExtension{
338 Raw: []byte("{}"),
339 Object: handler.validator,
340 },
341 OldObject: runtime.RawExtension{
342 Raw: []byte("{}"),
343 Object: handler.validator,
344 },
345 },
346 })
347 Expect(response.Allowed).Should(BeFalse())
348 Expect(response.Result.Code).Should(Equal(int32(http.StatusForbidden)))
349 Expect(response.Result.Reason).Should(Equal(metav1.StatusReasonForbidden))
350 Expect(response.Result.Message).Should(Equal(expectedError.Error()))
351
352 })
353
354 It("should return 403 response when ValidateDelete returns non-APIStatus error", func() {
355 response := handler.Handle(context.TODO(), Request{
356 AdmissionRequest: admissionv1.AdmissionRequest{
357 Operation: admissionv1.Delete,
358 OldObject: runtime.RawExtension{
359 Raw: []byte("{}"),
360 Object: handler.validator,
361 },
362 },
363 })
364 Expect(response.Allowed).Should(BeFalse())
365 Expect(response.Result.Code).Should(Equal(int32(http.StatusForbidden)))
366 Expect(response.Result.Reason).Should(Equal(metav1.StatusReasonForbidden))
367 Expect(response.Result.Message).Should(Equal(expectedError.Error()))
368 })
369 })
370
371 Context("when dealing with non-status errors, with warning messages", func() {
372
373 expectedError := errors.New("some error")
374 f := &fakeValidator{ErrorToReturn: expectedError, GVKToReturn: fakeValidatorVK, WarningsToReturn: []string{warningMessage, anotherWarningMessage}}
375 handler := validatingHandler{validator: f, decoder: decoder}
376
377 It("should return 403 response when ValidateCreate with error message embedded", func() {
378
379 response := handler.Handle(context.TODO(), Request{
380 AdmissionRequest: admissionv1.AdmissionRequest{
381 Operation: admissionv1.Create,
382 Object: runtime.RawExtension{
383 Raw: []byte("{}"),
384 Object: handler.validator,
385 },
386 },
387 })
388 Expect(response.Allowed).Should(BeFalse())
389 Expect(response.Result.Code).Should(Equal(int32(http.StatusForbidden)))
390 Expect(response.Result.Reason).Should(Equal(metav1.StatusReasonForbidden))
391 Expect(response.Result.Message).Should(Equal(expectedError.Error()))
392 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(warningMessage))
393 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(anotherWarningMessage))
394 })
395
396 It("should return 403 response when ValidateUpdate returns non-APIStatus error", func() {
397
398 response := handler.Handle(context.TODO(), Request{
399 AdmissionRequest: admissionv1.AdmissionRequest{
400 Operation: admissionv1.Update,
401 Object: runtime.RawExtension{
402 Raw: []byte("{}"),
403 Object: handler.validator,
404 },
405 OldObject: runtime.RawExtension{
406 Raw: []byte("{}"),
407 Object: handler.validator,
408 },
409 },
410 })
411 Expect(response.Allowed).Should(BeFalse())
412 Expect(response.Result.Code).Should(Equal(int32(http.StatusForbidden)))
413 Expect(response.Result.Reason).Should(Equal(metav1.StatusReasonForbidden))
414 Expect(response.Result.Message).Should(Equal(expectedError.Error()))
415 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(warningMessage))
416 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(anotherWarningMessage))
417
418 })
419
420 It("should return 403 response when ValidateDelete returns non-APIStatus error", func() {
421 response := handler.Handle(context.TODO(), Request{
422 AdmissionRequest: admissionv1.AdmissionRequest{
423 Operation: admissionv1.Delete,
424 OldObject: runtime.RawExtension{
425 Raw: []byte("{}"),
426 Object: handler.validator,
427 },
428 },
429 })
430 Expect(response.Allowed).Should(BeFalse())
431 Expect(response.Result.Code).Should(Equal(int32(http.StatusForbidden)))
432 Expect(response.Result.Reason).Should(Equal(metav1.StatusReasonForbidden))
433 Expect(response.Result.Message).Should(Equal(expectedError.Error()))
434 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(warningMessage))
435 Expect(response.AdmissionResponse.Warnings).Should(ContainElement(anotherWarningMessage))
436
437 })
438 })
439
440 PIt("should return 400 in response when create fails on decode", func() {})
441
442 PIt("should return 400 in response when update fails on decoding new object", func() {})
443
444 PIt("should return 400 in response when update fails on decoding old object", func() {})
445
446 PIt("should return 400 in response when delete fails on decode", func() {})
447
448 })
449
450
451
452
453
454
455 type fakeValidator struct {
456
457 ErrorToReturn error `json:"errorToReturn,omitempty"`
458
459 GVKToReturn schema.GroupVersionKind
460
461 WarningsToReturn []string
462 }
463
464 func (v *fakeValidator) ValidateCreate() (warnings Warnings, err error) {
465 return v.WarningsToReturn, v.ErrorToReturn
466 }
467
468 func (v *fakeValidator) ValidateUpdate(old runtime.Object) (warnings Warnings, err error) {
469 return v.WarningsToReturn, v.ErrorToReturn
470 }
471
472 func (v *fakeValidator) ValidateDelete() (warnings Warnings, err error) {
473 return v.WarningsToReturn, v.ErrorToReturn
474 }
475
476 func (v *fakeValidator) SetGroupVersionKind(gvk schema.GroupVersionKind) {
477 v.GVKToReturn = gvk
478 }
479
480 func (v *fakeValidator) GroupVersionKind() schema.GroupVersionKind {
481 return v.GVKToReturn
482 }
483
484 func (v *fakeValidator) GetObjectKind() schema.ObjectKind {
485 return v
486 }
487
488 func (v *fakeValidator) DeepCopyObject() runtime.Object {
489 return &fakeValidator{
490 ErrorToReturn: v.ErrorToReturn,
491 GVKToReturn: v.GVKToReturn,
492 WarningsToReturn: v.WarningsToReturn,
493 }
494 }
495
View as plain text