diff --git a/pkg/utils/controller/handlers.go b/pkg/utils/controller/handlers.go index e18787ea80..87fccde0fe 100644 --- a/pkg/utils/controller/handlers.go +++ b/pkg/utils/controller/handlers.go @@ -22,7 +22,7 @@ func AddEventHandlers(informer cache.SharedInformer, a addFunc, u updateFunc, d } func AddDefaultEventHandlers(logger logr.Logger, informer cache.SharedInformer, queue workqueue.RateLimitingInterface) { - AddEventHandlers(informer, Add(logger, queue), Update(logger, queue), Delete(logger, queue)) + AddEventHandlers(informer, AddFunc(logger, queue), UpdateFunc(logger, queue), DeleteFunc(logger, queue)) } func Enqueue(logger logr.Logger, queue workqueue.RateLimitingInterface, obj interface{}) { @@ -33,19 +33,19 @@ func Enqueue(logger logr.Logger, queue workqueue.RateLimitingInterface, obj inte } } -func Add(logger logr.Logger, queue workqueue.RateLimitingInterface) addFunc { +func AddFunc(logger logr.Logger, queue workqueue.RateLimitingInterface) addFunc { return func(obj interface{}) { Enqueue(logger, queue, obj) } } -func Update(logger logr.Logger, queue workqueue.RateLimitingInterface) updateFunc { +func UpdateFunc(logger logr.Logger, queue workqueue.RateLimitingInterface) updateFunc { return func(_, obj interface{}) { Enqueue(logger, queue, obj) } } -func Delete(logger logr.Logger, queue workqueue.RateLimitingInterface) deleteFunc { +func DeleteFunc(logger logr.Logger, queue workqueue.RateLimitingInterface) deleteFunc { return func(obj interface{}) { Enqueue(logger, queue, kubeutils.GetObjectWithTombstone(obj)) } diff --git a/pkg/utils/controller/metadata.go b/pkg/utils/controller/metadata.go new file mode 100644 index 0000000000..657ae1edf2 --- /dev/null +++ b/pkg/utils/controller/metadata.go @@ -0,0 +1,34 @@ +package controller + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func SetLabel(obj metav1.Object, key, value string) map[string]string { + labels := obj.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[key] = value + obj.SetLabels(labels) + return labels +} + +func SetAnnotation(obj metav1.Object, key, value string) { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations[key] = value + obj.SetAnnotations(annotations) +} + +func SetOwner(obj metav1.Object, apiVersion, kind, name string, uid types.UID) { + obj.SetOwnerReferences([]metav1.OwnerReference{{ + APIVersion: apiVersion, + Kind: kind, + Name: name, + UID: uid, + }}) +} diff --git a/pkg/utils/controller/utils.go b/pkg/utils/controller/utils.go new file mode 100644 index 0000000000..b986737b4b --- /dev/null +++ b/pkg/utils/controller/utils.go @@ -0,0 +1,91 @@ +package controller + +import ( + "context" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" +) + +type Object[T any] interface { + *T + metav1.Object + DeepCopy() *T +} + +type Getter[T any] interface { + Get(string) (T, error) +} + +type Setter[T any] interface { + Create(context.Context, T, metav1.CreateOptions) (T, error) + Update(context.Context, T, metav1.UpdateOptions) (T, error) +} + +type Deleter interface { + Delete(context.Context, string, metav1.DeleteOptions) error +} + +func GetOrNew[T any, R Object[T], G Getter[R]](name string, getter G) (R, error) { + obj, err := getter.Get(name) + if err != nil { + if apierrors.IsNotFound(err) { + obj = new(T) + obj.SetName(name) + } else { + return nil, err + } + } + return obj, nil +} + +func CreateOrUpdate[T any, R Object[T], G Getter[R], S Setter[R]](name string, getter G, setter S, build func(R) error) (R, error) { + if obj, err := GetOrNew[T, R](name, getter); err != nil { + return nil, err + } else { + mutated := obj.DeepCopy() + if err := build(mutated); err != nil { + return nil, err + } else { + if obj.GetResourceVersion() == "" { + return setter.Create(context.TODO(), mutated, metav1.CreateOptions{}) + } else { + if reflect.DeepEqual(obj, mutated) { + return mutated, nil + } else { + return setter.Update(context.TODO(), mutated, metav1.UpdateOptions{}) + } + } + } + } +} + +func Update[T any, R Object[T], S Setter[R]](setter S, obj R, build func(R) error) (R, error) { + mutated := obj.DeepCopy() + if err := build(mutated); err != nil { + return nil, err + } else { + if reflect.DeepEqual(obj, mutated) { + return mutated, nil + } else { + return setter.Update(context.TODO(), mutated, metav1.UpdateOptions{}) + } + } +} + +func Cleanup[T any, R Object[T]](actual []R, expected []R, deleter Deleter) error { + keep := sets.NewString() + for _, obj := range expected { + keep.Insert(obj.GetName()) + } + for _, obj := range actual { + if !keep.Has(obj.GetName()) { + if err := deleter.Delete(context.TODO(), obj.GetName(), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { + return err + } + } + } + return nil +}