From f20e3e5a31901adacb2e64b40d6d406bceddd6f3 Mon Sep 17 00:00:00 2001 From: Daniel Lohse Date: Mon, 17 Nov 2025 17:37:25 +0100 Subject: [PATCH 1/2] Fix typo Signed-off-by: Daniel Lohse --- reconcilers/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reconcilers/resource.go b/reconcilers/resource.go index 55f64a0..0ca6f83 100644 --- a/reconcilers/resource.go +++ b/reconcilers/resource.go @@ -117,7 +117,7 @@ type ResourceReconciler[Type client.Object] struct { AfterReconcile func(ctx context.Context, req Request, res Result, err error) (Result, error) // SkipResource shortcuts the reconciler for the specific request. While the context and logger - // are initialized, no work is preformed. The request is removed from the workqueue. + // are initialized, no work is performed. The request is removed from the workqueue. // // +optional SkipRequest func(ctx context.Context, req Request) bool From 70ca1e5490cd4f5c715f3ebd55f28da2d83e093f Mon Sep 17 00:00:00 2001 From: Daniel Lohse Date: Mon, 17 Nov 2025 17:40:46 +0100 Subject: [PATCH 2/2] Add `IfNotFound` option to the ResourceReconciler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can be optionally set and will be used if the resource doesn’t exist yet. When reconciliations are triggered from the outside the user can create the resource. This is useful if the ChildReconciler cannot be used, i.e. if there’s no explicit parent resource. Signed-off-by: Daniel Lohse --- reconcilers/resource.go | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/reconcilers/resource.go b/reconcilers/resource.go index 0ca6f83..4d56a77 100644 --- a/reconcilers/resource.go +++ b/reconcilers/resource.go @@ -131,6 +131,12 @@ type ResourceReconciler[Type client.Object] struct { // +optional SkipResource func(ctx context.Context, resource Type) bool + // IfNotFound is called if the resource doesn't exist yet. Return nil if the reconcile should + // be aborted. + // + // +optional + IfNotFound func(ctx context.Context, req Request) Type + Config Config lazyInit sync.Once @@ -142,6 +148,12 @@ func (r *ResourceReconciler[T]) init() { var nilT T r.Type = newEmpty(nilT).(T) } + if r.IfNotFound == nil { + r.IfNotFound = func(ctx context.Context, req Request) T { + var nilT T + return nilT + } + } if r.Name == "" { r.Name = fmt.Sprintf("%sResourceReconciler", typeName(r.Type)) } @@ -320,13 +332,18 @@ func (r *ResourceReconciler[T]) reconcileOuter(ctx context.Context, req Request) originalResource := r.Type.DeepCopyObject().(T) - if err := c.Get(ctx, req.NamespacedName, originalResource); err != nil { - if apierrs.IsNotFound(err) { - // we'll ignore not-found errors, since they can't be fixed by an immediate - // requeue (we'll need to wait for a new notification), and we can get them - // on deleted requests. + err := c.Get(ctx, req.NamespacedName, originalResource) + switch { + case err == nil: + // all good, continue + case apierrs.IsNotFound(err): + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests – except if `IfNotFound` returns a new resource + if originalResource = r.IfNotFound(ctx, req); internal.IsNil(originalResource) { return Result{}, nil } + default: if !errors.Is(err, ErrQuiet) { log.Error(err, "unable to fetch resource") }