Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
4 changes: 4 additions & 0 deletions pkg/cluster/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
30 changes: 26 additions & 4 deletions pkg/internal/recorder/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}

Expand All @@ -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()
}
Expand Down
24 changes: 22 additions & 2 deletions pkg/internal/recorder/recorder_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}),
})
Expand Down Expand Up @@ -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"))
})
})
})
9 changes: 9 additions & 0 deletions pkg/internal/recorder/recorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/manager/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/recorder/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}