From 330bbcceef9dfb9452c97b18b6ec1b5dc28f3b3a Mon Sep 17 00:00:00 2001 From: Chaunceyctx Date: Sat, 28 Dec 2024 15:28:21 +0800 Subject: [PATCH] add CodecFactoryOptions for codecfactory --- pkg/cache/cache.go | 14 ++++--- pkg/cache/internal/informers.go | 58 +++++++++++++++++++++-------- pkg/client/client.go | 40 +++++++++++++++++--- pkg/client/client_rest_resources.go | 13 +++++-- 4 files changed, 96 insertions(+), 29 deletions(-) diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index ecffe07988..ac5856b6ed 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -140,6 +140,9 @@ type Options struct { // Scheme is the scheme to use for mapping objects to GroupVersionKinds Scheme *runtime.Scheme + // CodecFactoryOptionsByObject are used to indicate whether enable strict/pretty mode of codecFactory for specific object + CodecFactoryOptionsByObject map[client.Object]client.CodecFactoryOptions + // Mapper is the RESTMapper to use for mapping GroupVersionKinds to Resources Mapper meta.RESTMapper @@ -420,11 +423,12 @@ func newCache(restConfig *rest.Config, opts Options) newCacheFunc { 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, + HTTPClient: opts.HTTPClient, + Scheme: opts.Scheme, + CodecFactoryOptionsByObject: opts.CodecFactoryOptionsByObject, + Mapper: opts.Mapper, + ResyncPeriod: *opts.SyncPeriod, + Namespace: namespace, Selector: internal.Selector{ Label: config.LabelSelector, Field: config.FieldSelector, diff --git a/pkg/cache/internal/informers.go b/pkg/cache/internal/informers.go index 7f94860771..a2a1cdf7cf 100644 --- a/pkg/cache/internal/informers.go +++ b/pkg/cache/internal/informers.go @@ -36,23 +36,25 @@ import ( "k8s.io/client-go/metadata" "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/internal/syncs" ) // 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 - EnableWatchBookmarks bool - WatchErrorHandler cache.WatchErrorHandler + HTTPClient *http.Client + Scheme *runtime.Scheme + CodecFactoryOptionsByObject map[client.Object]client.CodecFactoryOptions + 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 + EnableWatchBookmarks bool + WatchErrorHandler cache.WatchErrorHandler } // NewInformers creates a new InformersMap that can create informers under the hood. @@ -61,6 +63,23 @@ func NewInformers(config *rest.Config, options *InformersOpts) *Informers { if options.NewInformer != nil { newInformer = options.NewInformer } + + codecFactories := make(map[schema.GroupVersionKind]serializer.CodecFactory) + for obj, codecFactoryOptions := range options.CodecFactoryOptionsByObject { + gvk, err := apiutil.GVKForObject(obj, options.Scheme) + if err != nil { + continue + } + var mutators []serializer.CodecFactoryOptionsMutator + if codecFactoryOptions.Strict { + mutators = append(mutators, serializer.EnableStrict) + } + if codecFactoryOptions.Pretty { + mutators = append(mutators, serializer.EnablePretty) + } + codecFactories[gvk] = serializer.NewCodecFactory(options.Scheme, mutators...) + } + return &Informers{ config: config, httpClient: options.HTTPClient, @@ -71,7 +90,8 @@ func NewInformers(config *rest.Config, options *InformersOpts) *Informers { Unstructured: make(map[schema.GroupVersionKind]*Cache), Metadata: make(map[schema.GroupVersionKind]*Cache), }, - codecs: serializer.NewCodecFactory(options.Scheme), + defaultCodecs: serializer.NewCodecFactory(options.Scheme), + codecsByObject: codecFactories, paramCodec: runtime.NewParameterCodec(options.Scheme), resync: options.ResyncPeriod, startWait: make(chan struct{}), @@ -139,8 +159,11 @@ type Informers struct { // tracker tracks informers keyed by their type and groupVersionKind tracker tracker - // codecs is used to create a new REST client - codecs serializer.CodecFactory + // codecsByObject is used to override defaultCodecs for specific GroupVersionKind(object) + codecsByObject map[schema.GroupVersionKind]serializer.CodecFactory + + // defaultCodecs is used to create a new REST client + defaultCodecs serializer.CodecFactory // paramCodec is used by list and watch paramCodec runtime.ParameterCodec @@ -512,7 +535,12 @@ func (ip *Informers) makeListWatcher(gvk schema.GroupVersionKind, obj runtime.Ob // Structured. // default: - client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, ip.codecs, ip.httpClient) + codecFactory := ip.defaultCodecs + if override, ok := ip.codecsByObject[gvk]; ok { + codecFactory = override + } + + client, err := apiutil.RESTClientForGVK(gvk, false, ip.config, codecFactory, ip.httpClient) if err != nil { return nil, err } diff --git a/pkg/client/client.go b/pkg/client/client.go index 6d87440174..ae83f049dc 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -44,6 +44,9 @@ type Options struct { // Scheme, if provided, will be used to map go structs to GroupVersionKinds Scheme *runtime.Scheme + // CodecFactoryOptionsByObject, if provided, will be used to indicate whether enable strict/pretty mode of CodecFactory for specific obj + CodecFactoryOptionsByObject map[Object]CodecFactoryOptions + // Mapper, if provided, will be used to map GroupVersionKinds to Resources Mapper meta.RESTMapper @@ -68,6 +71,15 @@ type CacheOptions struct { Unstructured bool } +// CodecFactoryOptions holds the options for configuring CodecFactory behavior which is same as serializer.CodecFactoryOptions +type CodecFactoryOptions struct { + // Strict configures all serializers in strict mode + Strict bool + // Pretty includes a pretty serializer along with the non-pretty one + Pretty bool + // drop serializers field in serializer.CodecFactoryOptions just for passing go-apidiff test +} + // NewClientFunc allows a user to define how to create a client. type NewClientFunc func(config *rest.Config, options Options) (Client, error) @@ -145,13 +157,29 @@ func newClient(config *rest.Config, options Options) (*client, error) { } } - resources := &clientRestResources{ - httpClient: options.HTTPClient, - config: config, - scheme: options.Scheme, - mapper: options.Mapper, - codecs: serializer.NewCodecFactory(options.Scheme), + codecFactories := make(map[schema.GroupVersionKind]serializer.CodecFactory) + for obj, codecFactoryOptions := range options.CodecFactoryOptionsByObject { + gvk, err := apiutil.GVKForObject(obj, options.Scheme) + if err != nil { + continue + } + var mutators []serializer.CodecFactoryOptionsMutator + if codecFactoryOptions.Strict { + mutators = append(mutators, serializer.EnableStrict) + } + if codecFactoryOptions.Pretty { + mutators = append(mutators, serializer.EnablePretty) + } + codecFactories[gvk] = serializer.NewCodecFactory(options.Scheme, mutators...) + } + resources := &clientRestResources{ + httpClient: options.HTTPClient, + config: config, + scheme: options.Scheme, + mapper: options.Mapper, + defaultCodecs: serializer.NewCodecFactory(options.Scheme), + codecsByObject: codecFactories, structuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta), unstructuredResourceByType: make(map[schema.GroupVersionKind]*resourceMeta), } diff --git a/pkg/client/client_rest_resources.go b/pkg/client/client_rest_resources.go index 2d07879520..7fd124a1e6 100644 --- a/pkg/client/client_rest_resources.go +++ b/pkg/client/client_rest_resources.go @@ -44,8 +44,11 @@ type clientRestResources struct { // mapper maps GroupVersionKinds to Resources mapper meta.RESTMapper - // codecs are used to create a REST client for a gvk - codecs serializer.CodecFactory + // codecsByObject is used to override defaultCodecs for specific GroupVersionKind(object) + codecsByObject map[schema.GroupVersionKind]serializer.CodecFactory + + // defaultCodecs are used to create a REST client for a gvk + defaultCodecs serializer.CodecFactory // structuredResourceByType stores structured type metadata structuredResourceByType map[schema.GroupVersionKind]*resourceMeta @@ -62,7 +65,11 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i gvk.Kind = gvk.Kind[:len(gvk.Kind)-4] } - client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, c.codecs, c.httpClient) + codecFactory := c.defaultCodecs + if override, ok := c.codecsByObject[gvk]; ok { + codecFactory = override + } + client, err := apiutil.RESTClientForGVK(gvk, isUnstructured, c.config, codecFactory, c.httpClient) if err != nil { return nil, err }