@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "errors"
2222 "fmt"
23+ "sort"
2324 "strings"
2425 "time"
2526
@@ -262,10 +263,7 @@ func (r *InstallEpinioReconciler) Reconcile(ctx context.Context, req ctrl.Reques
262263 if jobTerminalFailed (job .Status ) {
263264 failed := job .Status .Failed
264265 log .Info ("Install Job reported failure" , "job" , jobName , "namespace" , ctrlNs , "failed" , failed )
265- failMsg := fmt .Sprintf ("Install Job failed (%d failed)" , failed )
266- if failed == 0 {
267- failMsg = "Install Job failed"
268- }
266+ failMsg := r .jobFailureMessage (ctx , ctrlNs , jobName )
269267 setCondition (& inst , metav1.Condition {
270268 Type : conditionDegraded ,
271269 Status : metav1 .ConditionTrue ,
@@ -412,13 +410,35 @@ func (r *InstallEpinioReconciler) reconcileDelete(ctx context.Context, inst *epi
412410 return ctrl.Result {}, err
413411 }
414412
413+ // Check if this is the last InstallEpinio in the namespace before removing the finalizer.
414+ var remaining epiniov1alpha1.InstallEpinioList
415+ if err := r .List (ctx , & remaining , client .InNamespace (ctrlNs )); err != nil {
416+ log .Error (err , "failed to list remaining InstallEpinio resources" )
417+ return ctrl.Result {}, err
418+ }
419+ // The current resource is still present (finalizer not yet removed), so count > 1 means others exist.
420+ lastCR := len (remaining .Items ) <= 1
421+
422+ // Remove finalizer first so the CR can be fully deleted even if the
423+ // subsequent namespace deletion terminates the operator pod.
415424 controllerutil .RemoveFinalizer (inst , installCleanupFinalizer )
416425 log .Info ("Removing cleanup finalizer" )
417426 if err := r .Update (ctx , inst ); err != nil {
418427 log .Error (err , "failed to remove cleanup finalizer" )
419428 return ctrl.Result {}, err
420429 }
421430
431+ // Delete the operator namespace after the finalizer is gone so that
432+ // namespace termination is not blocked by the CR.
433+ if lastCR {
434+ ns := & corev1.Namespace {}
435+ ns .Name = ctrlNs
436+ if err := r .deleteIfExists (ctx , ns , "Namespace" ); err != nil {
437+ return ctrl.Result {}, err
438+ }
439+ log .Info ("Deleted operator namespace" , "namespace" , ctrlNs )
440+ }
441+
422442 return ctrl.Result {}, nil
423443}
424444
@@ -526,6 +546,59 @@ func installJobName(namespace, name string) string {
526546 return jobNamePrefix + namespace + "-" + name
527547}
528548
549+ // jobFailureMessage inspects the pods belonging to a failed Job and extracts
550+ // a human-readable error from init or main container termination details.
551+ func (r * InstallEpinioReconciler ) jobFailureMessage (ctx context.Context , namespace , jobName string ) string {
552+ fallback := fmt .Sprintf ("Install Job %q failed" , jobName )
553+
554+ var podList corev1.PodList
555+ if err := r .List (ctx , & podList ,
556+ client .InNamespace (namespace ),
557+ client.MatchingLabels {"job-name" : jobName },
558+ ); err != nil || len (podList .Items ) == 0 {
559+ return fallback
560+ }
561+
562+ pods := append ([]corev1.Pod (nil ), podList .Items ... )
563+ sort .Slice (pods , func (i , j int ) bool {
564+ return pods [i ].CreationTimestamp .After (pods [j ].CreationTimestamp .Time )
565+ })
566+
567+ for i := range pods {
568+ if msg := installFailureMessageFromPod (& pods [i ]); msg != "" {
569+ return msg
570+ }
571+ }
572+
573+ return fallback
574+ }
575+
576+ func installFailureMessageFromPod (pod * corev1.Pod ) string {
577+ if msg := installFailureMessageFromContainerStatuses (pod .Status .InitContainerStatuses ); msg != "" {
578+ return msg
579+ }
580+ return installFailureMessageFromContainerStatuses (pod .Status .ContainerStatuses )
581+ }
582+
583+ func installFailureMessageFromContainerStatuses (statuses []corev1.ContainerStatus ) string {
584+ for _ , cs := range statuses {
585+ if cs .State .Terminated == nil {
586+ continue
587+ }
588+ term := cs .State .Terminated
589+ if term .Message != "" {
590+ return fmt .Sprintf ("Install failed: %s" , term .Message )
591+ }
592+ if term .Reason != "" {
593+ return fmt .Sprintf ("Install failed: %s (exit code %d)" , term .Reason , term .ExitCode )
594+ }
595+ if term .ExitCode != 0 {
596+ return fmt .Sprintf ("Install failed with exit code %d" , term .ExitCode )
597+ }
598+ }
599+ return ""
600+ }
601+
529602func jobTerminalFailed (status batchv1.JobStatus ) bool {
530603 for i := range status .Conditions {
531604 if status .Conditions [i ].Type == batchv1 .JobFailed && status .Conditions [i ].Status == corev1 .ConditionTrue {
0 commit comments