1 // Package recerr implements custom controller reconciliation error types and 2 // utilities used during reconciliaton result summarization. 3 // 4 // This package allows for reconcilers to propagate contextual errors from internal 5 // functions to the top-level Reconcile function by capturing the error message 6 // and reason (to be displayed on the status condition) and providing a helper 7 // ([ToCondition]) for updating a condition with that state. This allows the 8 // internal reconcile functions to avoid requiring the condition to be manipulated 9 // on each of their own error paths without losing context (namely, the reason 10 // for the condition change). 11 package recerr 12 13 import ( 14 "time" 15 16 corev1 "k8s.io/api/core/v1" 17 18 "edge-infra.dev/pkg/k8s/runtime/conditions" 19 ) 20 21 const EventTypeNone = "None" 22 23 // Error defines the common behaviors of the reconciler errors this package 24 // provides. This behavior may be extended by specific error implementations, 25 // such as [Wait], which can be checked via type switches / assertions. This 26 // interface should be used when propagating reconciliation errors through 27 // function chains. 28 type Error interface { 29 error 30 Unwrap() error 31 32 // ToCondition uses the errors internal state to update condition on the 33 // setter. It can be used to easily propagate specialized reconcile error 34 // state to conditions. 35 ToCondition(to conditions.Setter, condition string) 36 37 // TODO(aw185176): ToNotification 38 } 39 40 // Base is a generic reconciliation error that public errors should be 41 // created from. It contains state for determining if the error should produce 42 // a K8s event, be logged, etc. It takes in a Config struct T that is available 43 // to processing and parsing logic for errors encountered during reconciliation. 44 type Base[T any] struct { 45 // Err is the error that caused the error condition. This can be used as the 46 // message for the condition. 47 Err error 48 49 // Reason is the string that is applied to a K8s Condition's Reason field, 50 // it describes "why" or "when" an error would have occurred. 51 Reason string 52 53 // Event is the event type of an error. It is used to configure what type of 54 // event an error should result in. 55 // Valid values: 56 // - EventTypeNone 57 // - corev1.EventTypeNormal 58 // - corev1.EventTypeWarning 59 Event string 60 61 // Notification controls whether or not the Event is actually pushed to the K8s 62 // control plane. By default corev1.EventTypeNormal is not emitted. 63 Notification bool 64 65 // Config is the internal error-specific configuration used during error 66 // processing, it should be set by public error constructors. 67 Config T 68 } 69 70 // Error implements error interface. 71 func (e *Base[T]) Error() string { 72 return e.Err.Error() 73 } 74 75 // Unwrap returns the underlying error. 76 func (e *Base[T]) Unwrap() error { 77 return e.Err 78 } 79 80 // ToCondition updates a condition on a K8s object based on the error's context, 81 // setting the error's Reason as the condition's Reason field and the Message 82 // to the error's text. 83 func (e *Base[T]) ToCondition(to conditions.Setter, t string) { 84 conditions.MarkFalse(to, t, e.Reason, "%v", e) 85 } 86 87 // Generic is a basic reconciliation error that doesn't contain any specialized 88 // configuration and doesn't imply specific states (such as [Wait] or [Stalled]). 89 // It sends a warning notification by default. 90 type Generic = Base[struct{}] 91 92 // New creates a [Generic] error. 93 func New(err error, reason string, opts ...Option) Error { 94 o := makeOptions(&options{}, opts...) 95 return &Generic{ 96 Err: err, 97 Reason: reason, 98 Event: o.eventType, 99 Notification: o.notification, 100 } 101 } 102 103 // WaitConfig contains configuration for requeue interval to wait for. 104 type WaitConfig struct{ RequeueAfter time.Duration } 105 106 // Wait represents a reconciliation error for a resource that is still waiting 107 // on some external state with a reason, e.g., resource dependencies. 108 // 109 // Wait errors are not returned to the runtime and are logged explicitly. 110 // Since this failure results in reconciliation delay, sends an informational 111 // notification. 112 type Wait = Base[WaitConfig] 113 114 // NewWait creates a Wait error and returns it. 115 func NewWait(err error, reason string, requeueAfter time.Duration, opts ...Option) *Wait { 116 o := makeOptions(&options{ 117 eventType: corev1.EventTypeNormal, notification: true, 118 }, opts...) 119 return &Wait{ 120 Reason: reason, 121 Err: err, 122 Event: o.eventType, 123 Notification: o.notification, 124 Config: WaitConfig{RequeueAfter: requeueAfter}, 125 } 126 } 127 128 type stalledCfg struct{} 129 130 // Stalled represents a reconciliation error for a reasource that is stalled 131 // with a reason for stalling. 132 // 133 // Stalled errors are not returned to the runtime and are logged explicitly. 134 // Since this failure requires user interaction, it sends a warning notification. 135 type Stalled = Base[stalledCfg] 136 137 // NewStalled creates a Stalled error and returns it. 138 func NewStalled(e error, reason string, opts ...Option) *Stalled { 139 o := makeOptions(&options{}, opts...) 140 return &Stalled{ 141 Reason: reason, 142 Err: e, 143 Event: o.eventType, 144 Notification: o.notification, 145 } 146 } 147