diff --git a/controllers/operatorpolicy_controller.go b/controllers/operatorpolicy_controller.go index 90f687a9..95061c5d 100644 --- a/controllers/operatorpolicy_controller.go +++ b/controllers/operatorpolicy_controller.go @@ -617,7 +617,7 @@ func (r *OperatorPolicyReconciler) musthaveOpGroup( earlyConds = append(earlyConds, calculateComplianceCondition(policy)) } - err := r.Create(ctx, desiredOpGroup) + err := r.createWithNamespace(ctx, policy, desiredOpGroup) if err != nil { return nil, changed, fmt.Errorf("error creating the OperatorGroup: %w", err) } @@ -719,6 +719,44 @@ func (r *OperatorPolicyReconciler) musthaveOpGroup( } } +// createWithNamespace will create the input object and the object's namespace if needed. +func (r *OperatorPolicyReconciler) createWithNamespace( + ctx context.Context, policy *policyv1beta1.OperatorPolicy, object client.Object, +) error { + err := r.Create(ctx, object) + if err == nil { + return nil + } + + // If the error is not due to a missing namespace or the namespace is not set on the object, return the error. + if !isNamespaceNotFound(err) || object.GetNamespace() == "" { + return err + } + + log.Info("Creating the namespace since it didn't exist", "name", object.GetNamespace(), "policy", policy.Name) + + ns := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: object.GetNamespace(), + }, + } + + err = r.Create(ctx, &ns) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return err + } + + // Try creating the object again now that the namespace was created. + return r.Create(ctx, object) +} + +// isNamespaceNotFound detects if the input error from r.Create failed due to the specified namespace not existing. +func isNamespaceNotFound(err error) bool { + statusErr := &k8serrors.StatusError{} + + return k8serrors.IsNotFound(err) && errors.As(err, &statusErr) && statusErr.Status().Details.Kind == "namespaces" +} + func (r *OperatorPolicyReconciler) mustnothaveOpGroup( ctx context.Context, policy *policyv1beta1.OperatorPolicy, @@ -863,7 +901,7 @@ func (r *OperatorPolicyReconciler) musthaveSubscription( earlyConds = append(earlyConds, calculateComplianceCondition(policy)) } - err := r.Create(ctx, desiredSub) + err := r.createWithNamespace(ctx, policy, desiredSub) if err != nil { return nil, nil, changed, fmt.Errorf("error creating the Subscription: %w", err) } diff --git a/test/e2e/case38_install_operator_test.go b/test/e2e/case38_install_operator_test.go index 559f16cf..65bb33ad 100644 --- a/test/e2e/case38_install_operator_test.go +++ b/test/e2e/case38_install_operator_test.go @@ -354,6 +354,39 @@ var _ = Describe("Testing OperatorPolicy", Ordered, func() { ) }) }) + + Describe("Testing namespace creation", Ordered, func() { + const ( + opPolYAML = "../resources/case38_operator_install/operator-policy-no-group-enforce.yaml" + opPolName = "oppol-no-group-enforce" + ) + BeforeAll(func() { + DeferCleanup(func() { + utils.Kubectl( + "delete", "-f", parentPolicyYAML, "-n", testNamespace, "--ignore-not-found", "--cascade=foreground", + ) + utils.Kubectl("delete", "ns", opPolTestNS, "--ignore-not-found") + }) + + createObjWithParent( + parentPolicyYAML, parentPolicyName, opPolYAML, testNamespace, gvrPolicy, gvrOperatorPolicy, + ) + }) + + It("Should be compliant when enforced", func() { + By("Waiting for the operator policy " + opPolName + " to be compliant") + Eventually(func() string { + opPolicy := utils.GetWithTimeout( + clientManagedDynamic, gvrOperatorPolicy, opPolName, testNamespace, true, defaultTimeoutSeconds, + ) + + compliance, _, _ := unstructured.NestedString(opPolicy.Object, "status", "compliant") + + return compliance + }, defaultTimeoutSeconds*2, 1).Should(Equal("Compliant")) + }) + }) + Describe("Testing OperatorGroup behavior when it is specified in the policy", Ordered, func() { const ( opPolYAML = "../resources/case38_operator_install/operator-policy-with-group.yaml"