Skip to content

Commit 6c0e245

Browse files
committed
Consolidate EventsRecorder into APIRecorder and document annotation drop
The duplicate EventsRecorder type was identical to the updated APIRecorder; consolidate to a single type. Document clearly that the events.k8s.io API has no AnnotatedEventf equivalent, so annotations accumulated via WithAnnotations are stored but cannot be forwarded to the API server. Also fix the FilterFn godoc (returning true prevents recording), update the stale godoc URL to point at the new events package, and tighten tests to use a single mockKubeRecorder that satisfies events.EventRecorder. Signed-off-by: Ross Golder <ross@golder.org>
1 parent 6fb93ec commit 6c0e245

2 files changed

Lines changed: 58 additions & 115 deletions

File tree

pkg/event/event.go

Lines changed: 19 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import (
2727
// A Type of event.
2828
type Type string
2929

30-
// Event types. See below for valid types.
31-
// https://godoc.org/k8s.io/client-go/tools/record#EventRecorder
30+
// Event types.
31+
// https://pkg.go.dev/k8s.io/client-go/tools/events#EventRecorder
3232
const (
3333
TypeNormal Type = "Normal"
3434
TypeWarning Type = "Warning"
@@ -77,28 +77,26 @@ type Recorder interface {
7777
WithAnnotations(keysAndValues ...string) Recorder
7878
}
7979

80+
// FilterFn is a function used to filter events. Returning true prevents the
81+
// event from being recorded.
82+
type FilterFn func(obj runtime.Object, e Event) bool
83+
8084
// An APIRecorder records Kubernetes events to an API server using the
81-
// events.k8s.io API.
85+
// events.k8s.io/v1 API introduced in Kubernetes 1.19.
86+
//
87+
// Note: the events.EventRecorder interface does not support per-event
88+
// annotations (unlike the deprecated record.EventRecorder.AnnotatedEventf).
89+
// Annotations accumulated via WithAnnotations are stored but cannot be
90+
// forwarded to the API server. Callers that relied on annotation propagation
91+
// should encode that metadata into the event message instead.
8292
type APIRecorder struct {
8393
kube events.EventRecorder
84-
annotations map[string]string
94+
annotations map[string]string // stored but not forwarded; see type doc.
8595
filterFns []FilterFn
8696
}
8797

88-
// An EventsRecorder records Kubernetes events to an API server using the
89-
// new events.k8s.io API.
90-
type EventsRecorder struct {
91-
kube events.EventRecorder
92-
annotations map[string]string
93-
filterFns []FilterFn
94-
}
95-
96-
// FilterFn is a function used to filter events.
97-
// It should return true when events should not be sent.
98-
type FilterFn func(obj runtime.Object, e Event) bool
99-
10098
// NewAPIRecorder returns an APIRecorder that records Kubernetes events to an
101-
// APIServer using the supplied EventRecorder.
99+
// API server using the supplied EventRecorder.
102100
func NewAPIRecorder(r events.EventRecorder, fns ...FilterFn) *APIRecorder {
103101
return &APIRecorder{kube: r, annotations: map[string]string{}, filterFns: fns}
104102
}
@@ -115,7 +113,10 @@ func (r *APIRecorder) Event(obj runtime.Object, e Event) {
115113
}
116114

117115
// WithAnnotations returns a new *APIRecorder that includes the supplied
118-
// annotations with all recorded events.
116+
// annotations. Note: the events.k8s.io API does not support per-event
117+
// annotations, so accumulated annotations are stored but not propagated to
118+
// the API server. Encode annotation data in the event message if it must
119+
// appear in the emitted event.
119120
func (r *APIRecorder) WithAnnotations(keysAndValues ...string) Recorder {
120121
ar := NewAPIRecorder(r.kube, r.filterFns...)
121122
maps.Copy(ar.annotations, r.annotations)
@@ -125,34 +126,6 @@ func (r *APIRecorder) WithAnnotations(keysAndValues ...string) Recorder {
125126
return ar
126127
}
127128

128-
// NewEventsRecorder returns an EventsRecorder that records Kubernetes events to an
129-
// APIServer using the new events.k8s.io API.
130-
func NewEventsRecorder(r events.EventRecorder, fns ...FilterFn) *EventsRecorder {
131-
return &EventsRecorder{kube: r, annotations: map[string]string{}, filterFns: fns}
132-
}
133-
134-
// Event records the supplied event using the new events API.
135-
func (r *EventsRecorder) Event(obj runtime.Object, e Event) {
136-
for _, filter := range r.filterFns {
137-
if filter(obj, e) {
138-
return
139-
}
140-
}
141-
142-
r.kube.Eventf(obj, nil, string(e.Type), string(e.Reason), "", "%s", e.Message)
143-
}
144-
145-
// WithAnnotations returns a new *EventsRecorder that includes the supplied
146-
// annotations with all recorded events.
147-
func (r *EventsRecorder) WithAnnotations(keysAndValues ...string) Recorder {
148-
ar := NewEventsRecorder(r.kube, r.filterFns...)
149-
maps.Copy(ar.annotations, r.annotations)
150-
151-
sliceMap(keysAndValues, ar.annotations)
152-
153-
return ar
154-
}
155-
156129
func sliceMap(from []string, to map[string]string) {
157130
for i := 0; i+1 < len(from); i += 2 {
158131
k, v := from[i], from[i+1]

pkg/event/event_test.go

Lines changed: 39 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,30 @@ limitations under the License.
1717
package event
1818

1919
import (
20+
"fmt"
2021
"testing"
2122

2223
"github.com/google/go-cmp/cmp"
24+
"github.com/google/go-cmp/cmp/cmpopts"
2325
"k8s.io/apimachinery/pkg/runtime"
2426
"k8s.io/apimachinery/pkg/runtime/schema"
2527
)
2628

27-
type mockRecordRecorder struct {
29+
// mockKubeRecorder satisfies events.EventRecorder.
30+
type mockKubeRecorder struct {
2831
events []mockEvent
2932
}
3033

3134
type mockEvent struct {
3235
obj runtime.Object
33-
annots map[string]string
3436
typeStr string
3537
reason string
3638
msg string
3739
}
3840

39-
func (m *mockRecordRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
40-
m.events = append(m.events, mockEvent{obj: obj, typeStr: eventtype, reason: reason, msg: message})
41-
}
42-
43-
func (m *mockRecordRecorder) Eventf(obj runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
44-
m.events = append(m.events, mockEvent{obj: obj, typeStr: eventtype, reason: reason, msg: args[0].(string)})
45-
}
46-
47-
func (m *mockRecordRecorder) AnnotatedEventf(obj runtime.Object, annots map[string]string, typeStr, reason, msg string, args ...interface{}) {
48-
m.events = append(m.events, mockEvent{obj: obj, annots: annots, typeStr: typeStr, reason: reason, msg: args[0].(string)})
49-
}
50-
51-
type mockEventsRecorder struct {
52-
events []mockEvent
53-
}
54-
55-
func (m *mockEventsRecorder) Eventf(obj runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
56-
m.events = append(m.events, mockEvent{obj: obj, typeStr: eventtype, reason: reason, msg: args[0].(string)})
41+
func (m *mockKubeRecorder) Eventf(obj runtime.Object, _ runtime.Object, eventtype, reason, _, note string, args ...interface{}) {
42+
msg := fmt.Sprintf(note, args...)
43+
m.events = append(m.events, mockEvent{obj: obj, typeStr: eventtype, reason: reason, msg: msg})
5744
}
5845

5946
type mockObj struct{}
@@ -128,73 +115,56 @@ func TestSliceMap(t *testing.T) {
128115
}
129116
}
130117

131-
func TestAPIRecorderWithAnnotationsFilterFns(t *testing.T) {
132-
filterCalled := false
133-
filter := func(obj runtime.Object, e Event) bool {
134-
filterCalled = true
135-
return false
118+
func TestAPIRecorderEvent(t *testing.T) {
119+
mr := &mockKubeRecorder{}
120+
rec := NewAPIRecorder(mr)
121+
122+
rec.Event(&mockObj{}, Normal("testReason", "test message"))
123+
124+
want := mockEvent{typeStr: "Normal", reason: "testReason", msg: "test message"}
125+
if diff := cmp.Diff(want, mr.events[0], cmp.AllowUnexported(mockEvent{}), cmpopts.IgnoreFields(mockEvent{}, "obj")); diff != "" {
126+
t.Errorf("unexpected event: -want, +got:\n%s", diff)
136127
}
128+
}
137129

138-
mr := &mockRecordRecorder{}
130+
func TestAPIRecorderFilter(t *testing.T) {
131+
mr := &mockKubeRecorder{}
132+
filter := func(_ runtime.Object, _ Event) bool { return true }
139133
rec := NewAPIRecorder(mr, filter)
140-
_ = rec.WithAnnotations("key", "val")
141134

142-
rec.Event(&mockObj{}, Normal("test", "msg"))
135+
rec.Event(&mockObj{}, Normal("testReason", "test message"))
143136

144-
if !filterCalled {
145-
t.Error("filter function was not preserved after WithAnnotations")
137+
if diff := cmp.Diff(0, len(mr.events)); diff != "" {
138+
t.Errorf("expected no events, got %d: %s", len(mr.events), diff)
146139
}
147140
}
148141

149-
func TestEventsRecorderWithAnnotationsFilterFns(t *testing.T) {
142+
func TestAPIRecorderWithAnnotationsPreservesFilterFns(t *testing.T) {
150143
filterCalled := false
151-
filter := func(obj runtime.Object, e Event) bool {
144+
filter := func(_ runtime.Object, _ Event) bool {
152145
filterCalled = true
153146
return false
154147
}
155148

156-
mr := &mockEventsRecorder{}
157-
rec := NewEventsRecorder(mr, filter)
158-
_ = rec.WithAnnotations("key", "val")
159-
160-
rec.Event(&mockObj{}, Normal("test", "msg"))
161-
162-
if !filterCalled {
163-
t.Error("filter function was not preserved after WithAnnotations")
164-
}
165-
}
166-
167-
func TestEventsRecorderEvent(t *testing.T) {
168-
mr := &mockEventsRecorder{}
169-
rec := NewEventsRecorder(mr)
170-
171-
rec.Event(&mockObj{}, Normal("testReason", "test message"))
149+
mr := &mockKubeRecorder{}
150+
rec := NewAPIRecorder(mr, filter)
151+
derived := rec.WithAnnotations("key", "val")
172152

173-
if len(mr.events) != 1 {
174-
t.Fatalf("expected 1 event, got %d", len(mr.events))
175-
}
153+
derived.Event(&mockObj{}, Normal("test", "msg"))
176154

177-
if mr.events[0].reason != "testReason" {
178-
t.Errorf("expected reason 'testReason', got %q", mr.events[0].reason)
179-
}
180-
if mr.events[0].msg != "test message" {
181-
t.Errorf("expected message 'test message', got %q", mr.events[0].msg)
182-
}
183-
if mr.events[0].typeStr != "Normal" {
184-
t.Errorf("expected type 'Normal', got %q", mr.events[0].typeStr)
155+
if diff := cmp.Diff(true, filterCalled); diff != "" {
156+
t.Errorf("filter function was not preserved after WithAnnotations: %s", diff)
185157
}
186158
}
187159

188-
func TestEventsRecorderFilter(t *testing.T) {
189-
mr := &mockEventsRecorder{}
190-
filter := func(obj runtime.Object, e Event) bool {
191-
return true
192-
}
193-
rec := NewEventsRecorder(mr, filter)
194-
195-
rec.Event(&mockObj{}, Normal("testReason", "test message"))
160+
func TestAPIRecorderWithAnnotationsPreservesExistingAnnotations(t *testing.T) {
161+
mr := &mockKubeRecorder{}
162+
rec := NewAPIRecorder(mr)
163+
r1 := rec.WithAnnotations("k1", "v1").(*APIRecorder)
164+
r2 := r1.WithAnnotations("k2", "v2").(*APIRecorder)
196165

197-
if len(mr.events) != 0 {
198-
t.Errorf("expected event to be filtered, got %d events", len(mr.events))
166+
want := map[string]string{"k1": "v1", "k2": "v2"}
167+
if diff := cmp.Diff(want, r2.annotations); diff != "" {
168+
t.Errorf("annotations not preserved correctly: -want, +got:\n%s", diff)
199169
}
200170
}

0 commit comments

Comments
 (0)