@@ -6,6 +6,7 @@ package careinstruction
66import (
77 "context"
88 "errors"
9+ "maps"
910 "reflect"
1011 "sync"
1112
@@ -18,9 +19,11 @@ import (
1819 "sigs.k8s.io/controller-runtime/pkg/client"
1920 "sigs.k8s.io/controller-runtime/pkg/config"
2021 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
22+ "sigs.k8s.io/controller-runtime/pkg/event"
2123 "sigs.k8s.io/controller-runtime/pkg/handler"
2224 "sigs.k8s.io/controller-runtime/pkg/log"
2325 "sigs.k8s.io/controller-runtime/pkg/metrics/server"
26+ "sigs.k8s.io/controller-runtime/pkg/predicate"
2427
2528 greenhouseapis "github.com/cloudoperators/greenhouse/api"
2629 greenhousemetav1alpha1 "github.com/cloudoperators/greenhouse/api/meta/v1alpha1"
@@ -52,6 +55,7 @@ type garden struct {
5255 careInstructionSpec * v1alpha1.CareInstructionSpec // The CareInstruction object for the garden cluster
5356 cancelFunc context.CancelFunc // Cancel function to stop the manager
5457 stopChan chan bool // Channel to know if the manager is stopped
58+ authConfigMapData map [string ]string // In-memory cache of the latest auth ConfigMap Data
5559}
5660
5761type careInstructionContextKey struct {}
@@ -64,11 +68,35 @@ type careInstructionContextKey struct{}
6468// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch;delete
6569
6670func (r * CareInstructionReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
71+ // Auth ConfigMap predicate: only re-enqueue the CareInstruction when ConfigMap Data changes.
72+ // Label-only patches (e.g. from our own MergeFrom calls in auth.go) are ignored.
73+ authCMDataChangedPredicate := predicate.Funcs {
74+ CreateFunc : func (_ event.CreateEvent ) bool { return false },
75+ UpdateFunc : func (e event.UpdateEvent ) bool {
76+ oldCM , ok1 := e .ObjectOld .(* corev1.ConfigMap )
77+ newCM , ok2 := e .ObjectNew .(* corev1.ConfigMap )
78+ if ! ok1 || ! ok2 {
79+ return false
80+ }
81+ return ! maps .Equal (oldCM .Data , newCM .Data )
82+ },
83+ DeleteFunc : func (_ event.DeleteEvent ) bool { return false },
84+ }
85+
6786 // Setup the controller with the manager
6887 return ctrl .NewControllerManagedBy (mgr ).
6988 For (& v1alpha1.CareInstruction {}).
7089 Watches (& corev1.Secret {}, handler .EnqueueRequestsFromMapFunc (r .enqueueCareInstructionForGardenCluster ), builder .WithPredicates (clientutil .PredicateFilterBySecretTypes (greenhouseapis .SecretTypeKubeConfig , greenhouseapis .SecretTypeOIDCConfig ))).
7190 Watches (& greenhousev1alpha1.Cluster {}, handler .EnqueueRequestsFromMapFunc (r .enqueueCareInstructionForCreatedClusters ), builder .WithPredicates (clientutil .PredicateHasLabel (v1alpha1 .CareInstructionLabel ))).
91+ // Watch auth ConfigMaps on the Greenhouse cluster. When their Data changes, re-enqueue the owning
92+ // CareInstruction so reconcileManager detects the change and restarts the ShootController with
93+ // the updated CM data.
94+ Watches (& corev1.ConfigMap {}, handler .EnqueueRequestsFromMapFunc (r .enqueueCareInstructionForAuthConfigMap ),
95+ builder .WithPredicates (
96+ clientutil .PredicateHasLabel (v1alpha1 .AuthConfigMapLabel ),
97+ authCMDataChangedPredicate ,
98+ ),
99+ ).
72100 Complete (r )
73101}
74102
@@ -172,6 +200,7 @@ func (r *CareInstructionReconciler) reconcileManager(ctx context.Context, careIn
172200 gardenClient : nil ,
173201 careInstructionSpec : & careInstruction .Spec ,
174202 cancelFunc : nil ,
203+ authConfigMapData : nil ,
175204 }
176205 }
177206 r .gardensMu .Unlock ()
@@ -197,6 +226,12 @@ func (r *CareInstructionReconciler) reconcileManager(ctx context.Context, careIn
197226 ),
198227 )
199228
229+ // Fetch the current auth ConfigMap data so we can detect changes and pass it to the ShootController.
230+ currentAuthCMData , authCMErr := r .fetchAuthConfigMapData (ctx , & careInstruction )
231+ if authCMErr != nil && ! apierrors .IsNotFound (authCMErr ) {
232+ r .Info ("failed to fetch auth ConfigMap data, will proceed without it" , "error" , authCMErr )
233+ }
234+
200235 // Now we check the following to see if we need to recreate and restart the manager (with read lock):
201236 r .gardensMu .RLock ()
202237 garden := r .gardens [gardenKey ]
@@ -221,9 +256,11 @@ func (r *CareInstructionReconciler) reconcileManager(ctx context.Context, careIn
221256 channelOpen = true
222257 }
223258 }
259+ // 6. If the auth ConfigMap data has changed, the ShootController must be restarted with the new data
260+ authConfigMapDataChanged := ! maps .Equal (garden .authConfigMapData , currentAuthCMData )
224261 r .gardensMu .RUnlock ()
225262
226- if mgrExists && shootControllerStarted && ! gardenConfigChanged && ! careInstructionSpecChanged && channelExists && channelOpen {
263+ if mgrExists && shootControllerStarted && ! gardenConfigChanged && ! careInstructionSpecChanged && channelExists && channelOpen && ! authConfigMapDataChanged {
227264 r .Info ("Manager is running, garden cluster config & careInstruction.Spec is unchanged, skipping client and manager recreation" , "careInstruction" , careInstruction .Name )
228265 return nil
229266 }
@@ -241,6 +278,8 @@ func (r *CareInstructionReconciler) reconcileManager(ctx context.Context, careIn
241278 reason = "stop channel is missing"
242279 case ! channelOpen :
243280 reason = "manager stop channel is closed"
281+ case authConfigMapDataChanged :
282+ reason = "auth ConfigMap data has changed"
244283 default :
245284 reason = "unknown reason"
246285 }
@@ -287,17 +326,17 @@ func (r *CareInstructionReconciler) reconcileManager(ctx context.Context, careIn
287326 return err
288327 }
289328
290- // Register the ShootController with the garden manager
329+ // Register the ShootController with the garden manager.
291330 // Note: EventRecorder is obtained from the Greenhouse manager to emit events on the Greenhouse cluster.
292- // GreenhouseMgr is passed so the ShootController can watch Greenhouse cluster resources (e.g. auth CMs) .
331+ // AuthConfigMapData is passed in-memory from the CareInstruction controller, which owns the auth CM watch .
293332 sc := & shoot.ShootController {
294- GreenhouseClient : r .Client ,
295- GreenhouseMgr : r . Manager ,
296- GardenClient : gardenClient ,
297- Logger : r . WithValues ( "careInstruction" , careInstruction .Name ),
298- Name : shoot . GenerateName ( careInstruction .Name ),
299- CareInstruction : careInstruction . DeepCopy ( ),
300- EventRecorder : r . GetEventRecorderFor ( shoot . GenerateName ( careInstruction . Name )) ,
333+ GreenhouseClient : r .Client ,
334+ GardenClient : gardenClient ,
335+ Logger : r . WithValues ( "careInstruction" , careInstruction . Name ) ,
336+ Name : shoot . GenerateName ( careInstruction .Name ),
337+ CareInstruction : careInstruction .DeepCopy ( ),
338+ EventRecorder : r . GetEventRecorderFor ( shoot . GenerateName ( careInstruction . Name ) ),
339+ AuthConfigMapData : currentAuthCMData ,
301340 }
302341 if err := sc .SetupWithManager (shootControllerMgr ); err != nil {
303342 return err
@@ -315,6 +354,7 @@ func (r *CareInstructionReconciler) reconcileManager(ctx context.Context, careIn
315354 r .gardens [gardenKey ].cancelFunc = cancel
316355 r .gardens [gardenKey ].stopChan = make (chan bool )
317356 r .gardens [gardenKey ].careInstructionSpec = & careInstruction .Spec
357+ r .gardens [gardenKey ].authConfigMapData = currentAuthCMData
318358 stopChan := r .gardens [gardenKey ].stopChan
319359 r .gardensMu .Unlock ()
320360
@@ -589,3 +629,43 @@ func (r *CareInstructionReconciler) enqueueCareInstructionForCreatedClusters(_ c
589629 },
590630 }
591631}
632+
633+ // enqueueCareInstructionForAuthConfigMap enqueues the CareInstruction that references the changed auth ConfigMap.
634+ // The CareInstructionLabel on the ConfigMap identifies the owning CareInstruction.
635+ func (r * CareInstructionReconciler ) enqueueCareInstructionForAuthConfigMap (_ context.Context , obj client.Object ) []ctrl.Request {
636+ cm , ok := obj .(* corev1.ConfigMap )
637+ if ! ok {
638+ return nil
639+ }
640+
641+ careInstructionName , exists := cm .Labels [v1alpha1 .CareInstructionLabel ]
642+ if ! exists {
643+ return nil
644+ }
645+
646+ r .Info ("Enqueuing CareInstruction for auth ConfigMap change" , "configMap" , cm .Name , "careInstruction" , careInstructionName )
647+ return []ctrl.Request {
648+ {
649+ NamespacedName : client.ObjectKey {
650+ Name : careInstructionName ,
651+ Namespace : cm .Namespace ,
652+ },
653+ },
654+ }
655+ }
656+
657+ // fetchAuthConfigMapData fetches the current Data of the auth ConfigMap referenced by the CareInstruction.
658+ // Returns nil (no error) when no auth ConfigMap is configured or the CM does not exist yet.
659+ func (r * CareInstructionReconciler ) fetchAuthConfigMapData (ctx context.Context , careInstruction * v1alpha1.CareInstruction ) (map [string ]string , error ) {
660+ if careInstruction .Spec .AuthenticationConfigMapName == "" {
661+ return nil , nil
662+ }
663+ var cm corev1.ConfigMap
664+ if err := r .Get (ctx , client.ObjectKey {
665+ Namespace : careInstruction .Namespace ,
666+ Name : careInstruction .Spec .AuthenticationConfigMapName ,
667+ }, & cm ); err != nil {
668+ return nil , err
669+ }
670+ return cm .Data , nil
671+ }
0 commit comments