-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: feature gate for ProxyAllNamespaced (#389)
Signed-off-by: Dario Tranchitella <[email protected]>
- Loading branch information
1 parent
38a2445
commit 777cc57
Showing
10 changed files
with
541 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.