From 15dc2239c1de778b7bd74c0cd6d2819052fa24bd Mon Sep 17 00:00:00 2001
From: Tayler Geiger <tayler@redhat.com>
Date: Fri, 25 Apr 2025 10:32:27 -0500
Subject: [PATCH] Add RBAC preflight test case, reorder
 wrapErrorWithResolutionInfo

Adds another test case to helm_test.go for when PreAuthorize() returns
missing RBAC rules.

Merges the noOpPreauthorizer and errPreAuthorizer into one
mockPreAuthorizer in helm_test.go.

Reverses the structure of the error string in
wrapErrorWithResolutionInfo such that the context is first and the error
follows.

Signed-off-by: Tayler Geiger <tayler@redhat.com>
---
 .../operator-controller/applier/helm_test.go  | 82 +++++++++++++++----
 .../clusterextension_controller.go            |  2 +-
 2 files changed, 66 insertions(+), 18 deletions(-)

diff --git a/internal/operator-controller/applier/helm_test.go b/internal/operator-controller/applier/helm_test.go
index b46991206..bfb7a67a1 100644
--- a/internal/operator-controller/applier/helm_test.go
+++ b/internal/operator-controller/applier/helm_test.go
@@ -16,6 +16,7 @@ import (
 	"helm.sh/helm/v3/pkg/release"
 	"helm.sh/helm/v3/pkg/storage/driver"
 	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	featuregatetesting "k8s.io/component-base/featuregate/testing"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -34,26 +35,17 @@ type mockPreflight struct {
 	upgradeErr error
 }
 
-type noOpPreAuthorizer struct{}
-
-func (p *noOpPreAuthorizer) PreAuthorize(
-	ctx context.Context,
-	ext *ocv1.ClusterExtension,
-	manifestReader io.Reader,
-) ([]authorization.ScopedPolicyRules, error) {
-	// No-op: always return an empty map and no error
-	return nil, nil
+type mockPreAuthorizer struct {
+	missingRules []authorization.ScopedPolicyRules
+	returnError  error
 }
 
-type errorPreAuthorizer struct{}
-
-func (p *errorPreAuthorizer) PreAuthorize(
+func (p *mockPreAuthorizer) PreAuthorize(
 	ctx context.Context,
 	ext *ocv1.ClusterExtension,
 	manifestReader io.Reader,
 ) ([]authorization.ScopedPolicyRules, error) {
-	// Always returns no missing rules and an error
-	return nil, errors.New("problem running preauthorization")
+	return p.missingRules, p.returnError
 }
 
 func (mp *mockPreflight) Install(context.Context, *release.Release) error {
@@ -163,6 +155,33 @@ spec:
 	testCE            = &ocv1.ClusterExtension{}
 	testObjectLabels  = map[string]string{"object": "label"}
 	testStorageLabels = map[string]string{"storage": "label"}
+	errPreAuth        = errors.New("problem running preauthorization")
+	missingRBAC       = []authorization.ScopedPolicyRules{
+		{
+			Namespace: "",
+			MissingRules: []rbacv1.PolicyRule{
+				{
+					Verbs:           []string{"list", "watch"},
+					APIGroups:       []string{""},
+					Resources:       []string{"services"},
+					ResourceNames:   []string(nil),
+					NonResourceURLs: []string(nil)},
+			},
+		},
+		{
+			Namespace: "test-namespace",
+			MissingRules: []rbacv1.PolicyRule{
+				{
+					Verbs:     []string{"create"},
+					APIGroups: []string{"*"},
+					Resources: []string{"certificates"}},
+			},
+		},
+	}
+
+	errMissingRBAC = `pre-authorization failed: service account requires the following permissions to manage cluster extension:
+  Namespace:"" APIGroups:[] Resources:[services] Verbs:[list,watch]
+  Namespace:"test-namespace" APIGroups:[*] Resources:[certificates] Verbs:[create]`
 )
 
 func TestApply_Base(t *testing.T) {
@@ -311,7 +330,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) {
 		helmApplier := applier.Helm{
 			ActionClientGetter:  mockAcg,
 			Preflights:          []applier.Preflight{mockPf},
-			PreAuthorizer:       &noOpPreAuthorizer{},
+			PreAuthorizer:       &mockPreAuthorizer{nil, nil},
 			BundleToHelmChartFn: convert.RegistryV1ToHelmChart,
 		}
 
@@ -332,7 +351,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) {
 		}
 		helmApplier := applier.Helm{
 			ActionClientGetter:  mockAcg,
-			PreAuthorizer:       &errorPreAuthorizer{},
+			PreAuthorizer:       &mockPreAuthorizer{nil, errPreAuth},
 			BundleToHelmChartFn: convert.RegistryV1ToHelmChart,
 		}
 		// Use a ClusterExtension with valid Spec fields.
@@ -351,6 +370,35 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) {
 		require.Nil(t, objs)
 	})
 
+	t.Run("fails during installation due to missing RBAC rules", func(t *testing.T) {
+		mockAcg := &mockActionGetter{
+			getClientErr: driver.ErrReleaseNotFound,
+			desiredRel: &release.Release{
+				Info:     &release.Info{Status: release.StatusDeployed},
+				Manifest: validManifest,
+			},
+		}
+		helmApplier := applier.Helm{
+			ActionClientGetter:  mockAcg,
+			PreAuthorizer:       &mockPreAuthorizer{missingRBAC, nil},
+			BundleToHelmChartFn: convert.RegistryV1ToHelmChart,
+		}
+		// Use a ClusterExtension with valid Spec fields.
+		validCE := &ocv1.ClusterExtension{
+			Spec: ocv1.ClusterExtensionSpec{
+				Namespace: "default",
+				ServiceAccount: ocv1.ServiceAccountReference{
+					Name: "default",
+				},
+			},
+		}
+		objs, state, err := helmApplier.Apply(context.TODO(), validFS, validCE, testObjectLabels, testStorageLabels)
+		require.Error(t, err)
+		require.ErrorContains(t, err, errMissingRBAC)
+		require.Equal(t, "", state)
+		require.Nil(t, objs)
+	})
+
 	t.Run("successful installation", func(t *testing.T) {
 		mockAcg := &mockActionGetter{
 			getClientErr: driver.ErrReleaseNotFound,
@@ -361,7 +409,7 @@ func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) {
 		}
 		helmApplier := applier.Helm{
 			ActionClientGetter:  mockAcg,
-			PreAuthorizer:       &noOpPreAuthorizer{},
+			PreAuthorizer:       &mockPreAuthorizer{nil, nil},
 			BundleToHelmChartFn: convert.RegistryV1ToHelmChart,
 		}
 
diff --git a/internal/operator-controller/controllers/clusterextension_controller.go b/internal/operator-controller/controllers/clusterextension_controller.go
index 07f54b94f..e571174b0 100644
--- a/internal/operator-controller/controllers/clusterextension_controller.go
+++ b/internal/operator-controller/controllers/clusterextension_controller.go
@@ -439,7 +439,7 @@ func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error {
 }
 
 func wrapErrorWithResolutionInfo(resolved ocv1.BundleMetadata, err error) error {
-	return fmt.Errorf("%w for resolved bundle %q with version %q", err, resolved.Name, resolved.Version)
+	return fmt.Errorf("error for resolved bundle %q with version %q: %w", resolved.Name, resolved.Version, err)
 }
 
 // Generate reconcile requests for all cluster extensions affected by a catalog change