Skip to content

Commit 7c02ed9

Browse files
committed
cache: add Options.AdditionalFieldIndexers
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent b1507b9 commit 7c02ed9

File tree

4 files changed

+90
-70
lines changed

4 files changed

+90
-70
lines changed

pkg/cache/cache.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ type Options struct {
203203
// DefaultTransform will be used as transform for all object types
204204
// unless there is already one set in ByObject or DefaultNamespaces.
205205
DefaultTransform toolscache.TransformFunc
206+
// AdditionalDefaultIndexes are indexes that are added to every informer
207+
// beyond the namespace-name and namespace ones.
208+
AdditionalDefaultIndexes client.Indexers
206209

207210
// DefaultWatchErrorHandler will be used to the WatchErrorHandler which is called
208211
// whenever ListAndWatch drops the connection with an error.
@@ -224,8 +227,10 @@ type Options struct {
224227
// If unset, this will fall through to the Default* settings.
225228
ByObject map[client.Object]ByObject
226229

227-
// newInformer allows overriding of NewSharedIndexInformer for testing.
228-
newInformer *func(toolscache.ListerWatcher, runtime.Object, time.Duration, toolscache.Indexers) toolscache.SharedIndexInformer
230+
// NewInformer allows to override NewSharedIndexInformer.
231+
// NOTE: LOW LEVEL PRIMITIVE!
232+
// Only use a custom informer if you know what you are doing.
233+
NewInformer func(toolscache.ListerWatcher, runtime.Object, time.Duration, toolscache.Indexers) toolscache.SharedIndexInformer
229234
}
230235

231236
// ByObject offers more fine-grained control over the cache's ListWatch by object.
@@ -379,10 +384,11 @@ func newCache(restConfig *rest.Config, opts Options) newCacheFunc {
379384
Label: config.LabelSelector,
380385
Field: config.FieldSelector,
381386
},
382-
Transform: config.Transform,
383-
WatchErrorHandler: opts.DefaultWatchErrorHandler,
384-
UnsafeDisableDeepCopy: ptr.Deref(config.UnsafeDisableDeepCopy, false),
385-
NewInformer: opts.newInformer,
387+
Transform: config.Transform,
388+
WatchErrorHandler: opts.DefaultWatchErrorHandler,
389+
UnsafeDisableDeepCopy: ptr.Deref(config.UnsafeDisableDeepCopy, false),
390+
NewInformer: opts.NewInformer,
391+
AdditionalDefaultIndexes: opts.AdditionalDefaultIndexes,
386392
}),
387393
readerFailOnMissingInformer: opts.ReaderFailOnMissingInformer,
388394
}

pkg/cache/informer_cache.go

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"fmt"
2222
"strings"
2323

24-
apimeta "k8s.io/apimachinery/pkg/api/meta"
2524
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2625
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2726
"k8s.io/apimachinery/pkg/runtime"
@@ -221,40 +220,5 @@ func (ic *informerCache) IndexField(ctx context.Context, obj client.Object, fiel
221220
}
222221

223222
func indexByField(informer Informer, field string, extractValue client.IndexerFunc) error {
224-
indexFunc := func(objRaw interface{}) ([]string, error) {
225-
// TODO(directxman12): check if this is the correct type?
226-
obj, isObj := objRaw.(client.Object)
227-
if !isObj {
228-
return nil, fmt.Errorf("object of type %T is not an Object", objRaw)
229-
}
230-
meta, err := apimeta.Accessor(obj)
231-
if err != nil {
232-
return nil, err
233-
}
234-
ns := meta.GetNamespace()
235-
236-
rawVals := extractValue(obj)
237-
var vals []string
238-
if ns == "" {
239-
// if we're not doubling the keys for the namespaced case, just create a new slice with same length
240-
vals = make([]string, len(rawVals))
241-
} else {
242-
// if we need to add non-namespaced versions too, double the length
243-
vals = make([]string, len(rawVals)*2)
244-
}
245-
for i, rawVal := range rawVals {
246-
// save a namespaced variant, so that we can ask
247-
// "what are all the object matching a given index *in a given namespace*"
248-
vals[i] = internal.KeyToNamespacedKey(ns, rawVal)
249-
if ns != "" {
250-
// if we have a namespace, also inject a special index key for listing
251-
// regardless of the object namespace
252-
vals[i+len(rawVals)] = internal.KeyToNamespacedKey("", rawVal)
253-
}
254-
}
255-
256-
return vals, nil
257-
}
258-
259-
return informer.AddIndexers(cache.Indexers{internal.FieldIndexName(field): indexFunc})
223+
return informer.AddIndexers(cache.Indexers{internal.FieldIndexName(field): internal.IndexFunc(extractValue)})
260224
}

pkg/cache/internal/informers.go

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,31 @@ import (
3535
"k8s.io/client-go/metadata"
3636
"k8s.io/client-go/rest"
3737
"k8s.io/client-go/tools/cache"
38+
"sigs.k8s.io/controller-runtime/pkg/client"
3839
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3940
"sigs.k8s.io/controller-runtime/pkg/internal/syncs"
4041
)
4142

4243
// InformersOpts configures an InformerMap.
4344
type InformersOpts struct {
44-
HTTPClient *http.Client
45-
Scheme *runtime.Scheme
46-
Mapper meta.RESTMapper
47-
ResyncPeriod time.Duration
48-
Namespace string
49-
NewInformer *func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
50-
Selector Selector
51-
Transform cache.TransformFunc
52-
UnsafeDisableDeepCopy bool
53-
WatchErrorHandler cache.WatchErrorHandler
45+
HTTPClient *http.Client
46+
Scheme *runtime.Scheme
47+
Mapper meta.RESTMapper
48+
ResyncPeriod time.Duration
49+
Namespace string
50+
NewInformer func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
51+
AdditionalDefaultIndexes client.Indexers
52+
Selector Selector
53+
Transform cache.TransformFunc
54+
UnsafeDisableDeepCopy bool
55+
WatchErrorHandler cache.WatchErrorHandler
5456
}
5557

5658
// NewInformers creates a new InformersMap that can create informers under the hood.
5759
func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
5860
newInformer := cache.NewSharedIndexInformer
5961
if options.NewInformer != nil {
60-
newInformer = *options.NewInformer
62+
newInformer = options.NewInformer
6163
}
6264
return &Informers{
6365
config: config,
@@ -69,16 +71,17 @@ func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
6971
Unstructured: make(map[schema.GroupVersionKind]*Cache),
7072
Metadata: make(map[schema.GroupVersionKind]*Cache),
7173
},
72-
codecs: serializer.NewCodecFactory(options.Scheme),
73-
paramCodec: runtime.NewParameterCodec(options.Scheme),
74-
resync: options.ResyncPeriod,
75-
startWait: make(chan struct{}),
76-
namespace: options.Namespace,
77-
selector: options.Selector,
78-
transform: options.Transform,
79-
unsafeDisableDeepCopy: options.UnsafeDisableDeepCopy,
80-
newInformer: newInformer,
81-
watchErrorHandler: options.WatchErrorHandler,
74+
codecs: serializer.NewCodecFactory(options.Scheme),
75+
paramCodec: runtime.NewParameterCodec(options.Scheme),
76+
resync: options.ResyncPeriod,
77+
startWait: make(chan struct{}),
78+
namespace: options.Namespace,
79+
selector: options.Selector,
80+
transform: options.Transform,
81+
additionalDefaultIndexes: options.AdditionalDefaultIndexes,
82+
unsafeDisableDeepCopy: options.UnsafeDisableDeepCopy,
83+
newInformer: newInformer,
84+
watchErrorHandler: options.WatchErrorHandler,
8285
}
8386
}
8487

@@ -170,9 +173,10 @@ type Informers struct {
170173
// default or empty string means all namespaces
171174
namespace string
172175

173-
selector Selector
174-
transform cache.TransformFunc
175-
unsafeDisableDeepCopy bool
176+
selector Selector
177+
transform cache.TransformFunc
178+
additionalDefaultIndexes client.Indexers
179+
unsafeDisableDeepCopy bool
176180

177181
// NewInformer allows overriding of the shared index informer constructor for testing.
178182
newInformer func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
@@ -346,6 +350,11 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
346350
if err != nil {
347351
return nil, false, err
348352
}
353+
indexers := make(cache.Indexers, len(ip.additionalDefaultIndexes)+1)
354+
for k, fn := range ip.additionalDefaultIndexes {
355+
indexers[FieldIndexName(k)] = IndexFunc(fn)
356+
}
357+
indexers[cache.NamespaceIndex] = cache.MetaNamespaceIndexFunc
349358
sharedIndexInformer := ip.newInformer(&cache.ListWatch{
350359
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
351360
ip.selector.ApplyToList(&opts)
@@ -356,9 +365,7 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
356365
opts.Watch = true // Watch needs to be set to true separately
357366
return listWatcher.WatchFunc(opts)
358367
},
359-
}, obj, calculateResyncPeriod(ip.resync), cache.Indexers{
360-
cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
361-
})
368+
}, obj, calculateResyncPeriod(ip.resync), indexers)
362369

363370
// Set WatchErrorHandler on SharedIndexInformer if set
364371
if ip.watchErrorHandler != nil {
@@ -585,3 +592,43 @@ func restrictNamespaceBySelector(namespaceOpt string, s Selector) string {
585592
}
586593
return ""
587594
}
595+
596+
// IndexFunc constructs a low-level cache.IndexFunc from a client.IndexerFunc.
597+
// Returned keys in the former are namespaced and non-namespaced variants of the
598+
// latter.
599+
func IndexFunc(extractValue client.IndexerFunc) cache.IndexFunc {
600+
return func(objRaw interface{}) ([]string, error) {
601+
// TODO(directxman12): check if this is the correct type?
602+
obj, isObj := objRaw.(client.Object)
603+
if !isObj {
604+
return nil, fmt.Errorf("object of type %T is not an Object", objRaw)
605+
}
606+
meta, err := meta.Accessor(obj)
607+
if err != nil {
608+
return nil, err
609+
}
610+
ns := meta.GetNamespace()
611+
612+
rawVals := extractValue(obj)
613+
var vals []string
614+
if ns == "" {
615+
// if we're not doubling the keys for the namespaced case, just create a new slice with same length
616+
vals = make([]string, len(rawVals))
617+
} else {
618+
// if we need to add non-namespaced versions too, double the length
619+
vals = make([]string, len(rawVals)*2)
620+
}
621+
for i, rawVal := range rawVals {
622+
// save a namespaced variant, so that we can ask
623+
// "what are all the object matching a given index *in a given namespace*"
624+
vals[i] = KeyToNamespacedKey(ns, rawVal)
625+
if ns != "" {
626+
// if we have a namespace, also inject a special index key for listing
627+
// regardless of the object namespace
628+
vals[i+len(rawVals)] = KeyToNamespacedKey("", rawVal)
629+
}
630+
}
631+
632+
return vals, nil
633+
}
634+
}

pkg/client/interfaces.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ type WithWatch interface {
190190
// namespaced and non-spaced variants, so keys do not need to include namespace.
191191
type IndexerFunc func(Object) []string
192192

193+
// Indexers is a map of field name to IndexerFunc.
194+
type Indexers map[string]IndexerFunc
195+
193196
// FieldIndexer knows how to index over a particular "field" such that it
194197
// can later be used by a field selector.
195198
type FieldIndexer interface {

0 commit comments

Comments
 (0)