From 45b2610f739122c67007f190771efe5b431b6de3 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 10 Nov 2023 18:34:10 +0100 Subject: [PATCH] WIP --- internal/controllers/interceptor.go | 24 ++++++- internal/controllers/watchdog/crds_watcher.go | 71 +++++++++++++++++++ internal/controllers/watchdog/dario.go | 35 +++++++++ internal/controllers/watchdog/interceptor.go | 38 +++++----- internal/modules/namespaced/api_prefixed.go | 59 +++++++++++++++ .../namespaced/{list.go => apis_prefixed.go} | 23 ++---- internal/webserver/webserver.go | 31 +++----- 7 files changed, 224 insertions(+), 57 deletions(-) create mode 100644 internal/controllers/watchdog/crds_watcher.go create mode 100644 internal/controllers/watchdog/dario.go create mode 100644 internal/modules/namespaced/api_prefixed.go rename internal/modules/namespaced/{list.go => apis_prefixed.go} (75%) diff --git a/internal/controllers/interceptor.go b/internal/controllers/interceptor.go index 77cbbb3f..2f2fd33f 100644 --- a/internal/controllers/interceptor.go +++ b/internal/controllers/interceptor.go @@ -1,7 +1,29 @@ package controllers -import "sigs.k8s.io/controller-runtime/pkg/client" +import ( + "context" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) type CapsuleInterceptor struct { Client client.Client } + +func (c CapsuleInterceptor) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + //TODO implement me + panic("implement me") +} + +func (c CapsuleInterceptor) SetupWithManager(mgr controllerruntime.Manager) error { + obj := unstructured.Unstructured{} + obj.SetGroupVersionKind() + + return controllerruntime.NewControllerManagedBy(mgr). + For(&capsulev1beta2.CapsuleConfiguration{}). + Complete(c) +} diff --git a/internal/controllers/watchdog/crds_watcher.go b/internal/controllers/watchdog/crds_watcher.go new file mode 100644 index 00000000..271f6879 --- /dev/null +++ b/internal/controllers/watchdog/crds_watcher.go @@ -0,0 +1,71 @@ +package watchdog + +import ( + "context" + + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type CRDWatcher struct { + client client.Client + + watchMap watchMap +} + +func (c *CRDWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + crd := apiextensions.CustomResourceDefinition{} + _ = c.client.Get(ctx, request.NamespacedName, &crd) + + for _, v := range crd.Spec.Versions { + gvk := v1.GroupVersionKind{ + Group: crd.Spec.Group, + Version: v.Name, + Kind: crd.Spec.Names.Kind, + } + + watched, ok := c.watchMap[gvk.String()] + if !ok { + scopedCtx, scopedCancelFn := context.WithCancel(ctx) + + mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: c.client.Scheme(), + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + }) + + _ = (&NamespacedWatcher{client: c.client}).SetupWithManager(mgr, gvk) + + go func() { + if err := mgr.Start(scopedCtx); err != nil { + // TODO: log me + } + }() + + c.watchMap[gvk.String()] = watchItem{ + cancelFn: scopedCancelFn, + } + } + + if crd.DeletionTimestamp != nil { + watched.cancelFn() + delete(c.watchMap, gvk.String()) + } + } + + return reconcile.Result{}, nil +} + +func (c *CRDWatcher) SetupWithManager(mgr manager.Manager) error { + c.watchMap = make(map[string]watchItem) + + return ctrl.NewControllerManagedBy(mgr). + For(&apiextensions.CustomResourceDefinition{}). + Complete(c) +} diff --git a/internal/controllers/watchdog/dario.go b/internal/controllers/watchdog/dario.go new file mode 100644 index 00000000..5ac49f35 --- /dev/null +++ b/internal/controllers/watchdog/dario.go @@ -0,0 +1,35 @@ +package watchdog + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" +) + +func main() { + + config := ctrl.GetConfigOrDie() + + mgr, err := ctrl.NewManager(config, ctrl.Options{ + Scheme: , + HealthProbeBindAddress: ":8081", + }) + + ctx, cancelFn := context.WithCancel(context.Background()) + + notifier := make(chan metav1.GroupVersionKind) + + go func() { + for { + select { + case <- notifier: + cancelFn() + } + } + }() + + + mgr.Start(ctx) + +} diff --git a/internal/controllers/watchdog/interceptor.go b/internal/controllers/watchdog/interceptor.go index 60b32bba..be7cbf54 100644 --- a/internal/controllers/watchdog/interceptor.go +++ b/internal/controllers/watchdog/interceptor.go @@ -1,32 +1,36 @@ package watchdog -import "context" +import ( + "context" -type NamespacedWatcher struct { - logger logr.Logger + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) - client client.Client - TriggerChannel chan event.GenericEvent +type NamespacedWatcher struct { + client client.Client } func (c *NamespacedWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + // LABEL ALL THE WORLD! return reconcile.Result{}, nil } -func (c *NamespacedWatcher) SetupWithManager(mgr manager.Manager) error { - c.logger = mgr.GetLogger().WithName("coredns") - c.TriggerChannel = make(chan event.GenericEvent) +func (c *NamespacedWatcher) SetupWithManager(mgr manager.Manager, gvk metav1.GroupVersionKind) error { + obj := unstructured.Unstructured{} + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + }) return controllerruntime.NewControllerManagedBy(mgr). - For(&rbacv1.ClusterRoleBinding{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { - return object.GetName() == kubeadm.CoreDNSClusterRoleBindingName - }))). - Watches(&source.Channel{Source: c.TriggerChannel}, &handler.EnqueueRequestForObject{}). - Owns(&rbacv1.ClusterRole{}). - Owns(&corev1.ServiceAccount{}). - Owns(&corev1.Service{}). - Owns(&corev1.ConfigMap{}). - Owns(&appsv1.Deployment{}). + For(&obj). Complete(c) } diff --git a/internal/modules/namespaced/api_prefixed.go b/internal/modules/namespaced/api_prefixed.go new file mode 100644 index 00000000..c593f0d6 --- /dev/null +++ b/internal/modules/namespaced/api_prefixed.go @@ -0,0 +1,59 @@ +package namespaced + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + + "github.com/projectcapsule/capsule-proxy/internal/modules" + "github.com/projectcapsule/capsule-proxy/internal/request" + "github.com/projectcapsule/capsule-proxy/internal/tenant" +) + +type apiPrefixed struct { + group string + version string + kind string +} + +func NewApiPrefixed(group string, version string, kind string) modules.Module { + return &apiPrefixed{group: group, version: version, kind: kind} +} + +func (p apiPrefixed) Path() string { + var parts []string + + if p.group != "" { + parts = append(parts, p.group) + } + + parts = append(parts, p.version) + parts = append(parts, p.kind) + + return fmt.Sprintf("/%s", strings.Join(parts, "/")) +} + +func (p apiPrefixed) Methods() []string { + return []string{"get"} +} + +func (p apiPrefixed) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { + var sourceTenants []string + + for _, tnt := range proxyTenants { + sourceTenants = append(sourceTenants, tnt.Tenant.Name) + } + + var r *labels.Requirement + + switch { + case len(sourceTenants) > 0: + r, err = labels.NewRequirement("capsule.clastix.io/managed-by", selection.In, sourceTenants) + default: + r, err = labels.NewRequirement("dontexistsignoreme", selection.Exists, []string{}) + } + + return labels.NewSelector().Add(*r), nil +} diff --git a/internal/modules/namespaced/list.go b/internal/modules/namespaced/apis_prefixed.go similarity index 75% rename from internal/modules/namespaced/list.go rename to internal/modules/namespaced/apis_prefixed.go index 4ddf2eeb..866a629f 100644 --- a/internal/modules/namespaced/list.go +++ b/internal/modules/namespaced/apis_prefixed.go @@ -4,42 +4,31 @@ package namespaced import ( - "fmt" - "github.com/go-logr/logr" - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcapsule/capsule-proxy/internal/modules" + "github.com/projectcapsule/capsule-proxy/internal/request" + "github.com/projectcapsule/capsule-proxy/internal/tenant" ) type list struct { client client.Reader log logr.Logger - gk schema.GroupVersionKind } -func List(resource schema.GroupVersionKind, client client.Reader) modules.Module { - +func ApisPrefixed(client client.Reader) modules.Module { return &list{ client: client, log: ctrl.Log.WithName("namespaced_list"), - gk: resource, } } func (l list) Path() string { - var path = "" - if l.gk.Group == "" { - path = fmt.Sprintf("/api/%s/{endpoint:%s/?}", l.gk.Version, l.gk.Kind) - } else { - path = fmt.Sprintf("/apis/%s/%s/{endpoint:%s/?}", l.gk.Group, l.gk.Version, l.gk.Kind) - } - return path + return "/apis/{group}/{version}/{kind}" } func (l list) Methods() []string { diff --git a/internal/webserver/webserver.go b/internal/webserver/webserver.go index 9fd5214e..07b3c363 100644 --- a/internal/webserver/webserver.go +++ b/internal/webserver/webserver.go @@ -19,11 +19,9 @@ import ( "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/pkg/errors" - "golang.org/x/exp/slices" "golang.org/x/net/http/httpguts" authenticationv1 "k8s.io/api/authentication/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/client-go/discovery" @@ -250,30 +248,19 @@ func (n *kubeFilter) registerModules(ctx context.Context, root *mux.Router) { panic(err.Error()) } - for _, list := range apiResourceLists { - for _, resource := range list.APIResources { - if resource.Namespaced && slices.Contains(resource.Verbs, "list") { - //modList = append(modList, namespaced.List(resource, n.client)) - groupVer := list.GroupVersion - parts := strings.SplitN(groupVer, "/", 2) - group := "" - version := parts[0] - if len(parts) > 1 { - group = parts[0] - version = parts[1] - } - - mod := namespaced.List(schema.GroupVersionKind{ - Group: group, - Version: version, - Kind: resource.Name, - }, n.reader) - fmt.Println(mod.Path()) - modList = append(modList, mod) + for _, ar := range apiResourceLists { + for _, i := range ar.APIResources { + if !i.Namespaced { + continue } + + modList = append(modList, namespaced.NewApiPrefixed(i.Group, i.Version, i.Kind)) } } + mod := namespaced.ApisPrefixed(n.reader) + modList = append(modList, mod) + for _, i := range modList { mod := i rp := root.Path(mod.Path())