Skip to content

Commit 15b6689

Browse files
authored
Merge pull request #1980 from joelanford/fix-cache-inheritance
⚠ cache.BuilderWithOptions inherit options from caller
2 parents d9593cf + b38c4a5 commit 15b6689

File tree

4 files changed

+729
-38
lines changed

4 files changed

+729
-38
lines changed

pkg/cache/cache.go

Lines changed: 265 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,18 @@ package cache
1919
import (
2020
"context"
2121
"fmt"
22+
"reflect"
2223
"time"
2324

2425
"k8s.io/apimachinery/pkg/api/meta"
26+
"k8s.io/apimachinery/pkg/fields"
27+
"k8s.io/apimachinery/pkg/labels"
2528
"k8s.io/apimachinery/pkg/runtime"
2629
"k8s.io/apimachinery/pkg/runtime/schema"
2730
"k8s.io/client-go/kubernetes/scheme"
2831
"k8s.io/client-go/rest"
2932
toolscache "k8s.io/client-go/tools/cache"
33+
3034
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
3135
"sigs.k8s.io/controller-runtime/pkg/client"
3236
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -150,49 +154,242 @@ func New(config *rest.Config, opts Options) (Cache, error) {
150154
if err != nil {
151155
return nil, err
152156
}
153-
selectorsByGVK, err := convertToSelectorsByGVK(opts.SelectorsByObject, opts.DefaultSelector, opts.Scheme)
157+
selectorsByGVK, err := convertToByGVK(opts.SelectorsByObject, opts.DefaultSelector, opts.Scheme)
154158
if err != nil {
155159
return nil, err
156160
}
157161
disableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(opts.UnsafeDisableDeepCopyByObject, opts.Scheme)
158162
if err != nil {
159163
return nil, err
160164
}
161-
transformByGVK, err := convertToTransformByKindAndGVK(opts.TransformByObject, opts.DefaultTransform, opts.Scheme)
165+
transformByGVK, err := convertToByGVK(opts.TransformByObject, opts.DefaultTransform, opts.Scheme)
162166
if err != nil {
163167
return nil, err
164168
}
169+
transformByObj := internal.TransformFuncByObjectFromMap(transformByGVK)
170+
171+
internalSelectorsByGVK := internal.SelectorsByGVK{}
172+
for gvk, selector := range selectorsByGVK {
173+
internalSelectorsByGVK[gvk] = internal.Selector(selector)
174+
}
165175

166-
im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, selectorsByGVK, disableDeepCopyByGVK, transformByGVK)
176+
im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, internalSelectorsByGVK, disableDeepCopyByGVK, transformByObj)
167177
return &informerCache{InformersMap: im}, nil
168178
}
169179

170-
// BuilderWithOptions returns a Cache constructor that will build the a cache
180+
// BuilderWithOptions returns a Cache constructor that will build a cache
171181
// honoring the options argument, this is useful to specify options like
172182
// SelectorsByObject
173183
// WARNING: If SelectorsByObject is specified, filtered out resources are not
174184
// returned.
175185
// WARNING: If UnsafeDisableDeepCopy is enabled, you must DeepCopy any object
176186
// returned from cache get/list before mutating it.
177187
func BuilderWithOptions(options Options) NewCacheFunc {
178-
return func(config *rest.Config, opts Options) (Cache, error) {
179-
if options.Scheme == nil {
180-
options.Scheme = opts.Scheme
188+
return func(config *rest.Config, inherited Options) (Cache, error) {
189+
var err error
190+
inherited, err = defaultOpts(config, inherited)
191+
if err != nil {
192+
return nil, err
181193
}
182-
if options.Mapper == nil {
183-
options.Mapper = opts.Mapper
194+
options, err = defaultOpts(config, options)
195+
if err != nil {
196+
return nil, err
184197
}
185-
if options.Resync == nil {
186-
options.Resync = opts.Resync
198+
combined, err := options.inheritFrom(inherited)
199+
if err != nil {
200+
return nil, err
187201
}
188-
if options.Namespace == "" {
189-
options.Namespace = opts.Namespace
202+
return New(config, *combined)
203+
}
204+
}
205+
206+
func (options Options) inheritFrom(inherited Options) (*Options, error) {
207+
var (
208+
combined Options
209+
err error
210+
)
211+
combined.Scheme = combineScheme(inherited.Scheme, options.Scheme)
212+
combined.Mapper = selectMapper(inherited.Mapper, options.Mapper)
213+
combined.Resync = selectResync(inherited.Resync, options.Resync)
214+
combined.Namespace = selectNamespace(inherited.Namespace, options.Namespace)
215+
combined.SelectorsByObject, combined.DefaultSelector, err = combineSelectors(inherited, options, combined.Scheme)
216+
if err != nil {
217+
return nil, err
218+
}
219+
combined.UnsafeDisableDeepCopyByObject, err = combineUnsafeDeepCopy(inherited, options, combined.Scheme)
220+
if err != nil {
221+
return nil, err
222+
}
223+
combined.TransformByObject, combined.DefaultTransform, err = combineTransforms(inherited, options, combined.Scheme)
224+
if err != nil {
225+
return nil, err
226+
}
227+
return &combined, nil
228+
}
229+
230+
func combineScheme(schemes ...*runtime.Scheme) *runtime.Scheme {
231+
var out *runtime.Scheme
232+
for _, sch := range schemes {
233+
if sch == nil {
234+
continue
190235
}
191-
if opts.Resync == nil {
192-
opts.Resync = options.Resync
236+
for gvk, t := range sch.AllKnownTypes() {
237+
if out == nil {
238+
out = runtime.NewScheme()
239+
}
240+
out.AddKnownTypeWithName(gvk, reflect.New(t).Interface().(runtime.Object))
193241
}
242+
}
243+
return out
244+
}
194245

195-
return New(config, options)
246+
func selectMapper(def, override meta.RESTMapper) meta.RESTMapper {
247+
if override != nil {
248+
return override
249+
}
250+
return def
251+
}
252+
253+
func selectResync(def, override *time.Duration) *time.Duration {
254+
if override != nil {
255+
return override
256+
}
257+
return def
258+
}
259+
260+
func selectNamespace(def, override string) string {
261+
if override != "" {
262+
return override
263+
}
264+
return def
265+
}
266+
267+
func combineSelectors(inherited, options Options, scheme *runtime.Scheme) (SelectorsByObject, ObjectSelector, error) {
268+
// Selectors are combined via logical AND.
269+
// - Combined label selector is a union of the selectors requirements from both sets of options.
270+
// - Combined field selector uses fields.AndSelectors with the combined list of non-nil field selectors
271+
// defined in both sets of options.
272+
//
273+
// There is a bunch of complexity here because we need to convert to SelectorsByGVK
274+
// to be able to match keys between options and inherited and then convert back to SelectorsByObject
275+
optionsSelectorsByGVK, err := convertToByGVK(options.SelectorsByObject, options.DefaultSelector, options.Scheme)
276+
if err != nil {
277+
return nil, ObjectSelector{}, err
278+
}
279+
inheritedSelectorsByGVK, err := convertToByGVK(inherited.SelectorsByObject, inherited.DefaultSelector, inherited.Scheme)
280+
if err != nil {
281+
return nil, ObjectSelector{}, err
282+
}
283+
284+
for gvk, inheritedSelector := range inheritedSelectorsByGVK {
285+
optionsSelectorsByGVK[gvk] = combineSelector(inheritedSelector, optionsSelectorsByGVK[gvk])
286+
}
287+
return convertToByObject(optionsSelectorsByGVK, scheme)
288+
}
289+
290+
func combineSelector(selectors ...ObjectSelector) ObjectSelector {
291+
ls := make([]labels.Selector, 0, len(selectors))
292+
fs := make([]fields.Selector, 0, len(selectors))
293+
for _, s := range selectors {
294+
ls = append(ls, s.Label)
295+
fs = append(fs, s.Field)
296+
}
297+
return ObjectSelector{
298+
Label: combineLabelSelectors(ls...),
299+
Field: combineFieldSelectors(fs...),
300+
}
301+
}
302+
303+
func combineLabelSelectors(ls ...labels.Selector) labels.Selector {
304+
var combined labels.Selector
305+
for _, l := range ls {
306+
if l == nil {
307+
continue
308+
}
309+
if combined == nil {
310+
combined = labels.NewSelector()
311+
}
312+
reqs, _ := l.Requirements()
313+
combined = combined.Add(reqs...)
314+
}
315+
return combined
316+
}
317+
318+
func combineFieldSelectors(fs ...fields.Selector) fields.Selector {
319+
nonNil := fs[:0]
320+
for _, f := range fs {
321+
if f == nil {
322+
continue
323+
}
324+
nonNil = append(nonNil, f)
325+
}
326+
if len(nonNil) == 0 {
327+
return nil
328+
}
329+
if len(nonNil) == 1 {
330+
return nonNil[0]
331+
}
332+
return fields.AndSelectors(nonNil...)
333+
}
334+
335+
func combineUnsafeDeepCopy(inherited, options Options, scheme *runtime.Scheme) (DisableDeepCopyByObject, error) {
336+
// UnsafeDisableDeepCopyByObject is combined via precedence. Only if a value for a particular GVK is unset
337+
// in options will a value from inherited be used.
338+
optionsDisableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(options.UnsafeDisableDeepCopyByObject, options.Scheme)
339+
if err != nil {
340+
return nil, err
341+
}
342+
inheritedDisableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(inherited.UnsafeDisableDeepCopyByObject, inherited.Scheme)
343+
if err != nil {
344+
return nil, err
345+
}
346+
347+
for gvk, inheritedDeepCopy := range inheritedDisableDeepCopyByGVK {
348+
if _, ok := optionsDisableDeepCopyByGVK[gvk]; !ok {
349+
if optionsDisableDeepCopyByGVK == nil {
350+
optionsDisableDeepCopyByGVK = map[schema.GroupVersionKind]bool{}
351+
}
352+
optionsDisableDeepCopyByGVK[gvk] = inheritedDeepCopy
353+
}
354+
}
355+
return convertToDisableDeepCopyByObject(optionsDisableDeepCopyByGVK, scheme)
356+
}
357+
358+
func combineTransforms(inherited, options Options, scheme *runtime.Scheme) (TransformByObject, toolscache.TransformFunc, error) {
359+
// Transform functions are combined via chaining. If both inherited and options define a transform
360+
// function, the transform function from inherited will be called first, and the transform function from
361+
// options will be called second.
362+
optionsTransformByGVK, err := convertToByGVK(options.TransformByObject, options.DefaultTransform, options.Scheme)
363+
if err != nil {
364+
return nil, nil, err
365+
}
366+
inheritedTransformByGVK, err := convertToByGVK(inherited.TransformByObject, inherited.DefaultTransform, inherited.Scheme)
367+
if err != nil {
368+
return nil, nil, err
369+
}
370+
371+
for gvk, inheritedTransform := range inheritedTransformByGVK {
372+
if optionsTransformByGVK == nil {
373+
optionsTransformByGVK = map[schema.GroupVersionKind]toolscache.TransformFunc{}
374+
}
375+
optionsTransformByGVK[gvk] = combineTransform(inheritedTransform, optionsTransformByGVK[gvk])
376+
}
377+
return convertToByObject(optionsTransformByGVK, scheme)
378+
}
379+
380+
func combineTransform(inherited, current toolscache.TransformFunc) toolscache.TransformFunc {
381+
if inherited == nil {
382+
return current
383+
}
384+
if current == nil {
385+
return inherited
386+
}
387+
return func(in interface{}) (interface{}, error) {
388+
mid, err := inherited(in)
389+
if err != nil {
390+
return nil, err
391+
}
392+
return current(mid)
196393
}
197394
}
198395

@@ -219,17 +416,40 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
219416
return opts, nil
220417
}
221418

222-
func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, defaultSelector ObjectSelector, scheme *runtime.Scheme) (internal.SelectorsByGVK, error) {
223-
selectorsByGVK := internal.SelectorsByGVK{}
224-
for object, selector := range selectorsByObject {
419+
func convertToByGVK[T any](byObject map[client.Object]T, def T, scheme *runtime.Scheme) (map[schema.GroupVersionKind]T, error) {
420+
byGVK := map[schema.GroupVersionKind]T{}
421+
for object, value := range byObject {
225422
gvk, err := apiutil.GVKForObject(object, scheme)
226423
if err != nil {
227424
return nil, err
228425
}
229-
selectorsByGVK[gvk] = internal.Selector(selector)
426+
byGVK[gvk] = value
427+
}
428+
byGVK[schema.GroupVersionKind{}] = def
429+
return byGVK, nil
430+
}
431+
432+
func convertToByObject[T any](byGVK map[schema.GroupVersionKind]T, scheme *runtime.Scheme) (map[client.Object]T, T, error) {
433+
var byObject map[client.Object]T
434+
def := byGVK[schema.GroupVersionKind{}]
435+
for gvk, value := range byGVK {
436+
if gvk == (schema.GroupVersionKind{}) {
437+
continue
438+
}
439+
obj, err := scheme.New(gvk)
440+
if err != nil {
441+
return nil, def, err
442+
}
443+
cObj, ok := obj.(client.Object)
444+
if !ok {
445+
return nil, def, fmt.Errorf("object %T for GVK %q does not implement client.Object", obj, gvk)
446+
}
447+
if byObject == nil {
448+
byObject = map[client.Object]T{}
449+
}
450+
byObject[cObj] = value
230451
}
231-
selectorsByGVK[schema.GroupVersionKind{}] = internal.Selector(defaultSelector)
232-
return selectorsByGVK, nil
452+
return byObject, def, nil
233453
}
234454

235455
// DisableDeepCopyByObject associate a client.Object's GVK to disable DeepCopy during get or list from cache.
@@ -259,17 +479,30 @@ func convertToDisableDeepCopyByGVK(disableDeepCopyByObject DisableDeepCopyByObje
259479
return disableDeepCopyByGVK, nil
260480
}
261481

262-
// TransformByObject associate a client.Object's GVK to a transformer function
263-
// to be applied when storing the object into the cache.
264-
type TransformByObject map[client.Object]toolscache.TransformFunc
265-
266-
func convertToTransformByKindAndGVK(t TransformByObject, defaultTransform toolscache.TransformFunc, scheme *runtime.Scheme) (internal.TransformFuncByObject, error) {
267-
result := internal.NewTransformFuncByObject()
268-
for obj, transformation := range t {
269-
if err := result.Set(obj, scheme, transformation); err != nil {
482+
func convertToDisableDeepCopyByObject(byGVK internal.DisableDeepCopyByGVK, scheme *runtime.Scheme) (DisableDeepCopyByObject, error) {
483+
var byObject DisableDeepCopyByObject
484+
for gvk, value := range byGVK {
485+
if byObject == nil {
486+
byObject = DisableDeepCopyByObject{}
487+
}
488+
if gvk == (schema.GroupVersionKind{}) {
489+
byObject[ObjectAll{}] = value
490+
continue
491+
}
492+
obj, err := scheme.New(gvk)
493+
if err != nil {
270494
return nil, err
271495
}
496+
cObj, ok := obj.(client.Object)
497+
if !ok {
498+
return nil, fmt.Errorf("object %T for GVK %q does not implement client.Object", obj, gvk)
499+
}
500+
501+
byObject[cObj] = value
272502
}
273-
result.SetDefault(defaultTransform)
274-
return result, nil
503+
return byObject, nil
275504
}
505+
506+
// TransformByObject associate a client.Object's GVK to a transformer function
507+
// to be applied when storing the object into the cache.
508+
type TransformByObject map[client.Object]toolscache.TransformFunc

pkg/cache/cache_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525

2626
. "github.com/onsi/ginkgo/v2"
2727
. "github.com/onsi/gomega"
28-
2928
corev1 "k8s.io/api/core/v1"
3029
apierrors "k8s.io/apimachinery/pkg/api/errors"
3130
"k8s.io/apimachinery/pkg/api/meta"

0 commit comments

Comments
 (0)