diff --git a/api/v2alpha1/helmrelease_types.go b/api/v2alpha1/helmrelease_types.go index d7c69431a..c9c28e9b9 100644 --- a/api/v2alpha1/helmrelease_types.go +++ b/api/v2alpha1/helmrelease_types.go @@ -571,6 +571,10 @@ type HelmReleaseStatus struct { // +optional Conditions []Condition `json:"conditions,omitempty"` + // KnownStateApplied represents whether the known state has been successfully applied. + // +optional + KnownStateApplied bool `json:"knownStateApplied,omitempty"` + // LastAppliedRevision is the revision of the last successfully applied source. // +optional LastAppliedRevision string `json:"lastAppliedRevision,omitempty"` @@ -592,15 +596,18 @@ type HelmReleaseStatus struct { // +optional HelmChart string `json:"helmChart,omitempty"` - // Failures is the reconciliation failure count. + // Failures is the reconciliation failure count against the known state. + // It is reset after a successful reconciliation. // +optional Failures int64 `json:"failures,omitempty"` - // InstallFailures is the install failure count. + // InstallFailures is the install failure count against the known state. + // It is reset after a successful reconciliation. // +optional InstallFailures int64 `json:"installFailures,omitempty"` - // UpgradeFailures is the upgrade failure count. + // UpgradeFailures is the upgrade failure count against the known state. + // It is reset after a successful reconciliation. // +optional UpgradeFailures int64 `json:"upgradeFailures,omitempty"` } @@ -617,27 +624,13 @@ func (in HelmReleaseStatus) GetHelmChart() (string, string) { // HelmReleaseProgressing resets any failures and registers progress toward reconciling the given HelmRelease // by setting the ReadyCondition to ConditionUnknown for ProgressingReason. func HelmReleaseProgressing(hr HelmRelease) HelmRelease { - hr.Status.Failures = 0 - hr.Status.InstallFailures = 0 - hr.Status.UpgradeFailures = 0 + resetFailureCounts(&hr) + hr.Status.KnownStateApplied = false hr.Status.Conditions = []Condition{} SetHelmReleaseCondition(&hr, ReadyCondition, corev1.ConditionUnknown, ProgressingReason, "reconciliation in progress") return hr } -// SetHelmReleaseCondition sets the given condition with the given status, reason and message -// on the HelmRelease. -func SetHelmReleaseCondition(hr *HelmRelease, condition string, status corev1.ConditionStatus, reason, message string) { - hr.Status.Conditions = filterOutCondition(hr.Status.Conditions, condition) - hr.Status.Conditions = append(hr.Status.Conditions, Condition{ - Type: condition, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }) -} - // HelmReleaseNotReady registers a failed release attempt of the given HelmRelease. func HelmReleaseNotReady(hr HelmRelease, reason, message string) HelmRelease { SetHelmReleaseCondition(&hr, ReadyCondition, corev1.ConditionFalse, reason, message) @@ -646,9 +639,11 @@ func HelmReleaseNotReady(hr HelmRelease, reason, message string) HelmRelease { } // HelmReleaseReady registers a successful release attempt of the given HelmRelease. -func HelmReleaseReady(hr HelmRelease, reason, message string) HelmRelease { - SetHelmReleaseCondition(&hr, ReadyCondition, corev1.ConditionTrue, reason, message) +func HelmReleaseReady(hr HelmRelease) HelmRelease { + resetFailureCounts(&hr) + hr.Status.KnownStateApplied = true hr.Status.LastAppliedRevision = hr.Status.LastAttemptedRevision + SetHelmReleaseCondition(&hr, ReadyCondition, corev1.ConditionTrue, ReconciliationSucceededReason, "release reconciliation succeeded") return hr } @@ -665,6 +660,25 @@ func HelmReleaseAttempted(hr HelmRelease, revision string, releaseRevision int, return hr, changed } +func resetFailureCounts(hr *HelmRelease) { + hr.Status.Failures = 0 + hr.Status.InstallFailures = 0 + hr.Status.UpgradeFailures = 0 +} + +// SetHelmReleaseCondition sets the given condition with the given status, reason and message +// on the HelmRelease. +func SetHelmReleaseCondition(hr *HelmRelease, condition string, status corev1.ConditionStatus, reason, message string) { + hr.Status.Conditions = filterOutCondition(hr.Status.Conditions, condition) + hr.Status.Conditions = append(hr.Status.Conditions, Condition{ + Type: condition, + Status: status, + LastTransitionTime: metav1.Now(), + Reason: reason, + Message: message, + }) +} + const ( // ReconcileAtAnnotation is the annotation used for triggering a // reconciliation outside of the defined schedule. diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml index f3e3467c3..14229be9c 100644 --- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml +++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml @@ -384,7 +384,8 @@ spec: type: object type: array failures: - description: Failures is the reconciliation failure count. + description: Failures is the reconciliation failure count against + the known state. It is reset after a successful reconciliation. format: int64 type: integer helmChart: @@ -392,9 +393,14 @@ spec: created by the controller for the HelmRelease. type: string installFailures: - description: InstallFailures is the install failure count. + description: InstallFailures is the install failure count against + the known state. It is reset after a successful reconciliation. format: int64 type: integer + knownStateApplied: + description: KnownStateApplied represents whether the known state + has been successfully applied. + type: boolean lastAppliedRevision: description: LastAppliedRevision is the revision of the last successfully applied source. @@ -416,7 +422,8 @@ spec: format: int64 type: integer upgradeFailures: - description: UpgradeFailures is the upgrade failure count. + description: UpgradeFailures is the upgrade failure count against + the known state. It is reset after a successful reconciliation. format: int64 type: integer type: object diff --git a/controllers/helmrelease_controller.go b/controllers/helmrelease_controller.go index c81673a24..6959b8e86 100644 --- a/controllers/helmrelease_controller.go +++ b/controllers/helmrelease_controller.go @@ -211,7 +211,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - reconciledHr, reconcileErr := r.release(log, *hr.DeepCopy(), hc, values, hasNewGeneration) + reconciledHr, reconcileErr := r.release(log, *hr.DeepCopy(), hc, values) if reconcileErr != nil { r.event(hr, hc.GetArtifact().Revision, recorder.EventSeverityError, fmt.Sprintf("reconciliation failed: %s", reconcileErr.Error())) } @@ -295,7 +295,7 @@ func (r *HelmReleaseReconciler) reconcileChart(ctx context.Context, hr *v2.HelmR return &helmChart, true, nil } -func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, source sourcev1.Source, values chartutil.Values, hasNewGeneration bool) (v2.HelmRelease, error) { +func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, source sourcev1.Source, values chartutil.Values) (v2.HelmRelease, error) { // Acquire lock unlock, err := lock(fmt.Sprintf("%s-%s", hr.GetName(), hr.GetNamespace())) if err != nil { @@ -347,15 +347,20 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour // Determine release deployment action. var deployAction v2.DeploymentAction switch { - // Install if there is none. + // Install if there is no release. case rel == nil: deployAction = hr.Spec.GetInstall() - // Upgrade if there is a new generation, new state, or this is an upgrade retry. - case hasNewGeneration || hasNewState || hr.Spec.GetUpgrade().GetRemediation().GetFailureCount(hr) > 0: - deployAction = hr.Spec.GetUpgrade() - // Otherwise no action needed. + // Fail if the release was due to a failed install (which was not uninstalled). + // The uninstall may have failed, or was not needed due to retries being exhausted + // and remediateLastFailure being false. + case hr.Spec.GetInstall().GetRemediation().GetFailureCount(hr) > 0: + return hr, fmt.Errorf("last install failed but was not uninstalled") + // Skip and mark ready if the known state was already applied. + case hr.Status.KnownStateApplied: + return v2.HelmReleaseReady(hr), nil + // Otherwise upgrade. default: - return hr, nil + deployAction = hr.Spec.GetUpgrade() } // Check if retries exhausted. @@ -405,17 +410,18 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour err = uninstallConditionErr } } - } - } - // Determine release revision after deployment/remediation. - rel, observeLastReleaseErr = observeLastRelease(cfg, hr) - if observeLastReleaseErr != nil { - err = &ConditionError{ - Reason: v2.GetLastReleaseFailedReason, - Err: errors.New("failed to get last release revision after deployment/remediation"), + // Determine release after remediation. + rel, observeLastReleaseErr = observeLastRelease(cfg, hr) + if observeLastReleaseErr != nil { + err = &ConditionError{ + Reason: v2.GetLastReleaseFailedReason, + Err: errors.New("failed to get last release revision after remediation"), + } + } } } + hr.Status.LastReleaseRevision = getReleaseRevision(rel) if err != nil { @@ -426,7 +432,7 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour } return v2.HelmReleaseNotReady(hr, reason, err.Error()), err } - return v2.HelmReleaseReady(hr, v2.ReconciliationSucceededReason, "release reconciliation succeeded"), nil + return v2.HelmReleaseReady(hr), nil } func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error { diff --git a/docs/api/helmrelease.md b/docs/api/helmrelease.md index 2d7acab09..0519a0651 100644 --- a/docs/api/helmrelease.md +++ b/docs/api/helmrelease.md @@ -789,6 +789,18 @@ int64
knownStateApplied
KnownStateApplied represents whether the known state has been successfully applied.
+lastAppliedRevision
Failures is the reconciliation failure count.
+Failures is the reconciliation failure count against the known state. +It is reset after a successful reconciliation.
InstallFailures is the install failure count.
+InstallFailures is the install failure count against the known state. +It is reset after a successful reconciliation.
UpgradeFailures is the upgrade failure count.
+UpgradeFailures is the upgrade failure count against the known state. +It is reset after a successful reconciliation.