package reconcile import ( "context" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "edge-infra.dev/pkg/k8s/meta/status" "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/k8s/runtime/controller/reconcile/recerr" ) // ResultProcessor processes the results of reconciliation (the object, result // and error). Any errors during processing need not result in the // reconciliation failure. The errors can be recorded as logs and events. type ResultProcessor func(context.Context, record.EventRecorder, conditions.Setter, Result, error) // RecordReconcileReq is a ResultProcessor that checks the reconcile // annotation value and sets it in the object status as // status.lastHandledReconcileAt. func RecordReconcileReq(_ context.Context, _ record.EventRecorder, obj conditions.Setter, _ Result, _ error) { if v, ok := GetAnnotation(obj.GetAnnotations()); ok { _ = SetStatusLastHandledReconcileAt(obj, v) } } // RecordResult is a ResultProcessor that handles logging and event recording for // a reconciled object based on the reconcile error and the object's readiness. func RecordResult(ctx context.Context, recorder record.EventRecorder, obj conditions.Setter, _ Result, err error) { reason := conditions.GetReason(obj, status.ReadyCondition) switch e := err.(type) { case *recerr.Generic: logError(ctx, e.Event, e, e.Error()) recordEvent(recorder, obj, e.Event, e.Notification, err.Error(), e.Reason) case *recerr.Wait: logError(ctx, e.Event, e, "reconciliation waiting", "reason", e.Err, "duration", e.Config.RequeueAfter, ) recordEvent(recorder, obj, e.Event, e.Notification, err.Error(), e.Reason) case *recerr.Stalled: logError(ctx, e.Event, e, "reconciliation stalled") recordEvent(recorder, obj, e.Event, e.Notification, err.Error(), e.Reason) case error: logError(ctx, corev1.EventTypeWarning, e, e.Error()) if reason == "" { reason = "ReconcileFailed" } recordEvent(recorder, obj, corev1.EventTypeWarning, true, err.Error(), reason) case nil: if !conditions.IsReady(obj) { return } // The error is 'nil' and the object is ready, so a 'Normal' event is // recorded to report successful reconciliation of the object. message := conditions.GetMessage(obj, status.ReadyCondition) recordEvent(recorder, obj, corev1.EventTypeNormal, true, message, reason) } } // logError logs error based on the passed error configurations. func logError(ctx context.Context, eventType string, err error, msg string, keysAndValues ...interface{}) { log := ctrl.LoggerFrom(ctx) switch eventType { case corev1.EventTypeNormal, recerr.EventTypeNone: log.Info(msg, keysAndValues...) case corev1.EventTypeWarning: log.Error(err, msg, keysAndValues...) } } // recordEvent records events based on the passed error configurations. func recordEvent(recorder record.EventRecorder, obj client.Object, eventType string, notification bool, message, reason string) { if recorder == nil || eventType == recerr.EventTypeNone { return } switch eventType { case corev1.EventTypeNormal: if notification { recorder.Eventf(obj, corev1.EventTypeNormal, reason, message) } return case corev1.EventTypeWarning: recorder.Eventf(obj, corev1.EventTypeWarning, reason, message) return } }