@@ -31,6 +31,8 @@ import (
3131 "github.com/robfig/cron"
3232 kbatch "k8s.io/api/batch/v1"
3333 corev1 "k8s.io/api/core/v1"
34+ apierrors "k8s.io/apimachinery/pkg/api/errors"
35+ "k8s.io/apimachinery/pkg/api/meta"
3436 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3537 "k8s.io/apimachinery/pkg/runtime"
3638 ref "k8s.io/client-go/tools/reference"
@@ -68,6 +70,16 @@ type Clock interface {
6870
6971// +kubebuilder:docs-gen:collapse=Clock
7072
73+ // Definitions to manage status conditions
74+ const (
75+ // typeAvailableCronJob represents the status of the CronJob reconciliation
76+ typeAvailableCronJob = "Available"
77+ // typeProgressingCronJob represents the status used when the CronJob is being reconciled
78+ typeProgressingCronJob = "Progressing"
79+ // typeDegradedCronJob represents the status used when the CronJob has encountered an error
80+ typeDegradedCronJob = "Degraded"
81+ )
82+
7183/*
7284Notice that we need a few more RBAC permissions -- since we're creating and
7385managing jobs now, we'll need permissions for those, which means adding
@@ -114,11 +126,35 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
114126 */
115127 var cronJob batchv1.CronJob
116128 if err := r .Get (ctx , req .NamespacedName , & cronJob ); err != nil {
117- log .Error (err , "unable to fetch CronJob" )
118- // we'll ignore not-found errors, since they can't be fixed by an immediate
119- // requeue (we'll need to wait for a new notification), and we can get them
120- // on deleted requests.
121- return ctrl.Result {}, client .IgnoreNotFound (err )
129+ if apierrors .IsNotFound (err ) {
130+ // If the custom resource is not found then it usually means that it was deleted or not created
131+ // In this way, we will stop the reconciliation
132+ log .Info ("CronJob resource not found. Ignoring since object must be deleted" )
133+ return ctrl.Result {}, nil
134+ }
135+ // Error reading the object - requeue the request.
136+ log .Error (err , "Failed to get CronJob" )
137+ return ctrl.Result {}, err
138+ }
139+
140+ // Initialize status conditions if not yet present
141+ if len (cronJob .Status .Conditions ) == 0 {
142+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
143+ Type : typeProgressingCronJob ,
144+ Status : metav1 .ConditionUnknown ,
145+ Reason : "Reconciling" ,
146+ Message : "Starting reconciliation" ,
147+ })
148+ if err := r .Status ().Update (ctx , & cronJob ); err != nil {
149+ log .Error (err , "Failed to update CronJob status" )
150+ return ctrl.Result {}, err
151+ }
152+
153+ // Re-fetch the CronJob after updating the status
154+ if err := r .Get (ctx , req .NamespacedName , & cronJob ); err != nil {
155+ log .Error (err , "Failed to re-fetch CronJob" )
156+ return ctrl.Result {}, err
157+ }
122158 }
123159
124160 /*
@@ -131,6 +167,16 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
131167 var childJobs kbatch.JobList
132168 if err := r .List (ctx , & childJobs , client .InNamespace (req .Namespace ), client.MatchingFields {jobOwnerKey : req .Name }); err != nil {
133169 log .Error (err , "unable to list child Jobs" )
170+ // Update status condition to reflect the error
171+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
172+ Type : typeDegradedCronJob ,
173+ Status : metav1 .ConditionTrue ,
174+ Reason : "ReconciliationError" ,
175+ Message : fmt .Sprintf ("Failed to list child jobs: %v" , err ),
176+ })
177+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
178+ log .Error (statusErr , "Failed to update CronJob status" )
179+ }
134180 return ctrl.Result {}, err
135181 }
136182
@@ -247,6 +293,58 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
247293 */
248294 log .V (1 ).Info ("job count" , "active jobs" , len (activeJobs ), "successful jobs" , len (successfulJobs ), "failed jobs" , len (failedJobs ))
249295
296+ // Check if CronJob is suspended
297+ isSuspended := cronJob .Spec .Suspend != nil && * cronJob .Spec .Suspend
298+
299+ // Update status conditions based on current state
300+ if isSuspended {
301+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
302+ Type : typeAvailableCronJob ,
303+ Status : metav1 .ConditionFalse ,
304+ Reason : "Suspended" ,
305+ Message : "CronJob is suspended" ,
306+ })
307+ } else if len (failedJobs ) > 0 {
308+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
309+ Type : typeDegradedCronJob ,
310+ Status : metav1 .ConditionTrue ,
311+ Reason : "JobsFailed" ,
312+ Message : fmt .Sprintf ("%d job(s) have failed" , len (failedJobs )),
313+ })
314+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
315+ Type : typeAvailableCronJob ,
316+ Status : metav1 .ConditionFalse ,
317+ Reason : "JobsFailed" ,
318+ Message : fmt .Sprintf ("%d job(s) have failed" , len (failedJobs )),
319+ })
320+ } else if len (activeJobs ) > 0 {
321+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
322+ Type : typeProgressingCronJob ,
323+ Status : metav1 .ConditionTrue ,
324+ Reason : "JobsActive" ,
325+ Message : fmt .Sprintf ("%d job(s) are currently active" , len (activeJobs )),
326+ })
327+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
328+ Type : typeAvailableCronJob ,
329+ Status : metav1 .ConditionTrue ,
330+ Reason : "JobsActive" ,
331+ Message : fmt .Sprintf ("CronJob is progressing with %d active job(s)" , len (activeJobs )),
332+ })
333+ } else {
334+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
335+ Type : typeAvailableCronJob ,
336+ Status : metav1 .ConditionTrue ,
337+ Reason : "AllJobsCompleted" ,
338+ Message : "All jobs have completed successfully" ,
339+ })
340+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
341+ Type : typeProgressingCronJob ,
342+ Status : metav1 .ConditionFalse ,
343+ Reason : "NoJobsActive" ,
344+ Message : "No jobs are currently active" ,
345+ })
346+ }
347+
250348 /*
251349 Using the data we've gathered, we'll update the status of our CRD.
252350 Just like before, we use our client. To specifically update the status
@@ -400,6 +498,16 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
400498 missedRun , nextRun , err := getNextSchedule (& cronJob , r .Now ())
401499 if err != nil {
402500 log .Error (err , "unable to figure out CronJob schedule" )
501+ // Update status condition to reflect the schedule error
502+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
503+ Type : typeDegradedCronJob ,
504+ Status : metav1 .ConditionTrue ,
505+ Reason : "InvalidSchedule" ,
506+ Message : fmt .Sprintf ("Failed to parse schedule: %v" , err ),
507+ })
508+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
509+ log .Error (statusErr , "Failed to update CronJob status" )
510+ }
403511 // we don't really care about requeuing until we get an update that
404512 // fixes the schedule, so don't return an error
405513 return ctrl.Result {}, nil
@@ -430,7 +538,16 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
430538 }
431539 if tooLate {
432540 log .V (1 ).Info ("missed starting deadline for last run, sleeping till next" )
433- // TODO(directxman12): events
541+ // Update status condition to reflect missed deadline
542+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
543+ Type : typeDegradedCronJob ,
544+ Status : metav1 .ConditionTrue ,
545+ Reason : "MissedSchedule" ,
546+ Message : fmt .Sprintf ("Missed starting deadline for run at %v" , missedRun ),
547+ })
548+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
549+ log .Error (statusErr , "Failed to update CronJob status" )
550+ }
434551 return scheduledResult , nil
435552 }
436553
@@ -511,11 +628,32 @@ func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
511628 // ...and create it on the cluster
512629 if err := r .Create (ctx , job ); err != nil {
513630 log .Error (err , "unable to create Job for CronJob" , "job" , job )
631+ // Update status condition to reflect the error
632+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
633+ Type : typeDegradedCronJob ,
634+ Status : metav1 .ConditionTrue ,
635+ Reason : "JobCreationFailed" ,
636+ Message : fmt .Sprintf ("Failed to create job: %v" , err ),
637+ })
638+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
639+ log .Error (statusErr , "Failed to update CronJob status" )
640+ }
514641 return ctrl.Result {}, err
515642 }
516643
517644 log .V (1 ).Info ("created Job for CronJob run" , "job" , job )
518645
646+ // Update status condition to reflect successful job creation
647+ meta .SetStatusCondition (& cronJob .Status .Conditions , metav1.Condition {
648+ Type : typeProgressingCronJob ,
649+ Status : metav1 .ConditionTrue ,
650+ Reason : "JobCreated" ,
651+ Message : fmt .Sprintf ("Created job %s" , job .Name ),
652+ })
653+ if statusErr := r .Status ().Update (ctx , & cronJob ); statusErr != nil {
654+ log .Error (statusErr , "Failed to update CronJob status" )
655+ }
656+
519657 /*
520658 ### 7: Requeue when we either see a running job or it's time for the next scheduled run
521659
0 commit comments