pkg/controller/common/reconciler/results.go (123 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package reconciler import ( "context" "time" k8serrors "k8s.io/apimachinery/pkg/util/errors" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/tracing" ) type resultKind int const ( noqueueKind resultKind = iota // reconcile.Result{} specificKind // reconcile.Result{RequeueAfter: x} genericKind // reconcile.Result{Requeue: true} ) func kindOf(r reconcile.Result) resultKind { switch { case r.RequeueAfter > 0: return specificKind case r.Requeue: return genericKind default: return noqueueKind } } // Results collects intermediate results of a reconciliation run and any errors that occurred. type Results struct { currResult ReconciliationState currKind resultKind errors []error ctx context.Context } var Requeue = ReconciliationState{Result: reconcile.Result{Requeue: true}} func RequeueAfter(requeueAfter time.Duration) ReconciliationState { return ReconciliationState{ incomplete: true, Result: reconcile.Result{ RequeueAfter: requeueAfter, }, } } // ReconciliationState extends a reconciliation result with an optional reason that can be surfaced in the status. type ReconciliationState struct { reconcile.Result // incomplete can be used to mark the current reconciliation as complete even if RequeueAfter is set. incomplete bool // reason is a string that might be surfaced to the user in the resource status. reason string } func (r ReconciliationState) WithReason(reason string) ReconciliationState { return ReconciliationState{ Result: r.Result, reason: reason, incomplete: true, } } func (r ReconciliationState) ReconciliationComplete() ReconciliationState { return ReconciliationState{ Result: r.Result, reason: r.reason, incomplete: false, } } func NewResult(ctx context.Context) *Results { return &Results{ ctx: ctx, } } // HasError returns true if Results contains one or more errors. func (r *Results) HasError() bool { if r == nil { return false } return len(r.errors) > 0 } func (r *Results) HasRequeue() bool { if r == nil { return false } return r.currResult.Result.Requeue || r.currResult.Result.RequeueAfter > 0 } // WithResults appends the results and error from the other Results. func (r *Results) WithResults(other *Results) *Results { if other != nil { r.mergeResult(other.currKind, other.currResult) r.errors = append(r.errors, other.errors...) } return r } // WithError adds an error to the results. func (r *Results) WithError(err error) *Results { if err != nil { r.errors = append(r.errors, tracing.CaptureError(r.ctx, err)) } return r } // WithResult adds a result to the results. func (r *Results) WithResult(res reconcile.Result) *Results { incomplete := res.Requeue || !res.IsZero() r.WithReconciliationState(ReconciliationState{incomplete: incomplete, Result: res}) return r } // WithReconciliationState adds a result and related state information to the results. func (r *Results) WithReconciliationState(res ReconciliationState) *Results { kind := kindOf(res.Result) r.mergeResult(kind, res) return r } // mergeResult updates the current result if the other result has higher priority. // Order of priority is: noqueue < specific < generic // When there are two specific results, the one with the lowest RequeueAfter takes precedence. func (r *Results) mergeResult(kind resultKind, res ReconciliationState) { switch { case kind > r.currKind: r.currKind = kind r.currResult = res case kind == specificKind && r.currKind == specificKind: if res.Result.RequeueAfter < r.currResult.Result.RequeueAfter { r.currResult = res } } // Reconciliation is considered as incomplete as soon as it has been reported, whatever the priority of the result. r.currResult.incomplete = r.currResult.incomplete || res.incomplete } // Aggregate returns the highest priority reconcile result and any errors seen so far. func (r *Results) Aggregate() (reconcile.Result, error) { return r.currResult.Result, k8serrors.NewAggregate(r.errors) } // IsReconciled returns true if no error has been reported and if RequeueAfter is 0. // It also returns true if ReconciliationComplete has been called while setting RequeueAfter to something // greater than 0, in which case Requeue and RequeueAfter are ignored. func (r *Results) IsReconciled() (bool, string) { if r.HasError() { err := k8serrors.NewAggregate(r.errors) return false, err.Error() } if !r.currResult.incomplete { return true, "" } return !r.HasRequeue(), r.currResult.reason }