rebase: Bump sigs.k8s.io/controller-runtime from 0.15.1 to 0.16.0

Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.15.1 to 0.16.0.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.15.1...v0.16.0)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-08-28 20:44:55 +00:00
committed by mergify[bot]
parent 97d9f701ec
commit a51516501c
78 changed files with 1539 additions and 2899 deletions

View File

@ -22,8 +22,8 @@ import (
"net/http"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -31,6 +31,7 @@ import (
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
toolscache "k8s.io/client-go/tools/cache"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -43,14 +44,28 @@ var (
defaultSyncPeriod = 10 * time.Hour
)
// InformerGetOptions defines the behavior of how informers are retrieved.
type InformerGetOptions internal.GetOptions
// InformerGetOption defines an option that alters the behavior of how informers are retrieved.
type InformerGetOption func(*InformerGetOptions)
// BlockUntilSynced determines whether a get request for an informer should block
// until the informer's cache has synced.
func BlockUntilSynced(shouldBlock bool) InformerGetOption {
return func(opts *InformerGetOptions) {
opts.BlockUntilSynced = &shouldBlock
}
}
// Cache knows how to load Kubernetes objects, fetch informers to request
// to receive events for Kubernetes objects (at a low-level),
// and add indices to fields on the objects stored in the cache.
type Cache interface {
// Cache acts as a client to objects stored in the cache.
// Reader acts as a client to objects stored in the cache.
client.Reader
// Cache loads informers and adds field indices.
// Informers loads informers and adds field indices.
Informers
}
@ -60,49 +75,53 @@ type Cache interface {
type Informers interface {
// GetInformer fetches or constructs an informer for the given object that corresponds to a single
// API kind and resource.
GetInformer(ctx context.Context, obj client.Object) (Informer, error)
GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error)
// GetInformerForKind is similar to GetInformer, except that it takes a group-version-kind, instead
// of the underlying object.
GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error)
GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error)
// Start runs all the informers known to this cache until the context is closed.
// It blocks.
Start(ctx context.Context) error
// WaitForCacheSync waits for all the caches to sync. Returns false if it could not sync a cache.
// WaitForCacheSync waits for all the caches to sync. Returns false if it could not sync a cache.
WaitForCacheSync(ctx context.Context) bool
// Informers knows how to add indices to the caches (informers) that it manages.
// FieldIndexer adds indices to the managed informers.
client.FieldIndexer
}
// Informer - informer allows you interact with the underlying informer.
// Informer allows you to interact with the underlying informer.
type Informer interface {
// AddEventHandler adds an event handler to the shared informer using the shared informer's resync
// period. Events to a single handler are delivered sequentially, but there is no coordination
// period. Events to a single handler are delivered sequentially, but there is no coordination
// between different handlers.
// It returns a registration handle for the handler that can be used to remove
// the handler again.
// the handler again and an error if the handler cannot be added.
AddEventHandler(handler toolscache.ResourceEventHandler) (toolscache.ResourceEventHandlerRegistration, error)
// AddEventHandlerWithResyncPeriod adds an event handler to the shared informer using the
// specified resync period. Events to a single handler are delivered sequentially, but there is
// specified resync period. Events to a single handler are delivered sequentially, but there is
// no coordination between different handlers.
// It returns a registration handle for the handler that can be used to remove
// the handler again and an error if the handler cannot be added.
AddEventHandlerWithResyncPeriod(handler toolscache.ResourceEventHandler, resyncPeriod time.Duration) (toolscache.ResourceEventHandlerRegistration, error)
// RemoveEventHandler removes a formerly added event handler given by
// RemoveEventHandler removes a previously added event handler given by
// its registration handle.
// This function is guaranteed to be idempotent, and thread-safe.
// This function is guaranteed to be idempotent and thread-safe.
RemoveEventHandler(handle toolscache.ResourceEventHandlerRegistration) error
// AddIndexers adds more indexers to this store. If you call this after you already have data
// AddIndexers adds indexers to this store. If this is called after there is already data
// in the store, the results are undefined.
AddIndexers(indexers toolscache.Indexers) error
// HasSynced return true if the informers underlying store has synced.
HasSynced() bool
}
// Options are the optional arguments for creating a new InformersMap object.
// Options are the optional arguments for creating a new Cache object.
type Options struct {
// HTTPClient is the http client to use for the REST client
HTTPClient *http.Client
@ -140,45 +159,80 @@ type Options struct {
// instead of `reconcile.Result{}`.
SyncPeriod *time.Duration
// Namespaces restricts the cache's ListWatch to the desired namespaces
// Default watches all namespaces
Namespaces []string
// ReaderFailOnMissingInformer configures the cache to return a ErrResourceNotCached error when a user
// requests, using Get() and List(), a resource the cache does not already have an informer for.
//
// This error is distinct from an errors.NotFound.
//
// Defaults to false, which means that the cache will start a new informer
// for every new requested resource.
ReaderFailOnMissingInformer bool
// DefaultLabelSelector will be used as a label selectors for all object types
// unless they have a more specific selector set in ByObject.
// DefaultNamespaces maps namespace names to cache configs. If set, only
// the namespaces in here will be watched and it will by used to default
// ByObject.Namespaces for all objects if that is nil.
//
// The options in the Config that are nil will be defaulted from
// the respective Default* settings.
DefaultNamespaces map[string]Config
// DefaultLabelSelector will be used as a label selector for all objects
// unless there is already one set in ByObject or DefaultNamespaces.
DefaultLabelSelector labels.Selector
// DefaultFieldSelector will be used as a field selectors for all object types
// unless they have a more specific selector set in ByObject.
// DefaultFieldSelector will be used as a field selector for all object types
// unless there is already one set in ByObject or DefaultNamespaces.
DefaultFieldSelector fields.Selector
// DefaultTransform will be used as transform for all object types
// unless they have a more specific transform set in ByObject.
// unless there is already one set in ByObject or DefaultNamespaces.
DefaultTransform toolscache.TransformFunc
// ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object.
ByObject map[client.Object]ByObject
// UnsafeDisableDeepCopy indicates not to deep copy objects during get or
// list objects for EVERY object.
// DefaultUnsafeDisableDeepCopy is the default for UnsafeDisableDeepCopy
// for everything that doesn't specify this.
//
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
// otherwise you will mutate the object in the cache.
//
// This is a global setting for all objects, and can be overridden by the ByObject setting.
UnsafeDisableDeepCopy *bool
// This will be used for all object types, unless it is set in ByObject or
// DefaultNamespaces.
DefaultUnsafeDisableDeepCopy *bool
// ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object.
// object, this will fall through to Default* settings.
ByObject map[client.Object]ByObject
// newInformer allows overriding of NewSharedIndexInformer for testing.
newInformer *func(toolscache.ListerWatcher, runtime.Object, time.Duration, toolscache.Indexers) toolscache.SharedIndexInformer
}
// ByObject offers more fine-grained control over the cache's ListWatch by object.
type ByObject struct {
// Namespaces maps a namespace name to cache configs. If set, only the
// namespaces in this map will be cached.
//
// Settings in the map value that are unset will be defaulted.
// Use an empty value for the specific setting to prevent that.
//
// A nil map allows to default this to the cache's DefaultNamespaces setting.
// An empty map prevents this and means that all namespaces will be cached.
//
// The defaulting follows the following precedence order:
// 1. ByObject
// 2. DefaultNamespaces[namespace]
// 3. Default*
//
// This must be unset for cluster-scoped objects.
Namespaces map[string]Config
// Label represents a label selector for the object.
Label labels.Selector
// Field represents a field selector for the object.
Field fields.Selector
// Transform is a map from objects to transformer functions which
// get applied when objects of the transformation are about to be committed
// to cache.
// Transform is a transformer function for the object which gets applied
// when objects of the transformation are about to be committed to the cache.
//
// This function is called both for new objects to enter the cache,
// and for updated objects.
@ -191,48 +245,120 @@ type ByObject struct {
UnsafeDisableDeepCopy *bool
}
// Config describes all potential options for a given watch.
type Config struct {
// LabelSelector specifies a label selector. A nil value allows to
// default this.
//
// Set to labels.Everything() if you don't want this defaulted.
LabelSelector labels.Selector
// FieldSelector specifics a field selector. A nil value allows to
// default this.
//
// Set to fields.Everything() if you don't want this defaulted.
FieldSelector fields.Selector
// Transform specifies a transform func. A nil value allows to default
// this.
//
// Set to an empty func to prevent this:
// func(in interface{}) (interface{}, error) { return in, nil }
Transform toolscache.TransformFunc
// UnsafeDisableDeepCopy specifies if List and Get requests against the
// cache should not DeepCopy. A nil value allows to default this.
UnsafeDisableDeepCopy *bool
}
// NewCacheFunc - Function for creating a new cache from the options and a rest config.
type NewCacheFunc func(config *rest.Config, opts Options) (Cache, error)
// New initializes and returns a new Cache.
func New(config *rest.Config, opts Options) (Cache, error) {
if len(opts.Namespaces) == 0 {
opts.Namespaces = []string{metav1.NamespaceAll}
}
if len(opts.Namespaces) > 1 {
return newMultiNamespaceCache(config, opts)
}
opts, err := defaultOpts(config, opts)
func New(cfg *rest.Config, opts Options) (Cache, error) {
opts, err := defaultOpts(cfg, opts)
if err != nil {
return nil, err
}
byGVK, err := convertToInformerOptsByGVK(opts.ByObject, opts.Scheme)
if err != nil {
return nil, err
newCacheFunc := newCache(cfg, opts)
var defaultCache Cache
if len(opts.DefaultNamespaces) > 0 {
defaultConfig := optionDefaultsToConfig(&opts)
defaultCache = newMultiNamespaceCache(newCacheFunc, opts.Scheme, opts.Mapper, opts.DefaultNamespaces, &defaultConfig)
} else {
defaultCache = newCacheFunc(optionDefaultsToConfig(&opts), corev1.NamespaceAll)
}
// Set the default selector and transform.
byGVK[schema.GroupVersionKind{}] = internal.InformersOptsByGVK{
Selector: internal.Selector{
Label: opts.DefaultLabelSelector,
Field: opts.DefaultFieldSelector,
},
if len(opts.ByObject) == 0 {
return defaultCache, nil
}
delegating := &delegatingByGVKCache{
scheme: opts.Scheme,
caches: make(map[schema.GroupVersionKind]Cache, len(opts.ByObject)),
defaultCache: defaultCache,
}
for obj, config := range opts.ByObject {
gvk, err := apiutil.GVKForObject(obj, opts.Scheme)
if err != nil {
return nil, fmt.Errorf("failed to get GVK for type %T: %w", obj, err)
}
var cache Cache
if len(config.Namespaces) > 0 {
cache = newMultiNamespaceCache(newCacheFunc, opts.Scheme, opts.Mapper, config.Namespaces, nil)
} else {
cache = newCacheFunc(byObjectToConfig(config), corev1.NamespaceAll)
}
delegating.caches[gvk] = cache
}
return delegating, nil
}
func optionDefaultsToConfig(opts *Options) Config {
return Config{
LabelSelector: opts.DefaultLabelSelector,
FieldSelector: opts.DefaultFieldSelector,
Transform: opts.DefaultTransform,
UnsafeDisableDeepCopy: opts.UnsafeDisableDeepCopy,
UnsafeDisableDeepCopy: opts.DefaultUnsafeDisableDeepCopy,
}
}
return &informerCache{
scheme: opts.Scheme,
Informers: internal.NewInformers(config, &internal.InformersOpts{
HTTPClient: opts.HTTPClient,
Scheme: opts.Scheme,
Mapper: opts.Mapper,
ResyncPeriod: *opts.SyncPeriod,
Namespace: opts.Namespaces[0],
ByGVK: byGVK,
}),
}, nil
func byObjectToConfig(byObject ByObject) Config {
return Config{
LabelSelector: byObject.Label,
FieldSelector: byObject.Field,
Transform: byObject.Transform,
UnsafeDisableDeepCopy: byObject.UnsafeDisableDeepCopy,
}
}
type newCacheFunc func(config Config, namespace string) Cache
func newCache(restConfig *rest.Config, opts Options) newCacheFunc {
return func(config Config, namespace string) Cache {
return &informerCache{
scheme: opts.Scheme,
Informers: internal.NewInformers(restConfig, &internal.InformersOpts{
HTTPClient: opts.HTTPClient,
Scheme: opts.Scheme,
Mapper: opts.Mapper,
ResyncPeriod: *opts.SyncPeriod,
Namespace: namespace,
Selector: internal.Selector{
Label: config.LabelSelector,
Field: config.FieldSelector,
},
Transform: config.Transform,
UnsafeDisableDeepCopy: pointer.BoolDeref(config.UnsafeDisableDeepCopy, false),
NewInformer: opts.newInformer,
}),
readerFailOnMissingInformer: opts.ReaderFailOnMissingInformer,
}
}
}
func defaultOpts(config *rest.Config, opts Options) (Options, error) {
@ -241,15 +367,12 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
logger := log.WithName("setup")
// Use the rest HTTP client for the provided config if unset
if opts.HTTPClient == nil {
var err error
opts.HTTPClient, err = rest.HTTPClientFor(config)
if err != nil {
logger.Error(err, "Failed to get HTTP client")
return opts, fmt.Errorf("could not create HTTP client from config: %w", err)
return Options{}, fmt.Errorf("could not create HTTP client from config: %w", err)
}
}
@ -263,11 +386,54 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
var err error
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config, opts.HTTPClient)
if err != nil {
logger.Error(err, "Failed to get API Group-Resources")
return opts, fmt.Errorf("could not create RESTMapper from config: %w", err)
return Options{}, fmt.Errorf("could not create RESTMapper from config: %w", err)
}
}
for namespace, cfg := range opts.DefaultNamespaces {
cfg = defaultConfig(cfg, optionDefaultsToConfig(&opts))
opts.DefaultNamespaces[namespace] = cfg
}
for obj, byObject := range opts.ByObject {
isNamespaced, err := apiutil.IsObjectNamespaced(obj, opts.Scheme, opts.Mapper)
if err != nil {
return opts, fmt.Errorf("failed to determine if %T is namespaced: %w", obj, err)
}
if !isNamespaced && byObject.Namespaces != nil {
return opts, fmt.Errorf("type %T is not namespaced, but its ByObject.Namespaces setting is not nil", obj)
}
// Default the namespace-level configs first, because they need to use the undefaulted type-level config.
for namespace, config := range byObject.Namespaces {
// 1. Default from the undefaulted type-level config
config = defaultConfig(config, byObjectToConfig(byObject))
// 2. Default from the namespace-level config. This was defaulted from the global default config earlier, but
// might not have an entry for the current namespace.
if defaultNamespaceSettings, hasDefaultNamespace := opts.DefaultNamespaces[namespace]; hasDefaultNamespace {
config = defaultConfig(config, defaultNamespaceSettings)
}
// 3. Default from the global defaults
config = defaultConfig(config, optionDefaultsToConfig(&opts))
byObject.Namespaces[namespace] = config
}
defaultedConfig := defaultConfig(byObjectToConfig(byObject), optionDefaultsToConfig(&opts))
byObject.Label = defaultedConfig.LabelSelector
byObject.Field = defaultedConfig.FieldSelector
byObject.Transform = defaultedConfig.Transform
byObject.UnsafeDisableDeepCopy = defaultedConfig.UnsafeDisableDeepCopy
if byObject.Namespaces == nil {
byObject.Namespaces = opts.DefaultNamespaces
}
opts.ByObject[obj] = byObject
}
// Default the resync period to 10 hours if unset
if opts.SyncPeriod == nil {
opts.SyncPeriod = &defaultSyncPeriod
@ -275,24 +441,19 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
return opts, nil
}
func convertToInformerOptsByGVK(in map[client.Object]ByObject, scheme *runtime.Scheme) (map[schema.GroupVersionKind]internal.InformersOptsByGVK, error) {
out := map[schema.GroupVersionKind]internal.InformersOptsByGVK{}
for object, byObject := range in {
gvk, err := apiutil.GVKForObject(object, scheme)
if err != nil {
return nil, err
}
if _, ok := out[gvk]; ok {
return nil, fmt.Errorf("duplicate cache options for GVK %v, cache.Options.ByObject has multiple types with the same GroupVersionKind", gvk)
}
out[gvk] = internal.InformersOptsByGVK{
Selector: internal.Selector{
Field: byObject.Field,
Label: byObject.Label,
},
Transform: byObject.Transform,
UnsafeDisableDeepCopy: byObject.UnsafeDisableDeepCopy,
}
func defaultConfig(toDefault, defaultFrom Config) Config {
if toDefault.LabelSelector == nil {
toDefault.LabelSelector = defaultFrom.LabelSelector
}
return out, nil
if toDefault.FieldSelector == nil {
toDefault.FieldSelector = defaultFrom.FieldSelector
}
if toDefault.Transform == nil {
toDefault.Transform = defaultFrom.Transform
}
if toDefault.UnsafeDisableDeepCopy == nil {
toDefault.UnsafeDisableDeepCopy = defaultFrom.UnsafeDisableDeepCopy
}
return toDefault
}

View File

@ -0,0 +1,127 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"context"
"strings"
"sync"
"golang.org/x/exp/maps"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
// delegatingByGVKCache delegates to a type-specific cache if present
// and uses the defaultCache otherwise.
type delegatingByGVKCache struct {
scheme *runtime.Scheme
caches map[schema.GroupVersionKind]Cache
defaultCache Cache
}
func (dbt *delegatingByGVKCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
cache, err := dbt.cacheForObject(obj)
if err != nil {
return err
}
return cache.Get(ctx, key, obj, opts...)
}
func (dbt *delegatingByGVKCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
cache, err := dbt.cacheForObject(list)
if err != nil {
return err
}
return cache.List(ctx, list, opts...)
}
func (dbt *delegatingByGVKCache) GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error) {
cache, err := dbt.cacheForObject(obj)
if err != nil {
return nil, err
}
return cache.GetInformer(ctx, obj, opts...)
}
func (dbt *delegatingByGVKCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error) {
return dbt.cacheForGVK(gvk).GetInformerForKind(ctx, gvk, opts...)
}
func (dbt *delegatingByGVKCache) Start(ctx context.Context) error {
allCaches := maps.Values(dbt.caches)
allCaches = append(allCaches, dbt.defaultCache)
wg := &sync.WaitGroup{}
errs := make(chan error)
for idx := range allCaches {
cache := allCaches[idx]
wg.Add(1)
go func() {
defer wg.Done()
if err := cache.Start(ctx); err != nil {
errs <- err
}
}()
}
select {
case err := <-errs:
return err
case <-ctx.Done():
wg.Wait()
return nil
}
}
func (dbt *delegatingByGVKCache) WaitForCacheSync(ctx context.Context) bool {
synced := true
for _, cache := range append(maps.Values(dbt.caches), dbt.defaultCache) {
if !cache.WaitForCacheSync(ctx) {
synced = false
}
}
return synced
}
func (dbt *delegatingByGVKCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
cache, err := dbt.cacheForObject(obj)
if err != nil {
return err
}
return cache.IndexField(ctx, obj, field, extractValue)
}
func (dbt *delegatingByGVKCache) cacheForObject(o runtime.Object) (Cache, error) {
gvk, err := apiutil.GVKForObject(o, dbt.scheme)
if err != nil {
return nil, err
}
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
return dbt.cacheForGVK(gvk), nil
}
func (dbt *delegatingByGVKCache) cacheForGVK(gvk schema.GroupVersionKind) Cache {
if specific, hasSpecific := dbt.caches[gvk]; hasSpecific {
return specific
}
return dbt.defaultCache
}

View File

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@ -45,11 +46,28 @@ func (*ErrCacheNotStarted) Error() string {
return "the cache is not started, can not read objects"
}
var _ error = (*ErrCacheNotStarted)(nil)
// ErrResourceNotCached indicates that the resource type
// the client asked the cache for is not cached, i.e. the
// corresponding informer does not exist yet.
type ErrResourceNotCached struct {
GVK schema.GroupVersionKind
}
// Error returns the error
func (r ErrResourceNotCached) Error() string {
return fmt.Sprintf("%s is not cached", r.GVK.String())
}
var _ error = (*ErrResourceNotCached)(nil)
// informerCache is a Kubernetes Object cache populated from internal.Informers.
// informerCache wraps internal.Informers.
type informerCache struct {
scheme *runtime.Scheme
*internal.Informers
readerFailOnMissingInformer bool
}
// Get implements Reader.
@ -59,7 +77,7 @@ func (ic *informerCache) Get(ctx context.Context, key client.ObjectKey, out clie
return err
}
started, cache, err := ic.Informers.Get(ctx, gvk, out)
started, cache, err := ic.getInformerForKind(ctx, gvk, out)
if err != nil {
return err
}
@ -67,7 +85,7 @@ func (ic *informerCache) Get(ctx context.Context, key client.ObjectKey, out clie
if !started {
return &ErrCacheNotStarted{}
}
return cache.Reader.Get(ctx, key, out)
return cache.Reader.Get(ctx, key, out, opts...)
}
// List implements Reader.
@ -77,7 +95,7 @@ func (ic *informerCache) List(ctx context.Context, out client.ObjectList, opts .
return err
}
started, cache, err := ic.Informers.Get(ctx, *gvk, cacheTypeObj)
started, cache, err := ic.getInformerForKind(ctx, *gvk, cacheTypeObj)
if err != nil {
return err
}
@ -123,33 +141,53 @@ func (ic *informerCache) objectTypeForListObject(list client.ObjectList) (*schem
return &gvk, cacheTypeObj, nil
}
// GetInformerForKind returns the informer for the GroupVersionKind.
func (ic *informerCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error) {
func applyGetOptions(opts ...InformerGetOption) *internal.GetOptions {
cfg := &InformerGetOptions{}
for _, opt := range opts {
opt(cfg)
}
return (*internal.GetOptions)(cfg)
}
// GetInformerForKind returns the informer for the GroupVersionKind. If no informer exists, one will be started.
func (ic *informerCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error) {
// Map the gvk to an object
obj, err := ic.scheme.New(gvk)
if err != nil {
return nil, err
}
_, i, err := ic.Informers.Get(ctx, gvk, obj)
_, i, err := ic.Informers.Get(ctx, gvk, obj, applyGetOptions(opts...))
if err != nil {
return nil, err
}
return i.Informer, err
return i.Informer, nil
}
// GetInformer returns the informer for the obj.
func (ic *informerCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
// GetInformer returns the informer for the obj. If no informer exists, one will be started.
func (ic *informerCache) GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error) {
gvk, err := apiutil.GVKForObject(obj, ic.scheme)
if err != nil {
return nil, err
}
_, i, err := ic.Informers.Get(ctx, gvk, obj)
_, i, err := ic.Informers.Get(ctx, gvk, obj, applyGetOptions(opts...))
if err != nil {
return nil, err
}
return i.Informer, err
return i.Informer, nil
}
func (ic *informerCache) getInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *internal.Cache, error) {
if ic.readerFailOnMissingInformer {
cache, started, ok := ic.Informers.Peek(gvk, obj)
if !ok {
return false, nil, &ErrResourceNotCached{GVK: gvk}
}
return started, cache, nil
}
return ic.Informers.Get(ctx, gvk, obj, &internal.GetOptions{})
}
// NeedLeaderElection implements the LeaderElectionRunnable interface
@ -158,11 +196,11 @@ func (ic *informerCache) NeedLeaderElection() bool {
return false
}
// IndexField adds an indexer to the underlying cache, using extraction function to get
// value(s) from the given field. This index can then be used by passing a field selector
// IndexField adds an indexer to the underlying informer, using extractValue function to get
// value(s) from the given field. This index can then be used by passing a field selector
// to List. For one-to-one compatibility with "normal" field selectors, only return one value.
// The values may be anything. They will automatically be prefixed with the namespace of the
// given object, if present. The objects passed are guaranteed to be objects of the correct type.
// The values may be anything. They will automatically be prefixed with the namespace of the
// given object, if present. The objects passed are guaranteed to be objects of the correct type.
func (ic *informerCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
informer, err := ic.GetInformer(ctx, obj)
if err != nil {
@ -171,7 +209,7 @@ func (ic *informerCache) IndexField(ctx context.Context, obj client.Object, fiel
return indexByField(informer, field, extractValue)
}
func indexByField(indexer Informer, field string, extractor client.IndexerFunc) error {
func indexByField(informer Informer, field string, extractValue client.IndexerFunc) error {
indexFunc := func(objRaw interface{}) ([]string, error) {
// TODO(directxman12): check if this is the correct type?
obj, isObj := objRaw.(client.Object)
@ -184,7 +222,7 @@ func indexByField(indexer Informer, field string, extractor client.IndexerFunc)
}
ns := meta.GetNamespace()
rawVals := extractor(obj)
rawVals := extractValue(obj)
var vals []string
if ns == "" {
// if we're not doubling the keys for the namespaced case, just create a new slice with same length
@ -207,5 +245,5 @@ func indexByField(indexer Informer, field string, extractor client.IndexerFunc)
return vals, nil
}
return indexer.AddIndexers(cache.Indexers{internal.FieldIndexName(field): indexFunc})
return informer.AddIndexers(cache.Indexers{internal.FieldIndexName(field): indexFunc})
}

View File

@ -53,7 +53,7 @@ type CacheReader struct {
}
// Get checks the indexer for the object and writes a copy of it if found.
func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, opts ...client.GetOption) error {
func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, _ ...client.GetOption) error {
if c.scopeName == apimeta.RESTScopeNameRoot {
key.Namespace = ""
}
@ -67,9 +67,9 @@ func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Ob
// Not found, return an error
if !exists {
// Resource gets transformed into Kind in the error anyway, so this is fine
return apierrors.NewNotFound(schema.GroupResource{
Group: c.groupVersionKind.Group,
Group: c.groupVersionKind.Group,
// Resource gets set as Kind in the error so this is fine
Resource: c.groupVersionKind.Kind,
}, key.Name)
}
@ -111,6 +111,10 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli
listOpts := client.ListOptions{}
listOpts.ApplyOptions(opts)
if listOpts.Continue != "" {
return fmt.Errorf("continue list option is not supported by the cache")
}
switch {
case listOpts.FieldSelector != nil:
// TODO(directxman12): support more complicated field selectors by
@ -119,8 +123,8 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli
if !requiresExact {
return fmt.Errorf("non-exact field matches are not supported by the cache")
}
// list all objects by the field selector. If this is namespaced and we have one, ask for the
// namespaced index key. Otherwise, ask for the non-namespaced variant by using the fake "all namespaces"
// list all objects by the field selector. If this is namespaced and we have one, ask for the
// namespaced index key. Otherwise, ask for the non-namespaced variant by using the fake "all namespaces"
// namespace.
objs, err = c.indexer.ByIndex(FieldIndexName(field), KeyToNamespacedKey(listOpts.Namespace, val))
case listOpts.Namespace != "":
@ -175,7 +179,7 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli
}
// objectKeyToStorageKey converts an object key to store key.
// It's akin to MetaNamespaceKeyFunc. It's separate from
// It's akin to MetaNamespaceKeyFunc. It's separate from
// String to allow keeping the key format easily in sync with
// MetaNamespaceKeyFunc.
func objectKeyToStoreKey(k client.ObjectKey) string {
@ -191,7 +195,7 @@ func FieldIndexName(field string) string {
return "field:" + field
}
// noNamespaceNamespace is used as the "namespace" when we want to list across all namespaces.
// allNamespacesNamespace is used as the "namespace" when we want to list across all namespaces.
const allNamespacesNamespace = "__all_namespaces"
// KeyToNamespacedKey prefixes the given index key with a namespace

View File

@ -40,24 +40,23 @@ import (
// InformersOpts configures an InformerMap.
type InformersOpts struct {
HTTPClient *http.Client
Scheme *runtime.Scheme
Mapper meta.RESTMapper
ResyncPeriod time.Duration
Namespace string
ByGVK map[schema.GroupVersionKind]InformersOptsByGVK
}
// InformersOptsByGVK configured additional by group version kind (or object)
// in an InformerMap.
type InformersOptsByGVK struct {
HTTPClient *http.Client
Scheme *runtime.Scheme
Mapper meta.RESTMapper
ResyncPeriod time.Duration
Namespace string
NewInformer *func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
Selector Selector
Transform cache.TransformFunc
UnsafeDisableDeepCopy *bool
UnsafeDisableDeepCopy bool
}
// NewInformers creates a new InformersMap that can create informers under the hood.
func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
newInformer := cache.NewSharedIndexInformer
if options.NewInformer != nil {
newInformer = *options.NewInformer
}
return &Informers{
config: config,
httpClient: options.HTTPClient,
@ -68,12 +67,15 @@ func NewInformers(config *rest.Config, options *InformersOpts) *Informers {
Unstructured: make(map[schema.GroupVersionKind]*Cache),
Metadata: make(map[schema.GroupVersionKind]*Cache),
},
codecs: serializer.NewCodecFactory(options.Scheme),
paramCodec: runtime.NewParameterCodec(options.Scheme),
resync: options.ResyncPeriod,
startWait: make(chan struct{}),
namespace: options.Namespace,
byGVK: options.ByGVK,
codecs: serializer.NewCodecFactory(options.Scheme),
paramCodec: runtime.NewParameterCodec(options.Scheme),
resync: options.ResyncPeriod,
startWait: make(chan struct{}),
namespace: options.Namespace,
selector: options.Selector,
transform: options.Transform,
unsafeDisableDeepCopy: options.UnsafeDisableDeepCopy,
newInformer: newInformer,
}
}
@ -92,6 +94,13 @@ type tracker struct {
Metadata map[schema.GroupVersionKind]*Cache
}
// GetOptions provides configuration to customize the behavior when
// getting an informer.
type GetOptions struct {
// BlockUntilSynced controls if the informer retrieval will block until the informer is synced. Defaults to `true`.
BlockUntilSynced *bool
}
// Informers create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs.
// It uses a standard parameter codec constructed based on the given generated Scheme.
type Informers struct {
@ -144,49 +153,15 @@ type Informers struct {
// default or empty string means all namespaces
namespace string
byGVK map[schema.GroupVersionKind]InformersOptsByGVK
selector Selector
transform cache.TransformFunc
unsafeDisableDeepCopy bool
// NewInformer allows overriding of the shared index informer constructor for testing.
newInformer func(cache.ListerWatcher, runtime.Object, time.Duration, cache.Indexers) cache.SharedIndexInformer
}
func (ip *Informers) getSelector(gvk schema.GroupVersionKind) Selector {
if ip.byGVK == nil {
return Selector{}
}
if res, ok := ip.byGVK[gvk]; ok {
return res.Selector
}
if res, ok := ip.byGVK[schema.GroupVersionKind{}]; ok {
return res.Selector
}
return Selector{}
}
func (ip *Informers) getTransform(gvk schema.GroupVersionKind) cache.TransformFunc {
if ip.byGVK == nil {
return nil
}
if res, ok := ip.byGVK[gvk]; ok {
return res.Transform
}
if res, ok := ip.byGVK[schema.GroupVersionKind{}]; ok {
return res.Transform
}
return nil
}
func (ip *Informers) getDisableDeepCopy(gvk schema.GroupVersionKind) bool {
if ip.byGVK == nil {
return false
}
if res, ok := ip.byGVK[gvk]; ok && res.UnsafeDisableDeepCopy != nil {
return *res.UnsafeDisableDeepCopy
}
if res, ok := ip.byGVK[schema.GroupVersionKind{}]; ok && res.UnsafeDisableDeepCopy != nil {
return *res.UnsafeDisableDeepCopy
}
return false
}
// Start calls Run on each of the informers and sets started to true. Blocks on the context.
// Start calls Run on each of the informers and sets started to true. Blocks on the context.
// It doesn't return start because it can't return an error, and it's not a runnable directly.
func (ip *Informers) Start(ctx context.Context) error {
func() {
@ -271,18 +246,19 @@ func (ip *Informers) WaitForCacheSync(ctx context.Context) bool {
return cache.WaitForCacheSync(ctx.Done(), ip.getHasSyncedFuncs()...)
}
func (ip *Informers) get(gvk schema.GroupVersionKind, obj runtime.Object) (res *Cache, started bool, ok bool) {
// Peek attempts to get the informer for the GVK, but does not start one if one does not exist.
func (ip *Informers) Peek(gvk schema.GroupVersionKind, obj runtime.Object) (res *Cache, started bool, ok bool) {
ip.mu.RLock()
defer ip.mu.RUnlock()
i, ok := ip.informersByType(obj)[gvk]
return i, ip.started, ok
}
// Get will create a new Informer and add it to the map of specificInformersMap if none exists. Returns
// Get will create a new Informer and add it to the map of specificInformersMap if none exists. Returns
// the Informer from the map.
func (ip *Informers) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *Cache, error) {
func (ip *Informers) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object, opts *GetOptions) (bool, *Cache, error) {
// Return the informer if it is found
i, started, ok := ip.get(gvk, obj)
i, started, ok := ip.Peek(gvk, obj)
if !ok {
var err error
if i, started, err = ip.addInformerToMap(gvk, obj); err != nil {
@ -290,7 +266,12 @@ func (ip *Informers) Get(ctx context.Context, gvk schema.GroupVersionKind, obj r
}
}
if started && !i.Informer.HasSynced() {
shouldBlock := true
if opts.BlockUntilSynced != nil {
shouldBlock = *opts.BlockUntilSynced
}
if shouldBlock && started && !i.Informer.HasSynced() {
// Wait for it to sync before returning the Informer so that folks don't read from a stale cache.
if !cache.WaitForCacheSync(ctx.Done(), i.Informer.HasSynced) {
return started, nil, apierrors.NewTimeoutError(fmt.Sprintf("failed waiting for %T Informer to sync", obj), 0)
@ -311,11 +292,12 @@ func (ip *Informers) informersByType(obj runtime.Object) map[schema.GroupVersion
}
}
// addInformerToMap either returns an existing informer or creates a new informer, adds it to the map and returns it.
func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.Object) (*Cache, bool, error) {
ip.mu.Lock()
defer ip.mu.Unlock()
// Check the cache to see if we already have an Informer. If we do, return the Informer.
// Check the cache to see if we already have an Informer. If we do, return the Informer.
// This is for the case where 2 routines tried to get the informer when it wasn't in the map
// so neither returned early, but the first one created it.
if i, ok := ip.informersByType(obj)[gvk]; ok {
@ -327,13 +309,13 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
if err != nil {
return nil, false, err
}
sharedIndexInformer := cache.NewSharedIndexInformer(&cache.ListWatch{
sharedIndexInformer := ip.newInformer(&cache.ListWatch{
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
ip.getSelector(gvk).ApplyToList(&opts)
ip.selector.ApplyToList(&opts)
return listWatcher.ListFunc(opts)
},
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
ip.getSelector(gvk).ApplyToList(&opts)
ip.selector.ApplyToList(&opts)
opts.Watch = true // Watch needs to be set to true separately
return listWatcher.WatchFunc(opts)
},
@ -342,7 +324,7 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
})
// Check to see if there is a transformer for this gvk
if err := sharedIndexInformer.SetTransform(ip.getTransform(gvk)); err != nil {
if err := sharedIndexInformer.SetTransform(ip.transform); err != nil {
return nil, false, err
}
@ -358,7 +340,7 @@ func (ip *Informers) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.O
indexer: sharedIndexInformer.GetIndexer(),
groupVersionKind: gvk,
scopeName: mapping.Scope.Name(),
disableDeepCopy: ip.getDisableDeepCopy(gvk),
disableDeepCopy: ip.unsafeDisableDeepCopy,
},
}
ip.informersByType(obj)[gvk] = i
@ -382,7 +364,7 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob
// Figure out if the GVK we're dealing with is global, or namespace scoped.
var namespace string
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
namespace = restrictNamespaceBySelector(ip.namespace, ip.getSelector(gvk))
namespace = restrictNamespaceBySelector(ip.namespace, ip.selector)
}
switch obj.(type) {

View File

@ -1,55 +0,0 @@
package internal
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
// TransformFuncByGVK provides access to the correct transform function for
// any given GVK.
type TransformFuncByGVK interface {
Set(runtime.Object, *runtime.Scheme, cache.TransformFunc) error
Get(schema.GroupVersionKind) cache.TransformFunc
SetDefault(transformer cache.TransformFunc)
}
type transformFuncByGVK struct {
defaultTransform cache.TransformFunc
transformers map[schema.GroupVersionKind]cache.TransformFunc
}
// TransformFuncByGVKFromMap creates a TransformFuncByGVK from a map that
// maps GVKs to TransformFuncs.
func TransformFuncByGVKFromMap(in map[schema.GroupVersionKind]cache.TransformFunc) TransformFuncByGVK {
byGVK := &transformFuncByGVK{}
if defaultFunc, hasDefault := in[schema.GroupVersionKind{}]; hasDefault {
byGVK.defaultTransform = defaultFunc
}
delete(in, schema.GroupVersionKind{})
byGVK.transformers = in
return byGVK
}
func (t *transformFuncByGVK) SetDefault(transformer cache.TransformFunc) {
t.defaultTransform = transformer
}
func (t *transformFuncByGVK) Set(obj runtime.Object, scheme *runtime.Scheme, transformer cache.TransformFunc) error {
gvk, err := apiutil.GVKForObject(obj, scheme)
if err != nil {
return err
}
t.transformers[gvk] = transformer
return nil
}
func (t transformFuncByGVK) Get(gvk schema.GroupVersionKind) cache.TransformFunc {
if val, ok := t.transformers[gvk]; ok {
return val
}
return t.defaultTransform
}

View File

@ -25,8 +25,8 @@ import (
apimeta "k8s.io/apimachinery/pkg/api/meta"
"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/client/apiutil"
)
@ -34,49 +34,31 @@ import (
// 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.
//
// Deprecated: Use cache.Options.Namespaces instead.
func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc {
return func(config *rest.Config, opts Options) (Cache, error) {
opts.Namespaces = namespaces
return newMultiNamespaceCache(config, opts)
}
}
func newMultiNamespaceCache(config *rest.Config, opts Options) (Cache, error) {
if len(opts.Namespaces) < 2 {
return nil, fmt.Errorf("must specify more than one namespace to use multi-namespace cache")
}
opts, err := defaultOpts(config, opts)
if err != nil {
return nil, err
}
func newMultiNamespaceCache(
newCache newCacheFunc,
scheme *runtime.Scheme,
restMapper apimeta.RESTMapper,
namespaces map[string]Config,
globalConfig *Config, // may be nil in which case no cache for cluster-scoped objects will be created
) Cache {
// Create every namespace cache.
caches := map[string]Cache{}
for _, ns := range opts.Namespaces {
opts.Namespaces = []string{ns}
c, err := New(config, opts)
if err != nil {
return nil, err
}
caches[ns] = c
for namespace, config := range namespaces {
caches[namespace] = newCache(config, namespace)
}
// Create a cache for cluster scoped resources.
opts.Namespaces = []string{}
gCache, err := New(config, opts)
if err != nil {
return nil, fmt.Errorf("error creating global cache: %w", err)
// Create a cache for cluster scoped resources if requested
var clusterCache Cache
if globalConfig != nil {
clusterCache = newCache(*globalConfig, corev1.NamespaceAll)
}
return &multiNamespaceCache{namespaceToCache: caches, Scheme: opts.Scheme, RESTMapper: opts.Mapper, clusterCache: gCache}, nil
return &multiNamespaceCache{
namespaceToCache: caches,
Scheme: scheme,
RESTMapper: restMapper,
clusterCache: clusterCache,
}
}
// multiNamespaceCache knows how to handle multiple namespaced caches
@ -84,90 +66,96 @@ func newMultiNamespaceCache(config *rest.Config, opts Options) (Cache, error) {
// operator to a list of namespaces instead of watching every namespace
// in the cluster.
type multiNamespaceCache struct {
namespaceToCache map[string]Cache
Scheme *runtime.Scheme
RESTMapper apimeta.RESTMapper
namespaceToCache map[string]Cache
clusterCache Cache
}
var _ Cache = &multiNamespaceCache{}
// Methods for multiNamespaceCache to conform to the Informers interface.
func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
informers := map[string]Informer{}
// If the object is clusterscoped, get the informer from clusterCache,
func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error) {
// If the object is cluster scoped, get the informer from clusterCache,
// if not use the namespaced caches.
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
if err != nil {
return nil, err
}
if !isNamespaced {
clusterCacheInf, err := c.clusterCache.GetInformer(ctx, obj)
clusterCacheInformer, err := c.clusterCache.GetInformer(ctx, obj, opts...)
if err != nil {
return nil, err
}
informers[globalCache] = clusterCacheInf
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
return &multiNamespaceInformer{
namespaceToInformer: map[string]Informer{
globalCache: clusterCacheInformer,
},
}, nil
}
namespaceToInformer := map[string]Informer{}
for ns, cache := range c.namespaceToCache {
informer, err := cache.GetInformer(ctx, obj)
informer, err := cache.GetInformer(ctx, obj, opts...)
if err != nil {
return nil, err
}
informers[ns] = informer
namespaceToInformer[ns] = informer
}
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
return &multiNamespaceInformer{namespaceToInformer: namespaceToInformer}, nil
}
func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error) {
informers := map[string]Informer{}
// If the object is clusterscoped, get the informer from clusterCache,
func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error) {
// If the object is cluster scoped, get the informer from clusterCache,
// if not use the namespaced caches.
isNamespaced, err := apiutil.IsGVKNamespaced(gvk, c.RESTMapper)
if err != nil {
return nil, err
}
if !isNamespaced {
clusterCacheInf, err := c.clusterCache.GetInformerForKind(ctx, gvk)
clusterCacheInformer, err := c.clusterCache.GetInformerForKind(ctx, gvk, opts...)
if err != nil {
return nil, err
}
informers[globalCache] = clusterCacheInf
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
return &multiNamespaceInformer{
namespaceToInformer: map[string]Informer{
globalCache: clusterCacheInformer,
},
}, nil
}
namespaceToInformer := map[string]Informer{}
for ns, cache := range c.namespaceToCache {
informer, err := cache.GetInformerForKind(ctx, gvk)
informer, err := cache.GetInformerForKind(ctx, gvk, opts...)
if err != nil {
return nil, err
}
informers[ns] = informer
namespaceToInformer[ns] = informer
}
return &multiNamespaceInformer{namespaceToInformer: informers}, nil
return &multiNamespaceInformer{namespaceToInformer: namespaceToInformer}, nil
}
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 {
go func(ns string, cache Cache) {
err := cache.Start(ctx)
if err != nil {
log.Error(err, "multinamespace cache failed to start namespaced informer", "namespace", ns)
if err := cache.Start(ctx); err != nil {
log.Error(err, "multi-namespace cache failed to start namespaced informer", "namespace", ns)
}
}(ns, cache)
}
@ -179,13 +167,13 @@ func (c *multiNamespaceCache) Start(ctx context.Context) error {
func (c *multiNamespaceCache) WaitForCacheSync(ctx context.Context) bool {
synced := true
for _, cache := range c.namespaceToCache {
if s := cache.WaitForCacheSync(ctx); !s {
synced = s
if !cache.WaitForCacheSync(ctx) {
synced = false
}
}
// check if cluster scoped cache has synced
if !c.clusterCache.WaitForCacheSync(ctx) {
if c.clusterCache != nil && !c.clusterCache.WaitForCacheSync(ctx) {
synced = false
}
return synced
@ -224,7 +212,7 @@ func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj
if !ok {
return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", key)
}
return cache.Get(ctx, key, obj)
return cache.Get(ctx, key, obj, opts...)
}
// List multi namespace cache will get all the objects in the namespaces that the cache is watching if asked for all namespaces.
@ -245,7 +233,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
if listOpts.Namespace != corev1.NamespaceAll {
cache, ok := c.namespaceToCache[listOpts.Namespace]
if !ok {
return fmt.Errorf("unable to get: %v because of unknown namespace for the cache", listOpts.Namespace)
return fmt.Errorf("unable to list: %v because of unknown namespace for the cache", listOpts.Namespace)
}
return cache.List(ctx, list, opts...)
}
@ -278,12 +266,14 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
return fmt.Errorf("object: %T must be a list type", list)
}
allItems = append(allItems, items...)
// The last list call should have the most correct resource version.
resourceVersion = accessor.GetResourceVersion()
if limitSet {
// decrement Limit by the number of items
// fetched from the current namespace.
listOpts.Limit -= int64(len(items))
// if a Limit was set and the number of
// items read has reached this set limit,
// then stop reading.
@ -325,9 +315,12 @@ func (h handlerRegistration) HasSynced() bool {
var _ Informer = &multiNamespaceInformer{}
// AddEventHandler adds the handler to each namespaced informer.
// AddEventHandler adds the handler to each informer.
func (i *multiNamespaceInformer) AddEventHandler(handler toolscache.ResourceEventHandler) (toolscache.ResourceEventHandlerRegistration, error) {
handles := handlerRegistration{handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer))}
handles := handlerRegistration{
handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer)),
}
for ns, informer := range i.namespaceToInformer {
registration, err := informer.AddEventHandler(handler)
if err != nil {
@ -335,12 +328,16 @@ func (i *multiNamespaceInformer) AddEventHandler(handler toolscache.ResourceEven
}
handles.handles[ns] = registration
}
return handles, nil
}
// AddEventHandlerWithResyncPeriod adds the handler with a resync period to each namespaced informer.
func (i *multiNamespaceInformer) AddEventHandlerWithResyncPeriod(handler toolscache.ResourceEventHandler, resyncPeriod time.Duration) (toolscache.ResourceEventHandlerRegistration, error) {
handles := handlerRegistration{handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer))}
handles := handlerRegistration{
handles: make(map[string]toolscache.ResourceEventHandlerRegistration, len(i.namespaceToInformer)),
}
for ns, informer := range i.namespaceToInformer {
registration, err := informer.AddEventHandlerWithResyncPeriod(handler, resyncPeriod)
if err != nil {
@ -348,14 +345,15 @@ func (i *multiNamespaceInformer) AddEventHandlerWithResyncPeriod(handler toolsca
}
handles.handles[ns] = registration
}
return handles, nil
}
// RemoveEventHandler removes a formerly added event handler given by its registration handle.
// RemoveEventHandler removes a previously added event handler given by its registration handle.
func (i *multiNamespaceInformer) RemoveEventHandler(h toolscache.ResourceEventHandlerRegistration) error {
handles, ok := h.(handlerRegistration)
if !ok {
return fmt.Errorf("it is not the registration returned by multiNamespaceInformer")
return fmt.Errorf("registration is not a registration returned by multiNamespaceInformer")
}
for ns, informer := range i.namespaceToInformer {
registration, ok := handles.handles[ns]
@ -369,7 +367,7 @@ func (i *multiNamespaceInformer) RemoveEventHandler(h toolscache.ResourceEventHa
return nil
}
// AddIndexers adds the indexer for each namespaced informer.
// AddIndexers adds the indexers to each informer.
func (i *multiNamespaceInformer) AddIndexers(indexers toolscache.Indexers) error {
for _, informer := range i.namespaceToInformer {
err := informer.AddIndexers(indexers)
@ -380,11 +378,11 @@ func (i *multiNamespaceInformer) AddIndexers(indexers toolscache.Indexers) error
return nil
}
// HasSynced checks if each namespaced informer has synced.
// HasSynced checks if each informer has synced.
func (i *multiNamespaceInformer) HasSynced() bool {
for _, informer := range i.namespaceToInformer {
if ok := informer.HasSynced(); !ok {
return ok
if !informer.HasSynced() {
return false
}
}
return true