Skip to content

Commit

Permalink
feat: feature gate for ProxyAllNamespaced (#389)
Browse files Browse the repository at this point in the history
Signed-off-by: Dario Tranchitella <[email protected]>
  • Loading branch information
prometherion authored Feb 20, 2024
1 parent 38a2445 commit 777cc57
Show file tree
Hide file tree
Showing 10 changed files with 541 additions and 38 deletions.
13 changes: 7 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ require (
go.uber.org/zap v1.26.0
golang.org/x/net v0.20.0
k8s.io/api v0.28.4
k8s.io/apiextensions-apiserver v0.28.4
k8s.io/apimachinery v0.28.4
k8s.io/apiserver v0.28.4
k8s.io/client-go v0.28.4
k8s.io/component-base v0.28.4
sigs.k8s.io/controller-runtime v0.16.3
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
Expand Down Expand Up @@ -63,12 +66,10 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.28.4 // indirect
k8s.io/component-base v0.28.4 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
20 changes: 11 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand Down Expand Up @@ -42,8 +44,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
Expand Down Expand Up @@ -352,17 +354,17 @@ k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
171 changes: 171 additions & 0 deletions internal/controllers/watchdog/crds_watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package watchdog

import (
"context"
"fmt"
"strings"

"github.com/pkg/errors"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)

type resourceManager struct {
cancelFn context.CancelFunc
watchedVersions sets.Set[string]
}

type watchMap map[string]resourceManager

type CRDWatcher struct {
Client client.Client
watchMap watchMap
requeue chan event.GenericEvent
}

func (c *CRDWatcher) keyFunction(group, kind string) string {
return fmt.Sprintf("%s-%s", group, kind)
}

func (c *CRDWatcher) register(ctx context.Context, group string, versions []string, kind string) error {
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: c.Client.Scheme(),
Metrics: metricsserver.Options{
BindAddress: "0",
},
})

watchedVersions := sets.New[string]()

for _, v := range versions {
watchedVersions.Insert(v)

gvk := metav1.GroupVersionKind{
Group: group,
Version: v,
Kind: kind,
}
//nolint:contextcheck
if err := (&NamespacedWatcher{Client: c.Client}).SetupWithManager(mgr, gvk); err != nil {
return err
}
}

scopedCtx, scopedCancelFn := context.WithCancel(ctx)

go func() {
if err := mgr.Start(scopedCtx); err != nil {
scopedCancelFn()
}
}()

c.watchMap[c.keyFunction(group, kind)] = resourceManager{
cancelFn: scopedCancelFn,
watchedVersions: watchedVersions,
}

return nil
}

func (c *CRDWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
crd := apiextensionsv1.CustomResourceDefinition{}
if err := c.Client.Get(ctx, request.NamespacedName, &crd); err != nil {
if k8serrors.IsNotFound(err) {
return reconcile.Result{}, nil
}

return reconcile.Result{}, err
}

key := c.keyFunction(crd.Spec.Group, crd.Spec.Names.Kind)

resourceMgr, found := c.watchMap[key]
if !found && crd.DeletionTimestamp != nil {
return reconcile.Result{}, nil
}

if !found && crd.DeletionTimestamp == nil {
versions := make([]string, 0, len(crd.Spec.Versions))

for _, v := range crd.Spec.Versions {
versions = append(versions, v.Name)
}

if err := c.register(ctx, crd.Spec.Group, versions, crd.Spec.Names.Kind); err != nil {
return reconcile.Result{}, err
}

resourceMgr = c.watchMap[key]
}

if crd.DeletionTimestamp != nil {
resourceMgr.cancelFn()
delete(c.watchMap, key)

return reconcile.Result{}, nil
}

for _, v := range crd.Spec.Versions {
if !resourceMgr.watchedVersions.Has(v.Name) {
resourceMgr.cancelFn()
delete(c.watchMap, key)

return reconcile.Result{Requeue: true}, nil
}
}

return reconcile.Result{}, nil
}

func (c *CRDWatcher) SetupWithManager(ctx context.Context, mgr manager.Manager) error {
c.watchMap = make(map[string]resourceManager)
c.requeue = make(chan event.GenericEvent)

apis, err := API(mgr.GetConfig())
if err != nil {
return err
}

bundleGroupAndKind := map[string]sets.Set[string]{}

for _, api := range apis {
slashedName := fmt.Sprintf("%s/%s", api.Group, api.Kind)

if _, ok := bundleGroupAndKind[slashedName]; !ok {
bundleGroupAndKind[slashedName] = sets.Set[string]{}
}

bundleGroupAndKind[slashedName].Insert(api.Version)
}

for group, versions := range bundleGroupAndKind {
parts := strings.Split(group, "/")

apiGroup, apiKind := parts[0], parts[1]

if registerErr := c.register(ctx, apiGroup, versions.UnsortedList(), apiKind); registerErr != nil {
return errors.Wrap(err, "cannot register watcher prior to start-up")
}
}

return ctrl.NewControllerManagedBy(mgr).
WatchesRawSource(&source.Channel{Source: c.requeue}, &handler.EnqueueRequestForObject{}).
For(&apiextensionsv1.CustomResourceDefinition{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
crd := object.(*apiextensionsv1.CustomResourceDefinition)

return crd.Spec.Scope == apiextensionsv1.NamespaceScoped
}))).
Complete(c)
}
96 changes: 96 additions & 0 deletions internal/controllers/watchdog/namespaced_watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package watchdog

import (
"context"

capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
log2 "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

capsulelabels "github.com/projectcapsule/capsule-proxy/internal/labels"
)

type NamespacedWatcher struct {
Client client.Client

object *unstructured.Unstructured
}

func (c *NamespacedWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
log := log2.FromContext(ctx)

obj := c.object.DeepCopy()
obj.SetName(request.Name)
obj.SetNamespace(request.Namespace)

tntList := capsulev1beta2.TenantList{}
if err := c.Client.List(ctx, &tntList, client.MatchingFields{".status.namespaces": obj.GetNamespace()}); err != nil {
log.Error(err, "cannot list unstructured object")

return reconcile.Result{}, err
}

if len(tntList.Items) == 0 {
return reconcile.Result{}, nil
}

if err := c.Client.Get(ctx, request.NamespacedName, obj); err != nil {
if k8serrors.IsNotFound(err) {
return reconcile.Result{}, nil
}

log.Error(err, "cannot retrieve object")

return reconcile.Result{}, err
}

_, err := controllerutil.CreateOrUpdate(ctx, c.Client, obj, func() error {
labels := obj.GetLabels()
if labels == nil {
labels = map[string]string{}
}

labels[capsulelabels.ManagedByCapsuleLabel] = tntList.Items[0].Name
obj.SetLabels(labels)

return nil
})

return reconcile.Result{}, err
}

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,
})

c.object = obj.DeepCopy()

return controllerruntime.NewControllerManagedBy(mgr).
For(&obj, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
ns := corev1.Namespace{}
_ = c.Client.Get(context.Background(), types.NamespacedName{Name: object.GetNamespace()}, &ns)

if len(ns.GetOwnerReferences()) > 0 && ns.GetOwnerReferences()[0].Kind == "Tenant" {
return true
}

return false
}))).
Complete(c)
}
Loading

0 comments on commit 777cc57

Please sign in to comment.