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.
27111func 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