// Package recerr implements custom controller reconciliation error types and // utilities used during reconciliaton result summarization. // // This package allows for reconcilers to propagate contextual errors from internal // functions to the top-level Reconcile function by capturing the error message // and reason (to be displayed on the status condition) and providing a helper // ([ToCondition]) for updating a condition with that state. This allows the // internal reconcile functions to avoid requiring the condition to be manipulated // on each of their own error paths without losing context (namely, the reason // for the condition change). package recerr import ( "time" corev1 "k8s.io/api/core/v1" "edge-infra.dev/pkg/k8s/runtime/conditions" ) const EventTypeNone = "None" // Error defines the common behaviors of the reconciler errors this package // provides. This behavior may be extended by specific error implementations, // such as [Wait], which can be checked via type switches / assertions. This // interface should be used when propagating reconciliation errors through // function chains. type Error interface { error Unwrap() error // ToCondition uses the errors internal state to update condition on the // setter. It can be used to easily propagate specialized reconcile error // state to conditions. ToCondition(to conditions.Setter, condition string) // TODO(aw185176): ToNotification } // Base is a generic reconciliation error that public errors should be // created from. It contains state for determining if the error should produce // a K8s event, be logged, etc. It takes in a Config struct T that is available // to processing and parsing logic for errors encountered during reconciliation. type Base[T any] struct { // Err is the error that caused the error condition. This can be used as the // message for the condition. Err error // Reason is the string that is applied to a K8s Condition's Reason field, // it describes "why" or "when" an error would have occurred. Reason string // Event is the event type of an error. It is used to configure what type of // event an error should result in. // Valid values: // - EventTypeNone // - corev1.EventTypeNormal // - corev1.EventTypeWarning Event string // Notification controls whether or not the Event is actually pushed to the K8s // control plane. By default corev1.EventTypeNormal is not emitted. Notification bool // Config is the internal error-specific configuration used during error // processing, it should be set by public error constructors. Config T } // Error implements error interface. func (e *Base[T]) Error() string { return e.Err.Error() } // Unwrap returns the underlying error. func (e *Base[T]) Unwrap() error { return e.Err } // ToCondition updates a condition on a K8s object based on the error's context, // setting the error's Reason as the condition's Reason field and the Message // to the error's text. func (e *Base[T]) ToCondition(to conditions.Setter, t string) { conditions.MarkFalse(to, t, e.Reason, "%v", e) } // Generic is a basic reconciliation error that doesn't contain any specialized // configuration and doesn't imply specific states (such as [Wait] or [Stalled]). // It sends a warning notification by default. type Generic = Base[struct{}] // New creates a [Generic] error. func New(err error, reason string, opts ...Option) Error { o := makeOptions(&options{}, opts...) return &Generic{ Err: err, Reason: reason, Event: o.eventType, Notification: o.notification, } } // WaitConfig contains configuration for requeue interval to wait for. type WaitConfig struct{ RequeueAfter time.Duration } // Wait represents a reconciliation error for a resource that is still waiting // on some external state with a reason, e.g., resource dependencies. // // Wait errors are not returned to the runtime and are logged explicitly. // Since this failure results in reconciliation delay, sends an informational // notification. type Wait = Base[WaitConfig] // NewWait creates a Wait error and returns it. func NewWait(err error, reason string, requeueAfter time.Duration, opts ...Option) *Wait { o := makeOptions(&options{ eventType: corev1.EventTypeNormal, notification: true, }, opts...) return &Wait{ Reason: reason, Err: err, Event: o.eventType, Notification: o.notification, Config: WaitConfig{RequeueAfter: requeueAfter}, } } type stalledCfg struct{} // Stalled represents a reconciliation error for a reasource that is stalled // with a reason for stalling. // // Stalled errors are not returned to the runtime and are logged explicitly. // Since this failure requires user interaction, it sends a warning notification. type Stalled = Base[stalledCfg] // NewStalled creates a Stalled error and returns it. func NewStalled(e error, reason string, opts ...Option) *Stalled { o := makeOptions(&options{}, opts...) return &Stalled{ Reason: reason, Err: e, Event: o.eventType, Notification: o.notification, } }