@@ -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.
@@ -106,6 +111,14 @@ func (r *BpfmanConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request
106111 return ctrl.Result {}, err
107112 }
108113
114+ // If we haven't added any conditions, yet, set them to unknown.
115+ if len (bpfmanConfig .Status .Conditions ) == 0 {
116+ r .Logger .Info ("Adding initial status conditions" , "name" , bpfmanConfig .Name )
117+ if err := r .setStatusConditions (ctx , bpfmanConfig ); err != nil {
118+ return ctrl.Result {}, err
119+ }
120+ }
121+
109122 // Check if Config is being deleted first to prevent race
110123 // conditions.
111124 if ! bpfmanConfig .DeletionTimestamp .IsZero () {
@@ -121,7 +134,7 @@ func (r *BpfmanConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request
121134 r .Logger .Error (err , "Failed to add finalizer to Config" )
122135 return ctrl.Result {}, err
123136 }
124- return ctrl.Result {}, nil
137+ return ctrl.Result {}, r . setStatusConditions ( ctx , bpfmanConfig )
125138 }
126139
127140 // Normal reconciliation - safe to create/update resources.
@@ -145,7 +158,7 @@ func (r *BpfmanConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request
145158 return ctrl.Result {}, err
146159 }
147160
148- return ctrl.Result {}, nil
161+ return ctrl.Result {}, r . setStatusConditions ( ctx , bpfmanConfig )
149162}
150163
151164func (r * BpfmanConfigReconciler ) reconcileCM (ctx context.Context , bpfmanConfig * v1alpha1.Config ) error {
@@ -225,6 +238,158 @@ func (r *BpfmanConfigReconciler) reconcileMetricsProxyDS(ctx context.Context, bp
225238 })
226239}
227240
241+ // setStatusConditions checks the status of all Config components (ConfigMap, DaemonSets, CSIDriver, SCC)
242+ // and updates the Config's status.componentStatuses and status.conditions accordingly.
243+ // It also emits Kubernetes events for status changes and re-fetches the Config object after updating.
244+ func (r * BpfmanConfigReconciler ) setStatusConditions (ctx context.Context , config * v1alpha1.Config ) error {
245+ if r == nil {
246+ return fmt .Errorf ("object BpfmanConfigReconciler r is nil" )
247+ }
248+ if config == nil {
249+ return fmt .Errorf ("object Config config is nil" )
250+ }
251+ if r .Recorder == nil {
252+ return fmt .Errorf ("object Recorder is nil" )
253+ }
254+
255+ // Check each resource and set appropriate status.
256+ statuses := v1alpha1.ConfigComponentStatuses {}
257+
258+ cm := & corev1.ConfigMap {}
259+ if err := r .Get (ctx , types.NamespacedName {
260+ Name : internal .BpfmanCmName , Namespace : config .Spec .Namespace }, cm ); err != nil {
261+ if errors .IsNotFound (err ) {
262+ statuses .ConfigMap = ptr .To (v1alpha1 .ConfigStatusUnknown )
263+ } else {
264+ return err
265+ }
266+ } else {
267+ statuses .ConfigMap = ptr .To (v1alpha1 .ConfigStatusReady )
268+ }
269+
270+ ds := & appsv1.DaemonSet {}
271+ if err := r .Get (ctx , types.NamespacedName {
272+ Name : internal .BpfmanDsName , Namespace : config .Spec .Namespace }, ds ); err != nil {
273+ if errors .IsNotFound (err ) {
274+ statuses .DaemonSet = ptr .To (v1alpha1 .ConfigStatusUnknown )
275+ } else {
276+ return err
277+ }
278+ } else {
279+ if ds .Status .NumberReady == ds .Status .DesiredNumberScheduled && ds .Status .DesiredNumberScheduled > 0 {
280+ statuses .DaemonSet = ptr .To (v1alpha1 .ConfigStatusReady )
281+ } else {
282+ statuses .DaemonSet = ptr .To (v1alpha1 .ConfigStatusProgressing )
283+ }
284+ }
285+
286+ metricsDS := & appsv1.DaemonSet {}
287+ if err := r .Get (ctx , types.NamespacedName {
288+ Name : internal .BpfmanMetricsProxyDsName , Namespace : config .Spec .Namespace }, metricsDS ); err != nil {
289+ if errors .IsNotFound (err ) {
290+ statuses .MetricsProxyDaemonSet = ptr .To (v1alpha1 .ConfigStatusUnknown )
291+ } else {
292+ return err
293+ }
294+ } else {
295+ if metricsDS .Status .NumberReady == metricsDS .Status .DesiredNumberScheduled &&
296+ metricsDS .Status .DesiredNumberScheduled > 0 {
297+ statuses .MetricsProxyDaemonSet = ptr .To (v1alpha1 .ConfigStatusReady )
298+ } else {
299+ statuses .MetricsProxyDaemonSet = ptr .To (v1alpha1 .ConfigStatusProgressing )
300+ }
301+ }
302+
303+ csiDriver := & storagev1.CSIDriver {}
304+ if err := r .Get (ctx , types.NamespacedName {Name : internal .BpfmanCsiDriverName }, csiDriver ); err != nil {
305+ if errors .IsNotFound (err ) {
306+ statuses .CsiDriver = ptr .To (v1alpha1 .ConfigStatusUnknown )
307+ } else {
308+ return err
309+ }
310+ } else {
311+ statuses .CsiDriver = ptr .To (v1alpha1 .ConfigStatusReady )
312+ }
313+
314+ if r .IsOpenshift {
315+ scc := & osv1.SecurityContextConstraints {}
316+ if err := r .Get (ctx , types.NamespacedName {Name : internal .BpfmanRestrictedSccName }, scc ); err != nil {
317+ if errors .IsNotFound (err ) {
318+ statuses .Scc = ptr .To (v1alpha1 .ConfigStatusUnknown )
319+ } else {
320+ return err
321+ }
322+ } else {
323+ statuses .Scc = ptr .To (v1alpha1 .ConfigStatusReady )
324+ }
325+ }
326+
327+ // Set component statuses, first.
328+ config .Status .ComponentStatuses = & statuses
329+
330+ // Set conditions, next.
331+ switch {
332+ case statuses .ConfigMap != nil && * statuses .ConfigMap == v1alpha1 .ConfigStatusProgressing ||
333+ statuses .DaemonSet != nil && * statuses .DaemonSet == v1alpha1 .ConfigStatusProgressing ||
334+ statuses .MetricsProxyDaemonSet != nil && * statuses .MetricsProxyDaemonSet == v1alpha1 .ConfigStatusProgressing ||
335+ statuses .CsiDriver != nil && * statuses .CsiDriver == v1alpha1 .ConfigStatusProgressing ||
336+ (r .IsOpenshift && statuses .Scc != nil && * statuses .Scc == v1alpha1 .ConfigStatusProgressing ):
337+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
338+ Type : internal .ConfigConditionProgressing ,
339+ Status : metav1 .ConditionTrue ,
340+ Reason : internal .ConfigReasonProgressing ,
341+ Message : internal .ConfigMessageProgressing ,
342+ })
343+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
344+ Type : internal .ConfigConditionAvailable ,
345+ Status : metav1 .ConditionFalse ,
346+ Reason : internal .ConfigReasonProgressing ,
347+ Message : internal .ConfigMessageProgressing ,
348+ })
349+ r .Recorder .Event (config , "Normal" , internal .ConfigReasonProgressing , internal .ConfigMessageProgressing )
350+ case statuses .ConfigMap != nil && * statuses .ConfigMap == v1alpha1 .ConfigStatusReady &&
351+ statuses .DaemonSet != nil && * statuses .DaemonSet == v1alpha1 .ConfigStatusReady &&
352+ statuses .MetricsProxyDaemonSet != nil && * statuses .MetricsProxyDaemonSet == v1alpha1 .ConfigStatusReady &&
353+ statuses .CsiDriver != nil && * statuses .CsiDriver == v1alpha1 .ConfigStatusReady &&
354+ (! r .IsOpenshift || statuses .Scc != nil && * statuses .Scc == v1alpha1 .ConfigStatusReady ):
355+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
356+ Type : internal .ConfigConditionProgressing ,
357+ Status : metav1 .ConditionFalse ,
358+ Reason : internal .ConfigReasonAvailable ,
359+ Message : internal .ConfigMessageAvailable ,
360+ })
361+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
362+ Type : internal .ConfigConditionAvailable ,
363+ Status : metav1 .ConditionTrue ,
364+ Reason : internal .ConfigReasonAvailable ,
365+ Message : internal .ConfigMessageAvailable ,
366+ })
367+ r .Recorder .Event (config , "Normal" , internal .ConfigReasonAvailable , internal .ConfigMessageAvailable )
368+ default :
369+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
370+ Type : internal .ConfigConditionProgressing ,
371+ Status : metav1 .ConditionUnknown ,
372+ Reason : internal .ConfigReasonUnknown ,
373+ Message : internal .ConfigMessageUnknown ,
374+ })
375+ meta .SetStatusCondition (& config .Status .Conditions , metav1.Condition {
376+ Type : internal .ConfigConditionAvailable ,
377+ Status : metav1 .ConditionUnknown ,
378+ Reason : internal .ConfigReasonUnknown ,
379+ Message : internal .ConfigMessageUnknown ,
380+ })
381+ r .Recorder .Event (config , "Normal" , internal .ConfigReasonUnknown , internal .ConfigMessageUnknown )
382+ }
383+
384+ // Update the status.
385+ if err := r .Status ().Update (ctx , config ); err != nil {
386+ return fmt .Errorf ("cannot update status for config %q, err: %w" , config .Name , err )
387+ }
388+
389+ // Update the object again (config is a pointer).
390+ return r .Get (ctx , types.NamespacedName {Name : config .Name }, config )
391+ }
392+
228393// resourcePredicate creates a predicate that filters events based on resource name.
229394// Only processes events for resources matching the specified resourceName.
230395func resourcePredicate (resourceName string ) predicate.Funcs {
0 commit comments