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 3c12154 commit 3dde8c2
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
17 changes: 17 additions & 0 deletions internal/action/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package action

import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/opencontainers/go-digest"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
Expand All @@ -29,6 +30,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 @@ -53,5 +55,20 @@ func MustResetFailures(obj *v2.HelmRelease, chart *chart.Metadata, values chartu
return differentValuesReason, true
}
}

// TODO(hidde): factor this out.
resetAt, resetOk := obj.ResetAnnotationValue()
reconcileAt, reconcileOk := meta.ReconcileAnnotationValue(obj.GetAnnotations())

if resetOk && reconcileOk && resetAt == reconcileAt {
lastHandledReconcile := obj.Status.GetLastHandledReconcileRequest()
lastHandledReset := obj.Status.LastHandledResetAt

if lastHandledReconcile != reconcileAt && lastHandledReset != resetAt {
obj.Status.LastHandledResetAt = resetAt
return resetRequestedReason, true
}
}

return "", false
}
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
39 changes: 38 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,9 @@ 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.
mustForce := r.mustForce(req.Object)

switch state.Status {
case ReleaseStatusInSync:
log.Info("release in-sync with desired state")
Expand All @@ -290,13 +293,23 @@ 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 mustForce {
log.Info(msgWithReason("forcing upgrade", "force annotation is set"))
return NewUpgrade(r.configFactory, r.eventRecorder), nil
}

return nil, nil
case ReleaseStatusLocked:
log.Info(msgWithReason("release locked", state.Reason))
return NewUnlock(r.configFactory, r.eventRecorder), nil
case ReleaseStatusAbsent:
log.Info(msgWithReason("release not installed", state.Reason))

if mustForce {
log.Info(msgWithReason("forcing install", "force annotation is set"))
return NewInstall(r.configFactory, r.eventRecorder), nil
}

if req.Object.GetInstall().GetRemediation().RetriesExhausted(req.Object) {
return nil, fmt.Errorf("%w: cannot install release", ErrExceededMaxRetries)
}
Expand Down Expand Up @@ -360,6 +373,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 mustForce {
log.Info(msgWithReason("forcing upgrade", "force annotation is set"))
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 Expand Up @@ -398,6 +418,23 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state
}
}

// mustForce returns true if the release must be forced.
func (r *AtomicRelease) mustForce(obj *v2.HelmRelease) bool {
forceAt, forceOk := obj.ForceAnnotationValue()
reconcileAt, reconcileOk := meta.ReconcileAnnotationValue(obj.GetAnnotations())

if forceOk && reconcileOk && forceAt == reconcileAt {
lastHandledReconcile := obj.Status.GetLastHandledReconcileRequest()
lastHandledForce := obj.Status.LastHandledForceAt

if lastHandledReconcile != reconcileAt && lastHandledForce != forceAt {
obj.Status.LastHandledForceAt = forceAt
return true
}
}
return false
}

func (r *AtomicRelease) Name() string {
return "atomic-release"
}
Expand Down

0 comments on commit 3dde8c2

Please sign in to comment.