@@ -22,15 +22,16 @@ import (
2222 "net/http"
2323 "time"
2424
25+ corev1 "k8s.io/api/core/v1"
2526 "k8s.io/apimachinery/pkg/api/meta"
26- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727 "k8s.io/apimachinery/pkg/fields"
2828 "k8s.io/apimachinery/pkg/labels"
2929 "k8s.io/apimachinery/pkg/runtime"
3030 "k8s.io/apimachinery/pkg/runtime/schema"
3131 "k8s.io/client-go/kubernetes/scheme"
3232 "k8s.io/client-go/rest"
3333 toolscache "k8s.io/client-go/tools/cache"
34+ "k8s.io/utils/pointer"
3435
3536 "sigs.k8s.io/controller-runtime/pkg/cache/internal"
3637 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -144,9 +145,13 @@ type Options struct {
144145 // instead of `reconcile.Result{}`.
145146 SyncPeriod * time.Duration
146147
147- // Namespaces restricts the cache's ListWatch to the desired namespaces
148- // Per default ListWatch watches all namespaces
149- Namespaces []string
148+ // DefaultNamespaces maps namespace names to cache configs. If set, only
149+ // the namespaces in here will be watched and it will by used to default
150+ // ByObject.Namespaces for all objects if that is nil.
151+ //
152+ // The options in the Config that are nil will be defaulted from
153+ // the respective Default* settings.
154+ DefaultNamespaces map [string ]Config
150155
151156 // DefaultLabelSelector will be used as a label selector for all object types
152157 // unless they have a more specific selector set in ByObject.
@@ -160,20 +165,39 @@ type Options struct {
160165 // unless they have a more specific transform set in ByObject.
161166 DefaultTransform toolscache.TransformFunc
162167
163- // UnsafeDisableDeepCopy indicates not to deep copy objects during get or
164- // list objects for EVERY object.
168+ // DefaultUnsafeDisableDeepCopy is the default for UnsafeDisableDeepCopy
169+ // for everything that doesn't specify this.
170+ //
165171 // Be very careful with this, when enabled you must DeepCopy any object before mutating it,
166172 // otherwise you will mutate the object in the cache.
167173 //
168174 // This is a global setting for all objects, and can be overridden by the ByObject setting.
169- UnsafeDisableDeepCopy * bool
175+ DefaultUnsafeDisableDeepCopy * bool
170176
171177 // ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object.
178+ // object, this will fall through to Default* settings.
172179 ByObject map [client.Object ]ByObject
173180}
174181
175182// ByObject offers more fine-grained control over the cache's ListWatch by object.
176183type ByObject struct {
184+ // Namespaces maps a namespace name to cache configs. If set, only the
185+ // namespaces in this map will be cached.
186+ //
187+ // Settings in the map value that are unset will be defaulted.
188+ // Use an empty value for the specific setting to prevent that.
189+ //
190+ // A nil map allows to default this to the cache's DefaultNamespaces setting.
191+ // An empty map prevents this and means that all namespaces will be cached.
192+ //
193+ // The defaulting follows the following precedence order:
194+ // 1. ByObject
195+ // 2. DefaultNamespaces[namespace]
196+ // 3. Default*
197+ //
198+ // This must be unset for cluster-scoped objects.
199+ Namespaces map [string ]Config
200+
177201 // Label represents a label selector for the object.
178202 Label labels.Selector
179203
@@ -194,48 +218,118 @@ type ByObject struct {
194218 UnsafeDisableDeepCopy * bool
195219}
196220
221+ // Config describes all potential options for a given watch.
222+ type Config struct {
223+ // LabelSelector specifies a label selector. A nil value allows to
224+ // default this.
225+ //
226+ // Set to labels.Everything() if you don't want this defaulted.
227+ LabelSelector labels.Selector
228+
229+ // FieldSelector specifics a field selector. A nil value allows to
230+ // default this.
231+ //
232+ // Set to fields.Everything() if you don't want this defaulted.
233+ FieldSelector fields.Selector
234+
235+ // Transform specifies a transform func. A nil value allows to default
236+ // this.
237+ //
238+ // Set to an empty func to prevent this:
239+ // func(in interface{}) (interface{}, error) { return in, nil }
240+ Transform toolscache.TransformFunc
241+
242+ // UnsafeDisableDeepCopy specifies if List and Get requests against the
243+ // cache should not DeepCopy. A nil value allows to default this.
244+ UnsafeDisableDeepCopy * bool
245+ }
246+
197247// NewCacheFunc - Function for creating a new cache from the options and a rest config.
198248type NewCacheFunc func (config * rest.Config , opts Options ) (Cache , error )
199249
200250// New initializes and returns a new Cache.
201- func New (config * rest.Config , opts Options ) (Cache , error ) {
202- if len (opts .Namespaces ) == 0 {
203- opts .Namespaces = []string {metav1 .NamespaceAll }
251+ func New (cfg * rest.Config , opts Options ) (Cache , error ) {
252+ opts , err := defaultOpts (cfg , opts )
253+ if err != nil {
254+ return nil , err
204255 }
205- if len (opts .Namespaces ) > 1 {
206- return newMultiNamespaceCache (config , opts )
256+
257+ newCacheFunc := newCache (cfg , opts )
258+
259+ var defaultCache Cache
260+ if len (opts .DefaultNamespaces ) > 0 {
261+ defaultConfig := optionDefaultsToConfig (& opts )
262+ defaultCache = newMultiNamespaceCache (newCacheFunc , opts .Scheme , opts .Mapper , opts .DefaultNamespaces , & defaultConfig )
263+ } else {
264+ defaultCache = newCacheFunc (optionDefaultsToConfig (& opts ), corev1 .NamespaceAll )
207265 }
208266
209- opts , err := defaultOpts (config , opts )
210- if err != nil {
211- return nil , err
267+ if len (opts .ByObject ) == 0 {
268+ return defaultCache , nil
212269 }
213270
214- byGVK , err := convertToInformerOptsByGVK (opts .ByObject , opts .Scheme )
215- if err != nil {
216- return nil , err
271+ delegating := & delegatingByTypeCache {
272+ scheme : opts .Scheme ,
273+ caches : make (map [schema.GroupVersionKind ]Cache , len (opts .ByObject )),
274+ defaultCache : defaultCache ,
275+ }
276+
277+ for obj , config := range opts .ByObject {
278+ gvk , err := apiutil .GVKForObject (obj , opts .Scheme )
279+ if err != nil {
280+ return nil , fmt .Errorf ("failed to get GVK for type %T: %w" , obj , err )
281+ }
282+ var cache Cache
283+ if len (config .Namespaces ) > 0 {
284+ cache = newMultiNamespaceCache (newCacheFunc , opts .Scheme , opts .Mapper , config .Namespaces , nil )
285+ } else {
286+ cache = newCacheFunc (byObjectToConfig (config ), corev1 .NamespaceAll )
287+ }
288+ delegating .caches [gvk ] = cache
217289 }
218- // Set the default selector and transform.
219- byGVK [schema.GroupVersionKind {}] = internal.InformersOptsByGVK {
220- Selector : internal.Selector {
221- Label : opts .DefaultLabelSelector ,
222- Field : opts .DefaultFieldSelector ,
223- },
290+
291+ return delegating , nil
292+ }
293+
294+ func optionDefaultsToConfig (opts * Options ) Config {
295+ return Config {
296+ LabelSelector : opts .DefaultLabelSelector ,
297+ FieldSelector : opts .DefaultFieldSelector ,
224298 Transform : opts .DefaultTransform ,
225- UnsafeDisableDeepCopy : opts .UnsafeDisableDeepCopy ,
299+ UnsafeDisableDeepCopy : opts .DefaultUnsafeDisableDeepCopy ,
300+ }
301+ }
302+
303+ func byObjectToConfig (byObject ByObject ) Config {
304+ return Config {
305+ LabelSelector : byObject .Label ,
306+ FieldSelector : byObject .Field ,
307+ Transform : byObject .Transform ,
308+ UnsafeDisableDeepCopy : byObject .UnsafeDisableDeepCopy ,
226309 }
310+ }
227311
228- return & informerCache {
229- scheme : opts .Scheme ,
230- Informers : internal .NewInformers (config , & internal.InformersOpts {
231- HTTPClient : opts .HTTPClient ,
232- Scheme : opts .Scheme ,
233- Mapper : opts .Mapper ,
234- ResyncPeriod : * opts .SyncPeriod ,
235- Namespace : opts .Namespaces [0 ],
236- ByGVK : byGVK ,
237- }),
238- }, nil
312+ type newCacheFunc func (config Config , namespace string ) Cache
313+
314+ func newCache (restConfig * rest.Config , opts Options ) newCacheFunc {
315+ return func (config Config , namespace string ) Cache {
316+ return & informerCache {
317+ scheme : opts .Scheme ,
318+ Informers : internal .NewInformers (restConfig , & internal.InformersOpts {
319+ HTTPClient : opts .HTTPClient ,
320+ Scheme : opts .Scheme ,
321+ Mapper : opts .Mapper ,
322+ ResyncPeriod : * opts .SyncPeriod ,
323+ Namespace : namespace ,
324+ Selector : internal.Selector {
325+ Label : config .LabelSelector ,
326+ Field : config .FieldSelector ,
327+ },
328+ Transform : config .Transform ,
329+ UnsafeDisableDeepCopy : pointer .BoolDeref (config .UnsafeDisableDeepCopy , false ),
330+ }),
331+ }
332+ }
239333}
240334
241335func defaultOpts (config * rest.Config , opts Options ) (Options , error ) {
@@ -262,31 +356,70 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
262356 }
263357 }
264358
359+ for namespace , cfg := range opts .DefaultNamespaces {
360+ cfg = defaultConfig (cfg , optionDefaultsToConfig (& opts ))
361+ opts .DefaultNamespaces [namespace ] = cfg
362+ }
363+
364+ for obj , byObject := range opts .ByObject {
365+ isNamespaced , err := apiutil .IsObjectNamespaced (obj , opts .Scheme , opts .Mapper )
366+ if err != nil {
367+ return opts , fmt .Errorf ("failed to determine if %T is namespaced: %w" , obj , err )
368+ }
369+ if ! isNamespaced && byObject .Namespaces != nil {
370+ return opts , fmt .Errorf ("type %T is not namespaced, but its ByObject.Namespaces setting is not nil" , obj )
371+ }
372+
373+ // Default the namespace-level configs first, because they need to use the undefaulted type-level config.
374+ for namespace , config := range byObject .Namespaces {
375+ // 1. Default from the undefaulted type-level config
376+ config = defaultConfig (config , byObjectToConfig (byObject ))
377+
378+ // 2. Default from the namespace-level config. This was defaulted from the global default config earlier, but
379+ // might not have an entry for the current namespace.
380+ if defaultNamespaceSettings , hasDefaultNamespace := opts .DefaultNamespaces [namespace ]; hasDefaultNamespace {
381+ config = defaultConfig (config , defaultNamespaceSettings )
382+ }
383+
384+ // 3. Default from the global defaults
385+ config = defaultConfig (config , optionDefaultsToConfig (& opts ))
386+
387+ byObject .Namespaces [namespace ] = config
388+ }
389+
390+ defaultedConfig := defaultConfig (byObjectToConfig (byObject ), optionDefaultsToConfig (& opts ))
391+ byObject .Label = defaultedConfig .LabelSelector
392+ byObject .Field = defaultedConfig .FieldSelector
393+ byObject .Transform = defaultedConfig .Transform
394+ byObject .UnsafeDisableDeepCopy = defaultedConfig .UnsafeDisableDeepCopy
395+
396+ if byObject .Namespaces == nil {
397+ byObject .Namespaces = opts .DefaultNamespaces
398+ }
399+
400+ opts .ByObject [obj ] = byObject
401+ }
402+
265403 // Default the resync period to 10 hours if unset
266404 if opts .SyncPeriod == nil {
267405 opts .SyncPeriod = & defaultSyncPeriod
268406 }
269407 return opts , nil
270408}
271409
272- func convertToInformerOptsByGVK (in map [client.Object ]ByObject , scheme * runtime.Scheme ) (map [schema.GroupVersionKind ]internal.InformersOptsByGVK , error ) {
273- out := map [schema.GroupVersionKind ]internal.InformersOptsByGVK {}
274- for object , byObject := range in {
275- gvk , err := apiutil .GVKForObject (object , scheme )
276- if err != nil {
277- return nil , err
278- }
279- if _ , ok := out [gvk ]; ok {
280- return nil , fmt .Errorf ("duplicate cache options for GVK %v, cache.Options.ByObject has multiple types with the same GroupVersionKind" , gvk )
281- }
282- out [gvk ] = internal.InformersOptsByGVK {
283- Selector : internal.Selector {
284- Field : byObject .Field ,
285- Label : byObject .Label ,
286- },
287- Transform : byObject .Transform ,
288- UnsafeDisableDeepCopy : byObject .UnsafeDisableDeepCopy ,
289- }
410+ func defaultConfig (toDefault , defaultFrom Config ) Config {
411+ if toDefault .LabelSelector == nil {
412+ toDefault .LabelSelector = defaultFrom .LabelSelector
290413 }
291- return out , nil
414+ if toDefault .FieldSelector == nil {
415+ toDefault .FieldSelector = defaultFrom .FieldSelector
416+ }
417+ if toDefault .Transform == nil {
418+ toDefault .Transform = defaultFrom .Transform
419+ }
420+ if toDefault .UnsafeDisableDeepCopy == nil {
421+ toDefault .UnsafeDisableDeepCopy = defaultFrom .UnsafeDisableDeepCopy
422+ }
423+
424+ return toDefault
292425}
0 commit comments