@@ -20,6 +20,7 @@ import (
2020	"fmt" 
2121	"hash/fnv" 
2222	"reflect" 
23+ 	"time" 
2324
2425	corev1 "k8s.io/api/core/v1" 
2526	k8serrors "k8s.io/apimachinery/pkg/api/errors" 
@@ -33,6 +34,7 @@ import (
3334	"sigs.k8s.io/controller-runtime/pkg/handler" 
3435	"sigs.k8s.io/controller-runtime/pkg/log" 
3536	"sigs.k8s.io/controller-runtime/pkg/predicate" 
37+ 	"sigs.k8s.io/controller-runtime/pkg/reconcile" 
3638
3739	sandboxv1alpha1 "sigs.k8s.io/agent-sandbox/api/v1alpha1" 
3840)
@@ -98,12 +100,15 @@ func (r *SandboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
98100	readyCondition  :=  r .computeReadyCondition (sandbox .Generation , allErrors , svc , pod )
99101	meta .SetStatusCondition (& sandbox .Status .Conditions , readyCondition )
100102
103+ 	result , err  :=  r .ReconcileSandboxTTL (ctx , sandbox )
104+ 	allErrors  =  errors .Join (allErrors , err )
105+ 
101106	// Update status 
102107	err  =  r .updateStatus (ctx , oldStatus , sandbox )
103108	allErrors  =  errors .Join (allErrors , err )
104109
105110	// return errors seen 
106- 	return  ctrl. Result {} , allErrors 
111+ 	return  result , allErrors 
107112}
108113
109114func  (r  * SandboxReconciler ) computeReadyCondition (generation  int64 , err  error , svc  * corev1.Service , pod  * corev1.Pod ) metav1.Condition  {
@@ -291,6 +296,42 @@ func (r *SandboxReconciler) mutatedPodSpec(originalSpec *corev1.PodSpec) *corev1
291296	return  spec 
292297}
293298
299+ // ReconcileSandboxTTL will check if a sandbox has expired, and if so, delete it. 
300+ // If the sandbox has not expired, it will requeue the request for the remaining time. 
301+ 
302+ func  (r  * SandboxReconciler ) ReconcileSandboxTTL (ctx  context.Context , sandbox  * sandboxv1alpha1.Sandbox ) (ctrl.Result , error ) {
303+ 	log  :=  log .FromContext (ctx )
304+ 
305+ 	var  expiryTime  time.Time 
306+ 	if  sandbox .Spec .ShutdownAt  !=  ""  {
307+ 		var  err  error 
308+ 		// Try parsing the endtime as a RFC 3339 string 
309+ 		expiryTime , err  =  time .Parse (time .RFC3339 , sandbox .Spec .ShutdownAt )
310+ 		if  err  !=  nil  {
311+ 			log .Error (err , "Failed to parse TTLFromTime as RFC3339 string" , "ShutdownAt" , sandbox .Spec .ShutdownAt )
312+ 			return  reconcile.Result {}, err 
313+ 		}
314+ 	} else  {
315+ 		log .Info ("Sandbox TTL is not set, skipping TTL check." )
316+ 		return  reconcile.Result {}, nil 
317+ 	}
318+ 
319+ 	// Calculate remaining time 
320+ 	remainingTime  :=  time .Until (expiryTime )
321+ 	if  remainingTime  <=  0  {
322+ 		log .Info ("Sandbox has expired, deleting" )
323+ 		if  err  :=  r .Delete (ctx , sandbox ); err  !=  nil  {
324+ 			return  ctrl.Result {}, fmt .Errorf ("failed to delete sandbox: %w" , err )
325+ 		}
326+ 		return  ctrl.Result {}, nil 
327+ 	}
328+ 
329+ 	requeueAfter  :=  max (remainingTime / 2 , 2 * time .Second ) // Requeue at most every 2 seconds 
330+ 	log .Info ("Requeuing sandbox for TTL" , "remaining time" , remainingTime , "requeue after" , requeueAfter ,
331+ 		"expiry time" , expiryTime )
332+ 	return  reconcile.Result {RequeueAfter : requeueAfter }, nil 
333+ }
334+ 
294335// SetupWithManager sets up the controller with the Manager. 
295336func  (r  * SandboxReconciler ) SetupWithManager (mgr  ctrl.Manager ) error  {
296337	labelSelectorPredicate , err  :=  predicate .LabelSelectorPredicate (metav1.LabelSelector {
0 commit comments