Skip to content

Commit

Permalink
Roughly implement forceAt and resetAt
Browse files Browse the repository at this point in the history
Signed-off-by: Hidde Beydals <[email protected]>
  • Loading branch information
hiddeco committed Nov 29, 2023
1 parent ba74b4a commit 2a5a56c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 1 deletion.
12 changes: 12 additions & 0 deletions internal/action/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
differentGenerationReason = "generation differs from last attempt"
differentRevisionReason = "chart version differs from last attempt"
differentValuesReason = "values differ from last attempt"
resetRequestedReason = "reset requested through annotation"
)

// MustResetFailures returns a reason and true if the HelmRelease's status
Expand All @@ -38,6 +39,12 @@ const (
// For example, a change in generation, chart version, or values.
// If no change is detected, an empty string is returned along with false.
func MustResetFailures(obj *v2.HelmRelease, chart *chart.Metadata, values chartutil.Values) (string, bool) {
// Always check if a reset is requested.
// This is done first, so that the HelmReleaseStatus.LastHandledResetAt
// field is updated even if the reset request is not handled due to other
// diverging data.
resetRequested := v2.ShouldHandleResetRequest(obj)

switch {
case obj.Status.LastAttemptedGeneration != obj.Generation:
return differentGenerationReason, true
Expand All @@ -53,5 +60,10 @@ func MustResetFailures(obj *v2.HelmRelease, chart *chart.Metadata, values chartu
return differentValuesReason, true
}
}

if resetRequested {
return resetRequestedReason, true
}

return "", false
}
26 changes: 26 additions & 0 deletions internal/action/reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package action
import (
"testing"

"github.com/fluxcd/pkg/apis/meta"
. "github.com/onsi/gomega"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
Expand Down Expand Up @@ -108,6 +109,31 @@ func TestMustResetFailures(t *testing.T) {
want: true,
wantReason: differentValuesReason,
},
{
name: "on reset request through annotation",
obj: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
Annotations: map[string]string{
meta.ReconcileRequestAnnotation: "a",
v2.ResetRequestAnnotation: "a",
},
},
Status: v2.HelmReleaseStatus{
LastAttemptedGeneration: 1,
LastAttemptedRevision: "1.0.0",
LastAttemptedConfigDigest: "sha256:1dabc4e3cbbd6a0818bd460f3a6c9855bfe95d506c74726bc0f2edb0aecb1f4e",
},
},
chart: &chart.Metadata{
Version: "1.0.0",
},
values: chartutil.Values{
"foo": "bar",
},
want: true,
wantReason: resetRequestedReason,
},
{
name: "without change no reset",
obj: &v2.HelmRelease{
Expand Down
4 changes: 4 additions & 0 deletions internal/controller/helmrelease_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ func (r *HelmReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request)

// Always attempt to patch the object after each reconciliation.
defer func() {
if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok {
obj.Status.SetLastHandledReconcileRequest(v)
}

patchOpts := []patch.Option{
patch.WithFieldOwner(r.FieldManager),
patch.WithOwnedConditions{Conditions: intreconcile.OwnedConditions},
Expand Down
31 changes: 30 additions & 1 deletion internal/reconcile/atomic_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (r *AtomicRelease) Reconcile(ctx context.Context, req *Request) error {
}
return fmt.Errorf("atomic release canceled: %w", ctx.Err())
default:
// Determine the next action to run based on the current state.
// Determine the current state of the Helm release.
log.V(logger.DebugLevel).Info("determining current state of Helm release")
state, err := DetermineReleaseState(ctx, r.configFactory, req)
if err != nil {
Expand Down Expand Up @@ -272,6 +272,13 @@ func (r *AtomicRelease) Reconcile(ctx context.Context, req *Request) error {
func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state ReleaseState) (ActionReconciler, error) {
log := ctrl.LoggerFrom(ctx)

// Determine whether we may need to force a release action.
// We do this before determining the next action to run, as otherwise we may
// end up running a Helm upgrade (due to e.g. ReleaseStatusUnmanaged) and
// then forcing an upgrade (due to the release now being in
// ReleaseStatusInSync with a yet unhandled force request).
forceRequested := v2.ShouldHandleForceRequest(req.Object)

switch state.Status {
case ReleaseStatusInSync:
log.Info("release in-sync with desired state")
Expand All @@ -290,6 +297,11 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state
// field, but should be removed in a future release.
req.Object.Status.LastAppliedRevision = req.Object.Status.History.Latest().ChartVersion

if forceRequested {
log.Info(msgWithReason("forcing upgrade for in-sync release", "force requested through annotation"))
return NewUpgrade(r.configFactory, r.eventRecorder), nil
}

return nil, nil
case ReleaseStatusLocked:
log.Info(msgWithReason("release locked", state.Reason))
Expand All @@ -298,6 +310,11 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state
log.Info(msgWithReason("release not installed", state.Reason))

if req.Object.GetInstall().GetRemediation().RetriesExhausted(req.Object) {
if forceRequested {
log.Info(msgWithReason("forcing install while out of retries", "force requested through annotation"))
return NewInstall(r.configFactory, r.eventRecorder), nil
}

return nil, fmt.Errorf("%w: cannot install release", ErrExceededMaxRetries)
}

Expand All @@ -313,6 +330,11 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state
log.Info(msgWithReason("release out-of-sync with desired state", state.Reason))

if req.Object.GetUpgrade().GetRemediation().RetriesExhausted(req.Object) {
if forceRequested {
log.Info(msgWithReason("forcing upgrade while out of retries", "force requested through annotation"))
return NewInstall(r.configFactory, r.eventRecorder), nil
}

return nil, fmt.Errorf("%w: cannot upgrade release", ErrExceededMaxRetries)
}

Expand Down Expand Up @@ -360,6 +382,13 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state
return NewUpgrade(r.configFactory, r.eventRecorder), nil
}

// If the force annotation is set, we can attempt to upgrade the release
// without any further checks.
if forceRequested {
log.Info(msgWithReason("forcing upgrade for failed release", "force requested through annotation"))
return NewUpgrade(r.configFactory, r.eventRecorder), nil
}

// We have exhausted the number of retries for the remediation
// strategy.
if remediation.RetriesExhausted(req.Object) && !remediation.MustRemediateLastFailure() {
Expand Down

0 comments on commit 2a5a56c

Please sign in to comment.