Skip to content

Commit 0c219e7

Browse files
committed
✨ Allow configuring a default cache selector
It is already possible to configure cache selectors per gvk, but it is not possible to default this selector for all types. This change adds that.
1 parent c73b143 commit 0c219e7

File tree

4 files changed

+132
-4
lines changed

4 files changed

+132
-4
lines changed

pkg/cache/cache.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"time"
2323

2424
"k8s.io/apimachinery/pkg/api/meta"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/runtime"
2627
"k8s.io/apimachinery/pkg/runtime/schema"
2728
"k8s.io/client-go/kubernetes/scheme"
@@ -90,8 +91,22 @@ type Informer interface {
9091
type ObjectSelector internal.Selector
9192

9293
// SelectorsByObject associate a client.Object's GVK to a field/label selector.
94+
// Use SelectorsByObjectDefaultKey as key to specify a default selector that
95+
// will be used for all types that do not a selector configured.
9396
type SelectorsByObject map[client.Object]ObjectSelector
9497

98+
// SelectorsByObjectDefaultKey can be used in SelectorsByObject to configure a default
99+
// selector for all kinds that do not have a specific selector set up.
100+
type SelectorsByObjectDefaultKey struct{ metav1.Object }
101+
102+
// GetObjectKind implements runtime.Object.
103+
func (s *SelectorsByObjectDefaultKey) GetObjectKind() schema.ObjectKind {
104+
return schema.EmptyObjectKind
105+
}
106+
107+
// DeepCopyObject implements runtime.Object.
108+
func (s *SelectorsByObjectDefaultKey) DeepCopyObject() runtime.Object { return s }
109+
95110
// Options are the optional arguments for creating a new InformersMap object.
96111
type Options struct {
97112
// Scheme is the scheme to use for mapping objects to GroupVersionKinds
@@ -197,6 +212,10 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
197212
func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, scheme *runtime.Scheme) (internal.SelectorsByGVK, error) {
198213
selectorsByGVK := internal.SelectorsByGVK{}
199214
for object, selector := range selectorsByObject {
215+
if _, isDefault := object.(*SelectorsByObjectDefaultKey); isDefault {
216+
selectorsByGVK[schema.GroupVersionKind{}] = internal.Selector(selector)
217+
continue
218+
}
200219
gvk, err := apiutil.GVKForObject(object, scheme)
201220
if err != nil {
202221
return nil, err

pkg/cache/cache_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"reflect"
2323
"sort"
24+
"strconv"
2425

2526
. "github.com/onsi/ginkgo"
2627
. "github.com/onsi/ginkgo/extensions/table"
@@ -73,6 +74,33 @@ func createPodWithLabels(name, namespace string, restartPolicy corev1.RestartPol
7374
return pod
7475
}
7576

77+
func createSvc(name, namespace string, cl client.Client) client.Object {
78+
svc := &corev1.Service{
79+
ObjectMeta: metav1.ObjectMeta{
80+
Name: name,
81+
Namespace: namespace,
82+
},
83+
Spec: corev1.ServiceSpec{
84+
Ports: []corev1.ServicePort{{Port: 1}},
85+
},
86+
}
87+
err := cl.Create(context.Background(), svc)
88+
Expect(err).NotTo(HaveOccurred())
89+
return svc
90+
}
91+
92+
func createSA(name, namespace string, cl client.Client) client.Object {
93+
sa := &corev1.ServiceAccount{
94+
ObjectMeta: metav1.ObjectMeta{
95+
Name: name,
96+
Namespace: namespace,
97+
},
98+
}
99+
err := cl.Create(context.Background(), sa)
100+
Expect(err).NotTo(HaveOccurred())
101+
return sa
102+
}
103+
76104
func createPod(name, namespace string, restartPolicy corev1.RestartPolicy) client.Object {
77105
return createPodWithLabels(name, namespace, restartPolicy, nil)
78106
}
@@ -93,6 +121,76 @@ var _ = Describe("Multi-Namespace Informer Cache", func() {
93121
var _ = Describe("Informer Cache without DeepCopy", func() {
94122
CacheTest(cache.New, cache.Options{UnsafeDisableDeepCopyByObject: cache.DisableDeepCopyByObject{cache.ObjectAll{}: true}})
95123
})
124+
var _ = Describe("Cache with selectors", func() {
125+
defer GinkgoRecover()
126+
var (
127+
informerCache cache.Cache
128+
informerCacheCtx context.Context
129+
informerCacheCancel context.CancelFunc
130+
)
131+
132+
BeforeEach(func() {
133+
informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
134+
Expect(cfg).NotTo(BeNil())
135+
cl, err := client.New(cfg, client.Options{})
136+
Expect(err).NotTo(HaveOccurred())
137+
err = ensureNamespace(testNamespaceOne, cl)
138+
Expect(err).NotTo(HaveOccurred())
139+
err = ensureNamespace(testNamespaceTwo, cl)
140+
Expect(err).NotTo(HaveOccurred())
141+
for idx, namespace := range []string{testNamespaceOne, testNamespaceTwo} {
142+
_ = createSA("test-sa-"+strconv.Itoa(idx), namespace, cl)
143+
_ = createSvc("test-svc-"+strconv.Itoa(idx), namespace, cl)
144+
}
145+
146+
opts := cache.Options{
147+
SelectorsByObject: cache.SelectorsByObject{
148+
&corev1.ServiceAccount{}: {Field: fields.OneTermEqualSelector("metadata.namespace", testNamespaceOne)},
149+
&cache.SelectorsByObjectDefaultKey{}: {Field: fields.OneTermEqualSelector("metadata.namespace", testNamespaceTwo)},
150+
},
151+
}
152+
153+
By("creating the informer cache")
154+
informerCache, err = cache.New(cfg, opts)
155+
Expect(err).NotTo(HaveOccurred())
156+
By("running the cache and waiting for it to sync")
157+
// pass as an arg so that we don't race between close and re-assign
158+
go func(ctx context.Context) {
159+
defer GinkgoRecover()
160+
Expect(informerCache.Start(ctx)).To(Succeed())
161+
}(informerCacheCtx)
162+
Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
163+
})
164+
165+
AfterEach(func() {
166+
ctx := context.Background()
167+
cl, err := client.New(cfg, client.Options{})
168+
Expect(err).NotTo(HaveOccurred())
169+
for idx, namespace := range []string{testNamespaceOne, testNamespaceTwo} {
170+
err = cl.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-sa-" + strconv.Itoa(idx)}})
171+
Expect(err).NotTo(HaveOccurred())
172+
err = cl.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-svc-" + strconv.Itoa(idx)}})
173+
Expect(err).NotTo(HaveOccurred())
174+
}
175+
informerCacheCancel()
176+
})
177+
178+
It("Should list serviceaccounts and find exactly one in namespace "+testNamespaceOne, func() {
179+
var sas corev1.ServiceAccountList
180+
err := informerCache.List(informerCacheCtx, &sas)
181+
Expect(err).NotTo(HaveOccurred())
182+
Expect(len(sas.Items)).To(Equal(1))
183+
Expect(sas.Items[0].Namespace).To(Equal(testNamespaceOne))
184+
})
185+
186+
It("Should list services and find exactly one in namespace "+testNamespaceTwo, func() {
187+
var svcs corev1.ServiceList
188+
err := informerCache.List(informerCacheCtx, &svcs)
189+
Expect(err).NotTo(HaveOccurred())
190+
Expect(len(svcs.Items)).To(Equal(1))
191+
Expect(svcs.Items[0].Namespace).To(Equal(testNamespaceTwo))
192+
})
193+
})
96194

97195
func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (cache.Cache, error), opts cache.Options) {
98196
Describe("Cache test", func() {

pkg/cache/internal/informers_map.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,19 +277,19 @@ func createStructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformer
277277
// Create a new ListWatch for the obj
278278
return &cache.ListWatch{
279279
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
280-
ip.selectors[gvk].ApplyToList(&opts)
280+
ip.selectors.forGVK(gvk).ApplyToList(&opts)
281281
res := listObj.DeepCopyObject()
282-
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors[gvk])
282+
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors.forGVK(gvk))
283283
isNamespaceScoped := namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot
284284
err := client.Get().NamespaceIfScoped(namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do(ctx).Into(res)
285285
return res, err
286286
},
287287
// Setup the watch function
288288
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
289-
ip.selectors[gvk].ApplyToList(&opts)
289+
ip.selectors.forGVK(gvk).ApplyToList(&opts)
290290
// Watch needs to be set to true separately
291291
opts.Watch = true
292-
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors[gvk])
292+
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors.forGVK(gvk))
293293
isNamespaceScoped := namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot
294294
return client.Get().NamespaceIfScoped(namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Watch(ctx)
295295
},

pkg/cache/internal/selector.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ import (
2626
// SelectorsByGVK associate a GroupVersionKind to a field/label selector.
2727
type SelectorsByGVK map[schema.GroupVersionKind]Selector
2828

29+
func (s SelectorsByGVK) forGVK(gvk schema.GroupVersionKind) Selector {
30+
if specific, found := s[gvk]; found {
31+
return specific
32+
}
33+
if defaultSelector, found := s[schema.GroupVersionKind{}]; found {
34+
return defaultSelector
35+
}
36+
37+
return Selector{}
38+
}
39+
2940
// Selector specify the label/field selector to fill in ListOptions.
3041
type Selector struct {
3142
Label labels.Selector

0 commit comments

Comments
 (0)