Skip to content

Commit 6182d4b

Browse files
committed
fix: cache.BuilderWithOptions inherit options from caller
using cache.BuilderWithOptions does not properly inherit all options passed in from the caller: - scheme was overridden instead of merged - selectors were not inherited at all, even if specified options selectors remained unset - transforms were not inherited at all, even if specified options transforms remained unset - disable deep copy settings were not inherited at all, even if specified options for disabling deep copies remained unset This commit resolves this issues by implementing merge logic for all fields in the cache.Options struct: - Schemes are merged - RESTMapper is chosen by precedence only falling back to inherited options if left unset in specified options - Resync is chosen by precedence only falling back to inherited options if left unset in specified options - Namespace is chosen by precedence only falling back to inherited options if left unset in specified options - Selectors are merged. If both inherited and specified Options defined selectors for a given type, those selectors are merged via logical AND. - DisableDeepCopy is combined via precedence. Only if a value for a particular GVK is unset in the specified options will a value from the inherited options be used. - Transform functions are combined via chaining. If both inherited and specified options define a transform function, the transform function from the inherited options will be called first, and the transform function from the specified options will be called second. Signed-off-by: Joe Lanford <[email protected]>
1 parent bcde6f0 commit 6182d4b

File tree

6 files changed

+730
-412
lines changed

6 files changed

+730
-412
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module sigs.k8s.io/controller-runtime
22

3-
go 1.17
3+
go 1.18
44

55
require (
66
github.com/evanphx/json-patch/v5 v5.6.0

go.sum

Lines changed: 0 additions & 373 deletions
Large diffs are not rendered by default.

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, selector := range inheritedSelectorsByGVK {
285+
optionsSelectorsByGVK[gvk] = combineSelector(optionsSelectorsByGVK[gvk], selector)
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
@@ -26,7 +26,6 @@ import (
2626
. "github.com/onsi/ginkgo"
2727
. "github.com/onsi/ginkgo/extensions/table"
2828
. "github.com/onsi/gomega"
29-
3029
corev1 "k8s.io/api/core/v1"
3130
apierrors "k8s.io/apimachinery/pkg/api/errors"
3231
"k8s.io/apimachinery/pkg/api/meta"

0 commit comments

Comments
 (0)