mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-23 16:00:46 +00:00
Upgrade the operator to use Operator SDK v1.33.0 (#182)
* Move controller package inside internal directory Based on the go/v4 project structure, the following changed: - Pakcage `controllers` is now named `controller` - Package `controller` now lives inside new `internal` directory * Move main.go in cmd directory Based on the new go/v4 project structure, `main.go` now lives in the `cmd` directory. * Change package import in main.go * Update go mod dependencies Update the dependencies based on the versions obtained by creating a new operator project using `kubebuilder init --domain onepassword.com --plugins=go/v4`. This is based on the migration steps provided to go from go/v3 to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4) * Update vendor * Adjust code for breaking changes from pkg update sigs.k8s.io/controller-runtime package had breaking changes from v0.14.5 to v0.16.3. This commit brings the changes needed to achieve the same things using the new functionality avaialble. * Adjust paths to connect yaml files Since `main.go` is now in `cmd` directory, the paths to the files for deploying Connect have to be adjusted based on the new location `main.go` is executed from. * Update files based on new structure and scaffolding These changes are made based on the new project structure and scaffolding obtained when using the new go/v4 project structure. These were done based on the migration steps mentioned when migrating to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4). * Update config files These updates are made based on the Kustomize v4 syntax. This is part of the upgrate to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4) * Update dependencies and GO version * Update vendor * Update Kubernetes tools versions * Update operator version in Makefile Now the version in the Makefile matches the version of the operator * Update Operator SDK version in version.go * Adjust generated deepcopy It seems that the +build tag is no longer needed based on the latest generated scaffolding, therefore it's removed. * Update copyright year * Bring back missing changes from migration Some customization in Makefile was lost during the migration process. Specifically, the namespace customization for `make deploy` command. Also, we push changes to kustomization.yaml for making the deploy process smoother. * Add RBAC perms for coordination.k8s.io It seems that with the latest changes to Kubernetes and Kustomize, we need to add additional RBAC to the service account used so that it can properly access the `leases` resource. * Optimize Dockerfile Dockerfile had a step for caching dependencies (go mod download). However, this is already done by the vendor directory, which we include. Therefore, this step can be removed to make the image build time faster.
This commit is contained in:
733
vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go
generated
vendored
733
vendor/sigs.k8s.io/controller-runtime/pkg/cache/cache.go
generated
vendored
@@ -19,10 +19,13 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
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"
|
||||
@@ -30,6 +33,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"
|
||||
@@ -37,16 +41,33 @@ import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||
)
|
||||
|
||||
var log = logf.RuntimeLog.WithName("object-cache")
|
||||
var (
|
||||
log = logf.RuntimeLog.WithName("object-cache")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -56,352 +77,321 @@ 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
|
||||
}
|
||||
|
||||
// ObjectSelector is an alias name of internal.Selector.
|
||||
type ObjectSelector internal.Selector
|
||||
// AllNamespaces should be used as the map key to deliminate namespace settings
|
||||
// that apply to all namespaces that themselves do not have explicit settings.
|
||||
const AllNamespaces = metav1.NamespaceAll
|
||||
|
||||
// SelectorsByObject associate a client.Object's GVK to a field/label selector.
|
||||
// There is also `DefaultSelector` to set a global default (which will be overridden by
|
||||
// a more specific setting here, if any).
|
||||
type SelectorsByObject map[client.Object]ObjectSelector
|
||||
|
||||
// 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
|
||||
|
||||
// Scheme is the scheme to use for mapping objects to GroupVersionKinds
|
||||
Scheme *runtime.Scheme
|
||||
|
||||
// Mapper is the RESTMapper to use for mapping GroupVersionKinds to Resources
|
||||
Mapper meta.RESTMapper
|
||||
|
||||
// Resync is the base frequency the informers are resynced.
|
||||
// Defaults to defaultResyncTime.
|
||||
// A 10 percent jitter will be added to the Resync period between informers
|
||||
// So that all informers will not send list requests simultaneously.
|
||||
Resync *time.Duration
|
||||
// SyncPeriod determines the minimum frequency at which watched resources are
|
||||
// reconciled. A lower period will correct entropy more quickly, but reduce
|
||||
// responsiveness to change if there are many watched resources. Change this
|
||||
// value only if you know what you are doing. Defaults to 10 hours if unset.
|
||||
// there will a 10 percent jitter between the SyncPeriod of all controllers
|
||||
// so that all controllers will not send list requests simultaneously.
|
||||
//
|
||||
// This applies to all controllers.
|
||||
//
|
||||
// A period sync happens for two reasons:
|
||||
// 1. To insure against a bug in the controller that causes an object to not
|
||||
// be requeued, when it otherwise should be requeued.
|
||||
// 2. To insure against an unknown bug in controller-runtime, or its dependencies,
|
||||
// that causes an object to not be requeued, when it otherwise should be
|
||||
// requeued, or to be removed from the queue, when it otherwise should not
|
||||
// be removed.
|
||||
//
|
||||
// If you want
|
||||
// 1. to insure against missed watch events, or
|
||||
// 2. to poll services that cannot be watched,
|
||||
// then we recommend that, instead of changing the default period, the
|
||||
// controller requeue, with a constant duration `t`, whenever the controller
|
||||
// is "done" with an object, and would otherwise not requeue it, i.e., we
|
||||
// recommend the `Reconcile` function return `reconcile.Result{RequeueAfter: t}`,
|
||||
// instead of `reconcile.Result{}`.
|
||||
SyncPeriod *time.Duration
|
||||
|
||||
// Namespace restricts the cache's ListWatch to the desired namespace
|
||||
// Default watches all namespaces
|
||||
Namespace 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
|
||||
|
||||
// SelectorsByObject restricts the cache's ListWatch to the desired
|
||||
// fields per GVK at the specified object, the map's value must implement
|
||||
// Selector [1] using for example a Set [2]
|
||||
// [1] https://pkg.go.dev/k8s.io/apimachinery/pkg/fields#Selector
|
||||
// [2] https://pkg.go.dev/k8s.io/apimachinery/pkg/fields#Set
|
||||
SelectorsByObject SelectorsByObject
|
||||
// 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.
|
||||
//
|
||||
// It is possible to have specific Config for just some namespaces
|
||||
// but cache all namespaces by using the AllNamespaces const as the map key.
|
||||
// This will then include all namespaces that do not have a more specific
|
||||
// setting.
|
||||
//
|
||||
// The options in the Config that are nil will be defaulted from
|
||||
// the respective Default* settings.
|
||||
DefaultNamespaces map[string]Config
|
||||
|
||||
// DefaultSelector will be used as selectors for all object types
|
||||
// that do not have a selector in SelectorsByObject defined.
|
||||
DefaultSelector ObjectSelector
|
||||
// DefaultLabelSelector will be used as a label selector for all objects
|
||||
// unless there is already one set in ByObject or DefaultNamespaces.
|
||||
DefaultLabelSelector labels.Selector
|
||||
|
||||
// UnsafeDisableDeepCopyByObject indicates not to deep copy objects during get or
|
||||
// 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 there is already one set in ByObject or DefaultNamespaces.
|
||||
DefaultTransform toolscache.TransformFunc
|
||||
|
||||
// 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 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.
|
||||
//
|
||||
// It is possible to have specific Config for just some namespaces
|
||||
// but cache all namespaces by using the AllNamespaces const as the map key.
|
||||
// This will then include all namespaces that do not have a more specific
|
||||
// setting.
|
||||
//
|
||||
// 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 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.
|
||||
Transform toolscache.TransformFunc
|
||||
|
||||
// UnsafeDisableDeepCopy indicates not to deep copy objects during get or
|
||||
// list objects per GVK at the specified object.
|
||||
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
|
||||
// otherwise you will mutate the object in the cache.
|
||||
UnsafeDisableDeepCopyByObject DisableDeepCopyByObject
|
||||
|
||||
// TransformByObject is a map from GVKs to transformer functions which
|
||||
// get applied when objects of the transformation are about to be committed
|
||||
// to cache.
|
||||
//
|
||||
// This function is called both for new objects to enter the cache,
|
||||
// and for updated objects.
|
||||
TransformByObject TransformByObject
|
||||
|
||||
// DefaultTransform is the transform used for all GVKs which do
|
||||
// not have an explicit transform func set in TransformByObject
|
||||
DefaultTransform toolscache.TransformFunc
|
||||
UnsafeDisableDeepCopy *bool
|
||||
}
|
||||
|
||||
var defaultResyncTime = 10 * time.Hour
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
selectorsByGVK, err := convertToByGVK(opts.SelectorsByObject, opts.DefaultSelector, opts.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(opts.UnsafeDisableDeepCopyByObject, opts.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transformByGVK, err := convertToByGVK(opts.TransformByObject, opts.DefaultTransform, opts.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transformByObj := internal.TransformFuncByObjectFromMap(transformByGVK)
|
||||
|
||||
internalSelectorsByGVK := internal.SelectorsByGVK{}
|
||||
for gvk, selector := range selectorsByGVK {
|
||||
internalSelectorsByGVK[gvk] = internal.Selector(selector)
|
||||
}
|
||||
|
||||
im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, internalSelectorsByGVK, disableDeepCopyByGVK, transformByObj)
|
||||
return &informerCache{InformersMap: im}, nil
|
||||
}
|
||||
newCacheFunc := newCache(cfg, opts)
|
||||
|
||||
// BuilderWithOptions returns a Cache constructor that will build a cache
|
||||
// honoring the options argument, this is useful to specify options like
|
||||
// SelectorsByObject
|
||||
// WARNING: If SelectorsByObject is specified, filtered out resources are not
|
||||
// returned.
|
||||
// WARNING: If UnsafeDisableDeepCopy is enabled, you must DeepCopy any object
|
||||
// returned from cache get/list before mutating it.
|
||||
func BuilderWithOptions(options Options) NewCacheFunc {
|
||||
return func(config *rest.Config, inherited Options) (Cache, error) {
|
||||
var err error
|
||||
inherited, err = defaultOpts(config, inherited)
|
||||
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)
|
||||
}
|
||||
|
||||
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, err
|
||||
return nil, fmt.Errorf("failed to get GVK for type %T: %w", obj, err)
|
||||
}
|
||||
options, err = defaultOpts(config, options)
|
||||
if err != nil {
|
||||
return nil, 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)
|
||||
}
|
||||
combined, err := options.inheritFrom(inherited)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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.DefaultUnsafeDisableDeepCopy,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
return New(config, *combined)
|
||||
}
|
||||
}
|
||||
|
||||
func (options Options) inheritFrom(inherited Options) (*Options, error) {
|
||||
var (
|
||||
combined Options
|
||||
err error
|
||||
)
|
||||
combined.Scheme = combineScheme(inherited.Scheme, options.Scheme)
|
||||
combined.Mapper = selectMapper(inherited.Mapper, options.Mapper)
|
||||
combined.Resync = selectResync(inherited.Resync, options.Resync)
|
||||
combined.Namespace = selectNamespace(inherited.Namespace, options.Namespace)
|
||||
combined.SelectorsByObject, combined.DefaultSelector, err = combineSelectors(inherited, options, combined.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
combined.UnsafeDisableDeepCopyByObject, err = combineUnsafeDeepCopy(inherited, options, combined.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
combined.TransformByObject, combined.DefaultTransform, err = combineTransforms(inherited, options, combined.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &combined, nil
|
||||
}
|
||||
|
||||
func combineScheme(schemes ...*runtime.Scheme) *runtime.Scheme {
|
||||
var out *runtime.Scheme
|
||||
for _, sch := range schemes {
|
||||
if sch == nil {
|
||||
continue
|
||||
}
|
||||
for gvk, t := range sch.AllKnownTypes() {
|
||||
if out == nil {
|
||||
out = runtime.NewScheme()
|
||||
}
|
||||
out.AddKnownTypeWithName(gvk, reflect.New(t).Interface().(runtime.Object))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func selectMapper(def, override meta.RESTMapper) meta.RESTMapper {
|
||||
if override != nil {
|
||||
return override
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func selectResync(def, override *time.Duration) *time.Duration {
|
||||
if override != nil {
|
||||
return override
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func selectNamespace(def, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func combineSelectors(inherited, options Options, scheme *runtime.Scheme) (SelectorsByObject, ObjectSelector, error) {
|
||||
// Selectors are combined via logical AND.
|
||||
// - Combined label selector is a union of the selectors requirements from both sets of options.
|
||||
// - Combined field selector uses fields.AndSelectors with the combined list of non-nil field selectors
|
||||
// defined in both sets of options.
|
||||
//
|
||||
// There is a bunch of complexity here because we need to convert to SelectorsByGVK
|
||||
// to be able to match keys between options and inherited and then convert back to SelectorsByObject
|
||||
optionsSelectorsByGVK, err := convertToByGVK(options.SelectorsByObject, options.DefaultSelector, scheme)
|
||||
if err != nil {
|
||||
return nil, ObjectSelector{}, err
|
||||
}
|
||||
inheritedSelectorsByGVK, err := convertToByGVK(inherited.SelectorsByObject, inherited.DefaultSelector, inherited.Scheme)
|
||||
if err != nil {
|
||||
return nil, ObjectSelector{}, err
|
||||
}
|
||||
|
||||
for gvk, inheritedSelector := range inheritedSelectorsByGVK {
|
||||
optionsSelectorsByGVK[gvk] = combineSelector(inheritedSelector, optionsSelectorsByGVK[gvk])
|
||||
}
|
||||
return convertToByObject(optionsSelectorsByGVK, scheme)
|
||||
}
|
||||
|
||||
func combineSelector(selectors ...ObjectSelector) ObjectSelector {
|
||||
ls := make([]labels.Selector, 0, len(selectors))
|
||||
fs := make([]fields.Selector, 0, len(selectors))
|
||||
for _, s := range selectors {
|
||||
ls = append(ls, s.Label)
|
||||
fs = append(fs, s.Field)
|
||||
}
|
||||
return ObjectSelector{
|
||||
Label: combineLabelSelectors(ls...),
|
||||
Field: combineFieldSelectors(fs...),
|
||||
}
|
||||
}
|
||||
|
||||
func combineLabelSelectors(ls ...labels.Selector) labels.Selector {
|
||||
var combined labels.Selector
|
||||
for _, l := range ls {
|
||||
if l == nil {
|
||||
continue
|
||||
}
|
||||
if combined == nil {
|
||||
combined = labels.NewSelector()
|
||||
}
|
||||
reqs, _ := l.Requirements()
|
||||
combined = combined.Add(reqs...)
|
||||
}
|
||||
return combined
|
||||
}
|
||||
|
||||
func combineFieldSelectors(fs ...fields.Selector) fields.Selector {
|
||||
nonNil := fs[:0]
|
||||
for _, f := range fs {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
nonNil = append(nonNil, f)
|
||||
}
|
||||
if len(nonNil) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(nonNil) == 1 {
|
||||
return nonNil[0]
|
||||
}
|
||||
return fields.AndSelectors(nonNil...)
|
||||
}
|
||||
|
||||
func combineUnsafeDeepCopy(inherited, options Options, scheme *runtime.Scheme) (DisableDeepCopyByObject, error) {
|
||||
// UnsafeDisableDeepCopyByObject is combined via precedence. Only if a value for a particular GVK is unset
|
||||
// in options will a value from inherited be used.
|
||||
optionsDisableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(options.UnsafeDisableDeepCopyByObject, options.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inheritedDisableDeepCopyByGVK, err := convertToDisableDeepCopyByGVK(inherited.UnsafeDisableDeepCopyByObject, inherited.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for gvk, inheritedDeepCopy := range inheritedDisableDeepCopyByGVK {
|
||||
if _, ok := optionsDisableDeepCopyByGVK[gvk]; !ok {
|
||||
if optionsDisableDeepCopyByGVK == nil {
|
||||
optionsDisableDeepCopyByGVK = map[schema.GroupVersionKind]bool{}
|
||||
}
|
||||
optionsDisableDeepCopyByGVK[gvk] = inheritedDeepCopy
|
||||
}
|
||||
}
|
||||
return convertToDisableDeepCopyByObject(optionsDisableDeepCopyByGVK, scheme)
|
||||
}
|
||||
|
||||
func combineTransforms(inherited, options Options, scheme *runtime.Scheme) (TransformByObject, toolscache.TransformFunc, error) {
|
||||
// Transform functions are combined via chaining. If both inherited and options define a transform
|
||||
// function, the transform function from inherited will be called first, and the transform function from
|
||||
// options will be called second.
|
||||
optionsTransformByGVK, err := convertToByGVK(options.TransformByObject, options.DefaultTransform, options.Scheme)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inheritedTransformByGVK, err := convertToByGVK(inherited.TransformByObject, inherited.DefaultTransform, inherited.Scheme)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for gvk, inheritedTransform := range inheritedTransformByGVK {
|
||||
if optionsTransformByGVK == nil {
|
||||
optionsTransformByGVK = map[schema.GroupVersionKind]toolscache.TransformFunc{}
|
||||
}
|
||||
optionsTransformByGVK[gvk] = combineTransform(inheritedTransform, optionsTransformByGVK[gvk])
|
||||
}
|
||||
return convertToByObject(optionsTransformByGVK, scheme)
|
||||
}
|
||||
|
||||
func combineTransform(inherited, current toolscache.TransformFunc) toolscache.TransformFunc {
|
||||
if inherited == nil {
|
||||
return current
|
||||
}
|
||||
if current == nil {
|
||||
return inherited
|
||||
}
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
mid, err := inherited(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return current(mid)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultOpts(config *rest.Config, opts Options) (Options, error) {
|
||||
config = rest.CopyConfig(config)
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return Options{}, fmt.Errorf("could not create HTTP client from config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Use the default Kubernetes Scheme if unset
|
||||
if opts.Scheme == nil {
|
||||
opts.Scheme = scheme.Scheme
|
||||
@@ -410,107 +400,106 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
|
||||
// Construct a new Mapper if unset
|
||||
if opts.Mapper == nil {
|
||||
var err error
|
||||
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config)
|
||||
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config, opts.HTTPClient)
|
||||
if err != nil {
|
||||
log.WithName("setup").Error(err, "Failed to get API Group-Resources")
|
||||
return opts, fmt.Errorf("could not create RESTMapper from config")
|
||||
return Options{}, fmt.Errorf("could not create RESTMapper from config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for namespace, cfg := range opts.DefaultNamespaces {
|
||||
cfg = defaultConfig(cfg, optionDefaultsToConfig(&opts))
|
||||
if namespace == metav1.NamespaceAll {
|
||||
cfg.FieldSelector = fields.AndSelectors(appendIfNotNil(namespaceAllSelector(maps.Keys(opts.DefaultNamespaces)), cfg.FieldSelector)...)
|
||||
}
|
||||
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))
|
||||
|
||||
if namespace == metav1.NamespaceAll {
|
||||
config.FieldSelector = fields.AndSelectors(
|
||||
appendIfNotNil(
|
||||
namespaceAllSelector(maps.Keys(byObject.Namespaces)),
|
||||
config.FieldSelector,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
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 isNamespaced && byObject.Namespaces == nil {
|
||||
byObject.Namespaces = opts.DefaultNamespaces
|
||||
}
|
||||
|
||||
opts.ByObject[obj] = byObject
|
||||
}
|
||||
|
||||
// Default the resync period to 10 hours if unset
|
||||
if opts.Resync == nil {
|
||||
opts.Resync = &defaultResyncTime
|
||||
if opts.SyncPeriod == nil {
|
||||
opts.SyncPeriod = &defaultSyncPeriod
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func convertToByGVK[T any](byObject map[client.Object]T, def T, scheme *runtime.Scheme) (map[schema.GroupVersionKind]T, error) {
|
||||
byGVK := map[schema.GroupVersionKind]T{}
|
||||
for object, value := range byObject {
|
||||
gvk, err := apiutil.GVKForObject(object, scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byGVK[gvk] = value
|
||||
func defaultConfig(toDefault, defaultFrom Config) Config {
|
||||
if toDefault.LabelSelector == nil {
|
||||
toDefault.LabelSelector = defaultFrom.LabelSelector
|
||||
}
|
||||
byGVK[schema.GroupVersionKind{}] = def
|
||||
return byGVK, nil
|
||||
}
|
||||
|
||||
func convertToByObject[T any](byGVK map[schema.GroupVersionKind]T, scheme *runtime.Scheme) (map[client.Object]T, T, error) {
|
||||
var byObject map[client.Object]T
|
||||
def := byGVK[schema.GroupVersionKind{}]
|
||||
for gvk, value := range byGVK {
|
||||
if gvk == (schema.GroupVersionKind{}) {
|
||||
continue
|
||||
}
|
||||
obj, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, def, err
|
||||
}
|
||||
cObj, ok := obj.(client.Object)
|
||||
if !ok {
|
||||
return nil, def, fmt.Errorf("object %T for GVK %q does not implement client.Object", obj, gvk)
|
||||
}
|
||||
if byObject == nil {
|
||||
byObject = map[client.Object]T{}
|
||||
}
|
||||
byObject[cObj] = value
|
||||
if toDefault.FieldSelector == nil {
|
||||
toDefault.FieldSelector = defaultFrom.FieldSelector
|
||||
}
|
||||
return byObject, def, nil
|
||||
if toDefault.Transform == nil {
|
||||
toDefault.Transform = defaultFrom.Transform
|
||||
}
|
||||
if toDefault.UnsafeDisableDeepCopy == nil {
|
||||
toDefault.UnsafeDisableDeepCopy = defaultFrom.UnsafeDisableDeepCopy
|
||||
}
|
||||
|
||||
return toDefault
|
||||
}
|
||||
|
||||
// DisableDeepCopyByObject associate a client.Object's GVK to disable DeepCopy during get or list from cache.
|
||||
type DisableDeepCopyByObject map[client.Object]bool
|
||||
|
||||
var _ client.Object = &ObjectAll{}
|
||||
|
||||
// ObjectAll is the argument to represent all objects' types.
|
||||
type ObjectAll struct {
|
||||
client.Object
|
||||
}
|
||||
|
||||
func convertToDisableDeepCopyByGVK(disableDeepCopyByObject DisableDeepCopyByObject, scheme *runtime.Scheme) (internal.DisableDeepCopyByGVK, error) {
|
||||
disableDeepCopyByGVK := internal.DisableDeepCopyByGVK{}
|
||||
for obj, disable := range disableDeepCopyByObject {
|
||||
switch obj.(type) {
|
||||
case ObjectAll, *ObjectAll:
|
||||
disableDeepCopyByGVK[internal.GroupVersionKindAll] = disable
|
||||
default:
|
||||
gvk, err := apiutil.GVKForObject(obj, scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
disableDeepCopyByGVK[gvk] = disable
|
||||
func namespaceAllSelector(namespaces []string) fields.Selector {
|
||||
selectors := make([]fields.Selector, 0, len(namespaces)-1)
|
||||
for _, namespace := range namespaces {
|
||||
if namespace != metav1.NamespaceAll {
|
||||
selectors = append(selectors, fields.OneTermNotEqualSelector("metadata.namespace", namespace))
|
||||
}
|
||||
}
|
||||
return disableDeepCopyByGVK, nil
|
||||
|
||||
return fields.AndSelectors(selectors...)
|
||||
}
|
||||
|
||||
func convertToDisableDeepCopyByObject(byGVK internal.DisableDeepCopyByGVK, scheme *runtime.Scheme) (DisableDeepCopyByObject, error) {
|
||||
var byObject DisableDeepCopyByObject
|
||||
for gvk, value := range byGVK {
|
||||
if byObject == nil {
|
||||
byObject = DisableDeepCopyByObject{}
|
||||
}
|
||||
if gvk == (schema.GroupVersionKind{}) {
|
||||
byObject[ObjectAll{}] = value
|
||||
continue
|
||||
}
|
||||
obj, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cObj, ok := obj.(client.Object)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("object %T for GVK %q does not implement client.Object", obj, gvk)
|
||||
}
|
||||
|
||||
byObject[cObj] = value
|
||||
func appendIfNotNil[T comparable](a, b T) []T {
|
||||
if b != *new(T) {
|
||||
return []T{a, b}
|
||||
}
|
||||
return byObject, nil
|
||||
return []T{a}
|
||||
}
|
||||
|
||||
// TransformByObject associate a client.Object's GVK to a transformer function
|
||||
// to be applied when storing the object into the cache.
|
||||
type TransformByObject map[client.Object]toolscache.TransformFunc
|
||||
|
127
vendor/sigs.k8s.io/controller-runtime/pkg/cache/delegating_by_gvk_cache.go
generated
vendored
Normal file
127
vendor/sigs.k8s.io/controller-runtime/pkg/cache/delegating_by_gvk_cache.go
generated
vendored
Normal 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
|
||||
}
|
160
vendor/sigs.k8s.io/controller-runtime/pkg/cache/informer_cache.go
generated
vendored
160
vendor/sigs.k8s.io/controller-runtime/pkg/cache/informer_cache.go
generated
vendored
@@ -19,14 +19,15 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"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,19 +46,38 @@ func (*ErrCacheNotStarted) Error() string {
|
||||
return "the cache is not started, can not read objects"
|
||||
}
|
||||
|
||||
// informerCache is a Kubernetes Object cache populated from InformersMap. informerCache wraps an InformersMap.
|
||||
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 {
|
||||
*internal.InformersMap
|
||||
scheme *runtime.Scheme
|
||||
*internal.Informers
|
||||
readerFailOnMissingInformer bool
|
||||
}
|
||||
|
||||
// Get implements Reader.
|
||||
func (ip *informerCache) Get(ctx context.Context, key client.ObjectKey, out client.Object, opts ...client.GetOption) error {
|
||||
gvk, err := apiutil.GVKForObject(out, ip.Scheme)
|
||||
func (ic *informerCache) Get(ctx context.Context, key client.ObjectKey, out client.Object, opts ...client.GetOption) error {
|
||||
gvk, err := apiutil.GVKForObject(out, ic.scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
started, cache, err := ip.InformersMap.Get(ctx, gvk, out)
|
||||
started, cache, err := ic.getInformerForKind(ctx, gvk, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,17 +85,17 @@ func (ip *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.
|
||||
func (ip *informerCache) List(ctx context.Context, out client.ObjectList, opts ...client.ListOption) error {
|
||||
gvk, cacheTypeObj, err := ip.objectTypeForListObject(out)
|
||||
func (ic *informerCache) List(ctx context.Context, out client.ObjectList, opts ...client.ListOption) error {
|
||||
gvk, cacheTypeObj, err := ic.objectTypeForListObject(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
started, cache, err := ip.InformersMap.Get(ctx, *gvk, cacheTypeObj)
|
||||
started, cache, err := ic.getInformerForKind(ctx, *gvk, cacheTypeObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,94 +110,106 @@ func (ip *informerCache) List(ctx context.Context, out client.ObjectList, opts .
|
||||
// objectTypeForListObject tries to find the runtime.Object and associated GVK
|
||||
// for a single object corresponding to the passed-in list type. We need them
|
||||
// because they are used as cache map key.
|
||||
func (ip *informerCache) objectTypeForListObject(list client.ObjectList) (*schema.GroupVersionKind, runtime.Object, error) {
|
||||
gvk, err := apiutil.GVKForObject(list, ip.Scheme)
|
||||
func (ic *informerCache) objectTypeForListObject(list client.ObjectList) (*schema.GroupVersionKind, runtime.Object, error) {
|
||||
gvk, err := apiutil.GVKForObject(list, ic.scheme)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// we need the non-list GVK, so chop off the "List" from the end of the kind
|
||||
if strings.HasSuffix(gvk.Kind, "List") && apimeta.IsListType(list) {
|
||||
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
|
||||
}
|
||||
// We need the non-list GVK, so chop off the "List" from the end of the kind.
|
||||
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
|
||||
|
||||
_, isUnstructured := list.(*unstructured.UnstructuredList)
|
||||
var cacheTypeObj runtime.Object
|
||||
if isUnstructured {
|
||||
// Handle unstructured.UnstructuredList.
|
||||
if _, isUnstructured := list.(runtime.Unstructured); isUnstructured {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetGroupVersionKind(gvk)
|
||||
cacheTypeObj = u
|
||||
} else {
|
||||
itemsPtr, err := apimeta.GetItemsPtr(list)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// http://knowyourmeme.com/memes/this-is-fine
|
||||
elemType := reflect.Indirect(reflect.ValueOf(itemsPtr)).Type().Elem()
|
||||
if elemType.Kind() != reflect.Ptr {
|
||||
elemType = reflect.PtrTo(elemType)
|
||||
}
|
||||
|
||||
cacheTypeValue := reflect.Zero(elemType)
|
||||
var ok bool
|
||||
cacheTypeObj, ok = cacheTypeValue.Interface().(runtime.Object)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("cannot get cache for %T, its element %T is not a runtime.Object", list, cacheTypeValue.Interface())
|
||||
}
|
||||
return &gvk, u, nil
|
||||
}
|
||||
// Handle metav1.PartialObjectMetadataList.
|
||||
if _, isPartialObjectMetadata := list.(*metav1.PartialObjectMetadataList); isPartialObjectMetadata {
|
||||
pom := &metav1.PartialObjectMetadata{}
|
||||
pom.SetGroupVersionKind(gvk)
|
||||
return &gvk, pom, nil
|
||||
}
|
||||
|
||||
// Any other list type should have a corresponding non-list type registered
|
||||
// in the scheme. Use that to create a new instance of the non-list type.
|
||||
cacheTypeObj, err := ic.scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &gvk, cacheTypeObj, nil
|
||||
}
|
||||
|
||||
// GetInformerForKind returns the informer for the GroupVersionKind.
|
||||
func (ip *informerCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (Informer, error) {
|
||||
// Map the gvk to an object
|
||||
obj, err := ip.Scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func applyGetOptions(opts ...InformerGetOption) *internal.GetOptions {
|
||||
cfg := &InformerGetOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
_, i, err := ip.InformersMap.Get(ctx, gvk, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.Informer, err
|
||||
return (*internal.GetOptions)(cfg)
|
||||
}
|
||||
|
||||
// GetInformer returns the informer for the obj.
|
||||
func (ip *informerCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
|
||||
gvk, err := apiutil.GVKForObject(obj, ip.Scheme)
|
||||
// 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 := ip.InformersMap.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. 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, applyGetOptions(opts...))
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
// to indicate that this can be started without requiring the leader lock.
|
||||
func (ip *informerCache) NeedLeaderElection() bool {
|
||||
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.
|
||||
func (ip *informerCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
|
||||
informer, err := ip.GetInformer(ctx, obj)
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
@@ -190,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
|
||||
@@ -213,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})
|
||||
}
|
||||
|
22
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/cache_reader.go
generated
vendored
22
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/cache_reader.go
generated
vendored
@@ -27,9 +27,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"
|
||||
)
|
||||
|
||||
// CacheReader is a client.Reader.
|
||||
@@ -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 != "":
|
||||
@@ -147,7 +151,7 @@ func (c *CacheReader) List(_ context.Context, out client.ObjectList, opts ...cli
|
||||
}
|
||||
obj, isObj := item.(runtime.Object)
|
||||
if !isObj {
|
||||
return fmt.Errorf("cache contained %T, which is not an Object", obj)
|
||||
return fmt.Errorf("cache contained %T, which is not an Object", item)
|
||||
}
|
||||
meta, err := apimeta.Accessor(obj)
|
||||
if err != nil {
|
||||
@@ -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
|
||||
|
126
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/deleg_map.go
generated
vendored
126
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/deleg_map.go
generated
vendored
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// InformersMap create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs.
|
||||
// It uses a standard parameter codec constructed based on the given generated Scheme.
|
||||
type InformersMap struct {
|
||||
// we abstract over the details of structured/unstructured/metadata with the specificInformerMaps
|
||||
// TODO(directxman12): genericize this over different projections now that we have 3 different maps
|
||||
|
||||
structured *specificInformersMap
|
||||
unstructured *specificInformersMap
|
||||
metadata *specificInformersMap
|
||||
|
||||
// Scheme maps runtime.Objects to GroupVersionKinds
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// NewInformersMap creates a new InformersMap that can create informers for
|
||||
// both structured and unstructured objects.
|
||||
func NewInformersMap(config *rest.Config,
|
||||
scheme *runtime.Scheme,
|
||||
mapper meta.RESTMapper,
|
||||
resync time.Duration,
|
||||
namespace string,
|
||||
selectors SelectorsByGVK,
|
||||
disableDeepCopy DisableDeepCopyByGVK,
|
||||
transformers TransformFuncByObject,
|
||||
) *InformersMap {
|
||||
return &InformersMap{
|
||||
structured: newStructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers),
|
||||
unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers),
|
||||
metadata: newMetadataInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers),
|
||||
|
||||
Scheme: scheme,
|
||||
}
|
||||
}
|
||||
|
||||
// Start calls Run on each of the informers and sets started to true. Blocks on the context.
|
||||
func (m *InformersMap) Start(ctx context.Context) error {
|
||||
go m.structured.Start(ctx)
|
||||
go m.unstructured.Start(ctx)
|
||||
go m.metadata.Start(ctx)
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForCacheSync waits until all the caches have been started and synced.
|
||||
func (m *InformersMap) WaitForCacheSync(ctx context.Context) bool {
|
||||
syncedFuncs := append([]cache.InformerSynced(nil), m.structured.HasSyncedFuncs()...)
|
||||
syncedFuncs = append(syncedFuncs, m.unstructured.HasSyncedFuncs()...)
|
||||
syncedFuncs = append(syncedFuncs, m.metadata.HasSyncedFuncs()...)
|
||||
|
||||
if !m.structured.waitForStarted(ctx) {
|
||||
return false
|
||||
}
|
||||
if !m.unstructured.waitForStarted(ctx) {
|
||||
return false
|
||||
}
|
||||
if !m.metadata.waitForStarted(ctx) {
|
||||
return false
|
||||
}
|
||||
return cache.WaitForCacheSync(ctx.Done(), syncedFuncs...)
|
||||
}
|
||||
|
||||
// Get will create a new Informer and add it to the map of InformersMap if none exists. Returns
|
||||
// the Informer from the map.
|
||||
func (m *InformersMap) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *MapEntry, error) {
|
||||
switch obj.(type) {
|
||||
case *unstructured.Unstructured:
|
||||
return m.unstructured.Get(ctx, gvk, obj)
|
||||
case *unstructured.UnstructuredList:
|
||||
return m.unstructured.Get(ctx, gvk, obj)
|
||||
case *metav1.PartialObjectMetadata:
|
||||
return m.metadata.Get(ctx, gvk, obj)
|
||||
case *metav1.PartialObjectMetadataList:
|
||||
return m.metadata.Get(ctx, gvk, obj)
|
||||
default:
|
||||
return m.structured.Get(ctx, gvk, obj)
|
||||
}
|
||||
}
|
||||
|
||||
// newStructuredInformersMap creates a new InformersMap for structured objects.
|
||||
func newStructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
||||
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByObject) *specificInformersMap {
|
||||
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createStructuredListWatch)
|
||||
}
|
||||
|
||||
// newUnstructuredInformersMap creates a new InformersMap for unstructured objects.
|
||||
func newUnstructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
||||
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByObject) *specificInformersMap {
|
||||
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createUnstructuredListWatch)
|
||||
}
|
||||
|
||||
// newMetadataInformersMap creates a new InformersMap for metadata-only objects.
|
||||
func newMetadataInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
|
||||
namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByObject) *specificInformersMap {
|
||||
return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createMetadataListWatch)
|
||||
}
|
35
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/disabledeepcopy.go
generated
vendored
35
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/disabledeepcopy.go
generated
vendored
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 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 internal
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
// GroupVersionKindAll is the argument to represent all GroupVersionKind types.
|
||||
var GroupVersionKindAll = schema.GroupVersionKind{}
|
||||
|
||||
// DisableDeepCopyByGVK associate a GroupVersionKind to disable DeepCopy during get or list from cache.
|
||||
type DisableDeepCopyByGVK map[schema.GroupVersionKind]bool
|
||||
|
||||
// IsDisabled returns whether a GroupVersionKind is disabled DeepCopy.
|
||||
func (disableByGVK DisableDeepCopyByGVK) IsDisabled(gvk schema.GroupVersionKind) bool {
|
||||
if d, ok := disableByGVK[gvk]; ok {
|
||||
return d
|
||||
} else if d, ok = disableByGVK[GroupVersionKindAll]; ok {
|
||||
return d
|
||||
}
|
||||
return false
|
||||
}
|
542
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go
generated
vendored
Normal file
542
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers.go
generated
vendored
Normal file
@@ -0,0 +1,542 @@
|
||||
/*
|
||||
Copyright 2018 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/metadata"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
||||
// InformersOpts configures an InformerMap.
|
||||
type InformersOpts 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
scheme: options.Scheme,
|
||||
mapper: options.Mapper,
|
||||
tracker: tracker{
|
||||
Structured: make(map[schema.GroupVersionKind]*Cache),
|
||||
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,
|
||||
selector: options.Selector,
|
||||
transform: options.Transform,
|
||||
unsafeDisableDeepCopy: options.UnsafeDisableDeepCopy,
|
||||
newInformer: newInformer,
|
||||
}
|
||||
}
|
||||
|
||||
// Cache contains the cached data for an Cache.
|
||||
type Cache struct {
|
||||
// Informer is the cached informer
|
||||
Informer cache.SharedIndexInformer
|
||||
|
||||
// CacheReader wraps Informer and implements the CacheReader interface for a single type
|
||||
Reader CacheReader
|
||||
}
|
||||
|
||||
type tracker struct {
|
||||
Structured map[schema.GroupVersionKind]*Cache
|
||||
Unstructured map[schema.GroupVersionKind]*Cache
|
||||
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 {
|
||||
// httpClient is used to create a new REST client
|
||||
httpClient *http.Client
|
||||
|
||||
// scheme maps runtime.Objects to GroupVersionKinds
|
||||
scheme *runtime.Scheme
|
||||
|
||||
// config is used to talk to the apiserver
|
||||
config *rest.Config
|
||||
|
||||
// mapper maps GroupVersionKinds to Resources
|
||||
mapper meta.RESTMapper
|
||||
|
||||
// tracker tracks informers keyed by their type and groupVersionKind
|
||||
tracker tracker
|
||||
|
||||
// codecs is used to create a new REST client
|
||||
codecs serializer.CodecFactory
|
||||
|
||||
// paramCodec is used by list and watch
|
||||
paramCodec runtime.ParameterCodec
|
||||
|
||||
// resync is the base frequency the informers are resynced
|
||||
// a 10 percent jitter will be added to the resync period between informers
|
||||
// so that all informers will not send list requests simultaneously.
|
||||
resync time.Duration
|
||||
|
||||
// mu guards access to the map
|
||||
mu sync.RWMutex
|
||||
|
||||
// started is true if the informers have been started
|
||||
started bool
|
||||
|
||||
// startWait is a channel that is closed after the
|
||||
// informer has been started.
|
||||
startWait chan struct{}
|
||||
|
||||
// waitGroup is the wait group that is used to wait for all informers to stop
|
||||
waitGroup sync.WaitGroup
|
||||
|
||||
// stopped is true if the informers have been stopped
|
||||
stopped bool
|
||||
|
||||
// ctx is the context to stop informers
|
||||
ctx context.Context
|
||||
|
||||
// namespace is the namespace that all ListWatches are restricted to
|
||||
// default or empty string means all namespaces
|
||||
namespace string
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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() {
|
||||
ip.mu.Lock()
|
||||
defer ip.mu.Unlock()
|
||||
|
||||
// Set the context so it can be passed to informers that are added later
|
||||
ip.ctx = ctx
|
||||
|
||||
// Start each informer
|
||||
for _, i := range ip.tracker.Structured {
|
||||
ip.startInformerLocked(i.Informer)
|
||||
}
|
||||
for _, i := range ip.tracker.Unstructured {
|
||||
ip.startInformerLocked(i.Informer)
|
||||
}
|
||||
for _, i := range ip.tracker.Metadata {
|
||||
ip.startInformerLocked(i.Informer)
|
||||
}
|
||||
|
||||
// Set started to true so we immediately start any informers added later.
|
||||
ip.started = true
|
||||
close(ip.startWait)
|
||||
}()
|
||||
<-ctx.Done() // Block until the context is done
|
||||
ip.mu.Lock()
|
||||
ip.stopped = true // Set stopped to true so we don't start any new informers
|
||||
ip.mu.Unlock()
|
||||
ip.waitGroup.Wait() // Block until all informers have stopped
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *Informers) startInformerLocked(informer cache.SharedIndexInformer) {
|
||||
// Don't start the informer in case we are already waiting for the items in
|
||||
// the waitGroup to finish, since waitGroups don't support waiting and adding
|
||||
// at the same time.
|
||||
if ip.stopped {
|
||||
return
|
||||
}
|
||||
|
||||
ip.waitGroup.Add(1)
|
||||
go func() {
|
||||
defer ip.waitGroup.Done()
|
||||
informer.Run(ip.ctx.Done())
|
||||
}()
|
||||
}
|
||||
|
||||
func (ip *Informers) waitForStarted(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ip.startWait:
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// getHasSyncedFuncs returns all the HasSynced functions for the informers in this map.
|
||||
func (ip *Informers) getHasSyncedFuncs() []cache.InformerSynced {
|
||||
ip.mu.RLock()
|
||||
defer ip.mu.RUnlock()
|
||||
|
||||
res := make([]cache.InformerSynced, 0,
|
||||
len(ip.tracker.Structured)+len(ip.tracker.Unstructured)+len(ip.tracker.Metadata),
|
||||
)
|
||||
for _, i := range ip.tracker.Structured {
|
||||
res = append(res, i.Informer.HasSynced)
|
||||
}
|
||||
for _, i := range ip.tracker.Unstructured {
|
||||
res = append(res, i.Informer.HasSynced)
|
||||
}
|
||||
for _, i := range ip.tracker.Metadata {
|
||||
res = append(res, i.Informer.HasSynced)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// WaitForCacheSync waits until all the caches have been started and synced.
|
||||
func (ip *Informers) WaitForCacheSync(ctx context.Context) bool {
|
||||
if !ip.waitForStarted(ctx) {
|
||||
return false
|
||||
}
|
||||
return cache.WaitForCacheSync(ctx.Done(), ip.getHasSyncedFuncs()...)
|
||||
}
|
||||
|
||||
// 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
|
||||
// the Informer from the map.
|
||||
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.Peek(gvk, obj)
|
||||
if !ok {
|
||||
var err error
|
||||
if i, started, err = ip.addInformerToMap(gvk, obj); err != nil {
|
||||
return started, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return started, i, nil
|
||||
}
|
||||
|
||||
func (ip *Informers) informersByType(obj runtime.Object) map[schema.GroupVersionKind]*Cache {
|
||||
switch obj.(type) {
|
||||
case runtime.Unstructured:
|
||||
return ip.tracker.Unstructured
|
||||
case *metav1.PartialObjectMetadata, *metav1.PartialObjectMetadataList:
|
||||
return ip.tracker.Metadata
|
||||
default:
|
||||
return ip.tracker.Structured
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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 {
|
||||
return i, ip.started, nil
|
||||
}
|
||||
|
||||
// Create a NewSharedIndexInformer and add it to the map.
|
||||
listWatcher, err := ip.makeListWatcher(gvk, obj)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
sharedIndexInformer := ip.newInformer(&cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
ip.selector.ApplyToList(&opts)
|
||||
return listWatcher.ListFunc(opts)
|
||||
},
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
ip.selector.ApplyToList(&opts)
|
||||
opts.Watch = true // Watch needs to be set to true separately
|
||||
return listWatcher.WatchFunc(opts)
|
||||
},
|
||||
}, obj, calculateResyncPeriod(ip.resync), cache.Indexers{
|
||||
cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
|
||||
})
|
||||
|
||||
// Check to see if there is a transformer for this gvk
|
||||
if err := sharedIndexInformer.SetTransform(ip.transform); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Create the new entry and set it in the map.
|
||||
i := &Cache{
|
||||
Informer: sharedIndexInformer,
|
||||
Reader: CacheReader{
|
||||
indexer: sharedIndexInformer.GetIndexer(),
|
||||
groupVersionKind: gvk,
|
||||
scopeName: mapping.Scope.Name(),
|
||||
disableDeepCopy: ip.unsafeDisableDeepCopy,
|
||||
},
|
||||
}
|
||||
ip.informersByType(obj)[gvk] = i
|
||||
|
||||
// Start the informer in case the InformersMap has started, otherwise it will be
|
||||
// started when the InformersMap starts.
|
||||
if ip.started {
|
||||
ip.startInformerLocked(i.Informer)
|
||||
}
|
||||
return i, ip.started, nil
|
||||
}
|
||||
|
||||
func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Object) (*cache.ListWatch, error) {
|
||||
// Kubernetes APIs work against Resources, not GroupVersionKinds. Map the
|
||||
// groupVersionKind to the Resource API we will use.
|
||||
mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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.selector)
|
||||
}
|
||||
|
||||
switch obj.(type) {
|
||||
//
|
||||
// Unstructured
|
||||
//
|
||||
case runtime.Unstructured:
|
||||
// If the rest configuration has a negotiated serializer passed in,
|
||||
// we should remove it and use the one that the dynamic client sets for us.
|
||||
cfg := rest.CopyConfig(ip.config)
|
||||
cfg.NegotiatedSerializer = nil
|
||||
dynamicClient, err := dynamic.NewForConfigAndClient(cfg, ip.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resources := dynamicClient.Resource(mapping.Resource)
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
if namespace != "" {
|
||||
return resources.Namespace(namespace).List(ip.ctx, opts)
|
||||
}
|
||||
return resources.List(ip.ctx, opts)
|
||||
},
|
||||
// Setup the watch function
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
if namespace != "" {
|
||||
return resources.Namespace(namespace).Watch(ip.ctx, opts)
|
||||
}
|
||||
return resources.Watch(ip.ctx, opts)
|
||||
},
|
||||
}, nil
|
||||
//
|
||||
// Metadata
|
||||
//
|
||||
case *metav1.PartialObjectMetadata, *metav1.PartialObjectMetadataList:
|
||||
// Always clear the negotiated serializer and use the one
|
||||
// set from the metadata client.
|
||||
cfg := rest.CopyConfig(ip.config)
|
||||
cfg.NegotiatedSerializer = nil
|
||||
|
||||
// Grab the metadata metadataClient.
|
||||
metadataClient, err := metadata.NewForConfigAndClient(cfg, ip.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resources := metadataClient.Resource(mapping.Resource)
|
||||
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
var (
|
||||
list *metav1.PartialObjectMetadataList
|
||||
err error
|
||||
)
|
||||
if namespace != "" {
|
||||
list, err = resources.Namespace(namespace).List(ip.ctx, opts)
|
||||
} else {
|
||||
list, err = resources.List(ip.ctx, opts)
|
||||
}
|
||||
if list != nil {
|
||||
for i := range list.Items {
|
||||
list.Items[i].SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
},
|
||||
// Setup the watch function
|
||||
WatchFunc: func(opts metav1.ListOptions) (watcher watch.Interface, err error) {
|
||||
if namespace != "" {
|
||||
watcher, err = resources.Namespace(namespace).Watch(ip.ctx, opts)
|
||||
} else {
|
||||
watcher, err = resources.Watch(ip.ctx, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newGVKFixupWatcher(gvk, watcher), nil
|
||||
},
|
||||
}, nil
|
||||
//
|
||||
// Structured.
|
||||
//
|
||||
default:
|
||||
client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs, ip.httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listGVK := gvk.GroupVersion().WithKind(gvk.Kind + "List")
|
||||
listObj, err := ip.scheme.New(listGVK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
// Build the request.
|
||||
req := client.Get().Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec)
|
||||
if namespace != "" {
|
||||
req.Namespace(namespace)
|
||||
}
|
||||
|
||||
// Create the resulting object, and execute the request.
|
||||
res := listObj.DeepCopyObject()
|
||||
if err := req.Do(ip.ctx).Into(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
// Setup the watch function
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
// Build the request.
|
||||
req := client.Get().Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec)
|
||||
if namespace != "" {
|
||||
req.Namespace(namespace)
|
||||
}
|
||||
// Call the watch.
|
||||
return req.Watch(ip.ctx)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// newGVKFixupWatcher adds a wrapper that preserves the GVK information when
|
||||
// events come in.
|
||||
//
|
||||
// This works around a bug where GVK information is not passed into mapping
|
||||
// functions when using the OnlyMetadata option in the builder.
|
||||
// This issue is most likely caused by kubernetes/kubernetes#80609.
|
||||
// See kubernetes-sigs/controller-runtime#1484.
|
||||
//
|
||||
// This was originally implemented as a cache.ResourceEventHandler wrapper but
|
||||
// that contained a data race which was resolved by setting the GVK in a watch
|
||||
// wrapper, before the objects are written to the cache.
|
||||
// See kubernetes-sigs/controller-runtime#1650.
|
||||
//
|
||||
// The original watch wrapper was found to be incompatible with
|
||||
// k8s.io/client-go/tools/cache.Reflector so it has been re-implemented as a
|
||||
// watch.Filter which is compatible.
|
||||
// See kubernetes-sigs/controller-runtime#1789.
|
||||
func newGVKFixupWatcher(gvk schema.GroupVersionKind, watcher watch.Interface) watch.Interface {
|
||||
return watch.Filter(
|
||||
watcher,
|
||||
func(in watch.Event) (watch.Event, bool) {
|
||||
in.Object.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return in, true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// calculateResyncPeriod returns a duration based on the desired input
|
||||
// this is so that multiple controllers don't get into lock-step and all
|
||||
// hammer the apiserver with list requests simultaneously.
|
||||
func calculateResyncPeriod(resync time.Duration) time.Duration {
|
||||
// the factor will fall into [0.9, 1.1)
|
||||
factor := rand.Float64()/5.0 + 0.9 //nolint:gosec
|
||||
return time.Duration(float64(resync.Nanoseconds()) * factor)
|
||||
}
|
||||
|
||||
// restrictNamespaceBySelector returns either a global restriction for all ListWatches
|
||||
// if not default/empty, or the namespace that a ListWatch for the specific resource
|
||||
// is restricted to, based on a specified field selector for metadata.namespace field.
|
||||
func restrictNamespaceBySelector(namespaceOpt string, s Selector) string {
|
||||
if namespaceOpt != "" {
|
||||
// namespace is already restricted
|
||||
return namespaceOpt
|
||||
}
|
||||
fieldSelector := s.Field
|
||||
if fieldSelector == nil || fieldSelector.Empty() {
|
||||
return ""
|
||||
}
|
||||
// check whether a selector includes the namespace field
|
||||
value, found := fieldSelector.RequiresExactMatch("metadata.namespace")
|
||||
if found {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
480
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go
generated
vendored
480
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go
generated
vendored
@@ -1,480 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/metadata"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// clientListWatcherFunc knows how to create a ListWatcher.
|
||||
type createListWatcherFunc func(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error)
|
||||
|
||||
// newSpecificInformersMap returns a new specificInformersMap (like
|
||||
// the generical InformersMap, except that it doesn't implement WaitForCacheSync).
|
||||
func newSpecificInformersMap(config *rest.Config,
|
||||
scheme *runtime.Scheme,
|
||||
mapper meta.RESTMapper,
|
||||
resync time.Duration,
|
||||
namespace string,
|
||||
selectors SelectorsByGVK,
|
||||
disableDeepCopy DisableDeepCopyByGVK,
|
||||
transformers TransformFuncByObject,
|
||||
createListWatcher createListWatcherFunc,
|
||||
) *specificInformersMap {
|
||||
ip := &specificInformersMap{
|
||||
config: config,
|
||||
Scheme: scheme,
|
||||
mapper: mapper,
|
||||
informersByGVK: make(map[schema.GroupVersionKind]*MapEntry),
|
||||
codecs: serializer.NewCodecFactory(scheme),
|
||||
paramCodec: runtime.NewParameterCodec(scheme),
|
||||
resync: resync,
|
||||
startWait: make(chan struct{}),
|
||||
createListWatcher: createListWatcher,
|
||||
namespace: namespace,
|
||||
selectors: selectors.forGVK,
|
||||
disableDeepCopy: disableDeepCopy,
|
||||
transformers: transformers,
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// MapEntry contains the cached data for an Informer.
|
||||
type MapEntry struct {
|
||||
// Informer is the cached informer
|
||||
Informer cache.SharedIndexInformer
|
||||
|
||||
// CacheReader wraps Informer and implements the CacheReader interface for a single type
|
||||
Reader CacheReader
|
||||
}
|
||||
|
||||
// specificInformersMap create and caches Informers for (runtime.Object, schema.GroupVersionKind) pairs.
|
||||
// It uses a standard parameter codec constructed based on the given generated Scheme.
|
||||
type specificInformersMap struct {
|
||||
// Scheme maps runtime.Objects to GroupVersionKinds
|
||||
Scheme *runtime.Scheme
|
||||
|
||||
// config is used to talk to the apiserver
|
||||
config *rest.Config
|
||||
|
||||
// mapper maps GroupVersionKinds to Resources
|
||||
mapper meta.RESTMapper
|
||||
|
||||
// informersByGVK is the cache of informers keyed by groupVersionKind
|
||||
informersByGVK map[schema.GroupVersionKind]*MapEntry
|
||||
|
||||
// codecs is used to create a new REST client
|
||||
codecs serializer.CodecFactory
|
||||
|
||||
// paramCodec is used by list and watch
|
||||
paramCodec runtime.ParameterCodec
|
||||
|
||||
// stop is the stop channel to stop informers
|
||||
stop <-chan struct{}
|
||||
|
||||
// resync is the base frequency the informers are resynced
|
||||
// a 10 percent jitter will be added to the resync period between informers
|
||||
// so that all informers will not send list requests simultaneously.
|
||||
resync time.Duration
|
||||
|
||||
// mu guards access to the map
|
||||
mu sync.RWMutex
|
||||
|
||||
// start is true if the informers have been started
|
||||
started bool
|
||||
|
||||
// startWait is a channel that is closed after the
|
||||
// informer has been started.
|
||||
startWait chan struct{}
|
||||
|
||||
// createClient knows how to create a client and a list object,
|
||||
// and allows for abstracting over the particulars of structured vs
|
||||
// unstructured objects.
|
||||
createListWatcher createListWatcherFunc
|
||||
|
||||
// namespace is the namespace that all ListWatches are restricted to
|
||||
// default or empty string means all namespaces
|
||||
namespace string
|
||||
|
||||
// selectors are the label or field selectors that will be added to the
|
||||
// ListWatch ListOptions.
|
||||
selectors func(gvk schema.GroupVersionKind) Selector
|
||||
|
||||
// disableDeepCopy indicates not to deep copy objects during get or list objects.
|
||||
disableDeepCopy DisableDeepCopyByGVK
|
||||
|
||||
// transform funcs are applied to objects before they are committed to the cache
|
||||
transformers TransformFuncByObject
|
||||
}
|
||||
|
||||
// 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 *specificInformersMap) Start(ctx context.Context) {
|
||||
func() {
|
||||
ip.mu.Lock()
|
||||
defer ip.mu.Unlock()
|
||||
|
||||
// Set the stop channel so it can be passed to informers that are added later
|
||||
ip.stop = ctx.Done()
|
||||
|
||||
// Start each informer
|
||||
for _, informer := range ip.informersByGVK {
|
||||
go informer.Informer.Run(ctx.Done())
|
||||
}
|
||||
|
||||
// Set started to true so we immediately start any informers added later.
|
||||
ip.started = true
|
||||
close(ip.startWait)
|
||||
}()
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
func (ip *specificInformersMap) waitForStarted(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ip.startWait:
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// HasSyncedFuncs returns all the HasSynced functions for the informers in this map.
|
||||
func (ip *specificInformersMap) HasSyncedFuncs() []cache.InformerSynced {
|
||||
ip.mu.RLock()
|
||||
defer ip.mu.RUnlock()
|
||||
syncedFuncs := make([]cache.InformerSynced, 0, len(ip.informersByGVK))
|
||||
for _, informer := range ip.informersByGVK {
|
||||
syncedFuncs = append(syncedFuncs, informer.Informer.HasSynced)
|
||||
}
|
||||
return syncedFuncs
|
||||
}
|
||||
|
||||
// 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 *specificInformersMap) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *MapEntry, error) {
|
||||
// Return the informer if it is found
|
||||
i, started, ok := func() (*MapEntry, bool, bool) {
|
||||
ip.mu.RLock()
|
||||
defer ip.mu.RUnlock()
|
||||
i, ok := ip.informersByGVK[gvk]
|
||||
return i, ip.started, ok
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
var err error
|
||||
if i, started, err = ip.addInformerToMap(gvk, obj); err != nil {
|
||||
return started, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
|
||||
return started, i, nil
|
||||
}
|
||||
|
||||
func (ip *specificInformersMap) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.Object) (*MapEntry, 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.
|
||||
// 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.informersByGVK[gvk]; ok {
|
||||
return i, ip.started, nil
|
||||
}
|
||||
|
||||
// Create a NewSharedIndexInformer and add it to the map.
|
||||
var lw *cache.ListWatch
|
||||
lw, err := ip.createListWatcher(gvk, ip)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
ni := cache.NewSharedIndexInformer(lw, obj, resyncPeriod(ip.resync)(), cache.Indexers{
|
||||
cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
|
||||
})
|
||||
|
||||
// Check to see if there is a transformer for this gvk
|
||||
if err := ni.SetTransform(ip.transformers.Get(gvk)); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
rm, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
i := &MapEntry{
|
||||
Informer: ni,
|
||||
Reader: CacheReader{
|
||||
indexer: ni.GetIndexer(),
|
||||
groupVersionKind: gvk,
|
||||
scopeName: rm.Scope.Name(),
|
||||
disableDeepCopy: ip.disableDeepCopy.IsDisabled(gvk),
|
||||
},
|
||||
}
|
||||
ip.informersByGVK[gvk] = i
|
||||
|
||||
// Start the Informer if need by
|
||||
// TODO(seans): write thorough tests and document what happens here - can you add indexers?
|
||||
// can you add eventhandlers?
|
||||
if ip.started {
|
||||
go i.Informer.Run(ip.stop)
|
||||
}
|
||||
return i, ip.started, nil
|
||||
}
|
||||
|
||||
// newListWatch returns a new ListWatch object that can be used to create a SharedIndexInformer.
|
||||
func createStructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error) {
|
||||
// Kubernetes APIs work against Resources, not GroupVersionKinds. Map the
|
||||
// groupVersionKind to the Resource API we will use.
|
||||
mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listGVK := gvk.GroupVersion().WithKind(gvk.Kind + "List")
|
||||
listObj, err := ip.Scheme.New(listGVK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: the functions that make use of this ListWatch should be adapted to
|
||||
// pass in their own contexts instead of relying on this fixed one here.
|
||||
ctx := context.TODO()
|
||||
// Create a new ListWatch for the obj
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
ip.selectors(gvk).ApplyToList(&opts)
|
||||
res := listObj.DeepCopyObject()
|
||||
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors(gvk))
|
||||
isNamespaceScoped := namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot
|
||||
err := client.Get().NamespaceIfScoped(namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do(ctx).Into(res)
|
||||
return res, err
|
||||
},
|
||||
// Setup the watch function
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
ip.selectors(gvk).ApplyToList(&opts)
|
||||
// Watch needs to be set to true separately
|
||||
opts.Watch = true
|
||||
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors(gvk))
|
||||
isNamespaceScoped := namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot
|
||||
return client.Get().NamespaceIfScoped(namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Watch(ctx)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createUnstructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error) {
|
||||
// Kubernetes APIs work against Resources, not GroupVersionKinds. Map the
|
||||
// groupVersionKind to the Resource API we will use.
|
||||
mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the rest configuration has a negotiated serializer passed in,
|
||||
// we should remove it and use the one that the dynamic client sets for us.
|
||||
cfg := rest.CopyConfig(ip.config)
|
||||
cfg.NegotiatedSerializer = nil
|
||||
dynamicClient, err := dynamic.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: the functions that make use of this ListWatch should be adapted to
|
||||
// pass in their own contexts instead of relying on this fixed one here.
|
||||
ctx := context.TODO()
|
||||
// Create a new ListWatch for the obj
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
ip.selectors(gvk).ApplyToList(&opts)
|
||||
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors(gvk))
|
||||
if namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot {
|
||||
return dynamicClient.Resource(mapping.Resource).Namespace(namespace).List(ctx, opts)
|
||||
}
|
||||
return dynamicClient.Resource(mapping.Resource).List(ctx, opts)
|
||||
},
|
||||
// Setup the watch function
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
ip.selectors(gvk).ApplyToList(&opts)
|
||||
// Watch needs to be set to true separately
|
||||
opts.Watch = true
|
||||
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors(gvk))
|
||||
if namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot {
|
||||
return dynamicClient.Resource(mapping.Resource).Namespace(namespace).Watch(ctx, opts)
|
||||
}
|
||||
return dynamicClient.Resource(mapping.Resource).Watch(ctx, opts)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createMetadataListWatch(gvk schema.GroupVersionKind, ip *specificInformersMap) (*cache.ListWatch, error) {
|
||||
// Kubernetes APIs work against Resources, not GroupVersionKinds. Map the
|
||||
// groupVersionKind to the Resource API we will use.
|
||||
mapping, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Always clear the negotiated serializer and use the one
|
||||
// set from the metadata client.
|
||||
cfg := rest.CopyConfig(ip.config)
|
||||
cfg.NegotiatedSerializer = nil
|
||||
|
||||
// grab the metadata client
|
||||
client, err := metadata.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: the functions that make use of this ListWatch should be adapted to
|
||||
// pass in their own contexts instead of relying on this fixed one here.
|
||||
ctx := context.TODO()
|
||||
|
||||
// create the relevant listwatch
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
ip.selectors(gvk).ApplyToList(&opts)
|
||||
|
||||
var (
|
||||
list *metav1.PartialObjectMetadataList
|
||||
err error
|
||||
)
|
||||
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors(gvk))
|
||||
if namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot {
|
||||
list, err = client.Resource(mapping.Resource).Namespace(namespace).List(ctx, opts)
|
||||
} else {
|
||||
list, err = client.Resource(mapping.Resource).List(ctx, opts)
|
||||
}
|
||||
if list != nil {
|
||||
for i := range list.Items {
|
||||
list.Items[i].SetGroupVersionKind(gvk)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
},
|
||||
// Setup the watch function
|
||||
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
ip.selectors(gvk).ApplyToList(&opts)
|
||||
// Watch needs to be set to true separately
|
||||
opts.Watch = true
|
||||
|
||||
var (
|
||||
watcher watch.Interface
|
||||
err error
|
||||
)
|
||||
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors(gvk))
|
||||
if namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot {
|
||||
watcher, err = client.Resource(mapping.Resource).Namespace(namespace).Watch(ctx, opts)
|
||||
} else {
|
||||
watcher, err = client.Resource(mapping.Resource).Watch(ctx, opts)
|
||||
}
|
||||
if watcher != nil {
|
||||
watcher = newGVKFixupWatcher(gvk, watcher)
|
||||
}
|
||||
return watcher, err
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newGVKFixupWatcher adds a wrapper that preserves the GVK information when
|
||||
// events come in.
|
||||
//
|
||||
// This works around a bug where GVK information is not passed into mapping
|
||||
// functions when using the OnlyMetadata option in the builder.
|
||||
// This issue is most likely caused by kubernetes/kubernetes#80609.
|
||||
// See kubernetes-sigs/controller-runtime#1484.
|
||||
//
|
||||
// This was originally implemented as a cache.ResourceEventHandler wrapper but
|
||||
// that contained a data race which was resolved by setting the GVK in a watch
|
||||
// wrapper, before the objects are written to the cache.
|
||||
// See kubernetes-sigs/controller-runtime#1650.
|
||||
//
|
||||
// The original watch wrapper was found to be incompatible with
|
||||
// k8s.io/client-go/tools/cache.Reflector so it has been re-implemented as a
|
||||
// watch.Filter which is compatible.
|
||||
// See kubernetes-sigs/controller-runtime#1789.
|
||||
func newGVKFixupWatcher(gvk schema.GroupVersionKind, watcher watch.Interface) watch.Interface {
|
||||
return watch.Filter(
|
||||
watcher,
|
||||
func(in watch.Event) (watch.Event, bool) {
|
||||
in.Object.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return in, true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// resyncPeriod returns a function which generates a duration each time it is
|
||||
// invoked; this is so that multiple controllers don't get into lock-step and all
|
||||
// hammer the apiserver with list requests simultaneously.
|
||||
func resyncPeriod(resync time.Duration) func() time.Duration {
|
||||
return func() time.Duration {
|
||||
// the factor will fall into [0.9, 1.1)
|
||||
factor := rand.Float64()/5.0 + 0.9 //nolint:gosec
|
||||
return time.Duration(float64(resync.Nanoseconds()) * factor)
|
||||
}
|
||||
}
|
||||
|
||||
// restrictNamespaceBySelector returns either a global restriction for all ListWatches
|
||||
// if not default/empty, or the namespace that a ListWatch for the specific resource
|
||||
// is restricted to, based on a specified field selector for metadata.namespace field.
|
||||
func restrictNamespaceBySelector(namespaceOpt string, s Selector) string {
|
||||
if namespaceOpt != "" {
|
||||
// namespace is already restricted
|
||||
return namespaceOpt
|
||||
}
|
||||
fieldSelector := s.Field
|
||||
if fieldSelector == nil || fieldSelector.Empty() {
|
||||
return ""
|
||||
}
|
||||
// check whether a selector includes the namespace field
|
||||
value, found := fieldSelector.RequiresExactMatch("metadata.namespace")
|
||||
if found {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
15
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/selector.go
generated
vendored
15
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/selector.go
generated
vendored
@@ -20,23 +20,8 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// SelectorsByGVK associate a GroupVersionKind to a field/label selector.
|
||||
type SelectorsByGVK map[schema.GroupVersionKind]Selector
|
||||
|
||||
func (s SelectorsByGVK) forGVK(gvk schema.GroupVersionKind) Selector {
|
||||
if specific, found := s[gvk]; found {
|
||||
return specific
|
||||
}
|
||||
if defaultSelector, found := s[schema.GroupVersionKind{}]; found {
|
||||
return defaultSelector
|
||||
}
|
||||
|
||||
return Selector{}
|
||||
}
|
||||
|
||||
// Selector specify the label/field selector to fill in ListOptions.
|
||||
type Selector struct {
|
||||
Label labels.Selector
|
||||
|
55
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/transformers.go
generated
vendored
55
vendor/sigs.k8s.io/controller-runtime/pkg/cache/internal/transformers.go
generated
vendored
@@ -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"
|
||||
)
|
||||
|
||||
// TransformFuncByObject provides access to the correct transform function for
|
||||
// any given GVK.
|
||||
type TransformFuncByObject 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
|
||||
}
|
||||
|
||||
// TransformFuncByObjectFromMap creates a TransformFuncByObject from a map that
|
||||
// maps GVKs to TransformFuncs.
|
||||
func TransformFuncByObjectFromMap(in map[schema.GroupVersionKind]cache.TransformFunc) TransformFuncByObject {
|
||||
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
|
||||
}
|
206
vendor/sigs.k8s.io/controller-runtime/pkg/cache/multi_namespace_cache.go
generated
vendored
206
vendor/sigs.k8s.io/controller-runtime/pkg/cache/multi_namespace_cache.go
generated
vendored
@@ -23,50 +23,42 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewCacheFunc - Function for creating a new cache from the options and a rest config.
|
||||
type NewCacheFunc func(config *rest.Config, opts Options) (Cache, error)
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
return func(config *rest.Config, opts Options) (Cache, error) {
|
||||
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 namespace, config := range namespaces {
|
||||
caches[namespace] = newCache(config, namespace)
|
||||
}
|
||||
|
||||
caches := map[string]Cache{}
|
||||
// Create a cache for cluster scoped resources if requested
|
||||
var clusterCache Cache
|
||||
if globalConfig != nil {
|
||||
clusterCache = newCache(*globalConfig, corev1.NamespaceAll)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
opts.Namespace = ns
|
||||
c, err := New(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: caches,
|
||||
Scheme: scheme,
|
||||
RESTMapper: restMapper,
|
||||
clusterCache: clusterCache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,90 +67,96 @@ func MultiNamespacedCacheBuilder(namespaces []string) NewCacheFunc {
|
||||
// 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 := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
|
||||
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 := objectutil.IsAPINamespacedWithGVK(gvk, c.Scheme, c.RESTMapper)
|
||||
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)
|
||||
}
|
||||
@@ -170,22 +168,22 @@ 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
|
||||
}
|
||||
|
||||
func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
|
||||
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
|
||||
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
|
||||
if err != nil {
|
||||
return nil //nolint:nilerr
|
||||
return err
|
||||
}
|
||||
|
||||
if !isNamespaced {
|
||||
@@ -201,7 +199,7 @@ func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object,
|
||||
}
|
||||
|
||||
func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
|
||||
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
|
||||
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -213,9 +211,12 @@ func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj
|
||||
|
||||
cache, ok := c.namespaceToCache[key.Namespace]
|
||||
if !ok {
|
||||
if global, hasGlobal := c.namespaceToCache[metav1.NamespaceAll]; hasGlobal {
|
||||
return global.Get(ctx, key, obj, opts...)
|
||||
}
|
||||
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.
|
||||
@@ -223,7 +224,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
|
||||
listOpts := client.ListOptions{}
|
||||
listOpts.ApplyOptions(opts)
|
||||
|
||||
isNamespaced, err := objectutil.IsAPINamespaced(list, c.Scheme, c.RESTMapper)
|
||||
isNamespaced, err := apiutil.IsObjectNamespaced(list, c.Scheme, c.RESTMapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -236,7 +237,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...)
|
||||
}
|
||||
@@ -269,12 +270,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.
|
||||
@@ -293,42 +296,71 @@ type multiNamespaceInformer struct {
|
||||
namespaceToInformer map[string]Informer
|
||||
}
|
||||
|
||||
type handlerRegistration struct {
|
||||
handles map[string]toolscache.ResourceEventHandlerRegistration
|
||||
}
|
||||
|
||||
type syncer interface {
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// HasSynced asserts that the handler has been called for the full initial state of the informer.
|
||||
// This uses syncer to be compatible between client-go 1.27+ and older versions when the interface changed.
|
||||
func (h handlerRegistration) HasSynced() bool {
|
||||
for _, reg := range h.handles {
|
||||
if s, ok := reg.(syncer); ok {
|
||||
if !s.HasSynced() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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 := 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 {
|
||||
return nil, err
|
||||
}
|
||||
handles[ns] = registration
|
||||
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 := 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 {
|
||||
return nil, err
|
||||
}
|
||||
handles[ns] = registration
|
||||
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.(map[string]toolscache.ResourceEventHandlerRegistration)
|
||||
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[ns]
|
||||
registration, ok := handles.handles[ns]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -339,7 +371,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)
|
||||
@@ -350,11 +382,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
|
||||
|
Reference in New Issue
Block a user