From 422b18c2a749a51b0d32ad87a0b93d5a9f7e8380 Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Wed, 31 May 2023 14:05:40 +0530 Subject: [PATCH] feat: add generic reconciler --- manager.go | 32 +++--------------- reconciler.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 reconciler.go diff --git a/manager.go b/manager.go index 72bdaec..e71aaf7 100644 --- a/manager.go +++ b/manager.go @@ -12,11 +12,6 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/manager" - - "github.com/flanksource/commons/collections" - missioncontrolv1 "github.com/flanksource/kopper/api/v1" - "github.com/flanksource/kopper/controllers" - //+kubebuilder:scaffold:imports ) var ( @@ -25,22 +20,20 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - utilruntime.Must(missioncontrolv1.AddToScheme(scheme)) } type ManagerOptions struct { - MetricsBindAddress string - LeaderElectionID string - Reconcilers []string - ConnectionOnUpsertFunc func(missioncontrolv1.Connection) error - ConnectionOnDeleteFunc func(string) error + MetricsBindAddress string + LeaderElectionID string + AddToSchemeFunc func(*runtime.Scheme) error } func Manager(opts *ManagerOptions) (manager.Manager, error) { if opts == nil { opts = &ManagerOptions{} } + + utilruntime.Must(opts.AddToSchemeFunc(scheme)) // Use options for reconciling setup mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -64,20 +57,5 @@ func Manager(opts *ManagerOptions) (manager.Manager, error) { return nil, fmt.Errorf("error setting up manager: %w", err) } - if len(opts.Reconcilers) == 0 { - return nil, fmt.Errorf("no reconcilers given") - } - - if collections.Contains(opts.Reconcilers, "Connection") { - if err = (&controllers.ConnectionReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - OnUpsertFunc: opts.ConnectionOnUpsertFunc, - OnDeleteFunc: opts.ConnectionOnDeleteFunc, - }).SetupWithManager(mgr); err != nil { - return nil, fmt.Errorf("unable to create controller for Connection: %v", err) - } - } - return mgr, nil } diff --git a/reconciler.go b/reconciler.go new file mode 100644 index 0000000..4399581 --- /dev/null +++ b/reconciler.go @@ -0,0 +1,93 @@ +package kopper + +import ( + "context" + "fmt" + "time" + + "github.com/flanksource/commons/logger" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func SetupReconciler[T any, PT interface { + *T + client.Object +}](mgr ctrl.Manager, OnUpsertFunc func(PT) error, OnDeleteFunc func(string) error, finalizer string) error { + if finalizer == "" { + return fmt.Errorf("field Finalizer cannot be empty") + } + + r := Reconciler[T, PT]{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + OnUpsertFunc: OnUpsertFunc, + OnDeleteFunc: OnDeleteFunc, + Finalizer: finalizer, + } + + return r.SetupWithManager(mgr) +} + +type Reconciler[T any, PT interface { + *T + client.Object +}] struct { + client.Client + Scheme *runtime.Scheme + OnUpsertFunc func(PT) error + OnDeleteFunc func(string) error + Finalizer string +} + +func (r *Reconciler[T, PT]) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + obj := PT(new(T)) + + err := r.Get(ctx, req.NamespacedName, obj) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + resourceName := fmt.Sprintf("%s[%s]", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetUID()) + + if !obj.GetDeletionTimestamp().IsZero() { + logger.Infof("[kopper] deleting %s", resourceName) + if err := r.OnDeleteFunc(string(obj.GetUID())); err != nil { + logger.Errorf("[kopper] failed to delete %s: %v", resourceName, err) + return ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}, err + } + controllerutil.RemoveFinalizer(obj, r.Finalizer) + return ctrl.Result{}, r.Update(ctx, obj) + } + + if !controllerutil.ContainsFinalizer(obj, r.Finalizer) { + controllerutil.AddFinalizer(obj, r.Finalizer) + if err := r.Update(ctx, obj); err != nil { + logger.Errorf("[kopper] failed to update finalizers %s: %v", resourceName, err) + return ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}, err + } + } + + if err := r.OnUpsertFunc(obj); err != nil { + logger.Errorf("[kopper] failed to upsert %s: %v", resourceName, err) + return ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}, err + } + + logger.Infof("[kopper] upserted %s", resourceName) + return ctrl.Result{}, nil + +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler[T, PT]) SetupWithManager(mgr ctrl.Manager) error { + pObj := PT(new(T)) + return ctrl.NewControllerManagedBy(mgr). + For(pObj). + Complete(r) +}