From 56f611b68a5316f45af221670e9111637f64d622 Mon Sep 17 00:00:00 2001 From: Adrian Fernandez De La Torre Date: Mon, 30 Mar 2026 17:31:15 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Add=20GetAnnotatedEventRecorder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing AnnotatedEventf as a new interface AnnotatedEventRecorder under cluster, manager, and recorder provider --- pkg/cluster/cluster_test.go | 6 ++++ pkg/cluster/internal.go | 4 +++ pkg/internal/recorder/recorder.go | 30 ++++++++++++++++--- .../recorder/recorder_integration_test.go | 24 +++++++++++++-- pkg/internal/recorder/recorder_test.go | 9 ++++++ pkg/manager/internal.go | 4 +++ pkg/recorder/recorder.go | 3 ++ 7 files changed, 74 insertions(+), 6 deletions(-) diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index e076faf3e5..9ae1ba7a15 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -156,6 +156,12 @@ var _ = Describe("cluster.Cluster", func() { Expect(c.GetFieldIndexer()).To(Equal(cluster.cache)) }) + It("should provide a function to get the AnnotatedEventRecorder", func() { + c, err := New(cfg) + Expect(err).NotTo(HaveOccurred()) + Expect(c.GetAnnotatedEventRecorder("test")).NotTo(BeNil()) + }) + It("should provide a function to get the EventRecorder", func() { c, err := New(cfg) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/cluster/internal.go b/pkg/cluster/internal.go index 755f83b546..c132c7f5d7 100644 --- a/pkg/cluster/internal.go +++ b/pkg/cluster/internal.go @@ -92,6 +92,10 @@ func (c *cluster) GetEventRecorder(name string) events.EventRecorder { return c.recorderProvider.GetEventRecorder(name) } +func (c *cluster) GetAnnotatedEventRecorder(name string) events.AnnotatedEventRecorder { + return c.recorderProvider.GetAnnotatedEventRecorder(name) +} + func (c *cluster) GetRESTMapper() meta.RESTMapper { return c.mapper } diff --git a/pkg/internal/recorder/recorder.go b/pkg/internal/recorder/recorder.go index 4a1ccd7c0c..84a5c39ec6 100644 --- a/pkg/internal/recorder/recorder.go +++ b/pkg/internal/recorder/recorder.go @@ -169,21 +169,33 @@ func (p *Provider) GetEventRecorder(name string) events.EventRecorder { } } +// GetAnnotatedEventRecorder returns an annotated event recorder that broadcasts to this provider's +// broadcaster. All events will be associated with a component of the given name. +func (p *Provider) GetAnnotatedEventRecorder(name string) events.AnnotatedEventRecorder { + return &lazyRecorder{ + prov: p, + name: name, + } +} + // lazyRecorder is a recorder that doesn't actually instantiate any underlying // recorder until the first event is emitted. type lazyRecorder struct { prov *Provider name string - recOnce sync.Once - rec events.EventRecorder + recOnce sync.Once + eventRecorder events.EventRecorder + annotatedEventRecorder events.AnnotatedEventRecorder } // ensureRecording ensures that a concrete recorder is populated for this recorder. func (l *lazyRecorder) ensureRecording() { l.recOnce.Do(func() { _, broadcaster := l.prov.getBroadcaster() - l.rec = broadcaster.NewRecorder(l.prov.scheme, l.name) + rec := broadcaster.NewRecorder(l.prov.scheme, l.name) + l.eventRecorder = rec + l.annotatedEventRecorder = rec.(events.AnnotatedEventRecorder) }) } @@ -192,7 +204,17 @@ func (l *lazyRecorder) Eventf(regarding runtime.Object, related runtime.Object, l.prov.lock.RLock() if !l.prov.stopped { - l.rec.Eventf(regarding, related, eventtype, reason, action, note, args...) + l.eventRecorder.Eventf(regarding, related, eventtype, reason, action, note, args...) + } + l.prov.lock.RUnlock() +} + +func (l *lazyRecorder) AnnotatedEventf(regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...any) { + l.ensureRecording() + + l.prov.lock.RLock() + if !l.prov.stopped { + l.annotatedEventRecorder.AnnotatedEventf(regarding, related, annotations, eventtype, reason, action, note, args...) } l.prov.lock.RUnlock() } diff --git a/pkg/internal/recorder/recorder_integration_test.go b/pkg/internal/recorder/recorder_integration_test.go index 72c8335cf7..7505a2cb5d 100644 --- a/pkg/internal/recorder/recorder_integration_test.go +++ b/pkg/internal/recorder/recorder_integration_test.go @@ -45,15 +45,17 @@ var _ = Describe("recorder", func() { Expect(err).NotTo(HaveOccurred()) By("Creating the Controller") - deprecatedRecorder := cm.GetEventRecorder("test-deprecated-recorder") + deprecatedRecorder := cm.GetEventRecorderFor("test-deprecated-recorder") //nolint:staticcheck // testing deprecated API recorder := cm.GetEventRecorder("test-recorder") + annotatedRecorder := cm.GetAnnotatedEventRecorder("test-annotated-recorder") instance, err := controller.New("foo-controller", cm, controller.Options{ Reconciler: reconcile.Func( func(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { dp, err := clientset.AppsV1().Deployments(request.Namespace).Get(ctx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - deprecatedRecorder.Eventf(dp, nil, corev1.EventTypeNormal, "deprecated-test-reason", "deprecatedAction", "deprecated-test-msg") + deprecatedRecorder.Eventf(dp, corev1.EventTypeNormal, "deprecated-test-reason", "deprecated-test-msg") recorder.Eventf(dp, nil, corev1.EventTypeNormal, "test-reason", "test-action", "test-note") + annotatedRecorder.AnnotatedEventf(dp, nil, map[string]string{"key": "value"}, corev1.EventTypeNormal, "test-annotated-reason", "test-annotated-action", "test-annotated-note") return reconcile.Result{}, nil }), }) @@ -129,6 +131,24 @@ var _ = Describe("recorder", func() { Expect(evt.Reason).To(Equal("test-reason")) Expect(evt.Action).To(Equal("test-action")) Expect(evt.Note).To(Equal("test-note")) + + By("Validate annotated event is published as expected") + annotatedEvtWatcher, err := clientset.EventsV1().Events("default").Watch(ctx, + metav1.ListOptions{FieldSelector: fields.OneTermEqualSelector("reason", "test-annotated-reason").String()}) + Expect(err).NotTo(HaveOccurred()) + + resultEvent = <-annotatedEvtWatcher.ResultChan() + + Expect(resultEvent.Type).To(Equal(watch.Added)) + annotatedEvt, isEvent := resultEvent.Object.(*eventsv1.Event) + Expect(isEvent).To(BeTrue()) + + Expect(annotatedEvt.Regarding).To(Equal(*dpRef)) + Expect(annotatedEvt.Type).To(Equal(corev1.EventTypeNormal)) + Expect(annotatedEvt.Reason).To(Equal("test-annotated-reason")) + Expect(annotatedEvt.Action).To(Equal("test-annotated-action")) + Expect(annotatedEvt.Note).To(Equal("test-annotated-note")) + Expect(annotatedEvt.Annotations).To(HaveKeyWithValue("key", "value")) }) }) }) diff --git a/pkg/internal/recorder/recorder_test.go b/pkg/internal/recorder/recorder_test.go index e592a1e189..7239d7adc0 100644 --- a/pkg/internal/recorder/recorder_test.go +++ b/pkg/internal/recorder/recorder_test.go @@ -62,6 +62,15 @@ var _ = Describe("recorder.Provider", func() { Expect(recorder).NotTo(BeNil()) }) }) + Describe("GetAnnotatedEventRecorder", func() { + It("should return a recorder instance.", func() { + provider, err := recorder.NewProvider(cfg, httpClient, scheme.Scheme, logr.Discard(), makeBroadcaster()) + Expect(err).NotTo(HaveOccurred()) + + recorder := provider.GetAnnotatedEventRecorder("test") + Expect(recorder).NotTo(BeNil()) + }) + }) }) func makeBroadcaster() func() (record.EventBroadcaster, events.EventBroadcaster, bool) { diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index ceb1450d1b..c51c23f512 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -268,6 +268,10 @@ func (cm *controllerManager) GetEventRecorder(name string) events.EventRecorder return cm.cluster.GetEventRecorder(name) } +func (cm *controllerManager) GetAnnotatedEventRecorder(name string) events.AnnotatedEventRecorder { + return cm.cluster.GetAnnotatedEventRecorder(name) +} + func (cm *controllerManager) GetRESTMapper() meta.RESTMapper { return cm.cluster.GetRESTMapper() } diff --git a/pkg/recorder/recorder.go b/pkg/recorder/recorder.go index b34fecb525..6b54167026 100644 --- a/pkg/recorder/recorder.go +++ b/pkg/recorder/recorder.go @@ -33,4 +33,7 @@ type Provider interface { GetEventRecorderFor(name string) record.EventRecorder // GetEventRecorder returns a EventRecorder with given name. GetEventRecorder(name string) events.EventRecorder + // GetAnnotatedEventRecorder returns an AnnotatedEventRecorder that supports + // attaching annotations to events. + GetAnnotatedEventRecorder(name string) events.AnnotatedEventRecorder }