mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-20 13:06:51 +00:00
* chore * perfromance improvements Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * perfromance improvements Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> * feat(performance): removed duplicate client calls from all admission paths Signed-off-by: Oliver Baehler <oliver@sudo-i.net> --------- Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
376 lines
11 KiB
Go
376 lines
11 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package selectors_test
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/projectcapsule/capsule/pkg/runtime/selectors"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
)
|
|
|
|
func TestMatchesSelectors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
labels labels.Set
|
|
selectors []metav1.LabelSelector
|
|
want bool
|
|
}{
|
|
{
|
|
name: "empty selectors match",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "matching selector returns true",
|
|
labels: labels.Set{"env": "prod"},
|
|
selectors: []metav1.LabelSelector{{
|
|
MatchLabels: map[string]string{"env": "prod"},
|
|
}},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "any matching selector returns true",
|
|
labels: labels.Set{"env": "prod"},
|
|
selectors: []metav1.LabelSelector{
|
|
{MatchLabels: map[string]string{"env": "dev"}},
|
|
{MatchLabels: map[string]string{"env": "prod"}},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "invalid selector is skipped",
|
|
labels: labels.Set{"env": "prod"},
|
|
selectors: []metav1.LabelSelector{
|
|
{MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "env", Operator: "invalid"}}},
|
|
{MatchLabels: map[string]string{"env": "prod"}},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "no selector matches",
|
|
labels: labels.Set{"env": "prod"},
|
|
selectors: []metav1.LabelSelector{{
|
|
MatchLabels: map[string]string{"env": "dev"},
|
|
}},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if got := selectors.MatchesSelectors(tt.labels, tt.selectors); got != tt.want {
|
|
t.Fatalf("MatchesSelectors() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchesSelector(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("matches", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := selectors.MatchesSelector(labels.Set{"tier": "frontend"}, metav1.LabelSelector{
|
|
MatchLabels: map[string]string{"tier": "frontend"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("MatchesSelector() unexpected error: %v", err)
|
|
}
|
|
if !got {
|
|
t.Fatalf("MatchesSelector() = false, want true")
|
|
}
|
|
})
|
|
|
|
t.Run("does not match", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := selectors.MatchesSelector(labels.Set{"tier": "backend"}, metav1.LabelSelector{
|
|
MatchLabels: map[string]string{"tier": "frontend"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("MatchesSelector() unexpected error: %v", err)
|
|
}
|
|
if got {
|
|
t.Fatalf("MatchesSelector() = true, want false")
|
|
}
|
|
})
|
|
|
|
t.Run("invalid selector returns error", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := selectors.MatchesSelector(labels.Set{"tier": "backend"}, metav1.LabelSelector{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "tier", Operator: "invalid"}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("MatchesSelector() expected error")
|
|
}
|
|
if got {
|
|
t.Fatalf("MatchesSelector() = true, want false on error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNamespaceSelectorGetMatchingNamespaces(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
cl := fakeClient(
|
|
namespace("alpha", map[string]string{"team": "a"}),
|
|
namespace("beta", map[string]string{"team": "b"}),
|
|
namespace("gamma", nil),
|
|
)
|
|
|
|
t.Run("nil selector returns nil", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := (&selectors.NamespaceSelector{}).GetMatchingNamespaces(ctx, cl)
|
|
if err != nil {
|
|
t.Fatalf("GetMatchingNamespaces() unexpected error: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Fatalf("GetMatchingNamespaces() = %#v, want nil", got)
|
|
}
|
|
})
|
|
|
|
t.Run("filters namespaces by labels", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := (&selectors.NamespaceSelector{
|
|
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"team": "a"}},
|
|
}).GetMatchingNamespaces(ctx, cl)
|
|
if err != nil {
|
|
t.Fatalf("GetMatchingNamespaces() unexpected error: %v", err)
|
|
}
|
|
|
|
if names := namespaceNames(got); !reflect.DeepEqual(names, []string{"alpha"}) {
|
|
t.Fatalf("GetMatchingNamespaces() names = %#v, want alpha", names)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid selector returns error", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := (&selectors.NamespaceSelector{
|
|
LabelSelector: &metav1.LabelSelector{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "team", Operator: "invalid"}},
|
|
},
|
|
}).GetMatchingNamespaces(ctx, cl)
|
|
if err == nil {
|
|
t.Fatalf("GetMatchingNamespaces() expected error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetNamespacesMatchingSelectors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
cl := fakeClient(
|
|
namespace("zeta", map[string]string{"team": "a", "region": "eu"}),
|
|
namespace("alpha", map[string]string{"team": "a", "region": "us"}),
|
|
namespace("beta", map[string]string{"team": "b", "region": "eu"}),
|
|
)
|
|
counting := &listCountingReader{Reader: cl}
|
|
|
|
got, err := selectors.GetNamespacesMatchingSelectors(ctx, counting, []selectors.NamespaceSelector{
|
|
{LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"team": "a"}}},
|
|
{LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"region": "eu"}}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetNamespacesMatchingSelectors() unexpected error: %v", err)
|
|
}
|
|
|
|
if names := namespaceNames(got); !reflect.DeepEqual(names, []string{"alpha", "beta", "zeta"}) {
|
|
t.Fatalf("GetNamespacesMatchingSelectors() names = %#v, want sorted unique names", names)
|
|
}
|
|
if counting.listCalls != 1 {
|
|
t.Fatalf("GetNamespacesMatchingSelectors() list calls = %d, want 1", counting.listCalls)
|
|
}
|
|
|
|
gotNames, err := selectors.GetNamespacesMatchingSelectorsStrings(ctx, cl, []selectors.NamespaceSelector{
|
|
{LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"team": "a"}}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetNamespacesMatchingSelectorsStrings() unexpected error: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(gotNames, []string{"alpha", "zeta"}) {
|
|
t.Fatalf("GetNamespacesMatchingSelectorsStrings() = %#v, want alpha and zeta", gotNames)
|
|
}
|
|
|
|
empty, err := selectors.GetNamespacesMatchingSelectors(ctx, cl, nil)
|
|
if err != nil {
|
|
t.Fatalf("GetNamespacesMatchingSelectors() unexpected error for nil selectors: %v", err)
|
|
}
|
|
if empty != nil {
|
|
t.Fatalf("GetNamespacesMatchingSelectors() = %#v, want nil for no selectors", empty)
|
|
}
|
|
}
|
|
|
|
type listCountingReader struct {
|
|
client.Reader
|
|
|
|
listCalls int
|
|
}
|
|
|
|
func (r *listCountingReader) List(
|
|
ctx context.Context,
|
|
list client.ObjectList,
|
|
opts ...client.ListOption,
|
|
) error {
|
|
r.listCalls++
|
|
|
|
return r.Reader.List(ctx, list, opts...)
|
|
}
|
|
|
|
func TestSelectorWithNamespaceSelectorMatchObjects(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
cl := fakeClient(
|
|
namespace("allowed", map[string]string{"tenant": "one"}),
|
|
namespace("denied", map[string]string{"tenant": "two"}),
|
|
)
|
|
objects := []metav1.Object{
|
|
configMap("allowed", "match", map[string]string{"app": "api"}),
|
|
configMap("allowed", "skip-label", map[string]string{"app": "worker"}),
|
|
configMap("denied", "skip-namespace", map[string]string{"app": "api"}),
|
|
}
|
|
|
|
got, err := (&selectors.SelectorWithNamespaceSelector{
|
|
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "api"}},
|
|
NamespaceSelector: &selectors.NamespaceSelector{
|
|
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"tenant": "one"}},
|
|
},
|
|
}).MatchObjects(ctx, cl, objects)
|
|
if err != nil {
|
|
t.Fatalf("MatchObjects() unexpected error: %v", err)
|
|
}
|
|
|
|
if names := objectNames(got); !reflect.DeepEqual(names, []string{"match"}) {
|
|
t.Fatalf("MatchObjects() names = %#v, want match", names)
|
|
}
|
|
|
|
noNamespaceFilter, err := (&selectors.SelectorWithNamespaceSelector{
|
|
LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "api"}},
|
|
}).MatchObjects(ctx, cl, objects)
|
|
if err != nil {
|
|
t.Fatalf("MatchObjects() unexpected error without namespace selector: %v", err)
|
|
}
|
|
if names := objectNames(noNamespaceFilter); !reflect.DeepEqual(names, []string{"match", "skip-namespace"}) {
|
|
t.Fatalf("MatchObjects() names = %#v, want both app=api objects", names)
|
|
}
|
|
|
|
nilSelector, err := (*selectors.SelectorWithNamespaceSelector)(nil).MatchObjects(ctx, cl, objects)
|
|
if err != nil {
|
|
t.Fatalf("MatchObjects() unexpected error for nil selector: %v", err)
|
|
}
|
|
if nilSelector != nil {
|
|
t.Fatalf("MatchObjects() = %#v, want nil for nil receiver", nilSelector)
|
|
}
|
|
}
|
|
|
|
func TestListBySelectors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
cl := fakeClient(
|
|
configMap("ns-a", "one", map[string]string{"app": "api"}),
|
|
configMap("ns-a", "two", map[string]string{"app": "worker"}),
|
|
configMap("ns-b", "three", nil),
|
|
)
|
|
|
|
got, err := selectors.ListBySelectors[*corev1.ConfigMap](ctx, cl, &corev1.ConfigMapList{}, []*metav1.LabelSelector{
|
|
{MatchLabels: map[string]string{"app": "api"}},
|
|
{MatchLabels: map[string]string{"app": "worker"}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ListBySelectors() unexpected error: %v", err)
|
|
}
|
|
if names := configMapKeys(got); !reflect.DeepEqual(names, []string{"ns-a/one", "ns-a/two"}) {
|
|
t.Fatalf("ListBySelectors() = %#v, want matching config maps", names)
|
|
}
|
|
|
|
empty, err := selectors.ListBySelectors[*corev1.ConfigMap](ctx, cl, &corev1.ConfigMapList{}, nil)
|
|
if err != nil {
|
|
t.Fatalf("ListBySelectors() unexpected error for nil selectors: %v", err)
|
|
}
|
|
if empty != nil {
|
|
t.Fatalf("ListBySelectors() = %#v, want nil for nil selectors", empty)
|
|
}
|
|
|
|
empty, err = selectors.ListBySelectors[*corev1.ConfigMap](ctx, cl, &corev1.ConfigMapList{}, []*metav1.LabelSelector{nil})
|
|
if err != nil {
|
|
t.Fatalf("ListBySelectors() unexpected error for nil selector entry: %v", err)
|
|
}
|
|
if empty != nil {
|
|
t.Fatalf("ListBySelectors() = %#v, want nil when all selector entries are nil", empty)
|
|
}
|
|
|
|
if _, err = selectors.ListBySelectors[*corev1.ConfigMap](ctx, cl, nil, []*metav1.LabelSelector{{}}); err == nil {
|
|
t.Fatalf("ListBySelectors() expected error for nil list")
|
|
}
|
|
|
|
if _, err = selectors.ListBySelectors[*corev1.ConfigMap](ctx, cl, &corev1.ConfigMapList{}, []*metav1.LabelSelector{{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{{Key: "app", Operator: "invalid"}},
|
|
}}); err == nil {
|
|
t.Fatalf("ListBySelectors() expected error for invalid selector")
|
|
}
|
|
}
|
|
|
|
func fakeClient(objects ...client.Object) client.Client {
|
|
scheme := runtime.NewScheme()
|
|
if err := corev1.AddToScheme(scheme); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
|
|
}
|
|
|
|
func namespace(name string, lbls map[string]string) *corev1.Namespace {
|
|
return &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name, Labels: lbls}}
|
|
}
|
|
|
|
func configMap(namespace, name string, lbls map[string]string) *corev1.ConfigMap {
|
|
return &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name, Labels: lbls}}
|
|
}
|
|
|
|
func namespaceNames(namespaces []corev1.Namespace) []string {
|
|
names := make([]string, 0, len(namespaces))
|
|
for _, ns := range namespaces {
|
|
names = append(names, ns.Name)
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
func objectNames(objects []metav1.Object) []string {
|
|
names := make([]string, 0, len(objects))
|
|
for _, obj := range objects {
|
|
names = append(names, obj.GetName())
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
func configMapKeys(configMaps []*corev1.ConfigMap) []string {
|
|
names := make([]string, 0, len(configMaps))
|
|
for _, configMap := range configMaps {
|
|
names = append(names, configMap.Namespace+"/"+configMap.Name)
|
|
}
|
|
|
|
return names
|
|
}
|