Skip to content

Commit cdf6208

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 cdf6208

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: 267 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,244 @@ 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
193+
}
194+
options, err = defaultOpts(config, options)
195+
if err != nil {
196+
return nil, err
197+
}
198+
combined, err := options.inheritFrom(inherited)
199+
if err != nil {
200+
return nil, err
201+
}
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, fmt.Errorf("combine selectors: %v", err)
218+
}
219+
combined.UnsafeDisableDeepCopyByObject, err = combineUnsafeDeepCopy(inherited, options, combined.Scheme)
220+
if err != nil {
221+
return nil, fmt.Errorf("combine unsafe disable deep copy by object: %v", err)
222+
}
223+
224+
combined.TransformByObject, combined.DefaultTransform, err = combineTransforms(inherited, options, combined.Scheme)
225+
if err != nil {
226+
return nil, fmt.Errorf("combine transforms: %v", err)
227+
}
228+
229+
return &combined, nil
230+
}
231+
232+
func combineScheme(schemes ...*runtime.Scheme) *runtime.Scheme {
233+
var out *runtime.Scheme
234+
for _, sch := range schemes {
235+
if sch == nil {
236+
continue
237+
}
238+
for gvk, t := range sch.AllKnownTypes() {
239+
if out == nil {
240+
out = runtime.NewScheme()
241+
}
242+
out.AddKnownTypeWithName(gvk, reflect.New(t).Interface().(runtime.Object))
243+
}
244+
}
245+
return out
246+
}
247+
248+
func selectMapper(def, override meta.RESTMapper) meta.RESTMapper {
249+
if override != nil {
250+
return override
251+
}
252+
return def
253+
}
254+
255+
func selectResync(def, override *time.Duration) *time.Duration {
256+
if override != nil {
257+
return override
258+
}
259+
return def
260+
}
261+
262+
func selectNamespace(def, override string) string {
263+
if override != "" {
264+
return override
265+
}
266+
return def
267+
}
268+
269+
func combineSelectors(inherited, options Options, scheme *runtime.Scheme) (SelectorsByObject, ObjectSelector, error) {
270+
// Selectors are combined via logical AND.
271+
// - Combined label selector is a union of the selectors requirements from both sets of options.
272+
// - Combined field selector uses fields.AndSelectors with the combined list of non-nil field selectors
273+
// defined in both sets of options.
274+
//
275+
// There is a bunch of complexity here because we need to convert to SelectorsByGVK
276+
// to be able to match keys between options and inherited and then convert back to SelectorsByObject
277+
optionsSelectorsByGVK, err := convertToByGVK(options.SelectorsByObject, options.DefaultSelector, options.Scheme)
278+
if err != nil {
279+
return nil, ObjectSelector{}, err
280+
}
281+
inheritedSelectorsByGVK, err := convertToByGVK(inherited.SelectorsByObject, inherited.DefaultSelector, inherited.Scheme)
282+
if err != nil {
283+
return nil, ObjectSelector{}, err
284+
}
285+
286+
for gvk, selector := range inheritedSelectorsByGVK {
287+
optionsSelectorsByGVK[gvk] = combineSelector(optionsSelectorsByGVK[gvk], selector)
288+
}
289+
return convertToByObject(optionsSelectorsByGVK, scheme)
290+
}
291+
292+
func combineSelector(selectors ...ObjectSelector) ObjectSelector {
293+
ls := make([]labels.Selector, 0, len(selectors))
294+
fs := make([]fields.Selector, 0, len(selectors))
295+
for _, s := range selectors {
296+
ls = append(ls, s.Label)
297+
fs = append(fs, s.Field)
298+
}
299+
return ObjectSelector{
300+
Label: combineLabelSelectors(ls...),
301+
Field: combineFieldSelectors(fs...),
302+
}
303+
}
304+
305+
func combineLabelSelectors(ls ...labels.Selector) labels.Selector {
306+
var combined labels.Selector
307+
for _, l := range ls {
308+
if l == nil {
309+
continue
181310
}
182-
if options.Mapper == nil {
183-
options.Mapper = opts.Mapper
311+
if combined == nil {
312+
combined = labels.NewSelector()
184313
}
185-
if options.Resync == nil {
186-
options.Resync = opts.Resync
314+
reqs, _ := l.Requirements()
315+
combined = combined.Add(reqs...)
316+
}
317+
return combined
318+
}
319+
320+
func combineFieldSelectors(fs ...fields.Selector) fields.Selector {
321+
nonNil := fs[:0]
322+
for _, f := range fs {
323+
if f == nil {
324+
continue
187325
}
188-
if options.Namespace == "" {
189-
options.Namespace = opts.Namespace
326+
nonNil = append(nonNil, f)
327+
}
328+
if len(nonNil) == 0 {
329+
return nil
330+
}
331+
if len(nonNil) == 1 {
332+
return nonNil[0]
333+
}
334+
return fields.AndSelectors(nonNil...)
335+
}
336+
337+
func combineUnsafeDeepCopy(inherited, options Options, scheme *runtime.Scheme) (DisableDeepCopyByObject, error) {
338+
// UnsafeDisableDeepCopyByObject is combined via precedence. Only if a value for a particular GVK is unset
339+
// in options will a value from inherited be used.
340+
optionsDisableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(options.UnsafeDisableDeepCopyByObject, options.Scheme)
341+
if err != nil {
342+
return nil, err
343+
}
344+
inheritedDisableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(inherited.UnsafeDisableDeepCopyByObject, inherited.Scheme)
345+
if err != nil {
346+
return nil, err
347+
}
348+
349+
for gvk, inheritedDeepCopy := range inheritedDisableDeepCopyByGVK {
350+
if _, ok := optionsDisableDeepCopyByGVK[gvk]; !ok {
351+
if optionsDisableDeepCopyByGVK == nil {
352+
optionsDisableDeepCopyByGVK = map[schema.GroupVersionKind]bool{}
353+
}
354+
optionsDisableDeepCopyByGVK[gvk] = inheritedDeepCopy
190355
}
191-
if opts.Resync == nil {
192-
opts.Resync = options.Resync
356+
}
357+
return convertToDisableDeepCopyByObject(optionsDisableDeepCopyByGVK, scheme)
358+
}
359+
360+
func combineTransforms(inherited, options Options, scheme *runtime.Scheme) (TransformByObject, toolscache.TransformFunc, error) {
361+
// Transform functions are combined via chaining. If both inherited and options define a transform
362+
// function, the transform function from inherited will be called first, and the transform function from
363+
// options will be called second.
364+
optionsTransformByGVK, err := convertToByGVK(options.TransformByObject, options.DefaultTransform, options.Scheme)
365+
if err != nil {
366+
return nil, nil, err
367+
}
368+
inheritedTransformByGVK, err := convertToByGVK(inherited.TransformByObject, inherited.DefaultTransform, inherited.Scheme)
369+
if err != nil {
370+
return nil, nil, err
371+
}
372+
373+
for gvk, inheritedTransform := range inheritedTransformByGVK {
374+
if optionsTransformByGVK == nil {
375+
optionsTransformByGVK = map[schema.GroupVersionKind]toolscache.TransformFunc{}
193376
}
377+
optionsTransformByGVK[gvk] = combineTransform(inheritedTransform, optionsTransformByGVK[gvk])
378+
}
379+
return convertToByObject(optionsTransformByGVK, scheme)
380+
}
194381

195-
return New(config, options)
382+
func combineTransform(inherited, current toolscache.TransformFunc) toolscache.TransformFunc {
383+
if inherited == nil {
384+
return current
385+
}
386+
if current == nil {
387+
return inherited
388+
}
389+
return func(in interface{}) (interface{}, error) {
390+
mid, err := inherited(in)
391+
if err != nil {
392+
return nil, err
393+
}
394+
return current(mid)
196395
}
197396
}
198397

@@ -219,17 +418,40 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
219418
return opts, nil
220419
}
221420

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

235457
// DisableDeepCopyByObject associate a client.Object's GVK to disable DeepCopy during get or list from cache.
@@ -259,17 +481,30 @@ func convertToDisableDeepCopyByGVK(disableDeepCopyByObject DisableDeepCopyByObje
259481
return disableDeepCopyByGVK, nil
260482
}
261483

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 {
484+
func convertToDisableDeepCopyByObject(byGVK internal.DisableDeepCopyByGVK, scheme *runtime.Scheme) (DisableDeepCopyByObject, error) {
485+
var byObject DisableDeepCopyByObject
486+
for gvk, value := range byGVK {
487+
if byObject == nil {
488+
byObject = DisableDeepCopyByObject{}
489+
}
490+
if gvk == (schema.GroupVersionKind{}) {
491+
byObject[ObjectAll{}] = value
492+
continue
493+
}
494+
obj, err := scheme.New(gvk)
495+
if err != nil {
270496
return nil, err
271497
}
498+
cObj, ok := obj.(client.Object)
499+
if !ok {
500+
return nil, fmt.Errorf("object %T for GVK %q does not implement client.Object", obj, gvk)
501+
}
502+
503+
byObject[cObj] = value
272504
}
273-
result.SetDefault(defaultTransform)
274-
return result, nil
505+
return byObject, nil
275506
}
507+
508+
// TransformByObject associate a client.Object's GVK to a transformer function
509+
// to be applied when storing the object into the cache.
510+
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)