1
16
17 package admissionwebhook
18
19 import (
20 "context"
21 "crypto/tls"
22 "crypto/x509"
23 "encoding/json"
24 "io"
25 "net/http"
26 "net/http/httptest"
27 "strconv"
28 "sync"
29 "testing"
30 "time"
31
32 admissionv1 "k8s.io/api/admission/v1"
33 admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
34 corev1 "k8s.io/api/core/v1"
35 apierrors "k8s.io/apimachinery/pkg/api/errors"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/types"
38 "k8s.io/apimachinery/pkg/util/wait"
39 clientset "k8s.io/client-go/kubernetes"
40 apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
41 "k8s.io/kubernetes/test/integration/framework"
42 )
43
44 type admissionRecorder struct {
45 mu sync.Mutex
46 upCh chan struct{}
47 upOnce sync.Once
48 requests []*admissionv1.AdmissionRequest
49 }
50
51 func (r *admissionRecorder) Record(req *admissionv1.AdmissionRequest) {
52 r.mu.Lock()
53 defer r.mu.Unlock()
54 r.requests = append(r.requests, req)
55 }
56
57 func (r *admissionRecorder) MarkerReceived() {
58 r.mu.Lock()
59 defer r.mu.Unlock()
60 r.upOnce.Do(func() {
61 close(r.upCh)
62 })
63 }
64
65 func (r *admissionRecorder) Reset() chan struct{} {
66 r.mu.Lock()
67 defer r.mu.Unlock()
68 r.requests = []*admissionv1.AdmissionRequest{}
69 r.upCh = make(chan struct{})
70 r.upOnce = sync.Once{}
71 return r.upCh
72 }
73
74 func newMatchConditionHandler(recorder *admissionRecorder) http.Handler {
75 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76 defer r.Body.Close()
77 data, err := io.ReadAll(r.Body)
78 if err != nil {
79 http.Error(w, err.Error(), 400)
80 }
81 review := admissionv1.AdmissionReview{}
82 if err := json.Unmarshal(data, &review); err != nil {
83 http.Error(w, err.Error(), 400)
84 }
85
86 review.Response = &admissionv1.AdmissionResponse{
87 Allowed: true,
88 UID: review.Request.UID,
89 Result: &metav1.Status{Message: "admitted"},
90 }
91
92 w.Header().Set("Content-Type", "application/json")
93 if err := json.NewEncoder(w).Encode(review); err != nil {
94 http.Error(w, err.Error(), 400)
95 return
96 }
97
98 switch r.URL.Path {
99 case "/marker":
100 recorder.MarkerReceived()
101 return
102 }
103
104 recorder.Record(review.Request)
105 })
106 }
107
108
109 func TestMatchConditions(t *testing.T) {
110
111 fail := admissionregistrationv1.Fail
112 ignore := admissionregistrationv1.Ignore
113
114 testcases := []struct {
115 name string
116 matchConditions []admissionregistrationv1.MatchCondition
117 pods []*corev1.Pod
118 matchedPods []*corev1.Pod
119 expectErrorPod bool
120 failPolicy *admissionregistrationv1.FailurePolicyType
121 }{
122 {
123 name: "pods in namespace kube-system is ignored",
124 matchConditions: []admissionregistrationv1.MatchCondition{
125 {
126 Name: "pods-in-kube-system-exempt.kubernetes.io",
127 Expression: "object.metadata.namespace != 'kube-system'",
128 },
129 },
130 pods: []*corev1.Pod{
131 matchConditionsTestPod("test1", "kube-system"),
132 matchConditionsTestPod("test2", "default"),
133 },
134 matchedPods: []*corev1.Pod{
135 matchConditionsTestPod("test2", "default"),
136 },
137 },
138 {
139 name: "matchConditions are ANDed together",
140 matchConditions: []admissionregistrationv1.MatchCondition{
141 {
142 Name: "pods-in-kube-system-exempt.kubernetes.io",
143 Expression: "object.metadata.namespace != 'kube-system'",
144 },
145 {
146 Name: "pods-with-name-test1.kubernetes.io",
147 Expression: "object.metadata.name == 'test1'",
148 },
149 },
150 pods: []*corev1.Pod{
151 matchConditionsTestPod("test1", "kube-system"),
152 matchConditionsTestPod("test1", "default"),
153 matchConditionsTestPod("test2", "default"),
154 },
155 matchedPods: []*corev1.Pod{
156 matchConditionsTestPod("test1", "default"),
157 },
158 },
159 {
160 name: "mix of true, error and false should not match and not call webhook",
161 matchConditions: []admissionregistrationv1.MatchCondition{
162 {
163 Name: "test1",
164 Expression: "object.nonExistentProperty == 'someval'",
165 },
166 {
167 Name: "test2",
168 Expression: "true",
169 },
170 {
171 Name: "test3",
172 Expression: "false",
173 },
174 {
175 Name: "test4",
176 Expression: "true",
177 },
178 {
179 Name: "test5",
180 Expression: "object.nonExistentProperty == 'someval'",
181 },
182 },
183 pods: []*corev1.Pod{
184 matchConditionsTestPod("test1", "kube-system"),
185 matchConditionsTestPod("test2", "default"),
186 },
187 matchedPods: []*corev1.Pod{},
188 expectErrorPod: false,
189 },
190 {
191 name: "mix of true and error should reject request without fail policy",
192 matchConditions: []admissionregistrationv1.MatchCondition{
193 {
194 Name: "test1",
195 Expression: "object.nonExistentProperty == 'someval'",
196 },
197 {
198 Name: "test2",
199 Expression: "true",
200 },
201 {
202 Name: "test4",
203 Expression: "true",
204 },
205 {
206 Name: "test5",
207 Expression: "object.nonExistentProperty == 'someval'",
208 },
209 },
210 pods: []*corev1.Pod{
211 matchConditionsTestPod("test1", "kube-system"),
212 matchConditionsTestPod("test2", "default"),
213 },
214 matchedPods: []*corev1.Pod{},
215 expectErrorPod: true,
216 },
217 {
218 name: "mix of true and error should reject request with fail policy fail",
219 matchConditions: []admissionregistrationv1.MatchCondition{
220 {
221 Name: "test1",
222 Expression: "object.nonExistentProperty == 'someval'",
223 },
224 {
225 Name: "test2",
226 Expression: "true",
227 },
228 {
229 Name: "test4",
230 Expression: "true",
231 },
232 {
233 Name: "test5",
234 Expression: "object.nonExistentProperty == 'someval'",
235 },
236 },
237 pods: []*corev1.Pod{
238 matchConditionsTestPod("test1", "kube-system"),
239 matchConditionsTestPod("test2", "default"),
240 },
241 matchedPods: []*corev1.Pod{},
242 failPolicy: &fail,
243 expectErrorPod: true,
244 },
245 {
246 name: "mix of true and error should match request and call webhook with fail policy ignore",
247 matchConditions: []admissionregistrationv1.MatchCondition{
248 {
249 Name: "tes1",
250 Expression: "object.nonExistentProperty == 'someval'",
251 },
252 {
253 Name: "test2",
254 Expression: "true",
255 },
256 {
257 Name: "test4",
258 Expression: "true",
259 },
260 {
261 Name: "test5",
262 Expression: "object.nonExistentProperty == 'someval'",
263 },
264 },
265 pods: []*corev1.Pod{
266 matchConditionsTestPod("test1", "kube-system"),
267 matchConditionsTestPod("test2", "default"),
268 },
269 matchedPods: []*corev1.Pod{},
270 failPolicy: &ignore,
271 },
272 {
273 name: "has access to oldObject",
274 matchConditions: []admissionregistrationv1.MatchCondition{
275 {
276 Name: "old-object-is-null.kubernetes.io",
277 Expression: "oldObject == null",
278 },
279 },
280 pods: []*corev1.Pod{
281 matchConditionsTestPod("test2", "default"),
282 },
283 matchedPods: []*corev1.Pod{
284 matchConditionsTestPod("test2", "default"),
285 },
286 },
287 }
288
289 roots := x509.NewCertPool()
290 if !roots.AppendCertsFromPEM(localhostCert) {
291 t.Fatal("Failed to append Cert from PEM")
292 }
293 cert, err := tls.X509KeyPair(localhostCert, localhostKey)
294 if err != nil {
295 t.Fatalf("Failed to build cert with error: %+v", err)
296 }
297
298 recorder := &admissionRecorder{requests: []*admissionv1.AdmissionRequest{}}
299
300 webhookServer := httptest.NewUnstartedServer(newMatchConditionHandler(recorder))
301 webhookServer.TLS = &tls.Config{
302 RootCAs: roots,
303 Certificates: []tls.Certificate{cert},
304 }
305 webhookServer.StartTLS()
306 defer webhookServer.Close()
307
308 dryRunCreate := metav1.CreateOptions{
309 DryRun: []string{metav1.DryRunAll},
310 }
311
312 for _, testcase := range testcases {
313 t.Run(testcase.name, func(t *testing.T) {
314 upCh := recorder.Reset()
315
316 server, err := apiservertesting.StartTestServer(t, nil, []string{
317 "--disable-admission-plugins=ServiceAccount",
318 }, framework.SharedEtcd())
319 if err != nil {
320 t.Fatal(err)
321 }
322 defer server.TearDownFn()
323
324 config := server.ClientConfig
325
326 client, err := clientset.NewForConfig(config)
327 if err != nil {
328 t.Fatal(err)
329 }
330
331
332 markerNs := "marker"
333 _, err = client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: markerNs}}, metav1.CreateOptions{})
334 if err != nil {
335 t.Fatal(err)
336 }
337
338
339 marker, err := client.CoreV1().Pods(markerNs).Create(context.TODO(), newMarkerPod(markerNs), metav1.CreateOptions{})
340 if err != nil {
341 t.Fatal(err)
342 }
343
344 endpoint := webhookServer.URL
345 markerEndpoint := webhookServer.URL + "/marker"
346 validatingwebhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
347 ObjectMeta: metav1.ObjectMeta{
348 Name: "admission.integration.test",
349 },
350 Webhooks: []admissionregistrationv1.ValidatingWebhook{
351 {
352 Name: "admission.integration.test",
353 Rules: []admissionregistrationv1.RuleWithOperations{{
354 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
355 Rule: admissionregistrationv1.Rule{
356 APIGroups: []string{""},
357 APIVersions: []string{"v1"},
358 Resources: []string{"pods"},
359 },
360 }},
361 ClientConfig: admissionregistrationv1.WebhookClientConfig{
362 URL: &endpoint,
363 CABundle: localhostCert,
364 },
365
366 NamespaceSelector: &metav1.LabelSelector{
367 MatchExpressions: []metav1.LabelSelectorRequirement{
368 {
369 Key: corev1.LabelMetadataName,
370 Operator: metav1.LabelSelectorOpNotIn,
371 Values: []string{"marker"},
372 },
373 }},
374 FailurePolicy: testcase.failPolicy,
375 SideEffects: &noSideEffects,
376 AdmissionReviewVersions: []string{"v1"},
377 MatchConditions: testcase.matchConditions,
378 },
379 {
380 Name: "admission.integration.test.marker",
381 Rules: []admissionregistrationv1.RuleWithOperations{{
382 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
383 Rule: admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
384 }},
385 ClientConfig: admissionregistrationv1.WebhookClientConfig{
386 URL: &markerEndpoint,
387 CABundle: localhostCert,
388 },
389 NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
390 corev1.LabelMetadataName: "marker",
391 }},
392 ObjectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
393 FailurePolicy: testcase.failPolicy,
394 SideEffects: &noSideEffects,
395 AdmissionReviewVersions: []string{"v1"},
396 },
397 },
398 }
399
400 validatingcfg, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), validatingwebhook, metav1.CreateOptions{})
401 if err != nil {
402 t.Fatal(err)
403 }
404
405 vhwHasBeenCleanedUp := false
406 defer func() {
407 if !vhwHasBeenCleanedUp {
408 err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
409 if err != nil {
410 t.Fatal(err)
411 }
412 }
413 }()
414
415
416 if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
417 _, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
418 select {
419 case <-upCh:
420 return true, nil
421 default:
422 t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
423 return false, nil
424 }
425 }); err != nil {
426 t.Fatal(err)
427 }
428
429 for _, pod := range testcase.pods {
430 _, err := client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, dryRunCreate)
431 if !testcase.expectErrorPod && err != nil {
432 t.Fatalf("unexpected error creating test pod: %v", err)
433 } else if testcase.expectErrorPod && err == nil {
434 t.Fatal("expected error creating pods")
435 }
436 }
437
438 if len(recorder.requests) != len(testcase.matchedPods) {
439 t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
440 }
441
442 for i, request := range recorder.requests {
443 if request.Name != testcase.matchedPods[i].Name {
444 t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
445 }
446 if request.Namespace != testcase.matchedPods[i].Namespace {
447 t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
448 }
449 }
450
451
452
453 upCh = recorder.Reset()
454 err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
455 if err != nil {
456 t.Fatal(err)
457 } else {
458 vhwHasBeenCleanedUp = true
459 }
460
461 mutatingwebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
462 ObjectMeta: metav1.ObjectMeta{
463 Name: "admission.integration.test",
464 },
465 Webhooks: []admissionregistrationv1.MutatingWebhook{
466 {
467 Name: "admission.integration.test",
468 Rules: []admissionregistrationv1.RuleWithOperations{{
469 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
470 Rule: admissionregistrationv1.Rule{
471 APIGroups: []string{""},
472 APIVersions: []string{"v1"},
473 Resources: []string{"pods"},
474 },
475 }},
476 ClientConfig: admissionregistrationv1.WebhookClientConfig{
477 URL: &endpoint,
478 CABundle: localhostCert,
479 },
480
481 NamespaceSelector: &metav1.LabelSelector{
482 MatchExpressions: []metav1.LabelSelectorRequirement{
483 {
484 Key: corev1.LabelMetadataName,
485 Operator: metav1.LabelSelectorOpNotIn,
486 Values: []string{"marker"},
487 },
488 }},
489 FailurePolicy: testcase.failPolicy,
490 SideEffects: &noSideEffects,
491 AdmissionReviewVersions: []string{"v1"},
492 MatchConditions: testcase.matchConditions,
493 },
494 {
495 Name: "admission.integration.test.marker",
496 Rules: []admissionregistrationv1.RuleWithOperations{{
497 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
498 Rule: admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
499 }},
500 ClientConfig: admissionregistrationv1.WebhookClientConfig{
501 URL: &markerEndpoint,
502 CABundle: localhostCert,
503 },
504 NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
505 corev1.LabelMetadataName: "marker",
506 }},
507 ObjectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
508 FailurePolicy: testcase.failPolicy,
509 SideEffects: &noSideEffects,
510 AdmissionReviewVersions: []string{"v1"},
511 },
512 },
513 }
514
515 mutatingcfg, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingwebhook, metav1.CreateOptions{})
516 if err != nil {
517 t.Fatal(err)
518 }
519 defer func() {
520 err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(context.TODO(), mutatingcfg.GetName(), metav1.DeleteOptions{})
521 if err != nil {
522 t.Fatal(err)
523 }
524 }()
525
526
527 if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
528 _, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
529 select {
530 case <-upCh:
531 return true, nil
532 default:
533 t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
534 return false, nil
535 }
536 }); err != nil {
537 t.Fatal(err)
538 }
539
540 for _, pod := range testcase.pods {
541 _, err = client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, dryRunCreate)
542 if testcase.expectErrorPod == false && err != nil {
543 t.Fatalf("unexpected error creating test pod: %v", err)
544 } else if testcase.expectErrorPod == true && err == nil {
545 t.Fatal("expected error creating pods")
546 }
547 }
548
549 if len(recorder.requests) != len(testcase.matchedPods) {
550 t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
551 }
552
553 for i, request := range recorder.requests {
554 if request.Name != testcase.matchedPods[i].Name {
555 t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
556 }
557 if request.Namespace != testcase.matchedPods[i].Namespace {
558 t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
559 }
560 }
561 })
562 }
563 }
564
565 func TestMatchConditions_validation(t *testing.T) {
566
567 server := apiservertesting.StartTestServerOrDie(t, nil, []string{
568 "--disable-admission-plugins=ServiceAccount",
569 }, framework.SharedEtcd())
570 defer server.TearDownFn()
571
572 client := clientset.NewForConfigOrDie(server.ClientConfig)
573
574 testcases := []struct {
575 name string
576 matchConditions []admissionregistrationv1.MatchCondition
577 expectError bool
578 }{{
579 name: "valid match condition",
580 matchConditions: []admissionregistrationv1.MatchCondition{{
581 Name: "true",
582 Expression: "true",
583 }},
584 expectError: false,
585 }, {
586 name: "multiple valid match conditions",
587 matchConditions: []admissionregistrationv1.MatchCondition{{
588 Name: "exclude-leases",
589 Expression: "!(request.resource.group == 'coordination.k8s.io' && request.resource.resource == 'leases')",
590 }, {
591 Name: "exclude-kubelet-requests",
592 Expression: "!('system:nodes' in request.userInfo.groups)",
593 }, {
594 Name: "breakglass",
595 Expression: "!authorizer.group('admissionregistration.k8s.io').resource('validatingwebhookconfigurations').name('my-webhook.example.com').check('breakglass').allowed()",
596 }},
597 expectError: false,
598 }, {
599 name: "invalid field should error",
600 matchConditions: []admissionregistrationv1.MatchCondition{{
601 Name: "old-object-is-null.kubernetes.io",
602 Expression: "imnotafield == null",
603 }},
604 expectError: true,
605 }, {
606 name: "missing expression should error",
607 matchConditions: []admissionregistrationv1.MatchCondition{{
608 Name: "old-object-is-null.kubernetes.io",
609 }},
610 expectError: true,
611 }, {
612 name: "missing name should error",
613 matchConditions: []admissionregistrationv1.MatchCondition{{
614 Expression: "oldObject == null",
615 }},
616 expectError: true,
617 }, {
618 name: "empty name should error",
619 matchConditions: []admissionregistrationv1.MatchCondition{{
620 Name: "",
621 Expression: "oldObject == null",
622 }},
623 expectError: true,
624 }, {
625 name: "empty expression should error",
626 matchConditions: []admissionregistrationv1.MatchCondition{{
627 Name: "test-empty-expression.kubernetes.io",
628 Expression: "",
629 }},
630 expectError: true,
631 }, {
632 name: "duplicate name should error",
633 matchConditions: []admissionregistrationv1.MatchCondition{{
634 Name: "test1",
635 Expression: "oldObject == null",
636 }, {
637 Name: "test1",
638 Expression: "oldObject == null",
639 }},
640 expectError: true,
641 }, {
642 name: "name must be qualified name",
643 matchConditions: []admissionregistrationv1.MatchCondition{{
644 Name: " test1",
645 Expression: "oldObject == null",
646 }},
647 expectError: true,
648 }, {
649 name: "less than 65 match conditions should pass",
650 matchConditions: repeatedMatchConditions(64),
651 expectError: false,
652 }, {
653 name: "more than 64 match conditions should error",
654 matchConditions: repeatedMatchConditions(65),
655 expectError: true,
656 },
657 }
658
659 dryRunCreate := metav1.CreateOptions{
660 DryRun: []string{metav1.DryRunAll},
661 }
662 endpoint := "https://localhost:1234/server"
663 for _, testcase := range testcases {
664 t.Run(testcase.name, func(t *testing.T) {
665 rules := []admissionregistrationv1.RuleWithOperations{{
666 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
667 Rule: admissionregistrationv1.Rule{
668 APIGroups: []string{""},
669 APIVersions: []string{"v1"},
670 Resources: []string{"pods"},
671 },
672 }}
673 clientConfig := admissionregistrationv1.WebhookClientConfig{
674 URL: &endpoint,
675 CABundle: localhostCert,
676 }
677 versions := []string{"v1"}
678 validatingwebhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
679 ObjectMeta: metav1.ObjectMeta{
680 Name: "admission.integration.test",
681 },
682 Webhooks: []admissionregistrationv1.ValidatingWebhook{
683 {
684 Name: "admission.integration.test",
685 Rules: rules,
686 ClientConfig: clientConfig,
687 SideEffects: &noSideEffects,
688 AdmissionReviewVersions: versions,
689 MatchConditions: testcase.matchConditions,
690 },
691 },
692 }
693
694 _, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), validatingwebhook, dryRunCreate)
695 if testcase.expectError {
696 if err == nil {
697 t.Fatalf("Expected error creating ValidatingWebhookConfiguration; got nil")
698 } else if !apierrors.IsInvalid(err) {
699 t.Errorf("Expected Invalid error creating ValidatingWebhookConfiguration; got: %v", err)
700 }
701 } else if !testcase.expectError && err != nil {
702 t.Fatalf("Unexpected error creating ValidatingWebhookConfiguration: %v", err)
703 }
704
705 mutatingwebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
706 ObjectMeta: metav1.ObjectMeta{
707 Name: "admission.integration.test",
708 },
709 Webhooks: []admissionregistrationv1.MutatingWebhook{
710 {
711 Name: "admission.integration.test",
712 Rules: rules,
713 ClientConfig: clientConfig,
714 SideEffects: &noSideEffects,
715 AdmissionReviewVersions: versions,
716 MatchConditions: testcase.matchConditions,
717 },
718 },
719 }
720
721 _, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingwebhook, dryRunCreate)
722 if testcase.expectError {
723 if err == nil {
724 t.Fatalf("Expected error creating MutatingWebhookConfiguration; got: nil")
725 } else if !apierrors.IsInvalid(err) {
726 t.Errorf("Expected Invalid error creating MutatingWebhookConfiguration; got: %v", err)
727 }
728 } else if !testcase.expectError && err != nil {
729 t.Fatalf("Unexpected error creating MutatingWebhookConfiguration: %v", err)
730 }
731 })
732 }
733 }
734
735 func matchConditionsTestPod(name, ns string) *corev1.Pod {
736 return &corev1.Pod{
737 ObjectMeta: metav1.ObjectMeta{
738 Name: name,
739 Namespace: ns,
740 },
741 Spec: corev1.PodSpec{
742 Containers: []corev1.Container{
743 {
744 Name: "test",
745 Image: "test",
746 },
747 },
748 },
749 }
750 }
751
752 func newMarkerPod(namespace string) *corev1.Pod {
753 return &corev1.Pod{
754 ObjectMeta: metav1.ObjectMeta{
755 Namespace: namespace,
756 Name: "marker",
757 Labels: map[string]string{
758 "marker": "true",
759 },
760 },
761 Spec: corev1.PodSpec{
762 Containers: []corev1.Container{{
763 Name: "fake-name",
764 Image: "fakeimage",
765 }},
766 },
767 }
768 }
769
770 func repeatedMatchConditions(size int) []admissionregistrationv1.MatchCondition {
771 matchConditions := make([]admissionregistrationv1.MatchCondition, 0, size)
772 for i := 0; i < size; i++ {
773 matchConditions = append(matchConditions, admissionregistrationv1.MatchCondition{
774 Name: "repeated-" + strconv.Itoa(i),
775 Expression: "true",
776 })
777 }
778 return matchConditions
779 }
780
View as plain text