Skip to content

Commit 7e7bf8c

Browse files
authored
Merge pull request #1710 from alvaroaleman/default-selector
✨ Allow configuring a default cache selector
2 parents 8f43f26 + 0b60488 commit 7e7bf8c

File tree

4 files changed

+122
-6
lines changed

4 files changed

+122
-6
lines changed

pkg/cache/cache.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ type Informer interface {
9090
type ObjectSelector internal.Selector
9191

9292
// SelectorsByObject associate a client.Object's GVK to a field/label selector.
93+
// There is also `DefaultSelector` to set a global default (which will be overridden by
94+
// a more specific setting here, if any).
9395
type SelectorsByObject map[client.Object]ObjectSelector
9496

9597
// Options are the optional arguments for creating a new InformersMap object.
@@ -117,6 +119,10 @@ type Options struct {
117119
// [2] https://pkg.go.dev/k8s.io/apimachinery/pkg/fields#Set
118120
SelectorsByObject SelectorsByObject
119121

122+
// DefaultSelector will be used as selectors for all object types
123+
// that do not have a selector in SelectorsByObject defined.
124+
DefaultSelector ObjectSelector
125+
120126
// UnsafeDisableDeepCopyByObject indicates not to deep copy objects during get or
121127
// list objects per GVK at the specified object.
122128
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
@@ -132,7 +138,7 @@ func New(config *rest.Config, opts Options) (Cache, error) {
132138
if err != nil {
133139
return nil, err
134140
}
135-
selectorsByGVK, err := convertToSelectorsByGVK(opts.SelectorsByObject, opts.Scheme)
141+
selectorsByGVK, err := convertToSelectorsByGVK(opts.SelectorsByObject, opts.DefaultSelector, opts.Scheme)
136142
if err != nil {
137143
return nil, err
138144
}
@@ -194,7 +200,7 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
194200
return opts, nil
195201
}
196202

197-
func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, scheme *runtime.Scheme) (internal.SelectorsByGVK, error) {
203+
func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, defaultSelector ObjectSelector, scheme *runtime.Scheme) (internal.SelectorsByGVK, error) {
198204
selectorsByGVK := internal.SelectorsByGVK{}
199205
for object, selector := range selectorsByObject {
200206
gvk, err := apiutil.GVKForObject(object, scheme)
@@ -203,6 +209,7 @@ func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, scheme *runtim
203209
}
204210
selectorsByGVK[gvk] = internal.Selector(selector)
205211
}
212+
selectorsByGVK[schema.GroupVersionKind{}] = internal.Selector(defaultSelector)
206213
return selectorsByGVK, nil
207214
}
208215

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+
},
150+
DefaultSelector: cache.ObjectSelector{Field: fields.OneTermEqualSelector("metadata.namespace", testNamespaceTwo)},
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)