diff --git a/internal/reconcile/atomic_release.go b/internal/reconcile/atomic_release.go index 8025e22d9..5f5a1f1b3 100644 --- a/internal/reconcile/atomic_release.go +++ b/internal/reconcile/atomic_release.go @@ -372,6 +372,22 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state reason, msgFmt = v2.InstallSucceededReason, fmtInstallSuccess case v2.UpgradeFailedReason: reason, msgFmt = v2.UpgradeSucceededReason, fmtUpgradeSuccess + default: + // Released may be missing after a failed post-action + // condition patch while history was persisted. Derive + // the success reason from the last attempted action. + action := req.Object.Status.LastAttemptedReleaseAction + if action == "" { + if cur := req.Object.Status.History.Latest(); cur != nil { + action = cur.Action + } + } + switch action { + case v2.ReleaseActionInstall: + reason, msgFmt = v2.InstallSucceededReason, fmtInstallSuccess + case v2.ReleaseActionUpgrade: + reason, msgFmt = v2.UpgradeSucceededReason, fmtUpgradeSuccess + } } if reason != "" { cur := req.Object.Status.History.Latest() diff --git a/internal/reconcile/atomic_release_test.go b/internal/reconcile/atomic_release_test.go index 7637c3bc4..55bf6ea09 100644 --- a/internal/reconcile/atomic_release_test.go +++ b/internal/reconcile/atomic_release_test.go @@ -1574,6 +1574,65 @@ func TestAtomicRelease_actionForState(t *testing.T) { *conditions.TrueCondition(meta.ReadyCondition, v2.InstallSucceededReason, "install succeeded"), }, }, + { + name: "in-sync release with missing released condition after install", + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + {Version: 1}, + }, + LastAttemptedReleaseAction: v2.ReleaseActionInstall, + Conditions: []metav1.Condition{ + *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "Running 'install' action"), + }, + } + }, + state: ReleaseState{Status: ReleaseStatusInSync}, + want: nil, + assertConditions: []metav1.Condition{ + *conditions.TrueCondition(v2.ReleasedCondition, v2.InstallSucceededReason, "install succeeded"), + *conditions.TrueCondition(meta.ReadyCondition, v2.InstallSucceededReason, "install succeeded"), + }, + }, + { + name: "in-sync release with missing released condition after upgrade", + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + {Version: 2}, + }, + LastAttemptedReleaseAction: v2.ReleaseActionUpgrade, + Conditions: []metav1.Condition{ + *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "Running 'upgrade' action"), + }, + } + }, + state: ReleaseState{Status: ReleaseStatusInSync}, + want: nil, + assertConditions: []metav1.Condition{ + *conditions.TrueCondition(v2.ReleasedCondition, v2.UpgradeSucceededReason, "upgrade succeeded"), + *conditions.TrueCondition(meta.ReadyCondition, v2.UpgradeSucceededReason, "upgrade succeeded"), + }, + }, + { + name: "in-sync release with missing released condition recovers from history action", + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + {Version: 1, Action: v2.ReleaseActionInstall}, + }, + Conditions: []metav1.Condition{ + *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "Running 'install' action"), + }, + } + }, + state: ReleaseState{Status: ReleaseStatusInSync}, + want: nil, + assertConditions: []metav1.Condition{ + *conditions.TrueCondition(v2.ReleasedCondition, v2.InstallSucceededReason, "install succeeded"), + *conditions.TrueCondition(meta.ReadyCondition, v2.InstallSucceededReason, "install succeeded"), + }, + }, { name: "locked release triggers unlock action", state: ReleaseState{Status: ReleaseStatusLocked},