From d02b6a360dceb54c40a8bf131054315f30eb2e8d Mon Sep 17 00:00:00 2001 From: James Thompson Date: Thu, 9 Apr 2026 09:36:53 -0700 Subject: [PATCH] chore: migrate status controller to events.EventRecorder API The legacy k8s.io/client-go/tools/record.EventRecorder API is deprecated in controller-runtime v0.23 and slated for removal. Migrate the status controller's recorder field, NewController/NewGenericObjectController constructors, and tests to k8s.io/client-go/tools/events.EventRecorder. Event call sites translate Event(...) to Eventf(..., nil, type, reason, action, note, args...). Action strings are "Finalize" and "TransitionCondition". The new events.FakeRecorder produces the same " " channel format as the old record.FakeRecorder, so existing test assertions continue to pass unchanged. --- status/controller.go | 17 +++++++++-------- status/controller_test.go | 10 +++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/status/controller.go b/status/controller.go index 853a0e6..1814c23 100644 --- a/status/controller.go +++ b/status/controller.go @@ -20,7 +20,7 @@ import ( "github.com/samber/lo" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/tools/record" + "k8s.io/client-go/tools/events" controllerruntime "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -35,7 +35,7 @@ type Controller[T Object] struct { additionalMetricFields map[string]string additionalGaugeMetricFields map[string]string kubeClient client.Client - eventRecorder record.EventRecorder + eventRecorder events.EventRecorder observedConditions sync.Map // map[reconcile.Request]ConditionSet observedGaugeLabels sync.Map // map[reconcile.Request]map[string]string observedFinalizers sync.Map // map[reconcile.Request]Finalizer @@ -107,7 +107,7 @@ func WitMaxConcurrentReconciles(m int) func(*Option) { } } -func NewController[T Object](client client.Client, eventRecorder record.EventRecorder, opts ...option.Function[Option]) *Controller[T] { +func NewController[T Object](client client.Client, eventRecorder events.EventRecorder, opts ...option.Function[Option]) *Controller[T] { options := option.Resolve(opts...) obj := reflect.New(reflect.TypeOf(*new(T)).Elem()).Interface().(runtime.Object) obj.GetObjectKind().SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind()) @@ -166,7 +166,7 @@ type GenericObjectController[T client.Object] struct { *Controller[*UnstructuredAdapter[T]] } -func NewGenericObjectController[T client.Object](client client.Client, eventRecorder record.EventRecorder, opts ...option.Function[Option]) *GenericObjectController[T] { +func NewGenericObjectController[T client.Object](client client.Client, eventRecorder events.EventRecorder, opts ...option.Function[Option]) *GenericObjectController[T] { return &GenericObjectController[T]{ Controller: NewController[*UnstructuredAdapter[T]](client, eventRecorder, opts...), } @@ -236,7 +236,7 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o } if finalizers, ok := c.observedFinalizers.LoadAndDelete(req); ok { for _, finalizer := range finalizers.([]string) { - c.eventRecorder.Event(o, v1.EventTypeNormal, "Finalized", fmt.Sprintf("Finalized %s", finalizer)) + c.eventRecorder.Eventf(o, nil, v1.EventTypeNormal, "Finalized", "Finalize", "Finalized %s", finalizer) } } return reconcile.Result{}, nil @@ -248,7 +248,7 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o observedFinalizers, _ := c.observedFinalizers.Swap(req, o.GetFinalizers()) if observedFinalizers != nil { for _, finalizer := range lo.Without(observedFinalizers.([]string), o.GetFinalizers()...) { - c.eventRecorder.Event(o, v1.EventTypeNormal, "Finalized", fmt.Sprintf("Finalized %s", finalizer)) + c.eventRecorder.Eventf(o, nil, v1.EventTypeNormal, "Finalized", "Finalize", "Finalized %s", finalizer) } } @@ -347,13 +347,14 @@ func (c *Controller[T]) reconcile(ctx context.Context, req reconcile.Request, o pmetrics.LabelType: observedCondition.Type, MetricLabelConditionStatus: string(observedCondition.Status), }, c.toAdditionalMetricLabels(o)) - c.eventRecorder.Event(o, v1.EventTypeNormal, condition.Type, fmt.Sprintf("Status condition transitioned, Type: %s, Status: %s -> %s, Reason: %s%s", + c.eventRecorder.Eventf(o, nil, v1.EventTypeNormal, condition.Type, "TransitionCondition", + "Status condition transitioned, Type: %s, Status: %s -> %s, Reason: %s%s", condition.Type, observedCondition.Status, condition.Status, condition.Reason, lo.Ternary(condition.Message != "", fmt.Sprintf(", Message: %s", condition.Message), ""), - )) + ) } return reconcile.Result{RequeueAfter: time.Second * 10}, nil } diff --git a/status/controller_test.go b/status/controller_test.go index b7063dd..2a6a9d9 100644 --- a/status/controller_test.go +++ b/status/controller_test.go @@ -18,7 +18,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/tools/record" + "k8s.io/client-go/tools/events" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log" @@ -26,7 +26,7 @@ import ( ) var ctx context.Context -var recorder *record.FakeRecorder +var recorder *events.FakeRecorder var kubeClient client.Client var registry = metrics.Registry @@ -41,11 +41,11 @@ var _ = AfterEach(func() { var _ = Describe("Controller", func() { var ctx context.Context - var recorder *record.FakeRecorder + var recorder *events.FakeRecorder var controller *status.Controller[*test.CustomObject] var kubeClient client.Client BeforeEach(func() { - recorder = record.NewFakeRecorder(10) + recorder = events.NewFakeRecorder(10) kubeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).WithStatusSubresource(&test.CustomObject{}).Build() ctx = log.IntoContext(context.Background(), GinkgoLogr) controller = status.NewController[*test.CustomObject](kubeClient, recorder, status.EmitDeprecatedMetrics) @@ -807,7 +807,7 @@ var _ = Describe("Controller", func() { var _ = Describe("Generic Controller", func() { var genericController *status.GenericObjectController[*TestGenericObject] BeforeEach(func() { - recorder = record.NewFakeRecorder(10) + recorder = events.NewFakeRecorder(10) kubeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() ctx = log.IntoContext(context.Background(), GinkgoLogr) genericController = status.NewGenericObjectController[*TestGenericObject](kubeClient, recorder, status.EmitDeprecatedMetrics)