Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
Allow errors to be quiet (#429)
Browse files Browse the repository at this point in the history
ErrQuiet errors are not logged within reconciler-runtime and do not
trigger events to be recorded. This behavior is conventional rather than
technical. An implementor may choose to ignore these conventions when it
fits their purpose. Quite is not silent.

Also redefines ErrHaltSubReconcilers to wrap ErrQuiet.
HaltSubReconcilers is deprecated in favor of ErrHaltSubReconcilers.

Signed-off-by: Scott Andrews <andrewssc@vmware.com>
  • Loading branch information
scothis authored Sep 6, 2023
1 parent d577e94 commit 0a566ab
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 45 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1076,8 +1076,11 @@ reconciler-runtime is rapidly evolving. While we strive for API compatability be

### Current Deprecations

- status `InitializeConditions()` is deprecated in favor of `InitializeConditions(context.Context)`. Support may be removed in a future release, users are encouraged to migrate.
- `ConditionSet#Manage` is deprecated in favor of `ConditionSet#ManageWithContext`. Support may be removed in a future release, users are encuraged to migrate.
Backwards support may be removed in a future release, users are encouraged to migrate.

- status `InitializeConditions()` is deprecated in favor of `InitializeConditions(context.Context)`.
- `ConditionSet#Manage` is deprecated in favor of `ConditionSet#ManageWithContext`.
- `HaltSubReconcilers` is deprecated in favor of `ErrHaltSubReconcilers`.

## Contributing

Expand Down
8 changes: 5 additions & 3 deletions reconcilers/aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type AggregateReconciler[Type client.Object] struct {
// Reconciler is called for each reconciler request with the resource being reconciled.
// Typically, Reconciler is a Sequence of multiple SubReconcilers.
//
// When HaltSubReconcilers is returned as an error, execution continues as if no error was
// When ErrHaltSubReconcilers is returned as an error, execution continues as if no error was
// returned.
//
// +optional
Expand Down Expand Up @@ -227,7 +227,9 @@ func (r *AggregateReconciler[T]) Reconcile(ctx context.Context, req Request) (Re
resource.SetNamespace(r.Request.Namespace)
resource.SetName(r.Request.Name)
} else {
log.Error(err, "unable to fetch resource")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to fetch resource")
}
return Result{}, err
}
}
Expand All @@ -238,7 +240,7 @@ func (r *AggregateReconciler[T]) Reconcile(ctx context.Context, req Request) (Re
}

result, err := r.Reconciler.Reconcile(ctx, resource)
if err != nil && !errors.Is(err, HaltSubReconcilers) {
if err != nil && !errors.Is(err, ErrHaltSubReconcilers) {
return result, err
}

Expand Down
4 changes: 2 additions & 2 deletions reconcilers/aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ func TestAggregateReconciler(t *testing.T) {
r.Reconciler = reconcilers.Sequence[*corev1.ConfigMap]{
&reconcilers.SyncReconciler[*corev1.ConfigMap]{
Sync: func(ctx context.Context, resource *corev1.ConfigMap) error {
return reconcilers.HaltSubReconcilers
return reconcilers.ErrHaltSubReconcilers
},
},
&reconcilers.SyncReconciler[*corev1.ConfigMap]{
Expand Down Expand Up @@ -371,7 +371,7 @@ func TestAggregateReconciler(t *testing.T) {
r.Reconciler = reconcilers.Sequence[*corev1.ConfigMap]{
&reconcilers.SyncReconciler[*corev1.ConfigMap]{
SyncWithResult: func(ctx context.Context, resource *corev1.ConfigMap) (reconcilers.Result, error) {
return reconcilers.Result{Requeue: true}, reconcilers.HaltSubReconcilers
return reconcilers.Result{Requeue: true}, reconcilers.ErrHaltSubReconcilers
},
},
&reconcilers.SyncReconciler[*corev1.ConfigMap]{
Expand Down
10 changes: 7 additions & 3 deletions reconcilers/child.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ func (r *ChildReconciler[T, CT, CLT]) Reconcile(ctx context.Context, resource T)
r.ReflectChildStatusOnParent(ctx, resource, child, err)
return Result{}, nil
}
log.Error(err, "unable to reconcile child")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to reconcile child")
}
return Result{}, err
}
r.ReflectChildStatusOnParent(ctx, resource, child, nil)
Expand All @@ -301,8 +303,10 @@ func (r *ChildReconciler[T, CT, CLT]) reconcile(ctx context.Context, resource T)
for _, extra := range items {
log.Info("deleting extra child", "child", namespaceName(extra))
if err := c.Delete(ctx, extra); err != nil {
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "DeleteFailed",
"Failed to delete %s %q: %v", typeName(r.ChildType), extra.GetName(), err)
if !errors.Is(err, ErrQuiet) {
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "DeleteFailed",
"Failed to delete %s %q: %v", typeName(r.ChildType), extra.GetName(), err)
}
return nilCT, err
}
pc.Recorder.Eventf(resource, corev1.EventTypeNormal, "Deleted",
Expand Down
9 changes: 6 additions & 3 deletions reconcilers/finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package reconcilers

import (
"context"
"errors"
"fmt"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -142,9 +143,11 @@ func ensureFinalizer(ctx context.Context, resource client.Object, finalizer stri

patch := client.MergeFromWithOptions(current, client.MergeFromWithOptimisticLock{})
if err := config.Patch(ctx, desired, patch); err != nil {
log.Error(err, "unable to patch finalizers", "finalizer", finalizer)
config.Recorder.Eventf(current, corev1.EventTypeWarning, "FinalizerPatchFailed",
"Failed to patch finalizer %q: %s", finalizer, err)
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to patch finalizers", "finalizer", finalizer)
config.Recorder.Eventf(current, corev1.EventTypeWarning, "FinalizerPatchFailed",
"Failed to patch finalizer %q: %s", finalizer, err)
}
return err
}
config.Recorder.Eventf(current, corev1.EventTypeNormal, "FinalizerPatched",
Expand Down
21 changes: 17 additions & 4 deletions reconcilers/reconcilers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,23 @@ type SubReconciler[Type client.Object] interface {
}

var (
// HaltSubReconcilers is an error that instructs SubReconcilers to stop processing the request,
// while the root reconciler proceeds as if there was no error. HaltSubReconcilers may be
// ErrQuiet are not logged or recorded as events within reconciler-runtime.
// They are propagated as errors unless otherwise defined.
//
// Test for ErrQuiet with errors.Is(err, ErrQuiet)
ErrQuiet = fmt.Errorf("quiet errors are returned as errors, but not logged or recorded")

// ErrHaltSubReconcilers is an error that instructs SubReconcilers to stop processing the request,
// while the root reconciler proceeds as if there was no error. ErrHaltSubReconcilers may be
// wrapped by other errors.
//
// ErrHaltSubReconcilers wraps ErrQuiet to suppress spurious logs.
//
// See documentation for the specific SubReconciler caller to see how they handle this case.
HaltSubReconcilers = errors.New("stop processing SubReconcilers, without returning an error")
ErrHaltSubReconcilers = fmt.Errorf("stop processing SubReconcilers, without returning an error: %w", ErrQuiet)

// Deprecated HaltSubReconcilers use ErrHaltSubReconcilers instead
HaltSubReconcilers = ErrHaltSubReconcilers
)

const requestStashKey StashKey = "reconciler-runtime:request"
Expand Down Expand Up @@ -211,7 +222,9 @@ func EnqueueTracked(ctx context.Context) handler.EventHandler {

items, err := c.Tracker.GetObservers(obj)
if err != nil {
log.Error(err, "unable to get tracked requests")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to get tracked requests")
}
return nil
}

Expand Down
24 changes: 15 additions & 9 deletions reconcilers/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type ResourceReconciler[Type client.Object] struct {
// Reconciler is called for each reconciler request with the resource being reconciled.
// Typically, Reconciler is a Sequence of multiple SubReconcilers.
//
// When HaltSubReconcilers is returned as an error, execution continues as if no error was
// When ErrHaltSubReconcilers is returned as an error, execution continues as if no error was
// returned.
Reconciler SubReconciler[Type]

Expand Down Expand Up @@ -206,7 +206,9 @@ func (r *ResourceReconciler[T]) Reconcile(ctx context.Context, req Request) (Res
// on deleted requests.
return Result{}, nil
}
log.Error(err, "unable to fetch resource")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to fetch resource")
}
return Result{}, err
}
resource := originalResource.DeepCopyObject().(T)
Expand All @@ -233,9 +235,11 @@ func (r *ResourceReconciler[T]) Reconcile(ctx context.Context, req Request) (Res
// patch status
log.Info("patching status", "diff", cmp.Diff(originalResourceStatus, resourceStatus))
if patchErr := c.Status().Patch(ctx, resource, client.MergeFrom(originalResource)); patchErr != nil {
log.Error(patchErr, "unable to patch status")
c.Recorder.Eventf(resource, corev1.EventTypeWarning, "StatusPatchFailed",
"Failed to patch status: %v", patchErr)
if !errors.Is(patchErr, ErrQuiet) {
log.Error(patchErr, "unable to patch status")
c.Recorder.Eventf(resource, corev1.EventTypeWarning, "StatusPatchFailed",
"Failed to patch status: %v", patchErr)
}
return Result{}, patchErr
}
c.Recorder.Eventf(resource, corev1.EventTypeNormal, "StatusPatched",
Expand All @@ -244,9 +248,11 @@ func (r *ResourceReconciler[T]) Reconcile(ctx context.Context, req Request) (Res
// update status
log.Info("updating status", "diff", cmp.Diff(originalResourceStatus, resourceStatus))
if updateErr := c.Status().Update(ctx, resource); updateErr != nil {
log.Error(updateErr, "unable to update status")
c.Recorder.Eventf(resource, corev1.EventTypeWarning, "StatusUpdateFailed",
"Failed to update status: %v", updateErr)
if !errors.Is(updateErr, ErrQuiet) {
log.Error(updateErr, "unable to update status")
c.Recorder.Eventf(resource, corev1.EventTypeWarning, "StatusUpdateFailed",
"Failed to update status: %v", updateErr)
}
return Result{}, updateErr
}
c.Recorder.Eventf(resource, corev1.EventTypeNormal, "StatusUpdated",
Expand All @@ -265,7 +271,7 @@ func (r *ResourceReconciler[T]) reconcile(ctx context.Context, resource T) (Resu
}

result, err := r.Reconciler.Reconcile(ctx, resource)
if err != nil && !errors.Is(err, HaltSubReconcilers) {
if err != nil && !errors.Is(err, ErrHaltSubReconcilers) {
return Result{}, err
}

Expand Down
6 changes: 3 additions & 3 deletions reconcilers/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ func TestResourceReconciler_Duck(t *testing.T) {
resource.Status.Fields = map[string]string{
"want": "this to run",
}
return reconcilers.HaltSubReconcilers
return reconcilers.ErrHaltSubReconcilers
},
},
&reconcilers.SyncReconciler[*resources.TestDuck]{
Expand Down Expand Up @@ -642,7 +642,7 @@ func TestResourceReconciler_Duck(t *testing.T) {
resource.Status.Fields = map[string]string{
"want": "this to run",
}
return reconcilers.Result{Requeue: true}, reconcilers.HaltSubReconcilers
return reconcilers.Result{Requeue: true}, reconcilers.ErrHaltSubReconcilers
},
},
&reconcilers.SyncReconciler[*resources.TestDuck]{
Expand Down Expand Up @@ -1104,7 +1104,7 @@ func TestResourceReconciler(t *testing.T) {
resource.Status.Fields = map[string]string{
"want": "this to run",
}
return reconcilers.Result{Requeue: true}, reconcilers.HaltSubReconcilers
return reconcilers.Result{Requeue: true}, reconcilers.ErrHaltSubReconcilers
},
},
&reconcilers.SyncReconciler[*resources.TestResource]{
Expand Down
28 changes: 18 additions & 10 deletions reconcilers/resourcemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ func (r *ResourceManager[T]) Manage(ctx context.Context, resource client.Object,
if !actual.GetCreationTimestamp().Time.IsZero() && actual.GetDeletionTimestamp() == nil {
log.Info("deleting unwanted resource", "resource", namespaceName(actual))
if err := c.Delete(ctx, actual); err != nil {
log.Error(err, "unable to delete unwanted resource", "resource", namespaceName(actual))
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "DeleteFailed",
"Failed to delete %s %q: %v", typeName(actual), actual.GetName(), err)
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to delete unwanted resource", "resource", namespaceName(actual))
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "DeleteFailed",
"Failed to delete %s %q: %v", typeName(actual), actual.GetName(), err)
}
return nilT, err
}
pc.Recorder.Eventf(resource, corev1.EventTypeNormal, "Deleted",
Expand All @@ -151,9 +153,11 @@ func (r *ResourceManager[T]) Manage(ctx context.Context, resource client.Object,
if internal.IsNil(actual) || actual.GetCreationTimestamp().Time.IsZero() {
log.Info("creating resource", "resource", r.sanitize(desired))
if err := c.Create(ctx, desired); err != nil {
log.Error(err, "unable to create resource", "resource", namespaceName(desired))
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "CreationFailed",
"Failed to create %s %q: %v", typeName(desired), desired.GetName(), err)
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to create resource", "resource", namespaceName(desired))
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "CreationFailed",
"Failed to create %s %q: %v", typeName(desired), desired.GetName(), err)
}
return nilT, err
}
if r.TrackDesired {
Expand Down Expand Up @@ -200,9 +204,11 @@ func (r *ResourceManager[T]) Manage(ctx context.Context, resource client.Object,
}
log.Info("updating resource", "diff", cmp.Diff(r.sanitize(actual), r.sanitize(current)))
if err := c.Update(ctx, current); err != nil {
log.Error(err, "unable to update resource", "resource", namespaceName(current))
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed",
"Failed to update %s %q: %v", typeName(current), current.GetName(), err)
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to update resource", "resource", namespaceName(current))
pc.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed",
"Failed to update %s %q: %v", typeName(current), current.GetName(), err)
}
return nilT, err
}

Expand All @@ -211,7 +217,9 @@ func (r *ResourceManager[T]) Manage(ctx context.Context, resource client.Object,
r.MergeBeforeUpdate(base, desired)
patch, err := NewPatch(base, current)
if err != nil {
log.Error(err, "unable to generate mutation patch", "snapshot", r.sanitize(desired), "base", r.sanitize(base))
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to generate mutation patch", "snapshot", r.sanitize(desired), "base", r.sanitize(base))
}
} else {
r.mutationCache.Set(current.GetUID(), patch, 1*time.Hour)
}
Expand Down
2 changes: 1 addition & 1 deletion reconcilers/sequence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestSequence(t *testing.T) {
},
}, &reconcilers.SyncReconciler[*resources.TestResource]{
SyncWithResult: func(ctx context.Context, resource *resources.TestResource) (reconcilers.Result, error) {
return reconcilers.Result{RequeueAfter: 2 * time.Minute}, reconcilers.HaltSubReconcilers
return reconcilers.Result{RequeueAfter: 2 * time.Minute}, reconcilers.ErrHaltSubReconcilers
},
},
}
Expand Down
9 changes: 7 additions & 2 deletions reconcilers/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package reconcilers

import (
"context"
"errors"
"fmt"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -118,7 +119,9 @@ func (r *SyncReconciler[T]) Reconcile(ctx context.Context, resource T) (Result,
syncResult, err := r.sync(ctx, resource)
result = AggregateResults(result, syncResult)
if err != nil {
log.Error(err, "unable to sync")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to sync")
}
return result, err
}
}
Expand All @@ -127,7 +130,9 @@ func (r *SyncReconciler[T]) Reconcile(ctx context.Context, resource T) (Result,
finalizeResult, err := r.finalize(ctx, resource)
result = AggregateResults(result, finalizeResult)
if err != nil {
log.Error(err, "unable to finalize")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "unable to finalize")
}
return result, err
}
}
Expand Down
4 changes: 2 additions & 2 deletions reconcilers/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestSyncReconciler(t *testing.T) {
"SubReconciler": func(t *testing.T, c reconcilers.Config) reconcilers.SubReconciler[*resources.TestResource] {
return &reconcilers.SyncReconciler[*resources.TestResource]{
SyncWithResult: func(ctx context.Context, resource *resources.TestResource) (reconcilers.Result, error) {
return reconcilers.Result{Requeue: true}, reconcilers.HaltSubReconcilers
return reconcilers.Result{Requeue: true}, reconcilers.ErrHaltSubReconcilers
},
}
},
Expand Down Expand Up @@ -148,7 +148,7 @@ func TestSyncReconciler(t *testing.T) {
return reconcilers.Result{RequeueAfter: 2 * time.Hour}, nil
},
FinalizeWithResult: func(ctx context.Context, resource *resources.TestResource) (reconcilers.Result, error) {
return reconcilers.Result{RequeueAfter: 3 * time.Hour}, reconcilers.HaltSubReconcilers
return reconcilers.Result{RequeueAfter: 3 * time.Hour}, reconcilers.ErrHaltSubReconcilers
},
}
},
Expand Down
5 changes: 4 additions & 1 deletion reconcilers/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package reconcilers
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sync"
Expand Down Expand Up @@ -139,7 +140,9 @@ func (r *AdmissionWebhookAdapter[T]) Handle(ctx context.Context, req admission.R
})

if err := r.reconcile(ctx, req, resp); err != nil {
log.Error(err, "reconcile error")
if !errors.Is(err, ErrQuiet) {
log.Error(err, "reconcile error")
}
resp.Allowed = false
if resp.Result == nil {
resp.Result = &metav1.Status{
Expand Down

0 comments on commit 0a566ab

Please sign in to comment.