diff --git a/docs/draft/howto/enable-webhook-support.md b/docs/draft/howto/enable-webhook-support.md index 2ab856bf03..f5c7de7767 100644 --- a/docs/draft/howto/enable-webhook-support.md +++ b/docs/draft/howto/enable-webhook-support.md @@ -1,12 +1,11 @@ ## Installation of Bundles containing Webhooks !!! note -This feature is still in *alpha*. Either the `WebhookProviderCertManager`, or the `WebhookProviderOpenshiftServiceCA`, feature-gate -must be enabled to make use of it. See the instructions below on how to enable the feature-gate. +OLMv1 supports the installation of bundles containing webhooks by default. +By default, OLM v1 uses the community Cert Manager package for admission webhook via the feature-gate flag `WebhookProviderCertManager`. To use the OpenShift Service CA provider, set the `--feature-gates=WebhookProviderOpenshiftServiceCA=true` flag at startup. -OLMv1 currently does not support the installation of bundles containing webhooks. The webhook support feature enables this capability. -Webhooks, or more concretely Admission Webhooks, are part of Kuberntes' [Dynamic Admission Control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) -feature. Webhooks run as services called by the kube-apiservice in due course of processing a resource related request. They can be used to validate resources, ensure reasonable default values, +Admission webhooks are part of the Kubernetes suite of [Dynamic Admission Control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) +plugins. Webhooks run as services called by the kube-apiservice in due course of processing a resource related request. They can be used to validate resources, ensure reasonable default values, are set, or aid in the migration to new CustomResourceDefinition schema. The communication with the webhook service is secured by TLS. In OLMv1, the TLS certificate is managed by a certificate provider. Currently, two certificate providers are supported: CertManager and Openshift-ServiceCA. The certificate provider to use given by the feature-gate: @@ -15,14 +14,12 @@ certificate provider. Currently, two certificate providers are supported: CertMa As CertManager is already installed with OLMv1, we suggest using `WebhookProviderCertManager`. -### Run OLM v1with Experimental Features Enabled +### Run OLM v1 with Webhook Support -```terminal title=Enable Experimental Features in a New Kind Cluster -make run-experimental +```terminal title=Start the controller with webhook support +make run ``` -This will enable only the `WebhookProviderCertManager` feature-gate, which works with cert-manager. - Then, ```terminal title=Wait for rollout to complete diff --git a/docs/draft/tutorials/explore-available-content-metas-endpoint.md b/docs/draft/tutorials/explore-available-content-metas-endpoint.md index 70cb87424e..f17271d3e4 100644 --- a/docs/draft/tutorials/explore-available-content-metas-endpoint.md +++ b/docs/draft/tutorials/explore-available-content-metas-endpoint.md @@ -92,7 +92,7 @@ Then you can query the catalog by using `curl` commands and the `jq` CLI tool to ``` !!! important - Currently, OLM 1.0 does not support the installation of extensions that use webhooks or that target a single or specified set of namespaces. + OLM 1.0 supports installing extensions that define webhooks. Targeting a single or specified set of namespaces requires enabling the `SingleOwnNamespaceInstallSupport` feature-gate. 3. Return list of packages which support `AllNamespaces` install mode, do not use webhooks, and where the channel head version uses `olm.csv.metadata` format: diff --git a/docs/project/olmv1_limitations.md b/docs/project/olmv1_limitations.md index 26e2340ff5..01ce9436d3 100644 --- a/docs/project/olmv1_limitations.md +++ b/docs/project/olmv1_limitations.md @@ -8,8 +8,7 @@ hide: Currently, OLM v1 only supports installing operators packaged in [OLM v0 bundles](https://olm.operatorframework.io/docs/tasks/creating-operator-bundle/) , also known as `registry+v1` bundles. Additionally, the bundled operator, or cluster extension: -* **must** support installation via the `AllNamespaces` install mode. -* **must not** use webhooks. +* **must** support installation via the `AllNamespaces` install mode * **must not** declare dependencies using any of the following file-based catalog properties: * `olm.gvk.required` * `olm.package.required` diff --git a/docs/tutorials/explore-available-content.md b/docs/tutorials/explore-available-content.md index 0a1f468093..36e3cf8834 100644 --- a/docs/tutorials/explore-available-content.md +++ b/docs/tutorials/explore-available-content.md @@ -92,7 +92,7 @@ Then you can query the catalog by using `curl` commands and the `jq` CLI tool to ``` !!! important - Currently, OLM 1.0 does not support the installation of extensions that use webhooks or that target a single or specified set of namespaces. + OLM 1.0 supports installing extensions that define webhooks. Targeting a single or specified set of namespaces requires enabling the `SingleOwnNamespaceInstallSupport` feature-gate. 3. Return list of packages that support `AllNamespaces` install mode and do not use webhooks: diff --git a/helm/experimental.yaml b/helm/experimental.yaml index ae98c08031..b14b1b3034 100644 --- a/helm/experimental.yaml +++ b/helm/experimental.yaml @@ -9,7 +9,6 @@ options: operatorController: features: enabled: - - WebhookProviderCertManager - SingleOwnNamespaceInstallSupport - PreflightPermissions - HelmChartSupport diff --git a/helm/tilt.yaml b/helm/tilt.yaml index 3dbc373c7e..aaed7c71fb 100644 --- a/helm/tilt.yaml +++ b/helm/tilt.yaml @@ -14,7 +14,6 @@ options: operatorController: features: enabled: - - WebhookProviderCertManager - SingleOwnNamespaceInstallSupport - PreflightPermissions - HelmChartSupport diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go index 1abdf0a18a..4926ff8539 100644 --- a/internal/operator-controller/features/features.go +++ b/internal/operator-controller/features/features.go @@ -51,8 +51,8 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature // mutating, and/or conversion webhooks with CertManager // as the certificate provider. WebhookProviderCertManager: { - Default: false, - PreRelease: featuregate.Alpha, + Default: true, + PreRelease: featuregate.GA, LockToDefault: false, }, @@ -61,8 +61,8 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature // mutating, and/or conversion webhooks with Openshift Service CA // as the certificate provider. WebhookProviderOpenshiftServiceCA: { - Default: false, - PreRelease: featuregate.Alpha, + Default: true, + PreRelease: featuregate.GA, LockToDefault: false, }, diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 39ff01d611..d2fd981647 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -2183,7 +2183,6 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=:8443 - --leader-elect - - --feature-gates=WebhookProviderCertManager=true - --feature-gates=SingleOwnNamespaceInstallSupport=true - --feature-gates=PreflightPermissions=true - --feature-gates=HelmChartSupport=true diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index 86bba145d4..4aae61dbbd 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -2096,7 +2096,6 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=:8443 - --leader-elect - - --feature-gates=WebhookProviderCertManager=true - --feature-gates=SingleOwnNamespaceInstallSupport=true - --feature-gates=PreflightPermissions=true - --feature-gates=HelmChartSupport=true diff --git a/test/e2e/webhook_support_test.go b/test/e2e/webhook_support_test.go new file mode 100644 index 0000000000..0809efb541 --- /dev/null +++ b/test/e2e/webhook_support_test.go @@ -0,0 +1,240 @@ +package e2e + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/utils/ptr" + + ocv1 "github.com/operator-framework/operator-controller/api/v1" + utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils" +) + +var dynamicClient dynamic.Interface + +func TestNoop(t *testing.T) { + t.Log("Running experimental-e2e tests") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) +} + +func TestWebhookSupport(t *testing.T) { + t.Log("Test support for bundles with webhooks") + defer utils.CollectTestArtifacts(t, artifactName, c, cfg) + + if dynamicClient == nil { + var err error + dynamicClient, err = dynamic.NewForConfig(cfg) + require.NoError(t, err) + } + + t.Log("By creating install namespace, and necessary rbac resources") + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator", + }, + } + require.NoError(t, c.Create(t.Context(), &namespace)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &namespace)) + }) + + serviceAccount := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-installer", + Namespace: namespace.GetName(), + }, + } + require.NoError(t, c.Create(t.Context(), &serviceAccount)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), &serviceAccount)) + }) + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-installer", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: corev1.GroupName, + Name: serviceAccount.GetName(), + Namespace: serviceAccount.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + } + require.NoError(t, c.Create(t.Context(), clusterRoleBinding)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterRoleBinding)) + }) + + t.Log("By creating the webhook-operator ClusterCatalog") + extensionCatalog := &ocv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-catalog", + }, + Spec: ocv1.ClusterCatalogSpec{ + Source: ocv1.CatalogSource{ + Type: ocv1.SourceTypeImage, + Image: &ocv1.ImageSource{ + Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("CLUSTER_REGISTRY_HOST")), + PollIntervalMinutes: ptr.To(1), + }, + }, + }, + } + require.NoError(t, c.Create(t.Context(), extensionCatalog)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), extensionCatalog)) + }) + + t.Log("By waiting for the catalog to serve its metadata") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog)) + cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) + }, pollDuration, pollInterval) + + t.Log("By installing the webhook-operator ClusterExtension") + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "webhook-operator-extension", + }, + Spec: ocv1.ClusterExtensionSpec{ + Source: ocv1.SourceConfig{ + SourceType: "Catalog", + Catalog: &ocv1.CatalogFilter{ + PackageName: "webhook-operator", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, + }, + }, + }, + Namespace: namespace.GetName(), + ServiceAccount: ocv1.ServiceAccountReference{ + Name: serviceAccount.GetName(), + }, + }, + } + require.NoError(t, c.Create(t.Context(), clusterExtension)) + t.Cleanup(func() { + require.NoError(t, c.Delete(context.Background(), clusterExtension)) + }) + + t.Log("By waiting for webhook-operator extension to be installed successfully") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) + cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) + require.NotNil(ct, cond) + require.Equal(ct, metav1.ConditionTrue, cond.Status) + require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) + require.Contains(ct, cond.Message, "Installed bundle") + require.NotNil(ct, clusterExtension.Status.Install) + require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) + }, pollDuration, pollInterval) + + t.Log("By waiting for webhook-operator deployment to be available") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + deployment := &appsv1.Deployment{} + require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "webhook-operator-controller-manager"}, deployment)) + available := false + for _, cond := range deployment.Status.Conditions { + if cond.Type == appsv1.DeploymentAvailable { + available = cond.Status == corev1.ConditionTrue + } + } + require.True(ct, available) + }, pollDuration, pollInterval) + + v1Gvr := schema.GroupVersionResource{ + Group: "webhook.operators.coreos.io", + Version: "v1", + Resource: "webhooktests", + } + v1Client := dynamicClient.Resource(v1Gvr).Namespace(namespace.GetName()) + + t.Log("By eventually seeing that invalid CR creation is rejected by the validating webhook") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + obj := getWebhookOperatorResource("invalid-test-cr", namespace.GetName(), false) + _, err := v1Client.Create(t.Context(), obj, metav1.CreateOptions{}) + require.Error(ct, err) + require.Contains(ct, err.Error(), "Invalid value: false: Spec.Valid must be true") + }, pollDuration, pollInterval) + + var ( + res *unstructured.Unstructured + err error + obj = getWebhookOperatorResource("valid-test-cr", namespace.GetName(), true) + ) + + t.Log("By eventually creating a valid CR") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + res, err = v1Client.Create(t.Context(), obj, metav1.CreateOptions{}) + require.NoError(ct, err) + }, pollDuration, pollInterval) + t.Cleanup(func() { + require.NoError(t, v1Client.Delete(context.Background(), obj.GetName(), metav1.DeleteOptions{})) + }) + + require.Equal(t, map[string]interface{}{ + "valid": true, + "mutate": true, + }, res.Object["spec"]) + + t.Log("By checking a valid CR is converted to v2 by the conversion webhook") + v2Gvr := schema.GroupVersionResource{ + Group: "webhook.operators.coreos.io", + Version: "v2", + Resource: "webhooktests", + } + v2Client := dynamicClient.Resource(v2Gvr).Namespace(namespace.GetName()) + + t.Log("By eventually getting the valid CR with a v2 client") + require.EventuallyWithT(t, func(ct *assert.CollectT) { + res, err = v2Client.Get(t.Context(), obj.GetName(), metav1.GetOptions{}) + require.NoError(ct, err) + }, pollDuration, pollInterval) + + t.Log("and verifying that the CR is correctly converted") + require.Equal(t, map[string]interface{}{ + "conversion": map[string]interface{}{ + "valid": true, + "mutate": true, + }, + }, res.Object["spec"]) +} + +func getWebhookOperatorResource(name string, namespace string, valid bool) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "webhook.operators.coreos.io/v1", + "kind": "webhooktests", + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + }, + "spec": map[string]interface{}{ + "valid": valid, + }, + }, + } +} diff --git a/test/experimental-e2e/experimental_e2e_test.go b/test/experimental-e2e/single_namespace_support_test.go similarity index 55% rename from test/experimental-e2e/experimental_e2e_test.go rename to test/experimental-e2e/single_namespace_support_test.go index fca2511f76..d1ca134653 100644 --- a/test/experimental-e2e/experimental_e2e_test.go +++ b/test/experimental-e2e/single_namespace_support_test.go @@ -15,11 +15,8 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" @@ -38,9 +35,8 @@ const ( ) var ( - cfg *rest.Config - c client.Client - dynamicClient dynamic.Interface + cfg *rest.Config + c client.Client ) func TestMain(m *testing.M) { @@ -51,9 +47,6 @@ func TestMain(m *testing.M) { c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) utilruntime.Must(err) - dynamicClient, err = dynamic.NewForConfig(cfg) - utilruntime.Must(err) - os.Exit(m.Run()) } @@ -62,193 +55,6 @@ func TestNoop(t *testing.T) { defer utils.CollectTestArtifacts(t, artifactName, c, cfg) } -func TestWebhookSupport(t *testing.T) { - t.Log("Test support for bundles with webhooks") - defer utils.CollectTestArtifacts(t, artifactName, c, cfg) - - t.Log("By creating install namespace, and necessary rbac resources") - namespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "webhook-operator", - }, - } - require.NoError(t, c.Create(t.Context(), &namespace)) - t.Cleanup(func() { - require.NoError(t, c.Delete(context.Background(), &namespace)) - }) - - serviceAccount := corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: "webhook-operator-installer", - Namespace: namespace.GetName(), - }, - } - require.NoError(t, c.Create(t.Context(), &serviceAccount)) - t.Cleanup(func() { - require.NoError(t, c.Delete(context.Background(), &serviceAccount)) - }) - - clusterRoleBinding := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: "webhook-operator-installer", - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - APIGroup: corev1.GroupName, - Name: serviceAccount.GetName(), - Namespace: serviceAccount.GetNamespace(), - }, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: rbacv1.GroupName, - Kind: "ClusterRole", - Name: "cluster-admin", - }, - } - require.NoError(t, c.Create(t.Context(), clusterRoleBinding)) - t.Cleanup(func() { - require.NoError(t, c.Delete(context.Background(), clusterRoleBinding)) - }) - - t.Log("By creating the webhook-operator ClusterCatalog") - extensionCatalog := &ocv1.ClusterCatalog{ - ObjectMeta: metav1.ObjectMeta{ - Name: "webhook-operator-catalog", - }, - Spec: ocv1.ClusterCatalogSpec{ - Source: ocv1.CatalogSource{ - Type: ocv1.SourceTypeImage, - Image: &ocv1.ImageSource{ - Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("CLUSTER_REGISTRY_HOST")), - PollIntervalMinutes: ptr.To(1), - }, - }, - }, - } - require.NoError(t, c.Create(t.Context(), extensionCatalog)) - t.Cleanup(func() { - require.NoError(t, c.Delete(context.Background(), extensionCatalog)) - }) - - t.Log("By waiting for the catalog to serve its metadata") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog)) - cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing) - require.NotNil(ct, cond) - require.Equal(ct, metav1.ConditionTrue, cond.Status) - require.Equal(ct, ocv1.ReasonAvailable, cond.Reason) - }, pollDuration, pollInterval) - - t.Log("By installing the webhook-operator ClusterExtension") - clusterExtension := &ocv1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Name: "webhook-operator-extension", - }, - Spec: ocv1.ClusterExtensionSpec{ - Source: ocv1.SourceConfig{ - SourceType: "Catalog", - Catalog: &ocv1.CatalogFilter{ - PackageName: "webhook-operator", - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name}, - }, - }, - }, - Namespace: namespace.GetName(), - ServiceAccount: ocv1.ServiceAccountReference{ - Name: serviceAccount.GetName(), - }, - }, - } - require.NoError(t, c.Create(t.Context(), clusterExtension)) - t.Cleanup(func() { - require.NoError(t, c.Delete(context.Background(), clusterExtension)) - }) - - t.Log("By waiting for webhook-operator extension to be installed successfully") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension)) - cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled) - require.NotNil(ct, cond) - require.Equal(ct, metav1.ConditionTrue, cond.Status) - require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason) - require.Contains(ct, cond.Message, "Installed bundle") - require.NotNil(ct, clusterExtension.Status.Install) - require.NotEmpty(ct, clusterExtension.Status.Install.Bundle) - }, pollDuration, pollInterval) - - t.Log("By waiting for webhook-operator deployment to be available") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - deployment := &appsv1.Deployment{} - require.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "webhook-operator-controller-manager"}, deployment)) - available := false - for _, cond := range deployment.Status.Conditions { - if cond.Type == appsv1.DeploymentAvailable { - available = cond.Status == corev1.ConditionTrue - } - } - require.True(ct, available) - }, pollDuration, pollInterval) - - v1Gvr := schema.GroupVersionResource{ - Group: "webhook.operators.coreos.io", - Version: "v1", - Resource: "webhooktests", - } - v1Client := dynamicClient.Resource(v1Gvr).Namespace(namespace.GetName()) - - t.Log("By eventually seeing that invalid CR creation is rejected by the validating webhook") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - obj := getWebhookOperatorResource("invalid-test-cr", namespace.GetName(), false) - _, err := v1Client.Create(t.Context(), obj, metav1.CreateOptions{}) - require.Error(ct, err) - require.Contains(ct, err.Error(), "Invalid value: false: Spec.Valid must be true") - }, pollDuration, pollInterval) - - var ( - res *unstructured.Unstructured - err error - obj = getWebhookOperatorResource("valid-test-cr", namespace.GetName(), true) - ) - - t.Log("By eventually creating a valid CR") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - res, err = v1Client.Create(t.Context(), obj, metav1.CreateOptions{}) - require.NoError(ct, err) - }, pollDuration, pollInterval) - t.Cleanup(func() { - require.NoError(t, v1Client.Delete(context.Background(), obj.GetName(), metav1.DeleteOptions{})) - }) - - require.Equal(t, map[string]interface{}{ - "valid": true, - "mutate": true, - }, res.Object["spec"]) - - t.Log("By checking a valid CR is converted to v2 by the conversion webhook") - v2Gvr := schema.GroupVersionResource{ - Group: "webhook.operators.coreos.io", - Version: "v2", - Resource: "webhooktests", - } - v2Client := dynamicClient.Resource(v2Gvr).Namespace(namespace.GetName()) - - t.Log("By eventually getting the valid CR with a v2 client") - require.EventuallyWithT(t, func(ct *assert.CollectT) { - res, err = v2Client.Get(t.Context(), obj.GetName(), metav1.GetOptions{}) - require.NoError(ct, err) - }, pollDuration, pollInterval) - - t.Log("and verifying that the CR is correctly converted") - require.Equal(t, map[string]interface{}{ - "conversion": map[string]interface{}{ - "valid": true, - "mutate": true, - }, - }, res.Object["spec"]) -} - func TestClusterExtensionConfigSupport(t *testing.T) { t.Log("Test support for cluster extension config") defer utils.CollectTestArtifacts(t, artifactName, c, cfg) @@ -443,19 +249,3 @@ func TestClusterExtensionVersionUpdate(t *testing.T) { require.Len(ct, cerList.Items, 2) }, pollDuration, pollInterval) } - -func getWebhookOperatorResource(name string, namespace string, valid bool) *unstructured.Unstructured { - return &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "webhook.operators.coreos.io/v1", - "kind": "webhooktests", - "metadata": map[string]interface{}{ - "name": name, - "namespace": namespace, - }, - "spec": map[string]interface{}{ - "valid": valid, - }, - }, - } -}