diff --git a/openshift/tests-extension/test/olmv1-preflight.go b/openshift/tests-extension/test/olmv1-preflight.go index b130e225d..89fe9cdef 100644 --- a/openshift/tests-extension/test/olmv1-preflight.go +++ b/openshift/tests-extension/test/olmv1-preflight.go @@ -10,8 +10,10 @@ import ( . "github.com/onsi/gomega" "github.com/openshift/api/features" + "github.com/openshift/origin/test/extended/util/image" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" @@ -19,6 +21,8 @@ import ( olmv1 "github.com/operator-framework/operator-controller/api/v1" + singleownbundle "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/bindata/singleown/bundle" + singleownindex "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/bindata/singleown/index" "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/env" "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/helpers" ) @@ -35,16 +39,41 @@ const ( scenarioMissingClusterExtensionRevisionsFinalizerPerms preflightAuthTestScenario = 6 ) +const preflightBundleVersion = "0.0.5" + var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMPreflightPermissionChecks][Skipped:Disconnected] OLMv1 operator preflight checks", func() { var ( - namespace string - k8sClient client.Client + namespace string + k8sClient client.Client + catalogName string + packageName string ) - BeforeEach(func() { + BeforeEach(func(ctx SpecContext) { helpers.RequireOLMv1CapabilityOnOpenshift() + helpers.RequireImageRegistry(ctx) k8sClient = env.Get().K8sClient namespace = "preflight-test-ns-" + rand.String(4) + // Use an in-cluster catalog and bundle so tests do not depend on external indexes. + crdSuffix := rand.String(4) + packageName = fmt.Sprintf("preflight-operator-%s", crdSuffix) + crdName := fmt.Sprintf("webhooktests-%s.webhook.operators.coreos.io", crdSuffix) + helpers.EnsureCleanupClusterExtension(context.Background(), packageName, crdName) + + singleownImage := image.LocationFor("quay.io/olmtest/webhook-operator:v0.0.5") + replacements := map[string]string{ + "{{ TEST-BUNDLE }}": "", + "{{ NAMESPACE }}": "", + "{{ TEST-CONTROLLER }}": singleownImage, + "{{ CRD-SUFFIX }}": crdSuffix, + "{{ PACKAGE-NAME }}": packageName, + } + _, _, catalogName, _ = helpers.NewCatalogAndClusterBundles(ctx, replacements, + singleownindex.AssetNames, singleownindex.Asset, + singleownbundle.AssetNames, singleownbundle.Asset, + ) + By(fmt.Sprintf("catalog %q and package %q are ready", catalogName, packageName)) + By(fmt.Sprintf("creating namespace %s", namespace)) ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -58,39 +87,39 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMPreflightPermissionChecks][Sk }) It("should report error when {services} are not specified", func(ctx SpecContext) { - runNegativePreflightTest(ctx, scenarioMissingServicePerms, namespace) + runNegativePreflightTest(ctx, scenarioMissingServicePerms, namespace, packageName, catalogName) }) It("should report error when {create} verb is not specified", func(ctx SpecContext) { - runNegativePreflightTest(ctx, scenarioMissingCreateVerb, namespace) + runNegativePreflightTest(ctx, scenarioMissingCreateVerb, namespace, packageName, catalogName) }) It("should report error when {ClusterRoleBindings} are not specified", func(ctx SpecContext) { - runNegativePreflightTest(ctx, scenarioMissingClusterRoleBindingsPerms, namespace) + runNegativePreflightTest(ctx, scenarioMissingClusterRoleBindingsPerms, namespace, packageName, catalogName) }) It("should report error when {ConfigMap:resourceNames} are not all specified", func(ctx SpecContext) { - runNegativePreflightTest(ctx, scenarioMissingNamedConfigMapPerms, namespace) + runNegativePreflightTest(ctx, scenarioMissingNamedConfigMapPerms, namespace, packageName, catalogName) }) It("should report error when {clusterextension/finalizer} is not specified", func(ctx SpecContext) { helpers.RequireFeatureGateDisabled(features.FeatureGateNewOLMBoxCutterRuntime) - runNegativePreflightTest(ctx, scenarioMissingClusterExtensionsFinalizerPerms, namespace) + runNegativePreflightTest(ctx, scenarioMissingClusterExtensionsFinalizerPerms, namespace, packageName, catalogName) }) It("should report error when {clusterextensionrevisions/finalizer} is not specified", func(ctx SpecContext) { helpers.RequireFeatureGateEnabled(features.FeatureGateNewOLMBoxCutterRuntime) - runNegativePreflightTest(ctx, scenarioMissingClusterExtensionRevisionsFinalizerPerms, namespace) + runNegativePreflightTest(ctx, scenarioMissingClusterExtensionRevisionsFinalizerPerms, namespace, packageName, catalogName) }) It("should report error when {escalate, bind} is not specified", func(ctx SpecContext) { - runNegativePreflightTest(ctx, scenarioMissingEscalateAndBindPerms, namespace) + runNegativePreflightTest(ctx, scenarioMissingEscalateAndBindPerms, namespace, packageName, catalogName) }) }) -// runNegativePreflightTest creates a deficient ClusterRole and a ClusterExtension that -// relies on it, then waits for the expected preflight failure. -func runNegativePreflightTest(ctx context.Context, scenario preflightAuthTestScenario, namespace string) { +// runNegativePreflightTest creates a ClusterRole that is missing one required permission, +// a ClusterExtension that uses it (via the in-cluster catalog), then waits for the preflight failure. +func runNegativePreflightTest(ctx context.Context, scenario preflightAuthTestScenario, namespace, packageName, catalogName string) { k8sClient := env.Get().K8sClient unique := rand.String(8) @@ -121,28 +150,35 @@ func runNegativePreflightTest(ctx context.Context, scenario preflightAuthTestSce _ = k8sClient.Delete(ctx, crb) }) - // Step 4: Create ClusterExtension referencing that SA - ce := helpers.NewClusterExtensionObject("openshift-pipelines-operator-rh", "1.15.0", ceName, saName, namespace) + // Step 4: Create ClusterExtension for that SA using the in-cluster catalog. + // Set watchNamespace in config so the controller can run preflight; otherwise it fails on config validation first. + ce := helpers.NewClusterExtensionObject(packageName, preflightBundleVersion, ceName, saName, namespace, helpers.WithCatalogNameSelector(catalogName)) + ce.Spec.Config = &olmv1.ClusterExtensionConfig{ + ConfigType: "Inline", + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, namespace)), + }, + } Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension") DeferCleanup(func(ctx SpecContext) { _ = k8sClient.Delete(ctx, ce) }) - // Step 5: Wait for failure + // Step 5: Wait for the controller to report preflight failure. + // The error is in the Progressing condition. We only check the message, not True/False, so the test stays stable. By("waiting for ClusterExtension to report preflight failure") Eventually(func(g Gomega) { latest := &olmv1.ClusterExtension{} err := k8sClient.Get(ctx, client.ObjectKey{Name: ce.Name}, latest) g.Expect(err).NotTo(HaveOccurred()) - c := meta.FindStatusCondition(latest.Status.Conditions, "Progressing") - g.Expect(c).NotTo(BeNil()) - g.Expect(c.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(c.Message).To(ContainSubstring("pre-authorization failed")) + c := meta.FindStatusCondition(latest.Status.Conditions, olmv1.TypeProgressing) + g.Expect(c).NotTo(BeNil(), "Progressing condition should be set") + g.Expect(c.Message).To(ContainSubstring("pre-authorization failed"), "message should report pre-authorization failure") }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed()) } -// createDeficientClusterRole returns a modified ClusterRole according to the test scenario. +// createDeficientClusterRole returns a ClusterRole that is missing one permission needed by the test scenario. func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName string) *rbacv1.ClusterRole { var baseRules []rbacv1.PolicyRule if helpers.IsFeatureGateEnabled(features.FeatureGateNewOLMBoxCutterRuntime) { @@ -189,7 +225,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName switch scenario { case scenarioMissingServicePerms: - // Remove 'services' and 'services/finalizers' + // Remove services and services/finalizers so preflight fails. for i, r := range rules { if r.APIGroups[0] == "" { filtered := []string{} @@ -202,7 +238,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName } } case scenarioMissingCreateVerb: - // Remove 'create' verb + // Remove the create verb so preflight fails. for i, r := range rules { if r.APIGroups[0] == "" { filtered := []string{} @@ -215,7 +251,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName } } case scenarioMissingClusterRoleBindingsPerms: - // Remove 'clusterrolebindings' + // Remove clusterrolebindings so preflight fails. for i, r := range rules { if r.APIGroups[0] == "rbac.authorization.k8s.io" { filtered := []string{} @@ -228,26 +264,37 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName } } case scenarioMissingNamedConfigMapPerms: - // Restrict configmaps to named subset (resourceNames) - for i, r := range rules { - if r.APIGroups[0] == "" { + // Allow only one ClusterRole by name so the SA cannot manage the rest; preflight then fails. + // The singleown bundle uses ClusterRoles like webhook-operator-metrics-reader. + for i := range rules { + if rules[i].APIGroups[0] == "rbac.authorization.k8s.io" { filtered := []string{} - for _, res := range r.Resources { - if res != "configmaps" && res != "configmaps/finalizers" { + for _, res := range rules[i].Resources { + if res != "clusterroles" && res != "clusterroles/finalizers" { filtered = append(filtered, res) } } rules[i].Resources = filtered rules = append(rules, rbacv1.PolicyRule{ - APIGroups: []string{""}, - Resources: []string{"configmaps"}, - Verbs: r.Verbs, - ResourceNames: []string{"config-logging", "tekton-config-defaults", "tekton-config-observability"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"clusterroles", "clusterroles/finalizers"}, + Verbs: []string{"delete", "deletecollection", "create", "patch", "get", "list", "update", "watch"}, + ResourceNames: []string{"webhook-operator-metrics-reader"}, }) + break } } case scenarioMissingClusterExtensionsFinalizerPerms: - // Remove olm.operatorframework.io permission for finalizers + // Remove permission for clusterextensions/finalizers so preflight fails. + filtered := []rbacv1.PolicyRule{} + for _, r := range rules { + if len(r.APIGroups) != 1 || r.APIGroups[0] != "olm.operatorframework.io" { + filtered = append(filtered, r) + } + } + rules = filtered + case scenarioMissingClusterExtensionRevisionsFinalizerPerms: + // Remove permission for clusterextensionrevisions/finalizers so preflight fails. filtered := []rbacv1.PolicyRule{} for _, r := range rules { if len(r.APIGroups) != 1 || r.APIGroups[0] != "olm.operatorframework.io" { @@ -256,7 +303,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName } rules = filtered case scenarioMissingEscalateAndBindPerms: - // Remove 'bind' and 'escalate' verbs + // Remove bind and escalate verbs so preflight fails. for i, r := range rules { if r.APIGroups[0] == "rbac.authorization.k8s.io" { filtered := []string{}