From 408df4e1d52b2f37b501c7e8518a4402e826587b Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 16 Aug 2022 16:55:28 -0400 Subject: [PATCH 1/2] multiNamespaceCache: support custom newCache funcs per namespace Signed-off-by: Joe Lanford --- pkg/cache/cache_test.go | 180 +++++++++++++++++++++++++ pkg/cache/multi_namespace_cache.go | 205 ++++++++++++++++++++++++----- pkg/manager/example_test.go | 4 +- 3 files changed, 355 insertions(+), 34 deletions(-) diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go index e89e3a72de..4aed0f0ae3 100644 --- a/pkg/cache/cache_test.go +++ b/pkg/cache/cache_test.go @@ -121,6 +121,186 @@ var _ = Describe("Multi-Namespace Informer Cache", func() { var _ = Describe("Informer Cache without DeepCopy", func() { CacheTest(cache.New, cache.Options{UnsafeDisableDeepCopyByObject: cache.DisableDeepCopyByObject{cache.ObjectAll{}: true}}) }) +var _ = Describe("MultiNamespacedCacheWithOptionsBuilder", func() { + defer GinkgoRecover() + var ( + informerCache cache.Cache + informerCacheCtx context.Context + informerCacheCancel context.CancelFunc + pod1a client.Object + pod1b client.Object + pod2a client.Object + pod2b client.Object + pod2c client.Object + pod3a client.Object + pod3b client.Object + ) + BeforeEach(func() { + informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background()) + Expect(cfg).NotTo(BeNil()) + cl, err := client.New(cfg, client.Options{}) + Expect(err).NotTo(HaveOccurred()) + err = ensureNamespace(testNamespaceOne, cl) + Expect(err).NotTo(HaveOccurred()) + err = ensureNamespace(testNamespaceTwo, cl) + Expect(err).NotTo(HaveOccurred()) + err = ensureNamespace(testNamespaceThree, cl) + Expect(err).NotTo(HaveOccurred()) + err = ensureNode(testNodeOne, cl) + Expect(err).NotTo(HaveOccurred()) + // namespace 1 stuff + pod1a = createPod("pod-1a", testNamespaceOne, corev1.RestartPolicyNever) // matches (everything matches) + pod1b = createPodWithLabels("pod-1b", testNamespaceOne, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // matches (everything matches) + // namespace 2 stuff + pod2a = createPodWithLabels("pod-2a", testNamespaceTwo, corev1.RestartPolicyNever, map[string]string{"ns2-match": "false"}) // no match (does not match ns2 label selector) + pod2b = createPodWithLabels("pod-2b", testNamespaceTwo, corev1.RestartPolicyNever, map[string]string{"ns2-match": "true"}) // matches (matches ns2 label selector) + pod2c = createPodWithLabels("pod-2c", testNamespaceTwo, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // no match (does not match ns2 label selector) + // namespace 3 stuff + pod3a = createPodWithLabels("pod-3a", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"other-match": "false"}) // no match (does not match default cache label selector) + pod3b = createPodWithLabels("pod-3b", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // matches (matches default cache label selector) + By("creating the informer cache") + informerCache, err = cache.MultiNamespacedCacheWithOptionsBuilder( + cache.WithNamespaceCache(testNamespaceOne, cache.New), + cache.WithNamespaceCache(testNamespaceTwo, cache.BuilderWithOptions(cache.Options{ + DefaultSelector: cache.ObjectSelector{ + Label: labels.Set{"ns2-match": "true"}.AsSelector(), + }, + })), + cache.WithDefaultNamespacedCache(cache.BuilderWithOptions(cache.Options{ + DefaultSelector: cache.ObjectSelector{ + Label: labels.Set{"other-match": "true"}.AsSelector(), + }, + })), + cache.WithClusterScopedCache(cache.BuilderWithOptions(cache.Options{ + DefaultSelector: cache.ObjectSelector{ + Field: fields.OneTermEqualSelector("metadata.name", testNodeOne), + }, + })), + )(cfg, cache.Options{}) + Expect(err).NotTo(HaveOccurred()) + By("running the cache and waiting for it to sync") + // pass as an arg so that we don't race between close and re-assign + go func(ctx context.Context) { + defer GinkgoRecover() + Expect(informerCache.Start(ctx)).To(Succeed()) + }(informerCacheCtx) + Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue()) + }) + Describe("Get", func() { + It("should get an item from a namespace cache", func() { + pod := &corev1.Pod{} + err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod1a), pod) + Expect(err).NotTo(HaveOccurred()) + }) + It("should get an item from the default namespace cache", func() { + pod := &corev1.Pod{} + err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod3b), pod) + Expect(err).NotTo(HaveOccurred()) + }) + It("should get a cluster-scoped item", func() { + node := &corev1.Node{} + err := informerCache.Get(informerCacheCtx, client.ObjectKey{Name: testNodeOne}, node) + Expect(err).NotTo(HaveOccurred()) + }) + It("should not find an item from a namespace-specific cache if it is not matched", func() { + pod := &corev1.Pod{} + err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod2a), pod) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }) + It("should not find an item from the default namespace cache if it is not matched", func() { + pod := &corev1.Pod{} + err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod3a), pod) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }) + It("should not find an item at the cluster-scope if it is not matched", func() { + ns := &corev1.Namespace{} + err := informerCache.Get(informerCacheCtx, client.ObjectKey{Name: testNamespaceOne}, ns) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }) + }) + Describe("List", func() { + When("Request is cluster-scoped", func() { + It("Should list all pods and find exactly four", func() { + var pods corev1.PodList + err := informerCache.List(informerCacheCtx, &pods) + Expect(err).NotTo(HaveOccurred()) + sort.Slice(pods.Items, func(i, j int) bool { + if pods.Items[i].Namespace != pods.Items[j].Namespace { + return pods.Items[i].Namespace < pods.Items[j].Namespace + } + return pods.Items[i].Name < pods.Items[j].Name + }) + Expect(pods.Items).To(HaveLen(4)) + Expect(pods.Items[0].Namespace).To(Equal(testNamespaceOne)) + Expect(pods.Items[0].Name).To(Equal("pod-1a")) + Expect(pods.Items[1].Namespace).To(Equal(testNamespaceOne)) + Expect(pods.Items[1].Name).To(Equal("pod-1b")) + Expect(pods.Items[2].Namespace).To(Equal(testNamespaceTwo)) + Expect(pods.Items[2].Name).To(Equal("pod-2b")) + Expect(pods.Items[3].Namespace).To(Equal(testNamespaceThree)) + Expect(pods.Items[3].Name).To(Equal("pod-3b")) + }) + It("Should list nodes and find exactly one", func() { + var nodes corev1.NodeList + err := informerCache.List(informerCacheCtx, &nodes) + Expect(err).NotTo(HaveOccurred()) + Expect(nodes.Items).To(HaveLen(1)) + Expect(nodes.Items[0].Namespace).To(Equal("")) + Expect(nodes.Items[0].Name).To(Equal(testNodeOne)) + }) + It("Should list namespaces and find none", func() { + var namespaces corev1.NamespaceList + err := informerCache.List(informerCacheCtx, &namespaces) + Expect(err).NotTo(HaveOccurred()) + Expect(namespaces.Items).To(HaveLen(0)) + }) + }) + When("Request is namespace-scoped", func() { + It("Should list pods in namespace one", func() { + var pods corev1.PodList + err := informerCache.List(informerCacheCtx, &pods, client.InNamespace(testNamespaceOne)) + Expect(err).NotTo(HaveOccurred()) + sort.Slice(pods.Items, func(i, j int) bool { + if pods.Items[i].Namespace != pods.Items[j].Namespace { + return pods.Items[i].Namespace < pods.Items[j].Namespace + } + return pods.Items[i].Name < pods.Items[j].Name + }) + Expect(pods.Items).To(HaveLen(2)) + Expect(pods.Items[0].Namespace).To(Equal(testNamespaceOne)) + Expect(pods.Items[0].Name).To(Equal("pod-1a")) + Expect(pods.Items[1].Namespace).To(Equal(testNamespaceOne)) + Expect(pods.Items[1].Name).To(Equal("pod-1b")) + }) + It("Should list pods in namespace two", func() { + var pods corev1.PodList + err := informerCache.List(informerCacheCtx, &pods, client.InNamespace(testNamespaceTwo)) + Expect(err).NotTo(HaveOccurred()) + Expect(pods.Items).To(HaveLen(1)) + Expect(pods.Items[0].Namespace).To(Equal(testNamespaceTwo)) + Expect(pods.Items[0].Name).To(Equal("pod-2b")) + }) + It("Should list pods in namespace three", func() { + var pods corev1.PodList + err := informerCache.List(informerCacheCtx, &pods, client.InNamespace(testNamespaceThree)) + Expect(err).NotTo(HaveOccurred()) + Expect(pods.Items).To(HaveLen(1)) + Expect(pods.Items[0].Namespace).To(Equal(testNamespaceThree)) + Expect(pods.Items[0].Name).To(Equal("pod-3b")) + }) + }) + }) + AfterEach(func() { + deletePod(pod1a) + deletePod(pod1b) + deletePod(pod2a) + deletePod(pod2b) + deletePod(pod2c) + deletePod(pod3a) + deletePod(pod3b) + informerCacheCancel() + }) +}) var _ = Describe("Cache with transformers", func() { var ( diff --git a/pkg/cache/multi_namespace_cache.go b/pkg/cache/multi_namespace_cache.go index 64514c0c55..1b82719255 100644 --- a/pkg/cache/multi_namespace_cache.go +++ b/pkg/cache/multi_namespace_cache.go @@ -23,10 +23,12 @@ import ( corev1 "k8s.io/api/core/v1" apimeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" toolscache "k8s.io/client-go/tools/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/internal/objectutil" ) @@ -37,37 +39,153 @@ type NewCacheFunc func(config *rest.Config, opts Options) (Cache, error) // a new global namespaced cache to handle cluster scoped resources. const globalCache = "_cluster-scope" -// MultiNamespacedCacheBuilder - Builder function to create a new multi-namespaced cache. -// This will scope the cache to a list of namespaces. Listing for all namespaces -// will list for all the namespaces that this knows about. By default this will create -// a global cache for cluster scoped resource. Note that this is not intended -// to be used for excluding namespaces, this is better done via a Predicate. Also note that -// you may face performance issues when using this with a high number of namespaces. -func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc { +// MultiNamespacedOption is a function that modifies a MultiNamespacedOptions. +type MultiNamespacedOption func(*MultiNamespacedOptions) + +// MultiNamespacedOptions is used to configure the functions used to create caches +// on a per-namespace basis. +type MultiNamespacedOptions struct { + NewNamespaceCaches map[string]NewCacheFunc + NewClusterScopedCache NewCacheFunc + NewDefaultNamespacedCache NewCacheFunc +} + +// WithLegacyNamespaceCaches configures the MultiNamespacedCacheWithOptionsBuilder +// with standard caches in each of the namespaces provided as well as for cluster-scoped +// objects. This option enables use of the MultiNamespacedCacheWithOptionsBuilder +// to match the behavior of the deprecated MultiNamespacedCacheBuilder. +func WithLegacyNamespaceCaches(namespaces []string) MultiNamespacedOption { + return func(options *MultiNamespacedOptions) { + WithNamespaceCaches(namespaces, New)(options) + WithClusterScopedCache(New)(options) + } +} + +// WithNamespaceCaches configures MultiNamespacedCacheWithOptionsBuilder +// with namespace-specific caches that are created using the provided NewCacheFunc. +func WithNamespaceCaches(namespaces []string, f NewCacheFunc) MultiNamespacedOption { + return func(options *MultiNamespacedOptions) { + for _, ns := range namespaces { + WithNamespaceCache(ns, f)(options) + } + } +} + +// WithNamespaceCache configures MultiNamespacedCacheWithOptionsBuilder +// with a namespace cache that uses the provided NewCacheFunc. +func WithNamespaceCache(namespace string, f NewCacheFunc) MultiNamespacedOption { + return func(options *MultiNamespacedOptions) { + options.NewNamespaceCaches[namespace] = f + } +} + +// WithClusterScopedCache configures MultiNamespacedCacheWithOptionsBuilder +// with a cache for cluster-scoped objects that uses the provided NewCacheFunc. +func WithClusterScopedCache(f NewCacheFunc) MultiNamespacedOption { + return func(options *MultiNamespacedOptions) { + options.NewClusterScopedCache = f + } +} + +// WithDefaultNamespacedCache configures MultiNamespacedCacheWithOptionsBuilder +// with a "catch-all" cache for namespace-scoped objects that are in namespaces +// explicitly configured on the cache builder. +func WithDefaultNamespacedCache(f NewCacheFunc) MultiNamespacedOption { + return func(options *MultiNamespacedOptions) { + options.NewDefaultNamespacedCache = f + } +} + +// MultiNamespacedCacheWithOptionsBuilder builds a composite cache that delegates to per-namespace +// caches built according to the passed MultiNamespacedOption options. +// +// If the set of options passed to MultiNamespacedCacheWithOptionsBuilder results in the NewCacheFunc +// being set multiple times for the same namespace, the last such setting will be used. +// +// If a default namespaced cache is defined (e.g. with WithDefaultNamespacedCache), it will be used +// as a catch-all for objects not in the explicit namespaces configured on the cache builder. The +// default namespaced cache is automatically configured with extra field selectors to avoid duplicate +// caching of objects between namespace-specific caches and this catch-all cache. +// +// If a cluster-scoped cache is defined (e.g. with WithClusterScopedCache), it will be used for +// cluster-scoped objects. If it is undefined, the resulting cache will return an error during read +// operations, reporting that a cluster-scoped cache is not defined. +func MultiNamespacedCacheWithOptionsBuilder(opts ...MultiNamespacedOption) NewCacheFunc { + multiNamespaceOpts := MultiNamespacedOptions{ + NewNamespaceCaches: map[string]NewCacheFunc{}, + } + for _, opt := range opts { + opt(&multiNamespaceOpts) + } + return func(config *rest.Config, opts Options) (Cache, error) { opts, err := defaultOpts(config, opts) if err != nil { return nil, err } - caches := map[string]Cache{} + var clusterCache Cache + if multiNamespaceOpts.NewClusterScopedCache != nil { + clusterCache, err = multiNamespaceOpts.NewClusterScopedCache(config, opts) + if err != nil { + return nil, err + } + } - // create a cache for cluster scoped resources - gCache, err := New(config, opts) - if err != nil { - return nil, fmt.Errorf("error creating global cache: %w", err) + nsToCache := map[string]Cache{} + if multiNamespaceOpts.NewDefaultNamespacedCache != nil { + defaultNamespaceCache, err := multiNamespaceOpts.NewDefaultNamespacedCache(config, ignoreNamespaces(opts, multiNamespaceOpts.NewNamespaceCaches)) + if err != nil { + return nil, err + } + nsToCache[corev1.NamespaceAll] = defaultNamespaceCache } - for _, ns := range namespaces { + for ns, newCacheFunc := range multiNamespaceOpts.NewNamespaceCaches { opts.Namespace = ns - c, err := New(config, opts) + nsToCache[ns], err = newCacheFunc(config, opts) if err != nil { return nil, err } - caches[ns] = c } - return &multiNamespaceCache{namespaceToCache: caches, Scheme: opts.Scheme, RESTMapper: opts.Mapper, clusterCache: gCache}, nil + + return &multiNamespaceCache{ + namespaceToCache: nsToCache, + clusterCache: clusterCache, + RESTMapper: opts.Mapper, + Scheme: opts.Scheme, + }, nil + } +} + +func ignoreNamespaces(opts Options, newObjectCaches map[string]NewCacheFunc) Options { + fieldSelectors := []fields.Selector{} + if opts.DefaultSelector.Field != nil { + fieldSelectors = append(fieldSelectors, opts.DefaultSelector.Field) } + for ns := range newObjectCaches { + fieldSelectors = append(fieldSelectors, fields.OneTermNotEqualSelector("metadata.namespace", ns)) + } + opts.DefaultSelector.Field = fields.AndSelectors(fieldSelectors...) + return opts +} + +// MultiNamespacedCacheBuilder - Builder function to create a new multi-namespaced cache. +// This will scope the cache to a list of namespaces. Listing for all namespaces +// will list for all the namespaces that this knows about. By default this will create +// a global cache for cluster scoped resource. Note that this is not intended +// to be used for excluding namespaces, this is better done via a Predicate. Also note that +// you may face performance issues when using this with a high number of namespaces. +// +// Deprecated: Use MultiNamespacedCacheWithOptionsBuilder instead: +// +// cache.MultiNamespacedCacheWithOptionsBuilder( +// WithLegacyNamespaceCaches(namespaces), +// ) +func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc { + return MultiNamespacedCacheWithOptionsBuilder( + WithLegacyNamespaceCaches(namespaces), + ) } // multiNamespaceCache knows how to handle multiple namespaced caches @@ -94,12 +212,13 @@ func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object return nil, err } if !isNamespaced { - clusterCacheInf, err := c.clusterCache.GetInformer(ctx, obj) - if err != nil { - return nil, err + if c.clusterCache != nil { + clusterCacheInf, err := c.clusterCache.GetInformer(ctx, obj) + if err != nil { + return nil, err + } + informers[globalCache] = clusterCacheInf } - informers[globalCache] = clusterCacheInf - return &multiNamespaceInformer{namespaceToInformer: informers}, nil } @@ -124,12 +243,13 @@ func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema return nil, err } if !isNamespaced { - clusterCacheInf, err := c.clusterCache.GetInformerForKind(ctx, gvk) - if err != nil { - return nil, err + if c.clusterCache != nil { + clusterCacheInf, err := c.clusterCache.GetInformerForKind(ctx, gvk) + if err != nil { + return nil, err + } + informers[globalCache] = clusterCacheInf } - informers[globalCache] = clusterCacheInf - return &multiNamespaceInformer{namespaceToInformer: informers}, nil } @@ -146,12 +266,14 @@ func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema func (c *multiNamespaceCache) Start(ctx context.Context) error { // start global cache - go func() { - err := c.clusterCache.Start(ctx) - if err != nil { - log.Error(err, "cluster scoped cache failed to start") - } - }() + if c.clusterCache != nil { + go func() { + err := c.clusterCache.Start(ctx) + if err != nil { + log.Error(err, "cluster scoped cache failed to start") + } + }() + } // start namespaced caches for ns, cache := range c.namespaceToCache { @@ -176,7 +298,7 @@ func (c *multiNamespaceCache) WaitForCacheSync(ctx context.Context) bool { } // check if cluster scoped cache has synced - if !c.clusterCache.WaitForCacheSync(ctx) { + if c.clusterCache != nil && !c.clusterCache.WaitForCacheSync(ctx) { synced = false } return synced @@ -189,6 +311,9 @@ func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object, } if !isNamespaced { + if c.clusterCache == nil { + return nil + } return c.clusterCache.IndexField(ctx, obj, field, extractValue) } @@ -207,11 +332,18 @@ func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj } if !isNamespaced { + if c.clusterCache == nil { + return fmt.Errorf("unable to get: %v because cluster-scoped cache does not exist", key) + } // Look into the global cache to fetch the object return c.clusterCache.Get(ctx, key, obj) } cache, ok := c.namespaceToCache[key.Namespace] + if !ok { + // Use the default/catch-all namespace cache if we have one. + cache, ok = c.namespaceToCache[corev1.NamespaceAll] + } if !ok { return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", key) } @@ -229,12 +361,19 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList, } if !isNamespaced { + if c.clusterCache == nil { + return fmt.Errorf("unable to get because cluster-scoped cache does not exist") + } // Look at the global cache to get the objects with the specified GVK return c.clusterCache.List(ctx, list, opts...) } if listOpts.Namespace != corev1.NamespaceAll { cache, ok := c.namespaceToCache[listOpts.Namespace] + if !ok { + // Use the default/catch-all namespace cache if we have one. + cache, ok = c.namespaceToCache[corev1.NamespaceAll] + } if !ok { return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", listOpts.Namespace) } diff --git a/pkg/manager/example_test.go b/pkg/manager/example_test.go index 17557d1817..4fa79ace49 100644 --- a/pkg/manager/example_test.go +++ b/pkg/manager/example_test.go @@ -59,7 +59,9 @@ func ExampleNew_multinamespaceCache() { } mgr, err := manager.New(cfg, manager.Options{ - NewCache: cache.MultiNamespacedCacheBuilder([]string{"namespace1", "namespace2"}), + NewCache: cache.MultiNamespacedCacheWithOptionsBuilder( + cache.WithLegacyNamespaceCaches([]string{"namespace1", "namespace2"}), + ), }) if err != nil { log.Error(err, "unable to set up manager") From 40e2e1f93643bebe8943368d2e49c9d9528a3558 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 6 Sep 2022 08:19:13 -0400 Subject: [PATCH 2/2] address PR feedback Signed-off-by: Joe Lanford --- pkg/cache/cache_test.go | 4 +-- pkg/cache/multi_namespace_cache.go | 41 +++++++++++++++--------------- pkg/manager/example_test.go | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go index 4aed0f0ae3..fc203e2aef 100644 --- a/pkg/cache/cache_test.go +++ b/pkg/cache/cache_test.go @@ -160,8 +160,8 @@ var _ = Describe("MultiNamespacedCacheWithOptionsBuilder", func() { pod3b = createPodWithLabels("pod-3b", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // matches (matches default cache label selector) By("creating the informer cache") informerCache, err = cache.MultiNamespacedCacheWithOptionsBuilder( - cache.WithNamespaceCache(testNamespaceOne, cache.New), - cache.WithNamespaceCache(testNamespaceTwo, cache.BuilderWithOptions(cache.Options{ + cache.WithNamespacedCache(testNamespaceOne, cache.New), + cache.WithNamespacedCache(testNamespaceTwo, cache.BuilderWithOptions(cache.Options{ DefaultSelector: cache.ObjectSelector{ Label: labels.Set{"ns2-match": "true"}.AsSelector(), }, diff --git a/pkg/cache/multi_namespace_cache.go b/pkg/cache/multi_namespace_cache.go index 1b82719255..60079d5b2d 100644 --- a/pkg/cache/multi_namespace_cache.go +++ b/pkg/cache/multi_namespace_cache.go @@ -45,37 +45,37 @@ type MultiNamespacedOption func(*MultiNamespacedOptions) // MultiNamespacedOptions is used to configure the functions used to create caches // on a per-namespace basis. type MultiNamespacedOptions struct { - NewNamespaceCaches map[string]NewCacheFunc + NewNamespacedCaches map[string]NewCacheFunc NewClusterScopedCache NewCacheFunc NewDefaultNamespacedCache NewCacheFunc } -// WithLegacyNamespaceCaches configures the MultiNamespacedCacheWithOptionsBuilder +// WithLegacyNamespacedCaches configures the MultiNamespacedCacheWithOptionsBuilder // with standard caches in each of the namespaces provided as well as for cluster-scoped // objects. This option enables use of the MultiNamespacedCacheWithOptionsBuilder // to match the behavior of the deprecated MultiNamespacedCacheBuilder. -func WithLegacyNamespaceCaches(namespaces []string) MultiNamespacedOption { +func WithLegacyNamespacedCaches(namespaces []string) MultiNamespacedOption { return func(options *MultiNamespacedOptions) { - WithNamespaceCaches(namespaces, New)(options) + WithNamespacedCaches(namespaces, New)(options) WithClusterScopedCache(New)(options) } } -// WithNamespaceCaches configures MultiNamespacedCacheWithOptionsBuilder +// WithNamespacedCaches configures MultiNamespacedCacheWithOptionsBuilder // with namespace-specific caches that are created using the provided NewCacheFunc. -func WithNamespaceCaches(namespaces []string, f NewCacheFunc) MultiNamespacedOption { +func WithNamespacedCaches(namespaces []string, f NewCacheFunc) MultiNamespacedOption { return func(options *MultiNamespacedOptions) { for _, ns := range namespaces { - WithNamespaceCache(ns, f)(options) + WithNamespacedCache(ns, f)(options) } } } -// WithNamespaceCache configures MultiNamespacedCacheWithOptionsBuilder +// WithNamespacedCache configures MultiNamespacedCacheWithOptionsBuilder // with a namespace cache that uses the provided NewCacheFunc. -func WithNamespaceCache(namespace string, f NewCacheFunc) MultiNamespacedOption { +func WithNamespacedCache(namespace string, f NewCacheFunc) MultiNamespacedOption { return func(options *MultiNamespacedOptions) { - options.NewNamespaceCaches[namespace] = f + options.NewNamespacedCaches[namespace] = f } } @@ -89,7 +89,7 @@ func WithClusterScopedCache(f NewCacheFunc) MultiNamespacedOption { // WithDefaultNamespacedCache configures MultiNamespacedCacheWithOptionsBuilder // with a "catch-all" cache for namespace-scoped objects that are in namespaces -// explicitly configured on the cache builder. +// not explicitly configured on the cache builder. func WithDefaultNamespacedCache(f NewCacheFunc) MultiNamespacedOption { return func(options *MultiNamespacedOptions) { options.NewDefaultNamespacedCache = f @@ -112,7 +112,7 @@ func WithDefaultNamespacedCache(f NewCacheFunc) MultiNamespacedOption { // operations, reporting that a cluster-scoped cache is not defined. func MultiNamespacedCacheWithOptionsBuilder(opts ...MultiNamespacedOption) NewCacheFunc { multiNamespaceOpts := MultiNamespacedOptions{ - NewNamespaceCaches: map[string]NewCacheFunc{}, + NewNamespacedCaches: map[string]NewCacheFunc{}, } for _, opt := range opts { opt(&multiNamespaceOpts) @@ -134,14 +134,15 @@ func MultiNamespacedCacheWithOptionsBuilder(opts ...MultiNamespacedOption) NewCa nsToCache := map[string]Cache{} if multiNamespaceOpts.NewDefaultNamespacedCache != nil { - defaultNamespaceCache, err := multiNamespaceOpts.NewDefaultNamespacedCache(config, ignoreNamespaces(opts, multiNamespaceOpts.NewNamespaceCaches)) + defaultNamespacedOpts := setDefaultNamespacedCacheOpts(opts, multiNamespaceOpts.NewNamespacedCaches) + defaultNamespacedCache, err := multiNamespaceOpts.NewDefaultNamespacedCache(config, defaultNamespacedOpts) if err != nil { return nil, err } - nsToCache[corev1.NamespaceAll] = defaultNamespaceCache + nsToCache[corev1.NamespaceAll] = defaultNamespacedCache } - for ns, newCacheFunc := range multiNamespaceOpts.NewNamespaceCaches { + for ns, newCacheFunc := range multiNamespaceOpts.NewNamespacedCaches { opts.Namespace = ns nsToCache[ns], err = newCacheFunc(config, opts) if err != nil { @@ -158,7 +159,7 @@ func MultiNamespacedCacheWithOptionsBuilder(opts ...MultiNamespacedOption) NewCa } } -func ignoreNamespaces(opts Options, newObjectCaches map[string]NewCacheFunc) Options { +func setDefaultNamespacedCacheOpts(opts Options, newObjectCaches map[string]NewCacheFunc) Options { fieldSelectors := []fields.Selector{} if opts.DefaultSelector.Field != nil { fieldSelectors = append(fieldSelectors, opts.DefaultSelector.Field) @@ -180,11 +181,11 @@ func ignoreNamespaces(opts Options, newObjectCaches map[string]NewCacheFunc) Opt // Deprecated: Use MultiNamespacedCacheWithOptionsBuilder instead: // // cache.MultiNamespacedCacheWithOptionsBuilder( -// WithLegacyNamespaceCaches(namespaces), +// WithLegacyNamespacedCaches(namespaces), // ) func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc { return MultiNamespacedCacheWithOptionsBuilder( - WithLegacyNamespaceCaches(namespaces), + WithLegacyNamespacedCaches(namespaces), ) } @@ -345,7 +346,7 @@ func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj cache, ok = c.namespaceToCache[corev1.NamespaceAll] } if !ok { - return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", key) + return fmt.Errorf("unable to get %q: neither a per-namespace nor a default namespaced cache exists", key) } return cache.Get(ctx, key, obj) } @@ -375,7 +376,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList, cache, ok = c.namespaceToCache[corev1.NamespaceAll] } if !ok { - return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", listOpts.Namespace) + return fmt.Errorf("unable to list in namespace %q: neither a per-namespace nor a default namespaced cache exists", listOpts.Namespace) } return cache.List(ctx, list, opts...) } diff --git a/pkg/manager/example_test.go b/pkg/manager/example_test.go index 4fa79ace49..8248f4ad88 100644 --- a/pkg/manager/example_test.go +++ b/pkg/manager/example_test.go @@ -60,7 +60,7 @@ func ExampleNew_multinamespaceCache() { mgr, err := manager.New(cfg, manager.Options{ NewCache: cache.MultiNamespacedCacheWithOptionsBuilder( - cache.WithLegacyNamespaceCaches([]string{"namespace1", "namespace2"}), + cache.WithLegacyNamespacedCaches([]string{"namespace1", "namespace2"}), ), }) if err != nil {