1
16
17 package cel
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "strings"
24 "sync"
25 "testing"
26 "time"
27
28 "k8s.io/api/admission/v1beta1"
29 appsv1beta1 "k8s.io/api/apps/v1beta1"
30 authenticationv1 "k8s.io/api/authentication/v1"
31 corev1 "k8s.io/api/core/v1"
32 extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
33 policyv1 "k8s.io/api/policy/v1"
34 apierrors "k8s.io/apimachinery/pkg/api/errors"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37 "k8s.io/apimachinery/pkg/runtime"
38 "k8s.io/apimachinery/pkg/runtime/schema"
39 "k8s.io/apimachinery/pkg/types"
40 "k8s.io/apimachinery/pkg/util/sets"
41 "k8s.io/apimachinery/pkg/util/wait"
42 "k8s.io/client-go/dynamic"
43 clientset "k8s.io/client-go/kubernetes"
44 "k8s.io/client-go/util/retry"
45 "k8s.io/kubernetes/test/integration/etcd"
46 )
47
48
49
50
51
52
53 const (
54 testNamespace = "webhook-integration"
55 testClientUsername = "webhook-integration-client"
56
57 mutation = "mutation"
58 validation = "validation"
59 )
60
61
62
63 type admissionTestExpectationHolder interface {
64 reset(t *testing.T)
65 expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.GroupVersionKind, operation v1beta1.Operation, name, namespace string, object, oldObject, options bool)
66 verify(t *testing.T)
67 }
68
69 type testContext struct {
70 t *testing.T
71
72
73 admissionHolder admissionTestExpectationHolder
74
75 client dynamic.Interface
76 clientset clientset.Interface
77 verb string
78 gvr schema.GroupVersionResource
79 resource metav1.APIResource
80 resources map[schema.GroupVersionResource]metav1.APIResource
81 }
82
83 type testFunc func(*testContext)
84
85 var (
86
87
88 defaultResourceFuncs = map[string]testFunc{
89 "create": testResourceCreate,
90 "update": testResourceUpdate,
91 "patch": testResourcePatch,
92 "delete": testResourceDelete,
93 "deletecollection": testResourceDeletecollection,
94 }
95
96
97
98 defaultSubresourceFuncs = map[string]testFunc{
99 "update": testSubresourceUpdate,
100 "patch": testSubresourcePatch,
101 }
102
103
104 customTestFuncs = map[schema.GroupVersionResource]map[string]testFunc{
105 gvr("", "v1", "namespaces"): {"delete": testNamespaceDelete},
106
107 gvr("apps", "v1beta1", "deployments/rollback"): {"create": testDeploymentRollback},
108 gvr("extensions", "v1beta1", "deployments/rollback"): {"create": testDeploymentRollback},
109
110 gvr("", "v1", "pods/attach"): {"create": testPodConnectSubresource},
111 gvr("", "v1", "pods/exec"): {"create": testPodConnectSubresource},
112 gvr("", "v1", "pods/portforward"): {"create": testPodConnectSubresource},
113
114 gvr("", "v1", "bindings"): {"create": testPodBindingEviction},
115 gvr("", "v1", "pods/binding"): {"create": testPodBindingEviction},
116 gvr("", "v1", "pods/eviction"): {"create": testPodBindingEviction},
117
118 gvr("", "v1", "nodes/proxy"): {"*": testSubresourceProxy},
119 gvr("", "v1", "pods/proxy"): {"*": testSubresourceProxy},
120 gvr("", "v1", "services/proxy"): {"*": testSubresourceProxy},
121
122 gvr("", "v1", "serviceaccounts/token"): {"create": testTokenCreate},
123
124 gvr("random.numbers.com", "v1", "integers"): {"create": testPruningRandomNumbers},
125
126
127
128
129 }
130
131
132
133 admissionExemptResources = map[schema.GroupVersionResource]bool{
134
135
136
137
138
139
140 gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): true,
141 gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies/status"): true,
142 gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicybindings"): true,
143 gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): true,
144 gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies/status"): true,
145 gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicybindings"): true,
146 gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicies"): true,
147 gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicies/status"): true,
148 gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicybindings"): true,
149
150 gvr("authentication.k8s.io", "v1", "selfsubjectreviews"): true,
151 gvr("authentication.k8s.io", "v1beta1", "selfsubjectreviews"): true,
152 gvr("authentication.k8s.io", "v1alpha1", "selfsubjectreviews"): true,
153 gvr("authentication.k8s.io", "v1", "tokenreviews"): true,
154 gvr("authorization.k8s.io", "v1", "localsubjectaccessreviews"): true,
155 gvr("authorization.k8s.io", "v1", "selfsubjectaccessreviews"): true,
156 gvr("authorization.k8s.io", "v1", "selfsubjectrulesreviews"): true,
157 gvr("authorization.k8s.io", "v1", "subjectaccessreviews"): true,
158 }
159
160 parentResources = map[schema.GroupVersionResource]schema.GroupVersionResource{
161 gvr("extensions", "v1beta1", "replicationcontrollers/scale"): gvr("", "v1", "replicationcontrollers"),
162 }
163
164
165 stubDataOverrides = map[schema.GroupVersionResource]string{
166
167 gvr("authentication.k8s.io", "v1", "tokenreviews"): `{"metadata": {"name": "tokenreview"}, "spec": {"token": "token", "audience": ["audience1","audience2"]}}`,
168 gvr("authentication.k8s.io", "v1beta1", "tokenreviews"): `{"metadata": {"name": "tokenreview"}, "spec": {"token": "token", "audience": ["audience1","audience2"]}}`,
169 gvr("authentication.k8s.io", "v1alpha1", "selfsubjectreviews"): `{"metadata": {"name": "SelfSubjectReview"},"status":{"userInfo":{}}}`,
170 gvr("authentication.k8s.io", "v1beta1", "selfsubjectreviews"): `{"metadata": {"name": "SelfSubjectReview"},"status":{"userInfo":{}}}`,
171 gvr("authentication.k8s.io", "v1", "selfsubjectreviews"): `{"metadata": {"name": "SelfSubjectReview"},"status":{"userInfo":{}}}`,
172 gvr("authorization.k8s.io", "v1", "localsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"uid": "token", "user": "user1","groups": ["group1","group2"],"resourceAttributes": {"name":"name1","namespace":"` + testNamespace + `"}}}`,
173 gvr("authorization.k8s.io", "v1", "subjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"user":"user1","resourceAttributes": {"name":"name1", "namespace":"` + testNamespace + `"}}}`,
174 gvr("authorization.k8s.io", "v1", "selfsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"resourceAttributes": {"name":"name1", "namespace":""}}}`,
175 gvr("authorization.k8s.io", "v1", "selfsubjectrulesreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"namespace":"` + testNamespace + `"}}`,
176 gvr("authorization.k8s.io", "v1beta1", "localsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"uid": "token", "user": "user1","groups": ["group1","group2"],"resourceAttributes": {"name":"name1","namespace":"` + testNamespace + `"}}}`,
177 gvr("authorization.k8s.io", "v1beta1", "subjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"user":"user1","resourceAttributes": {"name":"name1", "namespace":"` + testNamespace + `"}}}`,
178 gvr("authorization.k8s.io", "v1beta1", "selfsubjectaccessreviews"): `{"metadata": {"name": "", "namespace":""}, "spec": {"resourceAttributes": {"name":"name1", "namespace":""}}}`,
179 gvr("authorization.k8s.io", "v1beta1", "selfsubjectrulesreviews"): `{"metadata": {"name": "", "namespace":"` + testNamespace + `"}, "spec": {"namespace":"` + testNamespace + `"}}`,
180
181
182 }
183 )
184
185 type webhookOptions struct {
186 version string
187
188
189 phase string
190
191
192
193 converted bool
194 }
195
196 type holder struct {
197 lock sync.RWMutex
198
199 t *testing.T
200
201
202
203
204 recordGVR metav1.GroupVersionResource
205 recordOperation string
206 recordNamespace string
207 recordName string
208
209 expectGVK schema.GroupVersionKind
210 expectObject bool
211 expectOldObject bool
212 expectOptionsGVK schema.GroupVersionKind
213 expectOptions bool
214
215
216
217 gvrToConvertedGVR map[metav1.GroupVersionResource]metav1.GroupVersionResource
218
219
220 gvrToConvertedGVK map[metav1.GroupVersionResource]schema.GroupVersionKind
221
222 recorded map[webhookOptions]*admissionRequest
223 }
224
225 func (h *holder) reset(t *testing.T) {
226 h.lock.Lock()
227 defer h.lock.Unlock()
228 h.t = t
229 h.recordGVR = metav1.GroupVersionResource{}
230 h.expectGVK = schema.GroupVersionKind{}
231 h.recordOperation = ""
232 h.recordName = ""
233 h.recordNamespace = ""
234 h.expectObject = false
235 h.expectOldObject = false
236 h.expectOptionsGVK = schema.GroupVersionKind{}
237 h.expectOptions = false
238
239
240
241
242 h.recorded = map[webhookOptions]*admissionRequest{}
243 for _, phase := range []string{mutation, validation} {
244 for _, converted := range []bool{true, false} {
245 for _, version := range []string{"v1", "v1beta1"} {
246 h.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = nil
247 }
248 }
249 }
250 }
251 func (h *holder) expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.GroupVersionKind, operation v1beta1.Operation, name, namespace string, object, oldObject, options bool) {
252
253 if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" {
254 namespace = name
255 }
256
257 h.lock.Lock()
258 defer h.lock.Unlock()
259 h.recordGVR = metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
260 h.expectGVK = gvk
261 h.recordOperation = string(operation)
262 h.recordName = name
263 h.recordNamespace = namespace
264 h.expectObject = object
265 h.expectOldObject = oldObject
266 h.expectOptionsGVK = optionsGVK
267 h.expectOptions = options
268
269
270
271
272 h.recorded = map[webhookOptions]*admissionRequest{}
273 for _, phase := range []string{mutation, validation} {
274 for _, converted := range []bool{true, false} {
275 for _, version := range []string{"v1", "v1beta1"} {
276 h.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = nil
277 }
278 }
279 }
280 }
281
282 type admissionRequest struct {
283 Operation string
284 Resource metav1.GroupVersionResource
285 SubResource string
286 Namespace string
287 Name string
288 Object runtime.RawExtension
289 OldObject runtime.RawExtension
290 Options runtime.RawExtension
291 }
292
293 func (h *holder) record(version string, phase string, converted bool, request *admissionRequest) {
294 h.lock.Lock()
295 defer h.lock.Unlock()
296
297
298 debug := false
299 if debug {
300 h.t.Logf("%s %#v %v", request.Operation, request.Resource, request.SubResource)
301 }
302
303 resource := request.Resource
304 if len(request.SubResource) > 0 {
305 resource.Resource += "/" + request.SubResource
306 }
307
308
309 gvrToRecord := h.recordGVR
310 if converted {
311
312 gvrToRecord = h.gvrToConvertedGVR[h.recordGVR]
313 }
314 if resource != gvrToRecord {
315 if debug {
316 h.t.Log(resource, "!=", gvrToRecord)
317 }
318 return
319 }
320
321 if request.Operation != h.recordOperation {
322 if debug {
323 h.t.Log(request.Operation, "!=", h.recordOperation)
324 }
325 return
326 }
327 if request.Namespace != h.recordNamespace {
328 if debug {
329 h.t.Log(request.Namespace, "!=", h.recordNamespace)
330 }
331 return
332 }
333
334 name := request.Name
335 if name != h.recordName {
336 if debug {
337 h.t.Log(name, "!=", h.recordName)
338 }
339 return
340 }
341
342 if debug {
343 h.t.Logf("recording: %#v = %s %#v %v", webhookOptions{version: version, phase: phase, converted: converted}, request.Operation, request.Resource, request.SubResource)
344 }
345 h.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = request
346 }
347
348 func (h *holder) verify(t *testing.T) {
349 h.lock.Lock()
350 defer h.lock.Unlock()
351
352 for options, value := range h.recorded {
353 if err := h.verifyRequest(options, value); err != nil {
354 t.Errorf("version: %v, phase:%v, converted:%v error: %v", options.version, options.phase, options.converted, err)
355 }
356 }
357 }
358
359 func (h *holder) verifyRequest(webhookOptions webhookOptions, request *admissionRequest) error {
360 converted := webhookOptions.converted
361
362
363 if admissionExemptResources[gvr(h.recordGVR.Group, h.recordGVR.Version, h.recordGVR.Resource)] {
364 if request == nil {
365 return nil
366 }
367 return fmt.Errorf("admission webhook was called, but not supposed to")
368 }
369
370 if request == nil {
371 return fmt.Errorf("no request received")
372 }
373
374 if h.expectObject {
375 if err := h.verifyObject(converted, request.Object.Object); err != nil {
376 return fmt.Errorf("object error: %v", err)
377 }
378 } else if request.Object.Object != nil {
379 return fmt.Errorf("unexpected object: %#v", request.Object.Object)
380 }
381
382 if h.expectOldObject {
383 if err := h.verifyObject(converted, request.OldObject.Object); err != nil {
384 return fmt.Errorf("old object error: %v", err)
385 }
386 } else if request.OldObject.Object != nil {
387 return fmt.Errorf("unexpected old object: %#v", request.OldObject.Object)
388 }
389
390 if h.expectOptions {
391 if err := h.verifyOptions(request.Options.Object); err != nil {
392 return fmt.Errorf("options error: %v", err)
393 }
394 } else if request.Options.Object != nil {
395 return fmt.Errorf("unexpected options: %#v", request.Options.Object)
396 }
397
398
399
400
401
402
403
404 return nil
405 }
406
407 func (h *holder) verifyObject(converted bool, obj runtime.Object) error {
408 if obj == nil {
409 return fmt.Errorf("no object sent")
410 }
411 expectGVK := h.expectGVK
412 if converted {
413 expectGVK = h.gvrToConvertedGVK[h.recordGVR]
414 }
415 if obj.GetObjectKind().GroupVersionKind() != expectGVK {
416 return fmt.Errorf("expected %#v, got %#v", expectGVK, obj.GetObjectKind().GroupVersionKind())
417 }
418 return nil
419 }
420
421 func (h *holder) verifyOptions(options runtime.Object) error {
422 if options == nil {
423 return fmt.Errorf("no options sent")
424 }
425 if options.GetObjectKind().GroupVersionKind() != h.expectOptionsGVK {
426 return fmt.Errorf("expected %#v, got %#v", h.expectOptionsGVK, options.GetObjectKind().GroupVersionKind())
427 }
428 return nil
429 }
430
431 func getTestFunc(gvr schema.GroupVersionResource, verb string) testFunc {
432 if f, found := customTestFuncs[gvr][verb]; found {
433 return f
434 }
435 if f, found := customTestFuncs[gvr]["*"]; found {
436 return f
437 }
438 if strings.Contains(gvr.Resource, "/") {
439 if f, found := defaultSubresourceFuncs[verb]; found {
440 return f
441 }
442 return unimplemented
443 }
444 if f, found := defaultResourceFuncs[verb]; found {
445 return f
446 }
447 return unimplemented
448 }
449
450 func getStubObj(gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) {
451 stub := ""
452 if data, ok := etcd.GetEtcdStorageDataForNamespace(testNamespace)[gvr]; ok {
453 stub = data.Stub
454 }
455 if data, ok := stubDataOverrides[gvr]; ok {
456 stub = data
457 }
458 if len(stub) == 0 {
459 return nil, fmt.Errorf("no stub data for %#v", gvr)
460 }
461
462 stubObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
463 if err := json.Unmarshal([]byte(stub), &stubObj.Object); err != nil {
464 return nil, fmt.Errorf("error unmarshaling stub for %#v: %v", gvr, err)
465 }
466 return stubObj, nil
467 }
468
469 func createOrGetResource(client dynamic.Interface, gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) {
470 stubObj, err := getStubObj(gvr, resource)
471 if err != nil {
472 return nil, err
473 }
474 ns := ""
475 if resource.Namespaced {
476 ns = testNamespace
477 }
478 obj, err := client.Resource(gvr).Namespace(ns).Get(context.TODO(), stubObj.GetName(), metav1.GetOptions{})
479 if err == nil {
480 return obj, nil
481 }
482 if !apierrors.IsNotFound(err) {
483 return nil, err
484 }
485 return client.Resource(gvr).Namespace(ns).Create(context.TODO(), stubObj, metav1.CreateOptions{})
486 }
487
488 func gvr(group, version, resource string) schema.GroupVersionResource {
489 return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
490 }
491 func gvk(group, version, kind string) schema.GroupVersionKind {
492 return schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
493 }
494
495 var (
496 gvkCreateOptions = metav1.SchemeGroupVersion.WithKind("CreateOptions")
497 gvkUpdateOptions = metav1.SchemeGroupVersion.WithKind("UpdateOptions")
498 gvkDeleteOptions = metav1.SchemeGroupVersion.WithKind("DeleteOptions")
499 )
500
501 func shouldTestResource(gvr schema.GroupVersionResource, resource metav1.APIResource) bool {
502 return sets.NewString(resource.Verbs...).HasAny("create", "update", "patch", "connect", "delete", "deletecollection")
503 }
504
505 func shouldTestResourceVerb(gvr schema.GroupVersionResource, resource metav1.APIResource, verb string) bool {
506 return sets.NewString(resource.Verbs...).Has(verb)
507 }
508
509
510
511
512
513 func testResourceCreate(c *testContext) {
514 stubObj, err := getStubObj(c.gvr, c.resource)
515 if err != nil {
516 c.t.Error(err)
517 return
518 }
519 ns := ""
520 if c.resource.Namespaced {
521 ns = testNamespace
522 }
523 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, stubObj.GetName(), ns, true, false, true)
524 _, err = c.client.Resource(c.gvr).Namespace(ns).Create(context.TODO(), stubObj, metav1.CreateOptions{})
525 if err != nil {
526 c.t.Error(err)
527 return
528 }
529 }
530
531 func testResourceUpdate(c *testContext) {
532 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
533 obj, err := createOrGetResource(c.client, c.gvr, c.resource)
534 if err != nil {
535 return err
536 }
537 obj.SetAnnotations(map[string]string{"update": "true"})
538 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
539 _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Update(context.TODO(), obj, metav1.UpdateOptions{})
540 return err
541 }); err != nil {
542 c.t.Error(err)
543 return
544 }
545 }
546
547 func testResourcePatch(c *testContext) {
548 obj, err := createOrGetResource(c.client, c.gvr, c.resource)
549 if err != nil {
550 c.t.Error(err)
551 return
552 }
553 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
554 _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
555 context.TODO(),
556 obj.GetName(),
557 types.MergePatchType,
558 []byte(`{"metadata":{"annotations":{"patch":"true"}}}`),
559 metav1.PatchOptions{})
560 if err != nil {
561 c.t.Error(err)
562 return
563 }
564 }
565
566 func testResourceDelete(c *testContext) {
567
568 obj, err := createOrGetResource(c.client, c.gvr, c.resource)
569 if err != nil {
570 c.t.Error(err)
571 return
572 }
573 background := metav1.DeletePropagationBackground
574 zero := int64(0)
575 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, true, true)
576 err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
577 if err != nil {
578 c.t.Error(err)
579 return
580 }
581 c.admissionHolder.verify(c.t)
582
583
584 err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
585 obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
586 if apierrors.IsNotFound(err) {
587 return true, nil
588 }
589 if err == nil {
590 c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
591 return false, nil
592 }
593 return false, err
594 })
595 if err != nil {
596 c.t.Error(err)
597 return
598 }
599
600
601 obj, err = createOrGetResource(c.client, c.gvr, c.resource)
602 if err != nil {
603 c.t.Error(err)
604 return
605 }
606
607
608
609
610 _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
611 context.TODO(),
612 obj.GetName(),
613 types.MergePatchType,
614 []byte(`{"metadata":{"finalizers":["test/k8s.io"]}}`),
615 metav1.PatchOptions{})
616 if err != nil {
617 c.t.Error(err)
618 return
619 }
620 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, true, true)
621 err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
622 if err != nil {
623 c.t.Error(err)
624 return
625 }
626 c.admissionHolder.verify(c.t)
627
628
629 err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
630 obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
631 if err != nil {
632 return false, err
633 }
634 finalizers := obj.GetFinalizers()
635 if len(finalizers) != 1 {
636 c.t.Logf("waiting for other finalizers on %#v %s to be removed, existing finalizers are %v", c.gvr, obj.GetName(), obj.GetFinalizers())
637 return false, nil
638 }
639 if finalizers[0] != "test/k8s.io" {
640 return false, fmt.Errorf("expected the single finalizer on %#v %s to be test/k8s.io, got %v", c.gvr, obj.GetName(), obj.GetFinalizers())
641 }
642 return true, nil
643 })
644 if err != nil {
645 c.t.Error(err)
646 return
647 }
648
649
650 _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
651 context.TODO(),
652 obj.GetName(),
653 types.MergePatchType,
654 []byte(`{"metadata":{"finalizers":[]}}`),
655 metav1.PatchOptions{})
656 if err != nil {
657 c.t.Error(err)
658 return
659 }
660
661 err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
662 obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
663 if apierrors.IsNotFound(err) {
664 return true, nil
665 }
666 if err == nil {
667 c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
668 return false, nil
669 }
670 return false, err
671 })
672 if err != nil {
673 c.t.Error(err)
674 return
675 }
676 }
677
678 func testResourceDeletecollection(c *testContext) {
679 obj, err := createOrGetResource(c.client, c.gvr, c.resource)
680 if err != nil {
681 c.t.Error(err)
682 return
683 }
684 background := metav1.DeletePropagationBackground
685 zero := int64(0)
686
687
688 _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
689 context.TODO(),
690 obj.GetName(),
691 types.MergePatchType,
692 []byte(`{"metadata":{"labels":{"webhooktest":"true"}}}`),
693 metav1.PatchOptions{})
694 if err != nil {
695 c.t.Error(err)
696 return
697 }
698
699
700 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, "", obj.GetNamespace(), false, true, true)
701
702
703 err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).DeleteCollection(context.TODO(), metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background}, metav1.ListOptions{LabelSelector: "webhooktest=true"})
704 if err != nil {
705 c.t.Error(err)
706 return
707 }
708
709
710 err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
711 obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
712 if apierrors.IsNotFound(err) {
713 return true, nil
714 }
715 if err == nil {
716 c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
717 return false, nil
718 }
719 return false, err
720 })
721 if err != nil {
722 c.t.Error(err)
723 return
724 }
725 }
726
727 func getParentGVR(gvr schema.GroupVersionResource) schema.GroupVersionResource {
728 parentGVR, found := parentResources[gvr]
729
730 if !found {
731 parentGVR = gvr
732 parentGVR.Resource = strings.Split(parentGVR.Resource, "/")[0]
733 }
734 return parentGVR
735 }
736
737 func testTokenCreate(c *testContext) {
738 saGVR := gvr("", "v1", "serviceaccounts")
739 sa, err := createOrGetResource(c.client, saGVR, c.resources[saGVR])
740 if err != nil {
741 c.t.Error(err)
742 return
743 }
744
745 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, sa.GetName(), sa.GetNamespace(), true, false, true)
746 if err = c.clientset.CoreV1().RESTClient().Post().Namespace(sa.GetNamespace()).Resource("serviceaccounts").Name(sa.GetName()).SubResource("token").Body(&authenticationv1.TokenRequest{
747 ObjectMeta: metav1.ObjectMeta{Name: sa.GetName()},
748 Spec: authenticationv1.TokenRequestSpec{
749 Audiences: []string{"api"},
750 },
751 }).Do(context.TODO()).Error(); err != nil {
752 c.t.Error(err)
753 return
754 }
755 c.admissionHolder.verify(c.t)
756 }
757
758 func testSubresourceUpdate(c *testContext) {
759 if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
760 parentGVR := getParentGVR(c.gvr)
761 parentResource := c.resources[parentGVR]
762 obj, err := createOrGetResource(c.client, parentGVR, parentResource)
763 if err != nil {
764 return err
765 }
766
767
768 submitObj := obj
769
770 gvrWithoutSubresources := c.gvr
771 gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
772 subresources := strings.Split(c.gvr.Resource, "/")[1:]
773
774
775 if sets.NewString(c.resource.Verbs...).Has("get") {
776 submitObj, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{}, subresources...)
777 if err != nil {
778 return err
779 }
780 }
781
782
783 submitObj.SetAnnotations(map[string]string{"subresourceupdate": "true"})
784
785
786 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
787
788 _, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Update(
789 context.TODO(),
790 submitObj,
791 metav1.UpdateOptions{},
792 subresources...,
793 )
794 return err
795 }); err != nil {
796 c.t.Error(err)
797 }
798 }
799
800 func testSubresourcePatch(c *testContext) {
801 parentGVR := getParentGVR(c.gvr)
802 parentResource := c.resources[parentGVR]
803 obj, err := createOrGetResource(c.client, parentGVR, parentResource)
804 if err != nil {
805 c.t.Error(err)
806 return
807 }
808
809 gvrWithoutSubresources := c.gvr
810 gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
811 subresources := strings.Split(c.gvr.Resource, "/")[1:]
812
813
814 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkUpdateOptions, v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true, true)
815
816 _, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Patch(
817 context.TODO(),
818 obj.GetName(),
819 types.MergePatchType,
820 []byte(`{"metadata":{"annotations":{"subresourcepatch":"true"}}}`),
821 metav1.PatchOptions{},
822 subresources...,
823 )
824 if err != nil {
825 c.t.Error(err)
826 return
827 }
828 }
829
830 func unimplemented(c *testContext) {
831 c.t.Errorf("Test function for %+v has not been implemented...", c.gvr)
832 }
833
834
835
836
837
838
839
840
841
842 func testNamespaceDelete(c *testContext) {
843 obj, err := createOrGetResource(c.client, c.gvr, c.resource)
844 if err != nil {
845 c.t.Error(err)
846 return
847 }
848 background := metav1.DeletePropagationBackground
849 zero := int64(0)
850
851 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkDeleteOptions, v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, true, true)
852 err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
853 if err != nil {
854 c.t.Error(err)
855 return
856 }
857 c.admissionHolder.verify(c.t)
858
859
860 obj, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
861 if err != nil {
862 c.t.Error(err)
863 return
864 }
865 err = unstructured.SetNestedField(obj.Object, nil, "spec", "finalizers")
866 if err != nil {
867 c.t.Error(err)
868 return
869 }
870 _, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Update(context.TODO(), obj, metav1.UpdateOptions{}, "finalize")
871 if err != nil {
872 c.t.Error(err)
873 return
874 }
875
876 obj, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
877 if err == nil || !apierrors.IsNotFound(err) {
878 c.t.Errorf("expected namespace to be gone, got %#v, %v", obj, err)
879 }
880 }
881
882
883
884
885 func testDeploymentRollback(c *testContext) {
886 deploymentGVR := gvr("apps", "v1", "deployments")
887 obj, err := createOrGetResource(c.client, deploymentGVR, c.resources[deploymentGVR])
888 if err != nil {
889 c.t.Error(err)
890 return
891 }
892
893 gvrWithoutSubresources := c.gvr
894 gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
895 subresources := strings.Split(c.gvr.Resource, "/")[1:]
896
897 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, obj.GetName(), obj.GetNamespace(), true, false, true)
898
899 var rollbackObj runtime.Object
900 switch c.gvr {
901 case gvr("apps", "v1beta1", "deployments/rollback"):
902 rollbackObj = &appsv1beta1.DeploymentRollback{
903 TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1beta1", Kind: "DeploymentRollback"},
904 Name: obj.GetName(),
905 RollbackTo: appsv1beta1.RollbackConfig{Revision: 0},
906 }
907 case gvr("extensions", "v1beta1", "deployments/rollback"):
908 rollbackObj = &extensionsv1beta1.DeploymentRollback{
909 TypeMeta: metav1.TypeMeta{APIVersion: "extensions/v1beta1", Kind: "DeploymentRollback"},
910 Name: obj.GetName(),
911 RollbackTo: extensionsv1beta1.RollbackConfig{Revision: 0},
912 }
913 default:
914 c.t.Errorf("unknown rollback resource %#v", c.gvr)
915 return
916 }
917
918 rollbackUnstructuredBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rollbackObj)
919 if err != nil {
920 c.t.Errorf("ToUnstructured failed: %v", err)
921 return
922 }
923 rollbackUnstructuredObj := &unstructured.Unstructured{Object: rollbackUnstructuredBody}
924 rollbackUnstructuredObj.SetName(obj.GetName())
925
926 _, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Create(context.TODO(), rollbackUnstructuredObj, metav1.CreateOptions{}, subresources...)
927 if err != nil {
928 c.t.Error(err)
929 return
930 }
931 }
932
933
934 func testPodConnectSubresource(c *testContext) {
935 podGVR := gvr("", "v1", "pods")
936 pod, err := createOrGetResource(c.client, podGVR, c.resources[podGVR])
937 if err != nil {
938 c.t.Error(err)
939 return
940 }
941
942
943 for _, httpMethod := range []string{"GET", "POST"} {
944 c.t.Logf("verifying %v", httpMethod)
945
946 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), schema.GroupVersionKind{}, v1beta1.Connect, pod.GetName(), pod.GetNamespace(), true, false, false)
947 var err error
948 switch c.gvr {
949 case gvr("", "v1", "pods/exec"):
950 err = c.clientset.CoreV1().RESTClient().Verb(httpMethod).Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("exec").Do(context.TODO()).Error()
951 case gvr("", "v1", "pods/attach"):
952 err = c.clientset.CoreV1().RESTClient().Verb(httpMethod).Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("attach").Do(context.TODO()).Error()
953 case gvr("", "v1", "pods/portforward"):
954 err = c.clientset.CoreV1().RESTClient().Verb(httpMethod).Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("portforward").Do(context.TODO()).Error()
955 default:
956 c.t.Errorf("unknown subresource %#v", c.gvr)
957 return
958 }
959
960 if err != nil {
961 c.t.Logf("debug: result of subresource connect: %v", err)
962 }
963 c.admissionHolder.verify(c.t)
964
965 }
966 }
967
968
969 func testPodBindingEviction(c *testContext) {
970 podGVR := gvr("", "v1", "pods")
971 pod, err := createOrGetResource(c.client, podGVR, c.resources[podGVR])
972 if err != nil {
973 c.t.Error(err)
974 return
975 }
976
977 background := metav1.DeletePropagationBackground
978 zero := int64(0)
979 forceDelete := metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background}
980 defer func() {
981 err := c.clientset.CoreV1().Pods(pod.GetNamespace()).Delete(context.TODO(), pod.GetName(), forceDelete)
982 if err != nil && !apierrors.IsNotFound(err) {
983 c.t.Error(err)
984 return
985 }
986 }()
987
988 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), gvkCreateOptions, v1beta1.Create, pod.GetName(), pod.GetNamespace(), true, false, true)
989
990 switch c.gvr {
991 case gvr("", "v1", "bindings"):
992 err = c.clientset.CoreV1().RESTClient().Post().Namespace(pod.GetNamespace()).Resource("bindings").Body(&corev1.Binding{
993 ObjectMeta: metav1.ObjectMeta{Name: pod.GetName()},
994 Target: corev1.ObjectReference{Name: "foo", Kind: "Node", APIVersion: "v1"},
995 }).Do(context.TODO()).Error()
996
997 case gvr("", "v1", "pods/binding"):
998 err = c.clientset.CoreV1().RESTClient().Post().Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("binding").Body(&corev1.Binding{
999 ObjectMeta: metav1.ObjectMeta{Name: pod.GetName()},
1000 Target: corev1.ObjectReference{Name: "foo", Kind: "Node", APIVersion: "v1"},
1001 }).Do(context.TODO()).Error()
1002
1003 case gvr("", "v1", "pods/eviction"):
1004 err = c.clientset.CoreV1().RESTClient().Post().Namespace(pod.GetNamespace()).Resource("pods").Name(pod.GetName()).SubResource("eviction").Body(&policyv1.Eviction{
1005 ObjectMeta: metav1.ObjectMeta{Name: pod.GetName()},
1006 DeleteOptions: &forceDelete,
1007 }).Do(context.TODO()).Error()
1008
1009 default:
1010 c.t.Errorf("unhandled resource %#v", c.gvr)
1011 return
1012 }
1013
1014 if err != nil {
1015 c.t.Error(err)
1016 return
1017 }
1018 }
1019
1020
1021 func testSubresourceProxy(c *testContext) {
1022 parentGVR := getParentGVR(c.gvr)
1023 parentResource := c.resources[parentGVR]
1024 obj, err := createOrGetResource(c.client, parentGVR, parentResource)
1025 if err != nil {
1026 c.t.Error(err)
1027 return
1028 }
1029
1030 gvrWithoutSubresources := c.gvr
1031 gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
1032 subresources := strings.Split(c.gvr.Resource, "/")[1:]
1033
1034 verbToHTTPMethods := map[string][]string{
1035 "create": {"POST", "GET", "HEAD", "OPTIONS"},
1036 "update": {"PUT"},
1037 "patch": {"PATCH"},
1038 "delete": {"DELETE"},
1039 }
1040 httpMethodsToTest, ok := verbToHTTPMethods[c.verb]
1041 if !ok {
1042 c.t.Errorf("unknown verb %v", c.verb)
1043 return
1044 }
1045
1046 for _, httpMethod := range httpMethodsToTest {
1047 c.t.Logf("testing %v", httpMethod)
1048 request := c.clientset.CoreV1().RESTClient().Verb(httpMethod)
1049
1050
1051 if len(obj.GetNamespace()) > 0 {
1052 request = request.Namespace(obj.GetNamespace())
1053 }
1054
1055
1056 c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), schema.GroupVersionKind{}, v1beta1.Connect, obj.GetName(), obj.GetNamespace(), true, false, false)
1057
1058 err = request.Resource(gvrWithoutSubresources.Resource).Name(obj.GetName()).SubResource(subresources...).Do(context.TODO()).Error()
1059 if err != nil {
1060 c.t.Logf("debug: result of subresource proxy (error expected): %v", err)
1061 }
1062
1063 c.admissionHolder.verify(c.t)
1064 }
1065 }
1066
1067 func testPruningRandomNumbers(c *testContext) {
1068 testResourceCreate(c)
1069
1070 cr2pant, err := c.client.Resource(c.gvr).Get(context.TODO(), "fortytwo", metav1.GetOptions{})
1071 if err != nil {
1072 c.t.Error(err)
1073 return
1074 }
1075
1076 foo, found, err := unstructured.NestedString(cr2pant.Object, "foo")
1077 if err != nil {
1078 c.t.Error(err)
1079 return
1080 }
1081 if found {
1082 c.t.Errorf("expected .foo to be pruned, but got: %s", foo)
1083 }
1084 }
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
View as plain text