@@ -17,19 +17,32 @@ limitations under the License.
1717package client
1818
1919import (
20+ "context"
2021 "net/http"
2122 "strings"
2223 "sync"
2324
25+ lru "github.com/hashicorp/golang-lru/v2"
26+ "github.com/kcp-dev/logicalcluster/v3"
2427 "k8s.io/apimachinery/pkg/api/meta"
2528 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2629 "k8s.io/apimachinery/pkg/runtime"
2730 "k8s.io/apimachinery/pkg/runtime/schema"
2831 "k8s.io/apimachinery/pkg/runtime/serializer"
2932 "k8s.io/client-go/rest"
3033 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
34+ "sigs.k8s.io/controller-runtime/pkg/kontext"
3135)
3236
37+ type clusterResources struct {
38+ mapper meta.RESTMapper
39+
40+ // structuredResourceByType stores structured type metadata
41+ structuredResourceByType map [schema.GroupVersionKind ]* resourceMeta
42+ // unstructuredResourceByType stores unstructured type metadata
43+ unstructuredResourceByType map [schema.GroupVersionKind ]* resourceMeta
44+ }
45+
3346// clientRestResources creates and stores rest clients and metadata for Kubernetes types.
3447type clientRestResources struct {
3548 // httpClient is the http client to use for requests
@@ -42,21 +55,18 @@ type clientRestResources struct {
4255 scheme * runtime.Scheme
4356
4457 // mapper maps GroupVersionKinds to Resources
45- mapper meta.RESTMapper
58+ mapper func ( ctx context. Context ) ( meta.RESTMapper , error )
4659
4760 // codecs are used to create a REST client for a gvk
4861 codecs serializer.CodecFactory
4962
50- // structuredResourceByType stores structured type metadata
51- structuredResourceByType map [schema.GroupVersionKind ]* resourceMeta
52- // unstructuredResourceByType stores unstructured type metadata
53- unstructuredResourceByType map [schema.GroupVersionKind ]* resourceMeta
54- mu sync.RWMutex
63+ clusterResources * lru.Cache [logicalcluster.Path , clusterResources ]
64+ mu sync.RWMutex
5565}
5666
5767// newResource maps obj to a Kubernetes Resource and constructs a client for that Resource.
5868// If the object is a list, the resource represents the item's type instead.
59- func (c * clientRestResources ) newResource (gvk schema.GroupVersionKind , isList , isUnstructured bool ) (* resourceMeta , error ) {
69+ func (c * clientRestResources ) newResource (gvk schema.GroupVersionKind , isList , isUnstructured bool , mapper meta. RESTMapper ) (* resourceMeta , error ) {
6070 if strings .HasSuffix (gvk .Kind , "List" ) && isList {
6171 // if this was a list, treat it as a request for the item's resource
6272 gvk .Kind = gvk .Kind [:len (gvk .Kind )- 4 ]
@@ -66,7 +76,7 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
6676 if err != nil {
6777 return nil , err
6878 }
69- mapping , err := c . mapper .RESTMapping (gvk .GroupKind (), gvk .Version )
79+ mapping , err := mapper .RESTMapping (gvk .GroupKind (), gvk .Version )
7080 if err != nil {
7181 return nil , err
7282 }
@@ -75,7 +85,7 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
7585
7686// getResource returns the resource meta information for the given type of object.
7787// If the object is a list, the resource represents the item's type instead.
78- func (c * clientRestResources ) getResource (obj runtime.Object ) (* resourceMeta , error ) {
88+ func (c * clientRestResources ) getResource (ctx context. Context , obj runtime.Object ) (* resourceMeta , error ) {
7989 gvk , err := apiutil .GVKForObject (obj , c .scheme )
8090 if err != nil {
8191 return nil , err
@@ -86,9 +96,25 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
8696 // It's better to do creation work twice than to not let multiple
8797 // people make requests at once
8898 c .mu .RLock ()
89- resourceByType := c .structuredResourceByType
99+ cluster , _ := kontext .ClusterFrom (ctx )
100+ cr , found := c .clusterResources .Get (cluster .Path ())
101+ if ! found {
102+ m , err := c .mapper (ctx )
103+ if err != nil {
104+ c .mu .RUnlock ()
105+ return nil , err
106+ }
107+ cr = clusterResources {
108+ mapper : m ,
109+ structuredResourceByType : make (map [schema.GroupVersionKind ]* resourceMeta ),
110+ unstructuredResourceByType : make (map [schema.GroupVersionKind ]* resourceMeta ),
111+ }
112+ c .clusterResources .Purge ()
113+ c .clusterResources .Add (cluster .Path (), cr )
114+ }
115+ resourceByType := cr .structuredResourceByType
90116 if isUnstructured {
91- resourceByType = c .unstructuredResourceByType
117+ resourceByType = cr .unstructuredResourceByType
92118 }
93119 r , known := resourceByType [gvk ]
94120 c .mu .RUnlock ()
@@ -100,7 +126,7 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
100126 // Initialize a new Client
101127 c .mu .Lock ()
102128 defer c .mu .Unlock ()
103- r , err = c .newResource (gvk , meta .IsListType (obj ), isUnstructured )
129+ r , err = c .newResource (gvk , meta .IsListType (obj ), isUnstructured , cr . mapper )
104130 if err != nil {
105131 return nil , err
106132 }
@@ -109,8 +135,8 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
109135}
110136
111137// getObjMeta returns objMeta containing both type and object metadata and state.
112- func (c * clientRestResources ) getObjMeta (obj runtime.Object ) (* objMeta , error ) {
113- r , err := c .getResource (obj )
138+ func (c * clientRestResources ) getObjMeta (ctx context. Context , obj runtime.Object ) (* objMeta , error ) {
139+ r , err := c .getResource (ctx , obj )
114140 if err != nil {
115141 return nil , err
116142 }
0 commit comments