@@ -154,6 +154,10 @@ func (r *reconciler) ensureControllers(ctx context.Context, namespace, name stri
154154 contexts := r .syncStore .ListPrefixed (exportKey )
155155 for _ , c := range contexts {
156156 schemaName := strings .TrimPrefix (string (c .Key ()), string (exportKey )+ "." ) // schemaName will be processed schema names.
157+ // Skip permission claim controllers - they are handled separately in ensureControllersForPermissionClaims
158+ if strings .HasPrefix (schemaName , "claim." ) {
159+ continue
160+ }
157161 if ! processedSchemas [schemaName ] {
158162 logger .V (1 ).Info ("Stopping APIServiceExport resource sync" , "key" , c .Key (), "reason" , "Schema no longer referenced" )
159163 r .syncStore .Delete (c .Key ())
@@ -299,100 +303,153 @@ func (r *reconciler) ensureControllersForPermissionClaims(
299303) error {
300304 logger := klog .FromContext (ctx )
301305
302- //ctxWithCancel, cancel := context.WithCancel(ctx)
303-
304- dynamicProviderClient := dynamicclient .NewForConfigOrDie (r .providerConfig )
305- dynamicConsumerClient := dynamicclient .NewForConfigOrDie (r .consumerConfig )
306-
307- // We will create a new filtered informer factory for each claim that has a label selector.
308- // We still need a default one for claims without a selector.
309- defaultConsumerInf := dynamicinformer .NewDynamicSharedInformerFactory (dynamicConsumerClient , time .Minute * 30 )
306+ // Track processed claims for cleanup
307+ processedClaims := make (map [string ]bool )
308+ var errs []error
310309
311- // PermissionClaims sync controller
312- claimControllers := make ([]func (context.Context , int ), len (binding .Spec .PermissionClaims ))
310+ // Process each permission claim
313311 for _ , claim := range binding .Spec .PermissionClaims {
314312 claimGVR , err := kubebindv1alpha2 .ResolveClaimableAPI (claim )
315313 if err != nil {
316314 logger .Info ("skipping unsupported claim" , "claim" , claim , "error" , err )
317315 continue
318316 }
319- // Define a function to tweak list options based on the claim's LabelSelector.
320- tweakListOptions := func (options * metav1.ListOptions ) {
321- if claim .Selector .LabelSelector != nil {
322- // Convert the LabelSelector struct to a string that the API server understands.
323- selector , err := metav1 .LabelSelectorAsSelector (claim .Selector .LabelSelector )
324- if err == nil {
325- options .LabelSelector = selector .String ()
326- logger .V (2 ).Info ("Applying label selector to informer" , "selector" , options .LabelSelector , "resource" , claim .Resource )
327- } else {
328- logger .Error (err , "failed to parse label selector" , "selector" , claim .Selector .LabelSelector )
329- }
317+
318+ // Create unique key for this claim controller
319+ claimKey := contextstore .NewKey (export .Namespace , export .Name , "claim" , claimGVR .String ())
320+ processedClaims [claimKey .String ()] = true
321+
322+ // Check if controller already exists with correct generation
323+ c , found := r .syncStore .Get (claimKey )
324+ if found {
325+ if c .Generation == binding .Generation {
326+ continue // controller is up to date
330327 }
328+ // Generation changed, stop old controller
329+ logger .V (1 ).Info ("Stopping permission claim controller" , "key" , claimKey , "reason" , "GenerationChanged" )
330+ r .syncStore .Delete (claimKey )
331331 }
332332
333- var providerInf multinsinformer.GetterInformer
334- if isClusterScoped {
335- factory := dynamicinformer .NewDynamicSharedInformerFactory (dynamicProviderClient , time .Minute * 30 )
336- factory .ForResource (claimGVR ).Lister () // wire the GVR up in the informer factory
337- providerInf = multinsinformer.GetterInformerWrapper {
338- GVR : claimGVR ,
339- Delegate : factory ,
340- }
341- } else {
342- providerInf , err = multinsinformer .NewDynamicMultiNamespaceInformer (
343- claimGVR ,
344- r .providerNamespace ,
345- r .providerConfig ,
346- r .serviceNamespaceInformer ,
347- )
348- if err != nil {
349- logger .Info ("aborting" , "error" , err )
350- return err
351- }
333+ // Start new controller for this claim
334+ if err := r .ensureControllerForPermissionClaim (ctx , binding , claim , claimGVR , isClusterScoped , claimKey ); err != nil {
335+ errs = append (errs , err )
336+ }
337+ }
338+
339+ // Cleanup controllers for claims that are no longer present
340+ claimPrefix := contextstore .NewKey (export .Namespace , export .Name , "claim" )
341+ contexts := r .syncStore .ListPrefixed (claimPrefix )
342+ for _ , c := range contexts {
343+ if ! processedClaims [c .Key ().String ()] {
344+ logger .V (1 ).Info ("Stopping permission claim controller" , "key" , c .Key (), "reason" , "Claim no longer present" )
345+ r .syncStore .Delete (c .Key ())
352346 }
347+ }
348+
349+ return utilerrors .NewAggregate (errs )
350+ }
353351
352+ func (r * reconciler ) ensureControllerForPermissionClaim (
353+ ctx context.Context ,
354+ binding * kubebindv1alpha2.APIServiceBinding ,
355+ claim kubebindv1alpha2.PermissionClaim ,
356+ claimGVR runtimeschema.GroupVersionResource ,
357+ isClusterScoped bool ,
358+ claimKey contextstore.Key ,
359+ ) error {
360+ logger := klog .FromContext (ctx )
361+
362+ ctxWithCancel , cancel := context .WithCancel (ctx )
363+
364+ dynamicProviderClient := dynamicclient .NewForConfigOrDie (r .providerConfig )
365+ dynamicConsumerClient := dynamicclient .NewForConfigOrDie (r .consumerConfig )
366+
367+ // Define a function to tweak list options based on the claim's LabelSelector.
368+ tweakListOptions := func (options * metav1.ListOptions ) {
354369 if claim .Selector .LabelSelector != nil {
355- defaultConsumerInf = dynamicinformer .NewFilteredDynamicSharedInformerFactory (dynamicConsumerClient , time .Minute * 30 , metav1 .NamespaceAll , tweakListOptions )
370+ // Convert the LabelSelector struct to a string that the API server understands.
371+ selector , err := metav1 .LabelSelectorAsSelector (claim .Selector .LabelSelector )
372+ if err == nil {
373+ options .LabelSelector = selector .String ()
374+ logger .V (2 ).Info ("Applying label selector to informer" , "selector" , options .LabelSelector , "resource" , claim .Resource )
375+ } else {
376+ logger .Error (err , "failed to parse label selector" , "selector" , claim .Selector .LabelSelector )
377+ }
356378 }
379+ }
357380
358- claimedCtrl , err := claimedresources .NewController (
381+ // Create consumer informer factory
382+ var defaultConsumerInf dynamicinformer.DynamicSharedInformerFactory
383+ if claim .Selector .LabelSelector != nil {
384+ defaultConsumerInf = dynamicinformer .NewFilteredDynamicSharedInformerFactory (dynamicConsumerClient , time .Minute * 30 , metav1 .NamespaceAll , tweakListOptions )
385+ } else {
386+ defaultConsumerInf = dynamicinformer .NewDynamicSharedInformerFactory (dynamicConsumerClient , time .Minute * 30 )
387+ }
388+
389+ var providerInf multinsinformer.GetterInformer
390+ if isClusterScoped {
391+ factory := dynamicinformer .NewDynamicSharedInformerFactory (dynamicProviderClient , time .Minute * 30 )
392+ factory .ForResource (claimGVR ).Lister () // wire the GVR up in the informer factory
393+ providerInf = multinsinformer.GetterInformerWrapper {
394+ GVR : claimGVR ,
395+ Delegate : factory ,
396+ }
397+ } else {
398+ var err error
399+ providerInf , err = multinsinformer .NewDynamicMultiNamespaceInformer (
359400 claimGVR ,
360- claim ,
361401 r .providerNamespace ,
362- r .consumerConfig ,
363402 r .providerConfig ,
364- defaultConsumerInf .ForResource (claimGVR ),
365- providerInf ,
366403 r .serviceNamespaceInformer ,
367404 )
368-
369405 if err != nil {
370- runtime .HandleError (err )
371- return nil // nothing we can do here
406+ logger .Info ("aborting" , "error" , err )
407+ cancel ()
408+ return err
372409 }
373- logger .Info ("creating claim reconciler" , "gvr" , claimGVR )
374-
375- claimControllers = append (claimControllers , func (ctx context.Context , i int ) {
376- providerInf .Start (ctx )
410+ }
377411
378- providerSynced := providerInf .WaitForCacheSync (ctx .Done ())
379- logger .V (2 ).Info ("Synced informers" , "provider" , slices .Collect (maps .Keys (providerSynced )))
412+ claimedCtrl , err := claimedresources .NewController (
413+ claimGVR ,
414+ claim ,
415+ r .providerNamespace ,
416+ r .consumerConfig ,
417+ r .providerConfig ,
418+ defaultConsumerInf .ForResource (claimGVR ),
419+ providerInf ,
420+ r .serviceNamespaceInformer ,
421+ )
380422
381- claimedCtrl .Start (ctx , i )
382- })
423+ if err != nil {
424+ cancel ()
425+ return err
383426 }
384- defaultConsumerInf .Start (ctx .Done ())
385427
428+ logger .Info ("creating claim reconciler" , "gvr" , claimGVR , "key" , claimKey )
429+
430+ // Start the informers and controllers in a goroutine
386431 go func () {
387- // to not block the main thread
388- consumerSynced := defaultConsumerInf .WaitForCacheSync (ctx .Done ())
389- logger .V (2 ).Info ("Synced informers" , "consumer" , slices .Collect (maps .Keys (consumerSynced )))
432+ defaultConsumerInf .Start (ctxWithCancel .Done ())
390433
391- for _ , ctrl := range claimControllers {
392- go ctrl (ctx , 1 )
393- }
434+ // Wait for consumer informers to sync
435+ consumerSynced := defaultConsumerInf .WaitForCacheSync (ctxWithCancel .Done ())
436+ logger .V (2 ).Info ("Synced consumer informers" , "consumer" , slices .Collect (maps .Keys (consumerSynced )), "key" , claimKey )
437+
438+ // Start provider informer and wait for sync
439+ providerInf .Start (ctxWithCancel )
440+ providerSynced := providerInf .WaitForCacheSync (ctxWithCancel .Done ())
441+ logger .V (2 ).Info ("Synced provider informers" , "provider" , slices .Collect (maps .Keys (providerSynced )), "key" , claimKey )
442+
443+ // Start the claimed resources controller
444+ claimedCtrl .Start (ctxWithCancel , 1 )
394445 }()
395446
447+ // Store the controller context for tracking
448+ r .syncStore .Set (claimKey , contextstore.SyncContext {
449+ Generation : binding .Generation ,
450+ Cancel : cancel ,
451+ })
452+
396453 return nil
397454}
398455
0 commit comments