Skip to content

Commit 918f62d

Browse files
committed
Expand ownership check for profile bundle controller
The controller previously only watched ProfileBundle objects. When the profileparser Deployment's pods changed state, the controller was never notified. Adding Owns means any change to the owned Deployment triggers a reconciliation of the parent ProfileBundle, so the controller is responsive to pod lifecycle events. Also, once the controller found an existing pod with no startup error, it exited the controller reconcilation loop without requeue — regardless of whether the ProfileBundle was still in PENDING state. If the profileparser hadn't finished (or never ran due to a rollout delay), the controller would never check again. This commit also updates the profile bundle controller to requeues every 10 seconds while the status is still DataStreamPending, ensuring the controller keeps monitoring until the profileparser either succeeds (sets VALID) or fails (sets INVALID / pod startup error detected). In particular, if the controller detects that the init containers for the profileparser have completed and the bundle is still in a PENDING state, we're deadlocked, and it should annotate the profileparser pod to rerun. This should improve the resilience of profile bundle parsing, especially in testing, where we delete deployments after modifying the profile bundle image to simulate operator updates. Assisted-By: Opus 4.6
1 parent 2bae8b1 commit 918f62d

File tree

1 file changed

+45
-0
lines changed

1 file changed

+45
-0
lines changed

pkg/controller/profilebundle/profilebundle_controller.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var oneReplica int32 = 1
4242
func (r *ReconcileProfileBundle) SetupWithManager(mgr ctrl.Manager) error {
4343
return ctrl.NewControllerManagedBy(mgr).
4444
For(&compliancev1alpha1.ProfileBundle{}).
45+
Owns(&appsv1.Deployment{}).
4546
Complete(r)
4647
}
4748

@@ -67,6 +68,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
6768
return ctrl.NewControllerManagedBy(mgr).
6869
Named("profilebundle-controller").
6970
For(&compliancev1alpha1.ProfileBundle{}).
71+
Owns(&appsv1.Deployment{}).
7072
Complete(r)
7173
}
7274

@@ -252,6 +254,38 @@ func (r *ReconcileProfileBundle) Reconcile(ctx context.Context, request reconcil
252254
// Pod already exists and its init container at least ran - don't requeue
253255
reqLogger.Info("Skip reconcile: Workload already up-to-date", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
254256

257+
// If the ProfileBundle is still pending, the profileparser hasn't
258+
// finished updating the status yet.
259+
if instance.Status.DataStreamStatus == compliancev1alpha1.DataStreamPending {
260+
// Check if the profileparser init container already completed.
261+
// If the pod is Running and the profileparser finished, it means
262+
// the profileparser ran for a previous content configuration and
263+
// won't run again. This can happen when the operator restarts
264+
// after updating the Deployment but before the rollout completes,
265+
// leaving the old pod as the only one. Force a new rollout so
266+
// the profileparser re-runs with the current content.
267+
if profileparserCompleted(relevantPod) {
268+
reqLogger.Info("ProfileBundle pending but profileparser already completed, forcing rollout",
269+
"Pod.Name", relevantPod.Name, "Pod.Phase", relevantPod.Status.Phase)
270+
updatedDepl := found.DeepCopy()
271+
if updatedDepl.Spec.Template.Annotations == nil {
272+
updatedDepl.Spec.Template.Annotations = make(map[string]string)
273+
}
274+
updatedDepl.Spec.Template.Annotations["compliance.openshift.io/restart"] = time.Now().Format(time.RFC3339)
275+
err = r.Client.Update(context.TODO(), updatedDepl)
276+
if err != nil {
277+
reqLogger.Error(err, "Couldn't force profileparser rollout")
278+
return reconcile.Result{}, err
279+
}
280+
return reconcile.Result{Requeue: true, RequeueAfter: 10 * time.Second}, nil
281+
}
282+
283+
// The profileparser is still running. Requeue to check again.
284+
reqLogger.Info("ProfileBundle still pending, requeueing to check status",
285+
"Pod.Name", relevantPod.Name, "Pod.Phase", relevantPod.Status.Phase)
286+
return reconcile.Result{Requeue: true, RequeueAfter: 10 * time.Second}, nil
287+
}
288+
255289
// Handle upgrades
256290
if instance.Status.DataStreamStatus == compliancev1alpha1.DataStreamValid &&
257291
instance.Status.Conditions.GetCondition("Ready") == nil {
@@ -577,6 +611,17 @@ func podStartupError(pod *corev1.Pod) bool {
577611
return false
578612
}
579613

614+
// profileparserCompleted returns true if the profileparser init container
615+
// has already terminated successfully.
616+
func profileparserCompleted(pod *corev1.Pod) bool {
617+
for _, initStatus := range pod.Status.InitContainerStatuses {
618+
if initStatus.Name == "profileparser" {
619+
return initStatus.State.Terminated != nil && initStatus.State.Terminated.ExitCode == 0
620+
}
621+
}
622+
return false
623+
}
624+
580625
func workloadNeedsUpdate(image string, depl *appsv1.Deployment) bool {
581626
initContainers := depl.Spec.Template.Spec.InitContainers
582627
if len(initContainers) != 2 {

0 commit comments

Comments
 (0)