diff --git a/changelog/fragments/helm_operator_install_rollback_change.yaml b/changelog/fragments/helm_operator_install_rollback_change.yaml new file mode 100644 index 0000000000..9d6ba5d4b9 --- /dev/null +++ b/changelog/fragments/helm_operator_install_rollback_change.yaml @@ -0,0 +1,5 @@ +entries: + - description: > + For Helm-based operators, if the release is not installed but status.DeployedRelease is populated, skip the rollback(uninstall) if the install errors. + kind: "change" + breaking: false diff --git a/internal/helm/controller/reconcile.go b/internal/helm/controller/reconcile.go index fb0e56d692..4a1707ef88 100644 --- a/internal/helm/controller/reconcile.go +++ b/internal/helm/controller/reconcile.go @@ -171,11 +171,20 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile status.RemoveCondition(types.ConditionIrreconcilable) if !manager.IsInstalled() { + // If all the Helm release records are deleted, then the Helm operator will try to install the release again. + // In that case, if the install errors, then don't perform the uninstall rollback because it might lead to unintended data loss. + // See: https://github.com/operator-framework/operator-sdk/issues/4296 + rollbackByUninstall := true + if status.DeployedRelease != nil { + log.Info("Release is not installed but status.DeployedRelease is populated. If the install error, skip rollback") + rollbackByUninstall = false + } + for k, v := range r.OverrideValues { r.EventRecorder.Eventf(o, "Warning", "OverrideValuesInUse", "Chart value %q overridden to %q by operator's watches.yaml", k, v) } - installedRelease, err := manager.InstallRelease(ctx) + installedRelease, err := manager.InstallRelease(ctx, rollbackByUninstall) if err != nil { log.Error(err, "Release failed") status.SetCondition(types.HelmAppCondition{ diff --git a/internal/helm/release/manager.go b/internal/helm/release/manager.go index d72c2a0c0c..5a71af21cd 100644 --- a/internal/helm/release/manager.go +++ b/internal/helm/release/manager.go @@ -49,7 +49,7 @@ type Manager interface { IsInstalled() bool IsUpgradeRequired() bool Sync(context.Context) error - InstallRelease(context.Context, ...InstallOption) (*rpb.Release, error) + InstallRelease(context.Context, bool, ...InstallOption) (*rpb.Release, error) UpgradeRelease(context.Context, ...UpgradeOption) (*rpb.Release, *rpb.Release, error) ReconcileRelease(context.Context) (*rpb.Release, error) UninstallRelease(context.Context, ...UninstallOption) (*rpb.Release, error) @@ -157,7 +157,7 @@ func (m manager) getCandidateRelease(namespace, name string, chart *cpb.Chart, } // InstallRelease performs a Helm release install. -func (m manager) InstallRelease(ctx context.Context, opts ...InstallOption) (*rpb.Release, error) { +func (m manager) InstallRelease(ctx context.Context, rollbackByUninstall bool, opts ...InstallOption) (*rpb.Release, error) { install := action.NewInstall(m.actionConfig) install.ReleaseName = m.releaseName install.Namespace = m.namespace @@ -170,7 +170,7 @@ func (m manager) InstallRelease(ctx context.Context, opts ...InstallOption) (*rp installedRelease, err := install.Run(m.chart, m.values) if err != nil { // Workaround for helm/helm#3338 - if installedRelease != nil { + if installedRelease != nil && rollbackByUninstall { uninstall := action.NewUninstall(m.actionConfig) _, uninstallErr := uninstall.Run(m.releaseName)