@@ -314,10 +314,232 @@ func (r *LightrunJavaAgentReconciler) reconcileDeployment(ctx context.Context, l
314314// reconcileStatefulSet handles the reconciliation logic for StatefulSet workloads
315315func (r * LightrunJavaAgentReconciler ) reconcileStatefulSet (ctx context.Context , lightrunJavaAgent * agentv1beta.LightrunJavaAgent , namespace string ) (ctrl.Result , error ) {
316316 log := r .Log .WithValues ("lightrunJavaAgent" , lightrunJavaAgent .Name , "statefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
317+ fieldManager := "lightrun-controller"
317318
318- // This is a placeholder for Phase 2 implementation
319- log .Info ("StatefulSet reconciliation not yet implemented" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
320- return r .errorStatus (ctx , lightrunJavaAgent , errors .New ("statefulset reconciliation not yet implemented" ))
319+ stsNamespacedObj := client.ObjectKey {
320+ Name : lightrunJavaAgent .Spec .StatefulSetName ,
321+ Namespace : namespace ,
322+ }
323+ originalStatefulSet := & appsv1.StatefulSet {}
324+ err = r .Get (ctx , stsNamespacedObj , originalStatefulSet )
325+ if err != nil {
326+ // StatefulSet not found
327+ if client .IgnoreNotFound (err ) == nil {
328+ log .Info ("StatefulSet not found. Verify name/namespace" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
329+ // remove our finalizer from the list and update it.
330+ err = r .removeFinalizer (ctx , lightrunJavaAgent , finalizerName )
331+ if err != nil {
332+ return r .errorStatus (ctx , lightrunJavaAgent , err )
333+ }
334+ return r .errorStatus (ctx , lightrunJavaAgent , errors .New ("statefulset not found" ))
335+ } else {
336+ log .Error (err , "unable to fetch statefulset" )
337+ return r .errorStatus (ctx , lightrunJavaAgent , err )
338+ }
339+ }
340+
341+ // Check if this LightrunJavaAgent is being deleted
342+ if ! lightrunJavaAgent .ObjectMeta .DeletionTimestamp .IsZero () {
343+ // The object is being deleted
344+ if containsString (lightrunJavaAgent .ObjectMeta .Finalizers , finalizerName ) {
345+ // our finalizer is present, so lets handle any cleanup operations
346+
347+ // Restore original StatefulSet (unpatch)
348+ // Volume and init container
349+ log .Info ("Unpatching StatefulSet" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
350+
351+ originalStatefulSet = & appsv1.StatefulSet {}
352+ err = r .Get (ctx , stsNamespacedObj , originalStatefulSet )
353+ if err != nil {
354+ if client .IgnoreNotFound (err ) == nil {
355+ log .Info ("StatefulSet not found" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
356+ // remove our finalizer from the list and update it.
357+ log .Info ("Removing finalizer" )
358+ err = r .removeFinalizer (ctx , lightrunJavaAgent , finalizerName )
359+ if err != nil {
360+ return r .errorStatus (ctx , lightrunJavaAgent , err )
361+ }
362+ // Successfully removed finalizer and nothing to restore
363+ return r .successStatus (ctx , lightrunJavaAgent , reconcileTypeReady )
364+ }
365+ log .Error (err , "unable to unpatch statefulset" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
366+ return r .errorStatus (ctx , lightrunJavaAgent , err )
367+ }
368+
369+ // Revert environment variable modifications
370+ clientSidePatch := client .MergeFrom (originalStatefulSet .DeepCopy ())
371+ for i , container := range originalStatefulSet .Spec .Template .Spec .Containers {
372+ for _ , targetContainer := range lightrunJavaAgent .Spec .ContainerSelector {
373+ if targetContainer == container .Name {
374+ r .unpatchJavaToolEnv (originalStatefulSet .Annotations , & originalStatefulSet .Spec .Template .Spec .Containers [i ])
375+ }
376+ }
377+ }
378+ delete (originalStatefulSet .Annotations , annotationPatchedEnvName )
379+ delete (originalStatefulSet .Annotations , annotationPatchedEnvValue )
380+ delete (originalStatefulSet .Annotations , annotationAgentName )
381+ err = r .Patch (ctx , originalStatefulSet , clientSidePatch )
382+ if err != nil {
383+ log .Error (err , "failed to unpatch statefulset environment variables" )
384+ return r .errorStatus (ctx , lightrunJavaAgent , err )
385+ }
386+
387+ // Remove Volumes and init container
388+ emptyApplyConfig := appsv1ac .StatefulSet (stsNamespacedObj .Name , stsNamespacedObj .Namespace )
389+ obj , err := runtime .DefaultUnstructuredConverter .ToUnstructured (emptyApplyConfig )
390+ if err != nil {
391+ log .Error (err , "failed to convert StatefulSet to unstructured" )
392+ return r .errorStatus (ctx , lightrunJavaAgent , err )
393+ }
394+ patch := & unstructured.Unstructured {
395+ Object : obj ,
396+ }
397+ err = r .Patch (ctx , patch , client .Apply , & client.PatchOptions {
398+ FieldManager : fieldManager ,
399+ Force : pointer .Bool (true ),
400+ })
401+ if err != nil {
402+ log .Error (err , "failed to unpatch statefulset" )
403+ return r .errorStatus (ctx , lightrunJavaAgent , err )
404+ }
405+
406+ // remove our finalizer from the list and update it.
407+ log .Info ("Removing finalizer" )
408+ err = r .removeFinalizer (ctx , lightrunJavaAgent , finalizerName )
409+ if err != nil {
410+ return r .errorStatus (ctx , lightrunJavaAgent , err )
411+ }
412+
413+ log .Info ("StatefulSet returned to original state" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
414+ return r .successStatus (ctx , lightrunJavaAgent , reconcileTypeProgressing )
415+ }
416+ // Nothing to do here
417+ return r .successStatus (ctx , lightrunJavaAgent , reconcileTypeProgressing )
418+ }
419+
420+ // Check if already patched by another LightrunJavaAgent
421+ if oldLrjaName , ok := originalStatefulSet .Annotations [annotationAgentName ]; ok && oldLrjaName != lightrunJavaAgent .Name {
422+ log .Error (err , "StatefulSet already patched by LightrunJavaAgent" , "Existing LightrunJavaAgent" , oldLrjaName )
423+ return r .errorStatus (ctx , lightrunJavaAgent , errors .New ("statefulset already patched" ))
424+ }
425+
426+ // Add finalizer if not already present
427+ if ! containsString (lightrunJavaAgent .ObjectMeta .Finalizers , finalizerName ) {
428+ log .V (2 ).Info ("Adding finalizer" )
429+ err = r .addFinalizer (ctx , lightrunJavaAgent , finalizerName )
430+ if err != nil {
431+ log .Error (err , "unable to add finalizer" )
432+ return r .errorStatus (ctx , lightrunJavaAgent , err )
433+ }
434+ }
435+
436+ // Get the secret
437+ secretObj := client.ObjectKey {
438+ Name : lightrunJavaAgent .Spec .SecretName ,
439+ Namespace : namespace ,
440+ }
441+ secret = & corev1.Secret {}
442+ err = r .Get (ctx , secretObj , secret )
443+ if err != nil {
444+ log .Error (err , "unable to fetch Secret" , "Secret" , lightrunJavaAgent .Spec .SecretName )
445+ return r .errorStatus (ctx , lightrunJavaAgent , err )
446+ }
447+
448+ // Verify that env var won't exceed 1024 chars
449+ agentArg , err := agentEnvVarArgument (lightrunJavaAgent .Spec .InitContainer .SharedVolumeMountPath , lightrunJavaAgent .Spec .AgentCliFlags )
450+ if err != nil {
451+ log .Error (err , "agentEnvVarArgument exceeds 1024 chars" )
452+ return r .errorStatus (ctx , lightrunJavaAgent , err )
453+ }
454+
455+ // Create config map
456+ log .V (2 ).Info ("Reconciling config map with agent configuration" )
457+ configMap , err := r .createAgentConfig (lightrunJavaAgent )
458+ if err != nil {
459+ log .Error (err , "unable to create configMap" )
460+ return r .errorStatus (ctx , lightrunJavaAgent , err )
461+ }
462+ applyOpts := []client.PatchOption {client .ForceOwnership , client .FieldOwner ("lightrun-controller" )}
463+
464+ err = r .Patch (ctx , & configMap , client .Apply , applyOpts ... )
465+ if err != nil {
466+ log .Error (err , "unable to apply configMap" )
467+ return r .errorStatus (ctx , lightrunJavaAgent , err )
468+ }
469+
470+ // Calculate ConfigMap data hash
471+ cmDataHash := configMapDataHash (configMap .Data )
472+
473+ // Extract StatefulSet for applying changes
474+ statefulSetApplyConfig , err := appsv1ac .ExtractStatefulSet (originalStatefulSet , fieldManager )
475+ if err != nil {
476+ log .Error (err , "failed to extract StatefulSet" )
477+ return r .errorStatus (ctx , lightrunJavaAgent , err )
478+ }
479+
480+ // Server side apply for StatefulSet changes
481+ log .V (2 ).Info ("Patching StatefulSet" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName , "LightunrJavaAgent" , lightrunJavaAgent .Name )
482+ err = r .patchStatefulSet (lightrunJavaAgent , secret , originalStatefulSet , statefulSetApplyConfig , cmDataHash )
483+ if err != nil {
484+ log .Error (err , "failed to patch statefulset" )
485+ return r .errorStatus (ctx , lightrunJavaAgent , err )
486+ }
487+
488+ obj , err := runtime .DefaultUnstructuredConverter .ToUnstructured (statefulSetApplyConfig )
489+ if err != nil {
490+ log .Error (err , "failed to convert StatefulSet to unstructured" )
491+ return r .errorStatus (ctx , lightrunJavaAgent , err )
492+ }
493+ patch := & unstructured.Unstructured {
494+ Object : obj ,
495+ }
496+ err = r .Patch (ctx , patch , client .Apply , & client.PatchOptions {
497+ FieldManager : fieldManager ,
498+ Force : pointer .Bool (true ),
499+ })
500+ if err != nil {
501+ log .Error (err , "failed to patch statefulset" )
502+ return r .errorStatus (ctx , lightrunJavaAgent , err )
503+ }
504+
505+ // Client side patch (we can't rollback JAVA_TOOL_OPTIONS env with server side apply)
506+ log .V (2 ).Info ("Patching Java Env" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName , "LightunrJavaAgent" , lightrunJavaAgent .Name )
507+ originalStatefulSet = & appsv1.StatefulSet {}
508+ err = r .Get (ctx , stsNamespacedObj , originalStatefulSet )
509+ if err != nil {
510+ if client .IgnoreNotFound (err ) == nil {
511+ log .Info ("StatefulSet not found" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName )
512+ err = r .removeFinalizer (ctx , lightrunJavaAgent , finalizerName )
513+ if err != nil {
514+ return r .errorStatus (ctx , lightrunJavaAgent , err )
515+ }
516+ return r .errorStatus (ctx , lightrunJavaAgent , errors .New ("statefulset not found" ))
517+ }
518+ return r .errorStatus (ctx , lightrunJavaAgent , err )
519+ }
520+ clientSidePatch := client .MergeFrom (originalStatefulSet .DeepCopy ())
521+ for i , container := range originalStatefulSet .Spec .Template .Spec .Containers {
522+ for _ , targetContainer := range lightrunJavaAgent .Spec .ContainerSelector {
523+ if targetContainer == container .Name {
524+ err = r .patchJavaToolEnv (originalStatefulSet .Annotations , & originalStatefulSet .Spec .Template .Spec .Containers [i ], lightrunJavaAgent .Spec .AgentEnvVarName , agentArg )
525+ if err != nil {
526+ log .Error (err , "failed to patch " + lightrunJavaAgent .Spec .AgentEnvVarName )
527+ return r .errorStatus (ctx , lightrunJavaAgent , err )
528+ }
529+ }
530+ }
531+ }
532+ originalStatefulSet .Annotations [annotationPatchedEnvName ] = lightrunJavaAgent .Spec .AgentEnvVarName
533+ originalStatefulSet .Annotations [annotationPatchedEnvValue ] = agentArg
534+ err = r .Patch (ctx , originalStatefulSet , clientSidePatch )
535+ if err != nil {
536+ log .Error (err , "failed to patch " + lightrunJavaAgent .Spec .AgentEnvVarName )
537+ return r .errorStatus (ctx , lightrunJavaAgent , err )
538+ }
539+
540+ // Update status to Healthy
541+ log .V (1 ).Info ("Reconciling finished successfully" , "StatefulSet" , lightrunJavaAgent .Spec .StatefulSetName , "LightunrJavaAgent" , lightrunJavaAgent .Name )
542+ return r .successStatus (ctx , lightrunJavaAgent , reconcileTypeReady )
321543}
322544
323545// SetupWithManager sets up the controller with the Manager.
@@ -360,7 +582,7 @@ func (r *LightrunJavaAgentReconciler) SetupWithManager(mgr ctrl.Manager) error {
360582 return err
361583 }
362584
363- // Add spec.container_selector. secret field to cache for future filtering
585+ // Add spec.secret field to cache for future filtering
364586 err = mgr .GetFieldIndexer ().IndexField (
365587 context .Background (),
366588 & agentv1beta.LightrunJavaAgent {},
@@ -381,7 +603,6 @@ func (r *LightrunJavaAgentReconciler) SetupWithManager(mgr ctrl.Manager) error {
381603
382604 return ctrl .NewControllerManagedBy (mgr ).
383605 For (& agentv1beta.LightrunJavaAgent {}).
384- Owns (& corev1.ConfigMap {}).
385606 Watches (
386607 & appsv1.Deployment {},
387608 handler .EnqueueRequestsFromMapFunc (r .mapDeploymentToAgent ),
0 commit comments