diff --git a/internal/applier/helm.go b/internal/applier/helm.go index beb778a85..1da49cad9 100644 --- a/internal/applier/helm.go +++ b/internal/applier/helm.go @@ -24,6 +24,7 @@ import ( helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" ocv1 "github.com/operator-framework/operator-controller/api/v1" + "github.com/operator-framework/operator-controller/internal/features" "github.com/operator-framework/operator-controller/internal/rukpak/convert" "github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety" "github.com/operator-framework/operator-controller/internal/rukpak/util" @@ -160,6 +161,10 @@ func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterE return nil }, helmclient.AppendInstallPostRenderer(post)) if err != nil { + if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { + _ = struct{}{} // minimal no-op to satisfy linter + // probably need to break out this error as it's the one for helm dry-run as opposed to any returned later + } return nil, nil, StateError, err } return nil, desiredRelease, StateNeedsInstall, nil diff --git a/internal/applier/helm_test.go b/internal/applier/helm_test.go index 3220fc42b..dd0bc2943 100644 --- a/internal/applier/helm_test.go +++ b/internal/applier/helm_test.go @@ -13,12 +13,14 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" + featuregatetesting "k8s.io/component-base/featuregate/testing" "sigs.k8s.io/controller-runtime/pkg/client" helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" v1 "github.com/operator-framework/operator-controller/api/v1" "github.com/operator-framework/operator-controller/internal/applier" + "github.com/operator-framework/operator-controller/internal/features" ) type mockPreflight struct { @@ -226,6 +228,71 @@ func TestApply_Installation(t *testing.T) { }) } +func TestApply_InstallationWithPreflightPermissionsEnabled(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true) + + t.Run("fails during dry-run installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + dryRunInstallErr: errors.New("failed attempting to dry-run install chart"), + } + helmApplier := applier.Helm{ActionClientGetter: mockAcg} + + objs, state, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "attempting to dry-run install chart") + require.Nil(t, objs) + require.Empty(t, state) + }) + + t.Run("fails during pre-flight installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + installErr: errors.New("failed installing chart"), + } + mockPf := &mockPreflight{installErr: errors.New("failed during install pre-flight check")} + helmApplier := applier.Helm{ActionClientGetter: mockAcg, Preflights: []applier.Preflight{mockPf}} + + objs, state, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "install pre-flight check") + require.Equal(t, applier.StateNeedsInstall, state) + require.Nil(t, objs) + }) + + t.Run("fails during installation", func(t *testing.T) { + mockAcg := &mockActionGetter{ + getClientErr: driver.ErrReleaseNotFound, + installErr: errors.New("failed installing chart"), + } + helmApplier := applier.Helm{ActionClientGetter: mockAcg} + + objs, state, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.Error(t, err) + require.ErrorContains(t, err, "installing chart") + require.Equal(t, applier.StateNeedsInstall, state) + require.Nil(t, objs) + }) + + t.Run("successful installation", 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} + + objs, state, err := helmApplier.Apply(context.TODO(), validFS, testCE, testObjectLabels, testStorageLabels) + require.NoError(t, err) + require.Equal(t, applier.StateNeedsInstall, state) + require.NotNil(t, objs) + assert.Equal(t, "service-a", objs[0].GetName()) + assert.Equal(t, "service-b", objs[1].GetName()) + }) +} + func TestApply_Upgrade(t *testing.T) { testCurrentRelease := &release.Release{ Info: &release.Info{Status: release.StatusDeployed}, diff --git a/internal/features/features.go b/internal/features/features.go index 399bc7356..7b308dae0 100644 --- a/internal/features/features.go +++ b/internal/features/features.go @@ -6,13 +6,19 @@ import ( ) const ( -// Add new feature gates constants (strings) -// Ex: SomeFeature featuregate.Feature = "SomeFeature" + // Add new feature gates constants (strings) + // Ex: SomeFeature featuregate.Feature = "SomeFeature" + PreflightPermissions featuregate.Feature = "PreflightPermissions" ) var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ // Add new feature gate definitions // Ex: SomeFeature: {...} + PreflightPermissions: { + Default: false, + PreRelease: featuregate.Alpha, + LockToDefault: false, + }, } var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()