Record SHA1 of values of last release attempt

This commit is contained in:
Hidde Beydals 2020-07-28 16:05:09 +02:00
parent 6dba659c3c
commit 6b1d28a736
4 changed files with 51 additions and 30 deletions

View File

@ -357,6 +357,10 @@ type HelmReleaseStatus struct {
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
// LastAttemptedValuesChecksum is the SHA1 checksum of the values of the last reconciliation attempt.
// +optional
LastAttemptedValuesChecksum string `json:"lastAttemptedValuesChecksum,omitempty"`
// LastReleaseRevision is the revision of the last successful Helm release.
// +optional
LastReleaseRevision int `json:"lastReleaseRevision,omitempty"`
@ -410,30 +414,31 @@ func SetHelmReleaseCondition(hr *HelmRelease, condition string, status corev1.Co
// SetHelmReleaseReadiness sets the ReadyCondition, ObservedGeneration, LastAttemptedRevision,
// and LastReleaseRevision, on the HelmRelease.
func SetHelmReleaseReadiness(hr *HelmRelease, status corev1.ConditionStatus, reason, message string, revision string, releaseRevision int) {
func SetHelmReleaseReadiness(hr *HelmRelease, status corev1.ConditionStatus, reason, message string, revision string, releaseRevision int, valuesChecksum string) {
SetHelmReleaseCondition(hr, ReadyCondition, status, reason, message)
hr.Status.ObservedGeneration = hr.Generation
hr.Status.LastAttemptedRevision = revision
hr.Status.LastReleaseRevision = releaseRevision
hr.Status.LastAttemptedValuesChecksum = valuesChecksum
}
// HelmReleaseNotReady registers a failed release attempt of the given HelmRelease.
func HelmReleaseNotReady(hr HelmRelease, revision string, releaseRevision int, reason, message string) HelmRelease {
SetHelmReleaseReadiness(&hr, corev1.ConditionFalse, reason, message, revision, releaseRevision)
func HelmReleaseNotReady(hr HelmRelease, revision string, releaseRevision int, valuesChecksum, reason, message string) HelmRelease {
SetHelmReleaseReadiness(&hr, corev1.ConditionFalse, reason, message, revision, releaseRevision, valuesChecksum)
hr.Status.Failures = hr.Status.Failures + 1
return hr
}
// HelmReleaseReady registers a successful release attempt of the given HelmRelease.
func HelmReleaseReady(hr HelmRelease, revision string, releaseRevision int, reason, message string) HelmRelease {
SetHelmReleaseReadiness(&hr, corev1.ConditionTrue, reason, message, revision, releaseRevision)
func HelmReleaseReady(hr HelmRelease, revision string, releaseRevision int, valuesChecksum, reason, message string) HelmRelease {
SetHelmReleaseReadiness(&hr, corev1.ConditionTrue, reason, message, revision, releaseRevision, valuesChecksum)
hr.Status.LastAppliedRevision = revision
hr.Status.Failures = 0
return hr
}
// ShouldUpgrade determines if an Helm upgrade action needs to be performed for the given HelmRelease.
func ShouldUpgrade(hr HelmRelease, revision string, releaseRevision int) bool {
func ShouldUpgrade(hr HelmRelease, revision string, releaseRevision int, valuesChecksum string) bool {
switch {
case hr.Status.LastAttemptedRevision != revision:
return true
@ -441,6 +446,8 @@ func ShouldUpgrade(hr HelmRelease, revision string, releaseRevision int) bool {
return true
case hr.Generation != hr.Status.ObservedGeneration:
return true
case hr.Status.LastAttemptedValuesChecksum != valuesChecksum:
return true
case hr.Status.Failures > 0 &&
(hr.Spec.Upgrade.MaxRetries < 0 || hr.Status.Failures < int64(hr.Spec.Upgrade.MaxRetries)):
return true
@ -489,17 +496,6 @@ func ShouldUninstall(hr HelmRelease, releaseRevision int) bool {
return false
}
// HelmReleaseReadyMessage returns the message of the HelmRelease
// of type Ready with status true if present, or an empty string.
func HelmReleaseReadyMessage(hr HelmRelease) string {
for _, condition := range hr.Status.Conditions {
if condition.Type == ReadyCondition && condition.Status == corev1.ConditionTrue {
return condition.Message
}
}
return ""
}
const (
// ReconcileAtAnnotation is the annotation used for triggering a
// reconciliation outside of the defined schedule.

View File

@ -345,6 +345,10 @@ spec:
description: LastAttemptedRevision is the revision of the last reconciliation
attempt.
type: string
lastAttemptedValuesChecksum:
description: LastAttemptedValuesChecksum is the SHA1 checksum of the
values of the last reconciliation attempt.
type: string
lastReleaseRevision:
description: LastReleaseRevision is the revision of the last successful
Helm release.

View File

@ -18,6 +18,7 @@ package controllers
import (
"context"
"crypto/sha1"
"errors"
"fmt"
"io"
@ -115,7 +116,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
if hr.Spec.Suspend {
msg := "HelmRelease is suspended, skipping reconciliation"
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.SuspendedReason, msg)
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.SuspendedReason, msg)
if err := r.Status().Update(ctx, &hr); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
@ -141,7 +142,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
msg = "HelmChart is not ready"
r.event(hr, hr.Status.LastAttemptedRevision, recorder.EventSeverityInfo, msg)
}
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.ArtifactFailedReason, msg)
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.ArtifactFailedReason, msg)
if err := r.Status().Update(ctx, &hr); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
@ -152,7 +153,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
// Check chart artifact readiness
if hc.GetArtifact() == nil {
msg := "HelmChart is not ready"
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.ArtifactFailedReason, msg)
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.ArtifactFailedReason, msg)
r.event(hr, hr.Status.LastAttemptedRevision, recorder.EventSeverityInfo, msg)
log.Info(msg)
if err := r.Status().Update(ctx, &hr); err != nil {
@ -169,7 +170,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
r.event(hr, hc.GetArtifact().Revision, recorder.EventSeverityInfo, msg)
log.Info(msg)
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.DependencyNotReadyReason, err.Error())
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.DependencyNotReadyReason, err.Error())
if err := r.Status().Update(ctx, &hr); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
@ -184,7 +185,7 @@ func (r *HelmReleaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
// Compose values
values, err := r.composeValues(ctx, hr)
if err != nil {
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.InitFailedReason, err.Error())
hr = v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.InitFailedReason, err.Error())
r.event(hr, hr.Status.LastAttemptedRevision, recorder.EventSeverityError, err.Error())
if err := r.Status().Update(ctx, &hr); err != nil {
log.Error(err, "unable to update status")
@ -282,7 +283,7 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour
unlock, err := lock(fmt.Sprintf("%s-%s", hr.GetName(), hr.GetNamespace()))
if err != nil {
err = fmt.Errorf("lockfile error: %w", err)
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, sourcev1.StorageOperationFailedReason, err.Error()), err
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, sourcev1.StorageOperationFailedReason, err.Error()), err
}
defer unlock()
@ -296,34 +297,36 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour
// Download artifact
artifactPath, err := download(source.GetArtifact().URL, tmpDir)
if err != nil {
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.ArtifactFailedReason, "artifact acquisition failed"), err
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.ArtifactFailedReason, "artifact acquisition failed"), err
}
// Load chart
loadedChart, err := loader.Load(artifactPath)
if err != nil {
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.ArtifactFailedReason, "failed to load chart"), err
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.ArtifactFailedReason, "failed to load chart"), err
}
// Initialize config
cfg, err := newActionCfg(log, r.Config, hr)
if err != nil {
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.InitFailedReason, "failed to initialize Helm action configuration"), err
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.InitFailedReason, "failed to initialize Helm action configuration"), err
}
// Get the current release
rel, err := cfg.Releases.Deployed(hr.GetReleaseName())
if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) {
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, v2.InitFailedReason, "failed to determine if release exists"), err
return v2.HelmReleaseNotReady(hr, hr.Status.LastAttemptedRevision, hr.Status.LastReleaseRevision, hr.Status.LastAttemptedValuesChecksum, v2.InitFailedReason, "failed to determine if release exists"), err
}
valuesChecksum := calculateValuesChecksum(values)
// Install or upgrade the release
success := true
if errors.Is(err, driver.ErrNoDeployedReleases) {
rel, err = install(cfg, loadedChart, hr, values)
r.handleHelmActionResult(hr, source, err, "install", v2.InstalledCondition, v2.InstallSucceededReason, v2.InstallFailedReason)
success = err == nil
} else if v2.ShouldUpgrade(hr, source.GetArtifact().Revision, rel.Version) {
} else if v2.ShouldUpgrade(hr, source.GetArtifact().Revision, rel.Version, valuesChecksum) {
rel, err = upgrade(cfg, loadedChart, hr, values)
r.handleHelmActionResult(hr, source, err, "upgrade", v2.UpgradedCondition, v2.UpgradeSucceededReason, v2.UpgradeFailedReason)
success = err == nil
@ -359,9 +362,9 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour
}
if !success {
return v2.HelmReleaseNotReady(hr, source.GetArtifact().Revision, releaseRevision, v2.ReconciliationFailedReason, "release reconciliation failed"), err
return v2.HelmReleaseNotReady(hr, source.GetArtifact().Revision, releaseRevision, valuesChecksum, v2.ReconciliationFailedReason, "release reconciliation failed"), err
}
return v2.HelmReleaseReady(hr, source.GetArtifact().Revision, releaseRevision, v2.ReconciliationSucceededReason, "release reconciliation succeeded"), nil
return v2.HelmReleaseReady(hr, source.GetArtifact().Revision, releaseRevision, valuesChecksum, v2.ReconciliationSucceededReason, "release reconciliation succeeded"), nil
}
func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error {
@ -658,6 +661,12 @@ func actionLogger(logger logr.Logger) func(format string, v ...interface{}) {
}
}
// calculateValuesChecksum calculates the SHA1 checksum for the given Values.
func calculateValuesChecksum(values chartutil.Values) string {
s, _ := values.YAML()
return fmt.Sprintf("%x", sha1.Sum([]byte(s)))
}
func containsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {

View File

@ -806,6 +806,18 @@ string
</tr>
<tr>
<td>
<code>lastAttemptedValuesChecksum</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>LastAttemptedValuesChecksum is the SHA1 checksum of the values of the last reconciliation attempt.</p>
</td>
</tr>
<tr>
<td>
<code>lastReleaseRevision</code><br>
<em>
int