diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index f381098fe0..e5affce5c4 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -128,6 +128,14 @@ type Options struct { // 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 + + // TransformFuncByObject 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. + TransformFuncByObject TransformFuncByObject } var defaultResyncTime = 10 * time.Hour @@ -146,7 +154,11 @@ func New(config *rest.Config, opts Options) (Cache, error) { if err != nil { return nil, err } - im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, selectorsByGVK, disableDeepCopyByGVK) + transformByObjectAndGVK, err := convertToTransformByKindAndGVK(opts.TransformFuncByObject, opts.Scheme) + if err != nil { + return nil, err + } + im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, selectorsByGVK, disableDeepCopyByGVK, transformByObjectAndGVK) return &informerCache{InformersMap: im}, nil } @@ -241,3 +253,15 @@ func convertToDisableDeepCopyByGVK(disableDeepCopyByObject DisableDeepCopyByObje } return disableDeepCopyByGVK, nil } + +type TransformFuncByObject map[client.Object]toolscache.TransformFunc + +func convertToTransformByKindAndGVK(t TransformFuncByObject, scheme *runtime.Scheme) (internal.TransformFuncByObject, error) { + result := internal.NewTransformFuncByObject() + for obj, transformation := range t { + if err := result.Set(obj, scheme, transformation); err != nil { + return nil, err + } + } + return result, nil +} diff --git a/pkg/cache/internal/deleg_map.go b/pkg/cache/internal/deleg_map.go index 9bfc8463fd..dbce7ad02a 100644 --- a/pkg/cache/internal/deleg_map.go +++ b/pkg/cache/internal/deleg_map.go @@ -52,11 +52,12 @@ func NewInformersMap(config *rest.Config, namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, + transformers TransformFuncByObject, ) *InformersMap { return &InformersMap{ - structured: newStructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy), - unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy), - metadata: newMetadataInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy), + structured: newStructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers.Get(runtimeObjectKindStructured)), + unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers.Get(runtimeObjectKindUnstructured)), + metadata: newMetadataInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers.Get(runtimeObjectKindMetadata)), Scheme: scheme, } @@ -108,18 +109,18 @@ func (m *InformersMap) Get(ctx context.Context, gvk schema.GroupVersionKind, 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) *specificInformersMap { - return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createStructuredListWatch) + namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByGVK) *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) *specificInformersMap { - return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createUnstructuredListWatch) + namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByGVK) *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) *specificInformersMap { - return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createMetadataListWatch) + namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, transformers TransformFuncByGVK) *specificInformersMap { + return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, transformers, createMetadataListWatch) } diff --git a/pkg/cache/internal/informers_map.go b/pkg/cache/internal/informers_map.go index 2eb68e840a..61e6f2f93c 100644 --- a/pkg/cache/internal/informers_map.go +++ b/pkg/cache/internal/informers_map.go @@ -54,7 +54,9 @@ func newSpecificInformersMap(config *rest.Config, namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK, - createListWatcher createListWatcherFunc) *specificInformersMap { + transformers TransformFuncByGVK, + createListWatcher createListWatcherFunc, +) *specificInformersMap { ip := &specificInformersMap{ config: config, Scheme: scheme, @@ -135,6 +137,9 @@ type specificInformersMap struct { // 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 TransformFuncByGVK } // Start calls Run on each of the informers and sets started to true. Blocks on the context. @@ -227,6 +232,10 @@ func (ip *specificInformersMap) addInformerToMap(gvk schema.GroupVersionKind, ob 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 + ni.SetTransform(ip.transformers.Get(gvk)) + rm, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return nil, false, err diff --git a/pkg/cache/internal/transformers.go b/pkg/cache/internal/transformers.go new file mode 100644 index 0000000000..1f39e1d259 --- /dev/null +++ b/pkg/cache/internal/transformers.go @@ -0,0 +1,72 @@ +package internal + +import ( + 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/client/apiutil" +) + +type runtimeObjectKind int + +const ( + // prefixed names since there are packages with these names that conflict + runtimeObjectKindStructured runtimeObjectKind = iota + runtimeObjectKindUnstructured + runtimeObjectKindMetadata +) + +func runtimeObjectKindForObject(obj runtime.Object) runtimeObjectKind { + switch obj.(type) { + case *unstructured.Unstructured: + return runtimeObjectKindUnstructured + case *unstructured.UnstructuredList: + return runtimeObjectKindUnstructured + case *metav1.PartialObjectMetadata: + return runtimeObjectKindMetadata + case *metav1.PartialObjectMetadataList: + return runtimeObjectKindMetadata + default: + return runtimeObjectKindStructured + } +} + +type TransformFuncByObject map[runtimeObjectKind]map[schema.GroupVersionKind]cache.TransformFunc +type TransformFuncByGVK map[schema.GroupVersionKind]cache.TransformFunc + +func NewTransformFuncByObject() TransformFuncByObject { + return TransformFuncByObject{} +} + +func (t TransformFuncByObject) Set(obj runtime.Object, scheme *runtime.Scheme, transformer cache.TransformFunc) error { + kind := runtimeObjectKindForObject(obj) + existing, ok := t[kind] + if !ok { + existing = TransformFuncByGVK{} + t[kind] = existing + } + + gvk, err := apiutil.GVKForObject(obj, nil) + if err != nil { + return err + } + + existing[gvk] = transformer + return nil +} + +func (t TransformFuncByObject) Get(kind runtimeObjectKind) TransformFuncByGVK { + if val, ok := t[kind]; ok { + return val + } + return nil +} + +func (t TransformFuncByGVK) Get(gvk schema.GroupVersionKind) cache.TransformFunc { + if val, ok := t[gvk]; ok { + return val + } + return nil +}