@@ -84,6 +84,8 @@ const (
8484// manager), and the current (potentially unsaved) state of the component.
8585// Post-hooks will only be called if the according operation (read, reconcile, delete)
8686// has been successful.
87+ // Note: hooks may change the status of the component, but must not alter the metadata or spec,
88+ // since changes might be persisted by the framework (e.g. when updating finalizers).
8789type HookFunc [T Component ] func (ctx context.Context , clnt client.Client , component T ) error
8890
8991// NewClientFunc is the function signature that can be used to modify or replace the default
@@ -181,9 +183,8 @@ func NewReconciler[T Component](name string, resourceGenerator manifests.Generat
181183 statusAnalyzer : status .NewStatusAnalyzer (name ),
182184 options : options ,
183185 // TODO: make backoff configurable via options?
184- backoff : backoff .NewBackoff (10 * time .Second ),
185- postReadHooks : []HookFunc [T ]{resolveReferences [T ]},
186- triggerCh : make (chan event.TypedGenericEvent [apitypes.NamespacedName ], triggerBufferSize ),
186+ backoff : backoff .NewBackoff (10 * time .Second ),
187+ triggerCh : make (chan event.TypedGenericEvent [apitypes.NamespacedName ], triggerBufferSize ),
187188 }
188189}
189190
@@ -203,7 +204,7 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
203204
204205 now := metav1 .Now ()
205206
206- // fetch reconciled object
207+ // fetch reconciled component
207208 component := newComponent [T ]()
208209 if err := r .client .Get (ctx , req .NamespacedName , component ); err != nil {
209210 if apierrors .IsNotFound (err ) {
@@ -213,6 +214,8 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
213214 return ctrl.Result {}, errors .Wrap (err , "unexpected get error" )
214215 }
215216 component .GetObjectKind ().SetGroupVersionKind (r .groupVersionKind )
217+ // componentDigest is populated after post-read hook phase
218+ componentDigest := ""
216219
217220 // fetch requeue interval, retry interval and timeout
218221 requeueInterval := time .Duration (0 )
@@ -306,14 +309,24 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
306309 }
307310 }
308311
309- // TODO: should we move this behind the DeepEqual check below to avoid noise?
312+ // TODO: should we move this behind the DeepEqual check below to reduce noise?
310313 // also note: it seems that no events will be written if the component's namespace is in deletion
311314 state , reason , message := status .GetState ()
312315 var eventAnnotations map [string ]string
313316 // TODO: formalize this into a real published interface
314- if eventAnnotationProvider , ok := Component (component ).(interface { GetEventAnnotations () map [string ]string }); ok {
315- eventAnnotations = eventAnnotationProvider .GetEventAnnotations ()
316- }
317+ // TODO: pass previousState, and especially componentDigest in a better way;
318+ // maybe we could even make the component aware of its own digest ...
319+ // another option could be to model this as a hook-like function (instead of a component method) ...
320+ // note: the passed component digest might be empty (that is, if we return before the post-read phase)
321+ // note: this interface is not released for usage; it may change without announcement
322+ if eventAnnotationProvider , ok := Component (component ).(interface {
323+ GetEventAnnotations (previousState State , componentDigest string ) map [string ]string
324+ }); ok {
325+ eventAnnotations = eventAnnotationProvider .GetEventAnnotations (savedStatus .State , componentDigest )
326+ }
327+ // TODO: sending events may block a little while (some seconds), in particular if enhanced recorders are installed through options.NewClient(),
328+ // such as the flux notfication recorder; should we therefore send the events asynchronously, or start synchronously and continue asynchronous
329+ // after a little while?
317330 if state == StateError {
318331 r .client .EventRecorder ().AnnotatedEventf (component , eventAnnotations , corev1 .EventTypeWarning , reason , message )
319332 } else {
@@ -350,6 +363,12 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
350363 return ctrl.Result {Requeue : true }, nil
351364 }
352365
366+ // resolve references
367+ componentDigest , err = resolveReferences (ctx , r .client , component )
368+ if err != nil {
369+ return ctrl.Result {}, errors .Wrap (err , "error resolving references" )
370+ }
371+
353372 // run post-read hooks
354373 // note: it's important that this happens after deferring the status handler
355374 // TODO: enhance ctx with tailored logger and event recorder
@@ -400,7 +419,7 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
400419 return ctrl.Result {}, errors .Wrapf (err , "error running pre-reconcile hook (%d)" , hookOrder )
401420 }
402421 }
403- ok , digest , err := target .Apply (ctx , component )
422+ ok , processingDigest , err := target .Apply (ctx , component , componentDigest )
404423 if err != nil {
405424 log .V (1 ).Info ("error while reconciling dependent resources" )
406425 return ctrl.Result {}, errors .Wrap (err , "error reconciling dependent resources" )
@@ -418,8 +437,8 @@ func (r *Reconciler[T]) Reconcile(ctx context.Context, req ctrl.Request) (result
418437 return ctrl.Result {RequeueAfter : requeueInterval }, nil
419438 } else {
420439 log .V (1 ).Info ("not all dependent resources successfully reconciled" )
421- if digest != status .ProcessingDigest {
422- status .ProcessingDigest = digest
440+ if processingDigest != status .ProcessingDigest {
441+ status .ProcessingDigest = processingDigest
423442 status .ProcessingSince = & now
424443 r .backoff .Forget (req )
425444 }
0 commit comments