@@ -21,9 +21,11 @@ import (
2121 "fmt"
2222 "reflect"
2323
24+ "github.com/onmetal/controller-utils/metautils"
2425 "github.com/onmetal/controller-utils/unstructuredutils"
2526 apierrors "k8s.io/apimachinery/pkg/api/errors"
2627 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+ "k8s.io/apimachinery/pkg/conversion"
2729 "k8s.io/apimachinery/pkg/runtime/schema"
2830 "sigs.k8s.io/controller-runtime/pkg/client"
2931)
@@ -384,3 +386,162 @@ func DeleteMultiple(ctx context.Context, c client.Client, objs []client.Object,
384386 }
385387 return nil
386388}
389+
390+ // ListAndFilter is a shorthand for doing a client.Client.List followed by filtering the list's elements
391+ // with the given function.
392+ func ListAndFilter (ctx context.Context , c client.Client , list client.ObjectList , filterFunc func (object client.Object ) (bool , error ), opts ... client.ListOption ) error {
393+ if err := c .List (ctx , list , opts ... ); err != nil {
394+ return err
395+ }
396+
397+ items , err := metautils .ExtractList (list )
398+ if err != nil {
399+ return fmt .Errorf ("error extracting list: %w" , err )
400+ }
401+
402+ var filtered []client.Object
403+ for _ , item := range items {
404+ ok , err := filterFunc (item )
405+ if err != nil {
406+ return err
407+ }
408+ if ok {
409+ item := item
410+ filtered = append (filtered , item )
411+ }
412+ }
413+
414+ if err := metautils .SetList (list , filtered ); err != nil {
415+ return fmt .Errorf ("error setting list: %w" , err )
416+ }
417+
418+ return nil
419+ }
420+
421+ // ListAndFilterControlledBy is a shorthand for doing a client.List followed by filtering the list's elements
422+ // using metautils.IsControlledBy.
423+ func ListAndFilterControlledBy (ctx context.Context , c client.Client , owner client.Object , list client.ObjectList , opts ... client.ListOption ) error {
424+ scheme := c .Scheme ()
425+ return ListAndFilter (ctx , c , list , func (object client.Object ) (bool , error ) {
426+ return metautils .IsControlledBy (scheme , owner , object )
427+ }, opts ... )
428+ }
429+
430+ func setObject (dst , src client.Object ) error {
431+ dstV , err := conversion .EnforcePtr (dst )
432+ if err != nil {
433+ return err
434+ }
435+
436+ srcV , err := conversion .EnforcePtr (src )
437+ if err != nil {
438+ return err
439+ }
440+
441+ if ! srcV .Type ().AssignableTo (dstV .Type ()) {
442+ return fmt .Errorf ("cannot assign %T to %T" , src , dst )
443+ }
444+
445+ dstV .Set (srcV .Convert (dstV .Type ()))
446+ return nil
447+ }
448+
449+ // CreateOrUseOperationResult is the result of a CreateOrUse call.
450+ type CreateOrUseOperationResult string
451+
452+ const (
453+ // CreateOrUseOperationResultUsed indicates that a match has been found and no object was created.
454+ CreateOrUseOperationResultUsed CreateOrUseOperationResult = "used"
455+ // CreateOrUseOperationResultCreated indicates that no match has been found and a new object was created.
456+ CreateOrUseOperationResultCreated CreateOrUseOperationResult = "created"
457+ // CreateOrUseOperationResultNone indicates that no match has been found / no object was created.
458+ CreateOrUseOperationResultNone CreateOrUseOperationResult = "unchanged"
459+ )
460+
461+ // CreateOrUse traverses through a slice of objects and tries to find a matching object using matchFunc.
462+ // If it does, the matching object is set to the object and the function returns the other objects.
463+ // If it matches multiple times, the winning object is the oldest.
464+ // If it does not match, initFunc is called and the new object is created.
465+ func CreateOrUse (ctx context.Context , c client.Client , objects []client.Object , obj client.Object , matchFunc func () (bool , error ), initFunc func () error ) (CreateOrUseOperationResult , []client.Object , error ) {
466+ var (
467+ base = obj .DeepCopyObject ().(client.Object )
468+ best client.Object
469+ other []client.Object
470+ )
471+ for _ , object := range objects {
472+ object := object
473+ if err := setObject (obj , object ); err != nil {
474+ return CreateOrUseOperationResultNone , nil , err
475+ }
476+
477+ match , err := matchFunc ()
478+ if err != nil {
479+ return CreateOrUseOperationResultNone , nil , err
480+ }
481+
482+ switch {
483+ case match && best == nil :
484+ best = object
485+ // The older object is better.
486+ // Using older objects over newer objects helps avoid creating too many new objects.
487+ case match && best .GetCreationTimestamp ().Time .After (object .GetCreationTimestamp ().Time ):
488+ other = append (other , best )
489+ best = object
490+ default :
491+ other = append (other , object )
492+ }
493+ }
494+ if best != nil {
495+ if err := setObject (obj , best ); err != nil {
496+ return CreateOrUseOperationResultNone , nil , err
497+ }
498+ return CreateOrUseOperationResultUsed , other , nil
499+ }
500+
501+ if err := setObject (obj , base ); err != nil {
502+ return CreateOrUseOperationResultNone , nil , err
503+ }
504+ if initFunc != nil {
505+ if err := initFunc (); err != nil {
506+ return CreateOrUseOperationResultNone , nil , err
507+ }
508+ }
509+ if err := c .Create (ctx , obj ); err != nil {
510+ return CreateOrUseOperationResultNone , nil , err
511+ }
512+ return CreateOrUseOperationResultCreated , other , nil
513+ }
514+
515+ // CreateOrUseWithList is a shorthand for using CreateOrUse with a client.ObjectList containing the objects.
516+ // Caution: In contrast to CreateOrUse, if setting the list elements fails, the unmatched objects are unknown.
517+ // The operation result will still reflect what happened.
518+ func CreateOrUseWithList (ctx context.Context , c client.Client , list client.ObjectList , obj client.Object , matchFunc func () (bool , error ), initFunc func () error ) (CreateOrUseOperationResult , error ) {
519+ items , err := metautils .ExtractList (list )
520+ if err != nil {
521+ return CreateOrUseOperationResultNone , err
522+ }
523+
524+ res , items , err := CreateOrUse (ctx , c , items , obj , matchFunc , initFunc )
525+ if err != nil {
526+ return res , err
527+ }
528+
529+ return res , metautils .SetList (list , items )
530+ }
531+
532+ // CreateOrUseWithObjectSlicePointer is a shorthand for using CreateOrUse with an object slice containing the objects.
533+ // Caution: In contrast to CreateOrUse, if setting the slice elements fails, the unmatched objects are unknown.
534+ // The operation result will still reflect what happened.
535+ func CreateOrUseWithObjectSlicePointer (ctx context.Context , c client.Client , slicePtr interface {}, obj client.Object , matchFunc func () (bool , error ), initFunc func () error ) (CreateOrUseOperationResult , error ) {
536+ items , err := metautils .ExtractObjectSlicePointer (slicePtr )
537+ if err != nil {
538+ return CreateOrUseOperationResultNone , err
539+ }
540+
541+ res , items , err := CreateOrUse (ctx , c , items , obj , matchFunc , initFunc )
542+ if err != nil {
543+ return res , err
544+ }
545+
546+ return res , metautils .SetObjectSlice (slicePtr , items )
547+ }
0 commit comments