Skip to content

Commit 36cc1aa

Browse files
committed
Fix nil pointer dereference panic in event recorder
1 parent e3391c6 commit 36cc1aa

File tree

2 files changed

+400
-11
lines changed

2 files changed

+400
-11
lines changed

pkg/lib/event/event.go

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55

66
v1 "k8s.io/api/core/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/runtime"
79
kscheme "k8s.io/client-go/kubernetes/scheme"
810
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
911
"k8s.io/client-go/tools/record"
@@ -22,24 +24,99 @@ func init() {
2224
}
2325
}
2426

27+
// safeSpamKeyFunc builds a spam key from event fields with nil checks to prevent panics.
28+
// This protects against nil pointer dereferences when event.InvolvedObject fields are empty.
29+
func safeSpamKeyFunc(event *v1.Event) string {
30+
if event == nil {
31+
return "unknown/unknown/unknown/unknown"
32+
}
33+
34+
kind := event.InvolvedObject.Kind
35+
namespace := event.InvolvedObject.Namespace
36+
name := event.InvolvedObject.Name
37+
reason := event.Reason
38+
39+
// Provide defaults for empty fields to avoid issues
40+
if kind == "" {
41+
kind = "Unknown"
42+
}
43+
if name == "" {
44+
name = "unknown"
45+
}
46+
47+
return fmt.Sprintf("%s/%s/%s/%s", kind, namespace, name, reason)
48+
}
49+
50+
// SafeEventRecorder wraps record.EventRecorder with nil checks to prevent panics
51+
// when recording events for objects with nil or invalid metadata.
52+
type SafeEventRecorder struct {
53+
recorder record.EventRecorder
54+
}
55+
56+
// isValidObject checks if the object has valid metadata required for event recording.
57+
func isValidObject(object runtime.Object) bool {
58+
if object == nil {
59+
return false
60+
}
61+
62+
// Handle ObjectReference type (used for events with FieldPath)
63+
if ref, ok := object.(*v1.ObjectReference); ok {
64+
return ref.Name != ""
65+
}
66+
67+
// Check if object implements metav1.Object interface
68+
accessor, ok := object.(metav1.Object)
69+
if !ok {
70+
return false
71+
}
72+
73+
// Ensure the object has a valid name (required for event recording)
74+
if accessor.GetName() == "" {
75+
return false
76+
}
77+
78+
return true
79+
}
80+
81+
// Event records an event for the given object, with nil checks.
82+
func (s *SafeEventRecorder) Event(object runtime.Object, eventtype, reason, message string) {
83+
if !isValidObject(object) {
84+
klog.V(4).Infof("Skipping event recording: invalid object (nil or missing name), reason=%s, message=%s", reason, message)
85+
return
86+
}
87+
s.recorder.Event(object, eventtype, reason, message)
88+
}
89+
90+
// Eventf records a formatted event for the given object, with nil checks.
91+
func (s *SafeEventRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
92+
if !isValidObject(object) {
93+
klog.V(4).Infof("Skipping event recording: invalid object (nil or missing name), reason=%s, messageFmt=%s", reason, messageFmt)
94+
return
95+
}
96+
s.recorder.Eventf(object, eventtype, reason, messageFmt, args...)
97+
}
98+
99+
// AnnotatedEventf records a formatted event with annotations for the given object, with nil checks.
100+
func (s *SafeEventRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
101+
if !isValidObject(object) {
102+
klog.V(4).Infof("Skipping event recording: invalid object (nil or missing name), reason=%s, messageFmt=%s", reason, messageFmt)
103+
return
104+
}
105+
s.recorder.AnnotatedEventf(object, annotations, eventtype, reason, messageFmt, args...)
106+
}
107+
25108
// NewRecorder returns an EventRecorder type that can be
26109
// used to post Events to different object's lifecycles.
110+
// The returned recorder includes nil checks to prevent panics from invalid objects.
27111
func NewRecorder(event typedcorev1.EventInterface) (record.EventRecorder, error) {
28112
eventBroadcaster := record.NewBroadcasterWithCorrelatorOptions(record.CorrelatorOptions{
29-
BurstSize: 10,
30-
SpamKeyFunc: func(event *v1.Event) string {
31-
return fmt.Sprintf(
32-
"%s/%s/%s/%s",
33-
event.InvolvedObject.Kind,
34-
event.InvolvedObject.Namespace,
35-
event.InvolvedObject.Name,
36-
event.Reason,
37-
)
38-
},
113+
BurstSize: 10,
114+
SpamKeyFunc: safeSpamKeyFunc,
39115
})
40116
eventBroadcaster.StartLogging(klog.Infof)
41117
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: event})
42118
recorder := eventBroadcaster.NewRecorder(s, v1.EventSource{Component: component})
43119

44-
return recorder, nil
120+
// Wrap the recorder with SafeEventRecorder for nil protection
121+
return &SafeEventRecorder{recorder: recorder}, nil
45122
}

0 commit comments

Comments
 (0)