From 3121773af796e1e40f07e4466e0851c37a493494 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Sun, 11 Aug 2019 15:34:54 -0700 Subject: [PATCH] Add admission e2e tests for untested stable operations --- test/e2e/apimachinery/BUILD | 1 + test/e2e/apimachinery/webhook.go | 495 +++++++++++++++++++++++-------- 2 files changed, 379 insertions(+), 117 deletions(-) diff --git a/test/e2e/apimachinery/BUILD b/test/e2e/apimachinery/BUILD index 4e46128380b..3825bcd93bc 100644 --- a/test/e2e/apimachinery/BUILD +++ b/test/e2e/apimachinery/BUILD @@ -74,6 +74,7 @@ go_library( "//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/util/cert:go_default_library", "//staging/src/k8s.io/client-go/util/keyutil:go_default_library", + "//staging/src/k8s.io/client-go/util/retry:go_default_library", "//staging/src/k8s.io/client-go/util/workqueue:go_default_library", "//staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1:go_default_library", "//staging/src/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset:go_default_library", diff --git a/test/e2e/apimachinery/webhook.go b/test/e2e/apimachinery/webhook.go index d4a2ec4215a..5eed215e13e 100644 --- a/test/e2e/apimachinery/webhook.go +++ b/test/e2e/apimachinery/webhook.go @@ -22,6 +22,8 @@ import ( "strings" "time" + "k8s.io/utils/pointer" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" @@ -33,17 +35,18 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" utilversion "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/dynamic" clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/util/retry" "k8s.io/kubernetes/test/e2e/framework" e2edeploy "k8s.io/kubernetes/test/e2e/framework/deployment" e2elog "k8s.io/kubernetes/test/e2e/framework/log" e2epod "k8s.io/kubernetes/test/e2e/framework/pod" "k8s.io/kubernetes/test/utils/crd" imageutils "k8s.io/kubernetes/test/utils/image" - "k8s.io/utils/pointer" "github.com/onsi/ginkgo" "github.com/onsi/gomega" @@ -244,6 +247,269 @@ var _ = SIGDescribe("AdmissionWebhook", func() { slowWebhookCleanup() }) + ginkgo.It("patching/updating a validating webhook should work", func() { + client := f.ClientSet + admissionClient := client.AdmissionregistrationV1() + + ginkgo.By("Creating a validating webhook configuration") + hook, err := createValidatingWebhookConfiguration(f, &admissionregistrationv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: f.UniqueName, + }, + Webhooks: []admissionregistrationv1.ValidatingWebhook{ + newDenyConfigMapWebhookFixture(f, context), + }, + }) + framework.ExpectNoError(err, "Creating validating webhook configuration") + defer func() { + err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(hook.Name, nil) + framework.ExpectNoError(err, "Deleting validating webhook configuration") + }() + ginkgo.By("Creating a configMap that does not comply to the validation webhook rules") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f) + _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err == nil { + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + return false, nil + } + if !strings.Contains(err.Error(), "denied") { + return false, err + } + return true, nil + }) + + ginkgo.By("Updating a validating webhook configuration's rules to not include the create operation") + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + h, err := admissionClient.ValidatingWebhookConfigurations().Get(f.UniqueName, metav1.GetOptions{}) + framework.ExpectNoError(err, "Getting validating webhook configuration") + h.Webhooks[0].Rules[0].Operations = []admissionregistrationv1.OperationType{admissionregistrationv1.Update} + _, err = admissionClient.ValidatingWebhookConfigurations().Update(h) + return err + }) + framework.ExpectNoError(err, "Updating validating webhook configuration") + + ginkgo.By("Creating a configMap that does not comply to the validation webhook rules") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f) + _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err != nil { + if !strings.Contains(err.Error(), "denied") { + return false, err + } + return false, nil + } + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + return true, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be allowed creation since webhook was updated to not validate create", f.Namespace.Name) + + ginkgo.By("Patching a validating webhook configuration's rules to include the create operation") + hook, err = admissionClient.ValidatingWebhookConfigurations().Patch( + f.UniqueName, + types.JSONPatchType, + []byte(`[{"op": "replace", "path": "/webhooks/0/rules/0/operations", "value": ["CREATE"]}]`)) + framework.ExpectNoError(err, "Patching validating webhook configuration") + + ginkgo.By("Creating a configMap that does not comply to the validation webhook rules") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f) + _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err == nil { + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + return false, nil + } + if !strings.Contains(err.Error(), "denied") { + return false, err + } + return true, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be denied creation by validating webhook", f.Namespace.Name) + }) + + ginkgo.It("patching/updating a mutating webhook should work", func() { + client := f.ClientSet + admissionClient := client.AdmissionregistrationV1() + + ginkgo.By("Creating a mutating webhook configuration") + hook, err := createMutatingWebhookConfiguration(f, &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: f.UniqueName, + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + newMutateConfigMapWebhookFixture(f, context, 1), + }, + }) + framework.ExpectNoError(err, "Creating mutating webhook configuration") + defer func() { + err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(hook.Name, nil) + framework.ExpectNoError(err, "Deleting mutating webhook configuration") + }() + + hook, err = admissionClient.MutatingWebhookConfigurations().Get(f.UniqueName, metav1.GetOptions{}) + framework.ExpectNoError(err, "Getting mutating webhook configuration") + ginkgo.By("Updating a mutating webhook configuration's rules to not include the create operation") + hook.Webhooks[0].Rules[0].Operations = []admissionregistrationv1.OperationType{admissionregistrationv1.Update} + hook, err = admissionClient.MutatingWebhookConfigurations().Update(hook) + framework.ExpectNoError(err, "Updating mutating webhook configuration") + + ginkgo.By("Creating a configMap that should not be mutated") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f) + created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err != nil { + return false, err + } + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + _, ok := created.Data["mutation-stage-1"] + return !ok, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s this is not mutated", f.Namespace.Name) + + ginkgo.By("Patching a mutating webhook configuration's rules to include the create operation") + hook, err = admissionClient.MutatingWebhookConfigurations().Patch( + f.UniqueName, + types.JSONPatchType, + []byte(`[{"op": "replace", "path": "/webhooks/0/rules/0/operations", "value": ["CREATE"]}]`)) + framework.ExpectNoError(err, "Patching mutating webhook configuration") + + ginkgo.By("Creating a configMap that should be mutated") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f) + created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err != nil { + return false, err + } + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + _, ok := created.Data["mutation-stage-1"] + return ok, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be mutated", f.Namespace.Name) + }) + + ginkgo.It("listing validating webhooks should work", func() { + testListSize := 10 + testUUID := string(uuid.NewUUID()) + + for i := 0; i < testListSize; i++ { + name := fmt.Sprintf("%s-%d", f.UniqueName, i) + _, err := createValidatingWebhookConfiguration(f, &admissionregistrationv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{"e2e-list-test-uuid": testUUID}, + }, + Webhooks: []admissionregistrationv1.ValidatingWebhook{ + newDenyConfigMapWebhookFixture(f, context), + }, + }) + framework.ExpectNoError(err, "Creating validating webhook configuration") + } + selectorListOpts := metav1.ListOptions{LabelSelector: "e2e-list-test-uuid=" + testUUID} + + ginkgo.By("Listing all of the created validation webhooks") + list, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().List(selectorListOpts) + framework.ExpectNoError(err, "Listing validating webhook configurations") + framework.ExpectEqual(len(list.Items), testListSize) + + ginkgo.By("Creating a configMap that does not comply to the validation webhook rules") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f) + _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err == nil { + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + return false, nil + } + if !strings.Contains(err.Error(), "denied") { + return false, err + } + return true, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be denied creation by validating webhook", f.Namespace.Name) + + ginkgo.By("Deleting the collection of validation webhooks") + err = client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().DeleteCollection(nil, selectorListOpts) + framework.ExpectNoError(err, "Deleting collection of validating webhook configurations") + + ginkgo.By("Creating a configMap that does not comply to the validation webhook rules") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f) + _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err != nil { + if !strings.Contains(err.Error(), "denied") { + return false, err + } + return false, nil + } + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + return true, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be allowed creation since there are no webhooks", f.Namespace.Name) + }) + + ginkgo.It("listing mutating webhooks should work", func() { + testListSize := 10 + testUUID := string(uuid.NewUUID()) + + for i := 0; i < testListSize; i++ { + name := fmt.Sprintf("%s-%d", f.UniqueName, i) + _, err := createMutatingWebhookConfiguration(f, &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{"e2e-list-test-uuid": testUUID}, + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + newMutateConfigMapWebhookFixture(f, context, 1), + }, + }) + framework.ExpectNoError(err, "Creating mutating webhook configuration") + } + selectorListOpts := metav1.ListOptions{LabelSelector: "e2e-list-test-uuid=" + testUUID} + + ginkgo.By("Listing all of the created validation webhooks") + list, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().List(selectorListOpts) + framework.ExpectNoError(err, "Listing mutating webhook configurations") + framework.ExpectEqual(len(list.Items), testListSize) + + ginkgo.By("Creating a configMap that should be mutated") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f) + created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err != nil { + return false, err + } + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + _, ok := created.Data["mutation-stage-1"] + return ok, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be mutated", f.Namespace.Name) + + ginkgo.By("Deleting the collection of validation webhooks") + err = client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().DeleteCollection(nil, selectorListOpts) + framework.ExpectNoError(err, "Deleting collection of mutating webhook configurations") + + ginkgo.By("Creating a configMap that should not be mutated") + err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) { + cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f) + created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(cm) + if err != nil { + return false, err + } + err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(cm.Name, nil) + framework.ExpectNoError(err, "Deleting successfully created configMap") + _, ok := created.Data["mutation-stage-1"] + return !ok, nil + }) + framework.ExpectNoError(err, "Waiting for configMap in namespace %s this is not mutated", f.Namespace.Name) + }) + // TODO: add more e2e tests for mutating webhooks // 1. mutating webhook that mutates pod // 2. mutating webhook that sends empty patch @@ -413,72 +679,13 @@ func registerWebhook(f *framework.Framework, configName string, context *certCon MatchLabels: map[string]string{f.UniqueName: "true"}, } - sideEffectsNone := admissionregistrationv1.SideEffectClassNone - _, err := createValidatingWebhookConfiguration(f, &admissionregistrationv1.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ Name: configName, }, Webhooks: []admissionregistrationv1.ValidatingWebhook{ - { - Name: "deny-unwanted-pod-container-name-and-label.k8s.io", - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods"}, - }, - }}, - ClientConfig: admissionregistrationv1.WebhookClientConfig{ - Service: &admissionregistrationv1.ServiceReference{ - Namespace: namespace, - Name: serviceName, - Path: strPtr("/pods"), - Port: pointer.Int32Ptr(servicePort), - }, - CABundle: context.signingCert, - }, - SideEffects: &sideEffectsNone, - AdmissionReviewVersions: []string{"v1beta1"}, - // Scope the webhook to just this namespace - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{f.UniqueName: "true"}, - }, - }, - { - Name: "deny-unwanted-configmap-data.k8s.io", - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"configmaps"}, - }, - }}, - // The webhook skips the namespace that has label "skip-webhook-admission":"yes" - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{f.UniqueName: "true"}, - MatchExpressions: []metav1.LabelSelectorRequirement{ - { - Key: skipNamespaceLabelKey, - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{skipNamespaceLabelValue}, - }, - }, - }, - ClientConfig: admissionregistrationv1.WebhookClientConfig{ - Service: &admissionregistrationv1.ServiceReference{ - Namespace: namespace, - Name: serviceName, - Path: strPtr("/configmaps"), - Port: pointer.Int32Ptr(servicePort), - }, - CABundle: context.signingCert, - }, - SideEffects: &sideEffectsNone, - AdmissionReviewVersions: []string{"v1beta1"}, - }, + newDenyPodWebhookFixture(f, context), + newDenyConfigMapWebhookFixture(f, context), // Server cannot talk to this webhook, so it always fails. // Because this webhook is configured fail-open, request should be admitted after the call fails. failOpenHook, @@ -549,65 +756,14 @@ func registerMutatingWebhookForConfigMap(f *framework.Framework, configName stri ginkgo.By("Registering the mutating configmap webhook via the AdmissionRegistration API") namespace := f.Namespace.Name - sideEffectsNone := admissionregistrationv1.SideEffectClassNone _, err := createMutatingWebhookConfiguration(f, &admissionregistrationv1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ Name: configName, }, Webhooks: []admissionregistrationv1.MutatingWebhook{ - { - Name: "adding-configmap-data-stage-1.k8s.io", - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"configmaps"}, - }, - }}, - ClientConfig: admissionregistrationv1.WebhookClientConfig{ - Service: &admissionregistrationv1.ServiceReference{ - Namespace: namespace, - Name: serviceName, - Path: strPtr("/mutating-configmaps"), - Port: pointer.Int32Ptr(servicePort), - }, - CABundle: context.signingCert, - }, - SideEffects: &sideEffectsNone, - AdmissionReviewVersions: []string{"v1beta1"}, - // Scope the webhook to just this namespace - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{f.UniqueName: "true"}, - }, - }, - { - Name: "adding-configmap-data-stage-2.k8s.io", - Rules: []admissionregistrationv1.RuleWithOperations{{ - Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, - Rule: admissionregistrationv1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"configmaps"}, - }, - }}, - ClientConfig: admissionregistrationv1.WebhookClientConfig{ - Service: &admissionregistrationv1.ServiceReference{ - Namespace: namespace, - Name: serviceName, - Path: strPtr("/mutating-configmaps"), - Port: pointer.Int32Ptr(servicePort), - }, - CABundle: context.signingCert, - }, - SideEffects: &sideEffectsNone, - AdmissionReviewVersions: []string{"v1beta1"}, - // Scope the webhook to just this namespace - NamespaceSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{f.UniqueName: "true"}, - }, - }, + newMutateConfigMapWebhookFixture(f, context, 1), + newMutateConfigMapWebhookFixture(f, context, 2), }, }) framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", configName, namespace) @@ -1259,9 +1415,13 @@ func toBeAttachedPod(f *framework.Framework) *v1.Pod { } func nonCompliantConfigMap(f *framework.Framework) *v1.ConfigMap { + return namedNonCompliantConfigMap(disallowedConfigMapName, f) +} + +func namedNonCompliantConfigMap(name string, f *framework.Framework) *v1.ConfigMap { return &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: disallowedConfigMapName, + Name: name, }, Data: map[string]string{ "webhook-e2e-test": "webhook-disallow", @@ -1281,9 +1441,13 @@ func nonDeletableConfigmap(f *framework.Framework) *v1.ConfigMap { } func toBeMutatedConfigMap(f *framework.Framework) *v1.ConfigMap { + return namedToBeMutatedConfigMap("to-be-mutated", f) +} + +func namedToBeMutatedConfigMap(name string, f *framework.Framework) *v1.ConfigMap { return &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "to-be-mutated", + Name: name, }, Data: map[string]string{ "mutation-start": "yes", @@ -1851,3 +2015,100 @@ func createMutatingWebhookConfiguration(f *framework.Framework, config *admissio } return f.ClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(config) } + +func newDenyPodWebhookFixture(f *framework.Framework, context *certContext) admissionregistrationv1.ValidatingWebhook { + sideEffectsNone := admissionregistrationv1.SideEffectClassNone + return admissionregistrationv1.ValidatingWebhook{ + Name: "deny-unwanted-pod-container-name-and-label.k8s.io", + Rules: []admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + }, + }}, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: f.Namespace.Name, + Name: serviceName, + Path: strPtr("/pods"), + Port: pointer.Int32Ptr(servicePort), + }, + CABundle: context.signingCert, + }, + SideEffects: &sideEffectsNone, + AdmissionReviewVersions: []string{"v1beta1"}, + // Scope the webhook to just this namespace + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{f.UniqueName: "true"}, + }, + } +} + +func newDenyConfigMapWebhookFixture(f *framework.Framework, context *certContext) admissionregistrationv1.ValidatingWebhook { + sideEffectsNone := admissionregistrationv1.SideEffectClassNone + return admissionregistrationv1.ValidatingWebhook{ + Name: "deny-unwanted-configmap-data.k8s.io", + Rules: []admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"configmaps"}, + }, + }}, + // The webhook skips the namespace that has label "skip-webhook-admission":"yes" + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{f.UniqueName: "true"}, + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: skipNamespaceLabelKey, + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{skipNamespaceLabelValue}, + }, + }, + }, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: f.Namespace.Name, + Name: serviceName, + Path: strPtr("/configmaps"), + Port: pointer.Int32Ptr(servicePort), + }, + CABundle: context.signingCert, + }, + SideEffects: &sideEffectsNone, + AdmissionReviewVersions: []string{"v1beta1"}, + } +} + +func newMutateConfigMapWebhookFixture(f *framework.Framework, context *certContext, stage int) admissionregistrationv1.MutatingWebhook { + sideEffectsNone := admissionregistrationv1.SideEffectClassNone + return admissionregistrationv1.MutatingWebhook{ + Name: fmt.Sprintf("adding-configmap-data-stage-%d.k8s.io", stage), + Rules: []admissionregistrationv1.RuleWithOperations{{ + Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"configmaps"}, + }, + }}, + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Namespace: f.Namespace.Name, + Name: serviceName, + Path: strPtr("/mutating-configmaps"), + Port: pointer.Int32Ptr(servicePort), + }, + CABundle: context.signingCert, + }, + SideEffects: &sideEffectsNone, + AdmissionReviewVersions: []string{"v1beta1"}, + // Scope the webhook to just this namespace + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{f.UniqueName: "true"}, + }, + } +}