@@ -29,9 +29,11 @@ import (
2929 storagev1 "k8s.io/api/storage/v1"
3030 "k8s.io/apimachinery/pkg/api/equality"
3131 "k8s.io/apimachinery/pkg/api/errors"
32+ meta "k8s.io/apimachinery/pkg/api/meta"
3233 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3334 "k8s.io/apimachinery/pkg/types"
3435 "k8s.io/client-go/kubernetes/scheme"
36+ "k8s.io/client-go/tools/record"
3537 "k8s.io/utils/ptr"
3638 ctrl "sigs.k8s.io/controller-runtime"
3739 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -51,7 +53,9 @@ import (
5153// +kubebuilder:rbac:groups=storage.k8s.io,resources=csidrivers,verbs=get;list;watch;create;update;patch;delete
5254// +kubebuilder:rbac:groups=security.openshift.io,resources=securitycontextconstraints,verbs=get;list;watch;create;update;patch;delete
5355// +kubebuilder:rbac:groups=bpfman.io,resources=configs,verbs=get;list;watch;update;patch;delete
56+ // +kubebuilder:rbac:groups=bpfman.io,resources=configs/status,verbs=get;update;patch
5457// +kubebuilder:rbac:groups=bpfman.io,resources=configs/finalizers,verbs=update
58+ // +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
5559
5660type BpfmanConfigReconciler struct {
5761 ClusterApplicationReconciler
@@ -60,6 +64,7 @@ type BpfmanConfigReconciler struct {
6064 CsiDriverDS string
6165 RestrictedSCC string
6266 IsOpenshift bool
67+ Recorder record.EventRecorder
6368}
6469
6570// SetupWithManager sets up the controller with the Manager.
@@ -104,6 +109,14 @@ func (r *BpfmanConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request
104109 return ctrl.Result {}, err
105110 }
106111
112+ // If we haven't added any conditions, yet, set them to unknown.
113+ if len (bpfmanConfig .Status .Conditions ) == 0 {
114+ r .Logger .Info ("Adding initial status conditions" , "name" , bpfmanConfig .Name )
115+ if err := r .setStatusConditions (ctx , bpfmanConfig ); err != nil {
116+ return ctrl.Result {}, err
117+ }
118+ }
119+
107120 // Check if Config is being deleted first to prevent race
108121 // conditions.
109122 if ! bpfmanConfig .DeletionTimestamp .IsZero () {
@@ -119,7 +132,7 @@ func (r *BpfmanConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request
119132 r .Logger .Error (err , "Failed to add finalizer to Config" )
120133 return ctrl.Result {}, err
121134 }
122- return ctrl.Result {}, nil
135+ return ctrl.Result {}, r . setStatusConditions ( ctx , bpfmanConfig )
123136 }
124137
125138 // Normal reconciliation - safe to create/update resources.
@@ -143,7 +156,7 @@ func (r *BpfmanConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request
143156 return ctrl.Result {}, err
144157 }
145158
146- return ctrl.Result {}, nil
159+ return ctrl.Result {}, r . setStatusConditions ( ctx , bpfmanConfig )
147160}
148161
149162func (r * BpfmanConfigReconciler ) reconcileCM (ctx context.Context , bpfmanConfig * v1alpha1.Config ) error {
@@ -223,6 +236,158 @@ func (r *BpfmanConfigReconciler) reconcileMetricsProxyDS(ctx context.Context, bp
223236 })
224237}
225238
239+ // setStatusConditions checks the status of all Config components (ConfigMap, DaemonSets, CSIDriver, SCC)
240+ // and updates the Config's status.componentStatuses and status.conditions accordingly.
241+ // It also emits Kubernetes events for status changes and re-fetches the Config object after updating.
242+ func (r * BpfmanConfigReconciler ) setStatusConditions (ctx context.Context , config * v1alpha1.Config ) error {
243+ if r == nil {
244+ return fmt .Errorf ("object BpfmanConfigReconciler r is nil" )
245+ }
246+ if config == nil {
247+ return fmt .Errorf ("object Config config is nil" )
248+ }
249+ if r .Recorder == nil {
250+ return fmt .Errorf ("object Recorder is nil" )
251+ }
252+
253+ // Check each resource and set appropriate status.
254+ statuses := v1alpha1.ConfigComponentStatuses {}
255+
256+ cm := & corev1.ConfigMap {}
257+ if err := r .Get (ctx , types.NamespacedName {
258+ Name : internal .BpfmanCmName , Namespace : config .Spec .Namespace }, cm ); err != nil {
259+ if errors .IsNotFound (err ) {
260+ statuses .ConfigMap = ptr .To (v1alpha1 .ConfigStatusUnknown )
261+ } else {
262+ return err
263+ }
264+ } else {
265+ statuses .ConfigMap = ptr .To (v1alpha1 .ConfigStatusReady )
266+ }
267+
268+ ds := & appsv1.DaemonSet {}
269+ if err := r .Get (ctx , types.NamespacedName {
270+ Name : internal .BpfmanDsName , Namespace : config .Spec .Namespace }, ds ); err != nil {
271+ if errors .IsNotFound (err ) {
272+ statuses .DaemonSet = ptr .To (v1alpha1 .ConfigStatusUnknown )
273+ } else {
274+ return err
275+ }
276+ } else {
277+ if ds .Status .NumberReady == ds .Status .DesiredNumberScheduled && ds .Status .DesiredNumberScheduled > 0 {
278+ statuses .DaemonSet = ptr .To (v1alpha1 .ConfigStatusReady )
279+ } else {
280+ statuses .DaemonSet = ptr .To (v1alpha1 .ConfigStatusProgressing )
281+ }
282+ }
283+
284+ metricsDS := & appsv1.DaemonSet {}
285+ if err := r .Get (ctx , types.NamespacedName {
286+ Name : internal .BpfmanMetricsProxyDsName , Namespace : config .Spec .Namespace }, metricsDS ); err != nil {
287+ if errors .IsNotFound (err ) {
288+ statuses .MetricsProxyDaemonSet = ptr .To (v1alpha1 .ConfigStatusUnknown )
289+ } else {
290+ return err
291+ }
292+ } else {
293+ if metricsDS .Status .NumberReady == metricsDS .Status .DesiredNumberScheduled &&
294+ metricsDS .Status .DesiredNumberScheduled > 0 {
295+ statuses .MetricsProxyDaemonSet = ptr .To (v1alpha1 .ConfigStatusReady )
296+ } else {
297+ statuses .MetricsProxyDaemonSet = ptr .To (v1alpha1 .ConfigStatusProgressing )
298+ }
299+ }
300+
301+ csiDriver := & storagev1.CSIDriver {}
302+ if err := r .Get (ctx , types.NamespacedName {Name : internal .BpfmanCsiDriverName }, csiDriver ); err != nil {
303+ if errors .IsNotFound (err ) {
304+ statuses .CsiDriver = ptr .To (v1alpha1 .ConfigStatusUnknown )
305+ } else {
306+ return err
307+ }
308+ } else {
309+ statuses .CsiDriver = ptr .To (v1alpha1 .ConfigStatusReady )
310+ }
311+
312+ if r .IsOpenshift {
313+ scc := & osv1.SecurityContextConstraints {}
314+ if err := r .Get (ctx , types.NamespacedName {Name : internal .BpfmanRestrictedSccName }, scc ); err != nil {
315+ if errors .IsNotFound (err ) {
316+ statuses .Scc = ptr .To (v1alpha1 .ConfigStatusUnknown )
317+ } else {
318+ return err
319+ }
320+ } else {
321+ statuses .Scc = ptr .To (v1alpha1 .ConfigStatusReady )
322+ }
323+ }
324+
325+ // Set component statuses, first.
326+ config .Status .ComponentStatuses = & statuses
327+
328+ // Set conditions, next.
329+ switch {
330+ case statuses .ConfigMap != nil && * statuses .ConfigMap == v1alpha1 .ConfigStatusProgressing ||
331+ statuses .DaemonSet != nil && * statuses .DaemonSet == v1alpha1 .ConfigStatusProgressing ||
332+ statuses .MetricsProxyDaemonSet != nil && * statuses .MetricsProxyDaemonSet == v1alpha1 .ConfigStatusProgressing ||
333+ statuses .CsiDriver != nil && * statuses .CsiDriver == v1alpha1 .ConfigStatusProgressing ||
334+ (r .IsOpenshift && statuses .Scc != nil && * statuses .Scc == v1alpha1 .ConfigStatusProgressing ):
335+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
336+ Type : internal .ConfigConditionProgressing ,
337+ Status : metav1 .ConditionTrue ,
338+ Reason : internal .ConfigReasonProgressing ,
339+ Message : internal .ConfigMessageProgressing ,
340+ })
341+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
342+ Type : internal .ConfigConditionAvailable ,
343+ Status : metav1 .ConditionFalse ,
344+ Reason : internal .ConfigReasonProgressing ,
345+ Message : internal .ConfigMessageProgressing ,
346+ })
347+ r .Recorder .Event (config , "Normal" , internal .ConfigReasonProgressing , internal .ConfigMessageProgressing )
348+ case statuses .ConfigMap != nil && * statuses .ConfigMap == v1alpha1 .ConfigStatusReady &&
349+ statuses .DaemonSet != nil && * statuses .DaemonSet == v1alpha1 .ConfigStatusReady &&
350+ statuses .MetricsProxyDaemonSet != nil && * statuses .MetricsProxyDaemonSet == v1alpha1 .ConfigStatusReady &&
351+ statuses .CsiDriver != nil && * statuses .CsiDriver == v1alpha1 .ConfigStatusReady &&
352+ (! r .IsOpenshift || statuses .Scc != nil && * statuses .Scc == v1alpha1 .ConfigStatusReady ):
353+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
354+ Type : internal .ConfigConditionProgressing ,
355+ Status : metav1 .ConditionFalse ,
356+ Reason : internal .ConfigReasonAvailable ,
357+ Message : internal .ConfigMessageAvailable ,
358+ })
359+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
360+ Type : internal .ConfigConditionAvailable ,
361+ Status : metav1 .ConditionTrue ,
362+ Reason : internal .ConfigReasonAvailable ,
363+ Message : internal .ConfigMessageAvailable ,
364+ })
365+ r .Recorder .Event (config , "Normal" , internal .ConfigReasonAvailable , internal .ConfigMessageAvailable )
366+ default :
367+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
368+ Type : internal .ConfigConditionProgressing ,
369+ Status : metav1 .ConditionUnknown ,
370+ Reason : internal .ConfigReasonUnknown ,
371+ Message : internal .ConfigMessageUnknown ,
372+ })
373+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
374+ Type : internal .ConfigConditionAvailable ,
375+ Status : metav1 .ConditionUnknown ,
376+ Reason : internal .ConfigReasonUnknown ,
377+ Message : internal .ConfigMessageUnknown ,
378+ })
379+ r .Recorder .Event (config , "Normal" , internal .ConfigReasonUnknown , internal .ConfigMessageUnknown )
380+ }
381+
382+ // Update the status.
383+ if err := r .Status ().Update (ctx , config ); err != nil {
384+ return fmt .Errorf ("cannot update status for config %q, err: %w" , config .Name , err )
385+ }
386+
387+ // Update the object again (config is a pointer).
388+ return r .Get (ctx , types.NamespacedName {Name : config .Name }, config )
389+ }
390+
226391// resourcePredicate creates a predicate that filters events based on resource name.
227392// Only processes events for resources matching the specified resourceName.
228393func resourcePredicate (resourceName string ) predicate.Funcs {
0 commit comments