Skip to content

Commit 777cc57

Browse files
authored
feat: feature gate for ProxyAllNamespaced (#389)
Signed-off-by: Dario Tranchitella <[email protected]>
1 parent 38a2445 commit 777cc57

File tree

10 files changed

+541
-38
lines changed

10 files changed

+541
-38
lines changed

go.mod

+7-6
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ require (
1515
go.uber.org/zap v1.26.0
1616
golang.org/x/net v0.20.0
1717
k8s.io/api v0.28.4
18+
k8s.io/apiextensions-apiserver v0.28.4
1819
k8s.io/apimachinery v0.28.4
1920
k8s.io/apiserver v0.28.4
2021
k8s.io/client-go v0.28.4
22+
k8s.io/component-base v0.28.4
2123
sigs.k8s.io/controller-runtime v0.16.3
2224
)
2325

2426
require (
2527
github.com/beorn7/perks v1.0.1 // indirect
28+
github.com/blang/semver/v4 v4.0.0 // indirect
2629
github.com/cespare/xxhash/v2 v2.2.0 // indirect
2730
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2831
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
@@ -63,12 +66,10 @@ require (
6366
gopkg.in/inf.v0 v0.9.1 // indirect
6467
gopkg.in/yaml.v2 v2.4.0 // indirect
6568
gopkg.in/yaml.v3 v3.0.1 // indirect
66-
k8s.io/apiextensions-apiserver v0.28.4 // indirect
67-
k8s.io/component-base v0.28.4 // indirect
68-
k8s.io/klog/v2 v2.100.1 // indirect
69-
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
70-
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
69+
k8s.io/klog/v2 v2.110.1 // indirect
70+
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
71+
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
7172
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
72-
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
73+
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
7374
sigs.k8s.io/yaml v1.4.0 // indirect
7475
)

go.sum

+11-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
99
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
1010
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1111
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
12+
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
13+
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
1214
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
1315
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
1416
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -42,8 +44,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
4244
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
4345
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
4446
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
45-
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
4647
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
48+
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
4749
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
4850
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
4951
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
@@ -352,17 +354,17 @@ k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
352354
k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
353355
k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
354356
k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
355-
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
356-
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
357-
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
358-
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
359-
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
360-
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
357+
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
358+
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
359+
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
360+
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
361+
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
362+
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
361363
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
362364
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
363365
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
364366
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
365-
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
366-
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
367+
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
368+
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
367369
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
368370
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package watchdog
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/pkg/errors"
9+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/util/sets"
13+
ctrl "sigs.k8s.io/controller-runtime"
14+
"sigs.k8s.io/controller-runtime/pkg/builder"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/event"
17+
"sigs.k8s.io/controller-runtime/pkg/handler"
18+
"sigs.k8s.io/controller-runtime/pkg/manager"
19+
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
20+
"sigs.k8s.io/controller-runtime/pkg/predicate"
21+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22+
"sigs.k8s.io/controller-runtime/pkg/source"
23+
)
24+
25+
type resourceManager struct {
26+
cancelFn context.CancelFunc
27+
watchedVersions sets.Set[string]
28+
}
29+
30+
type watchMap map[string]resourceManager
31+
32+
type CRDWatcher struct {
33+
Client client.Client
34+
watchMap watchMap
35+
requeue chan event.GenericEvent
36+
}
37+
38+
func (c *CRDWatcher) keyFunction(group, kind string) string {
39+
return fmt.Sprintf("%s-%s", group, kind)
40+
}
41+
42+
func (c *CRDWatcher) register(ctx context.Context, group string, versions []string, kind string) error {
43+
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
44+
Scheme: c.Client.Scheme(),
45+
Metrics: metricsserver.Options{
46+
BindAddress: "0",
47+
},
48+
})
49+
50+
watchedVersions := sets.New[string]()
51+
52+
for _, v := range versions {
53+
watchedVersions.Insert(v)
54+
55+
gvk := metav1.GroupVersionKind{
56+
Group: group,
57+
Version: v,
58+
Kind: kind,
59+
}
60+
//nolint:contextcheck
61+
if err := (&NamespacedWatcher{Client: c.Client}).SetupWithManager(mgr, gvk); err != nil {
62+
return err
63+
}
64+
}
65+
66+
scopedCtx, scopedCancelFn := context.WithCancel(ctx)
67+
68+
go func() {
69+
if err := mgr.Start(scopedCtx); err != nil {
70+
scopedCancelFn()
71+
}
72+
}()
73+
74+
c.watchMap[c.keyFunction(group, kind)] = resourceManager{
75+
cancelFn: scopedCancelFn,
76+
watchedVersions: watchedVersions,
77+
}
78+
79+
return nil
80+
}
81+
82+
func (c *CRDWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
83+
crd := apiextensionsv1.CustomResourceDefinition{}
84+
if err := c.Client.Get(ctx, request.NamespacedName, &crd); err != nil {
85+
if k8serrors.IsNotFound(err) {
86+
return reconcile.Result{}, nil
87+
}
88+
89+
return reconcile.Result{}, err
90+
}
91+
92+
key := c.keyFunction(crd.Spec.Group, crd.Spec.Names.Kind)
93+
94+
resourceMgr, found := c.watchMap[key]
95+
if !found && crd.DeletionTimestamp != nil {
96+
return reconcile.Result{}, nil
97+
}
98+
99+
if !found && crd.DeletionTimestamp == nil {
100+
versions := make([]string, 0, len(crd.Spec.Versions))
101+
102+
for _, v := range crd.Spec.Versions {
103+
versions = append(versions, v.Name)
104+
}
105+
106+
if err := c.register(ctx, crd.Spec.Group, versions, crd.Spec.Names.Kind); err != nil {
107+
return reconcile.Result{}, err
108+
}
109+
110+
resourceMgr = c.watchMap[key]
111+
}
112+
113+
if crd.DeletionTimestamp != nil {
114+
resourceMgr.cancelFn()
115+
delete(c.watchMap, key)
116+
117+
return reconcile.Result{}, nil
118+
}
119+
120+
for _, v := range crd.Spec.Versions {
121+
if !resourceMgr.watchedVersions.Has(v.Name) {
122+
resourceMgr.cancelFn()
123+
delete(c.watchMap, key)
124+
125+
return reconcile.Result{Requeue: true}, nil
126+
}
127+
}
128+
129+
return reconcile.Result{}, nil
130+
}
131+
132+
func (c *CRDWatcher) SetupWithManager(ctx context.Context, mgr manager.Manager) error {
133+
c.watchMap = make(map[string]resourceManager)
134+
c.requeue = make(chan event.GenericEvent)
135+
136+
apis, err := API(mgr.GetConfig())
137+
if err != nil {
138+
return err
139+
}
140+
141+
bundleGroupAndKind := map[string]sets.Set[string]{}
142+
143+
for _, api := range apis {
144+
slashedName := fmt.Sprintf("%s/%s", api.Group, api.Kind)
145+
146+
if _, ok := bundleGroupAndKind[slashedName]; !ok {
147+
bundleGroupAndKind[slashedName] = sets.Set[string]{}
148+
}
149+
150+
bundleGroupAndKind[slashedName].Insert(api.Version)
151+
}
152+
153+
for group, versions := range bundleGroupAndKind {
154+
parts := strings.Split(group, "/")
155+
156+
apiGroup, apiKind := parts[0], parts[1]
157+
158+
if registerErr := c.register(ctx, apiGroup, versions.UnsortedList(), apiKind); registerErr != nil {
159+
return errors.Wrap(err, "cannot register watcher prior to start-up")
160+
}
161+
}
162+
163+
return ctrl.NewControllerManagedBy(mgr).
164+
WatchesRawSource(&source.Channel{Source: c.requeue}, &handler.EnqueueRequestForObject{}).
165+
For(&apiextensionsv1.CustomResourceDefinition{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
166+
crd := object.(*apiextensionsv1.CustomResourceDefinition)
167+
168+
return crd.Spec.Scope == apiextensionsv1.NamespaceScoped
169+
}))).
170+
Complete(c)
171+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package watchdog
2+
3+
import (
4+
"context"
5+
6+
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
7+
corev1 "k8s.io/api/core/v1"
8+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"k8s.io/apimachinery/pkg/runtime/schema"
12+
"k8s.io/apimachinery/pkg/types"
13+
controllerruntime "sigs.k8s.io/controller-runtime"
14+
"sigs.k8s.io/controller-runtime/pkg/builder"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
17+
log2 "sigs.k8s.io/controller-runtime/pkg/log"
18+
"sigs.k8s.io/controller-runtime/pkg/manager"
19+
"sigs.k8s.io/controller-runtime/pkg/predicate"
20+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
21+
22+
capsulelabels "github.com/projectcapsule/capsule-proxy/internal/labels"
23+
)
24+
25+
type NamespacedWatcher struct {
26+
Client client.Client
27+
28+
object *unstructured.Unstructured
29+
}
30+
31+
func (c *NamespacedWatcher) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
32+
log := log2.FromContext(ctx)
33+
34+
obj := c.object.DeepCopy()
35+
obj.SetName(request.Name)
36+
obj.SetNamespace(request.Namespace)
37+
38+
tntList := capsulev1beta2.TenantList{}
39+
if err := c.Client.List(ctx, &tntList, client.MatchingFields{".status.namespaces": obj.GetNamespace()}); err != nil {
40+
log.Error(err, "cannot list unstructured object")
41+
42+
return reconcile.Result{}, err
43+
}
44+
45+
if len(tntList.Items) == 0 {
46+
return reconcile.Result{}, nil
47+
}
48+
49+
if err := c.Client.Get(ctx, request.NamespacedName, obj); err != nil {
50+
if k8serrors.IsNotFound(err) {
51+
return reconcile.Result{}, nil
52+
}
53+
54+
log.Error(err, "cannot retrieve object")
55+
56+
return reconcile.Result{}, err
57+
}
58+
59+
_, err := controllerutil.CreateOrUpdate(ctx, c.Client, obj, func() error {
60+
labels := obj.GetLabels()
61+
if labels == nil {
62+
labels = map[string]string{}
63+
}
64+
65+
labels[capsulelabels.ManagedByCapsuleLabel] = tntList.Items[0].Name
66+
obj.SetLabels(labels)
67+
68+
return nil
69+
})
70+
71+
return reconcile.Result{}, err
72+
}
73+
74+
func (c *NamespacedWatcher) SetupWithManager(mgr manager.Manager, gvk metav1.GroupVersionKind) error {
75+
obj := unstructured.Unstructured{}
76+
obj.SetGroupVersionKind(schema.GroupVersionKind{
77+
Group: gvk.Group,
78+
Version: gvk.Version,
79+
Kind: gvk.Kind,
80+
})
81+
82+
c.object = obj.DeepCopy()
83+
84+
return controllerruntime.NewControllerManagedBy(mgr).
85+
For(&obj, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
86+
ns := corev1.Namespace{}
87+
_ = c.Client.Get(context.Background(), types.NamespacedName{Name: object.GetNamespace()}, &ns)
88+
89+
if len(ns.GetOwnerReferences()) > 0 && ns.GetOwnerReferences()[0].Kind == "Tenant" {
90+
return true
91+
}
92+
93+
return false
94+
}))).
95+
Complete(c)
96+
}

0 commit comments

Comments
 (0)