@@ -17,19 +17,32 @@ limitations under the License.
17
17
package client
18
18
19
19
import (
20
+ "context"
20
21
"net/http"
21
22
"strings"
22
23
"sync"
23
24
25
+ lru "github.com/hashicorp/golang-lru/v2"
26
+ "github.com/kcp-dev/logicalcluster/v3"
24
27
"k8s.io/apimachinery/pkg/api/meta"
25
28
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26
29
"k8s.io/apimachinery/pkg/runtime"
27
30
"k8s.io/apimachinery/pkg/runtime/schema"
28
31
"k8s.io/apimachinery/pkg/runtime/serializer"
29
32
"k8s.io/client-go/rest"
30
33
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
34
+ "sigs.k8s.io/controller-runtime/pkg/kontext"
31
35
)
32
36
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
+
33
46
// clientRestResources creates and stores rest clients and metadata for Kubernetes types.
34
47
type clientRestResources struct {
35
48
// httpClient is the http client to use for requests
@@ -42,21 +55,18 @@ type clientRestResources struct {
42
55
scheme * runtime.Scheme
43
56
44
57
// mapper maps GroupVersionKinds to Resources
45
- mapper meta.RESTMapper
58
+ mapper func ( ctx context. Context ) ( meta.RESTMapper , error )
46
59
47
60
// codecs are used to create a REST client for a gvk
48
61
codecs serializer.CodecFactory
49
62
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
55
65
}
56
66
57
67
// newResource maps obj to a Kubernetes Resource and constructs a client for that Resource.
58
68
// 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 ) {
60
70
if strings .HasSuffix (gvk .Kind , "List" ) && isList {
61
71
// if this was a list, treat it as a request for the item's resource
62
72
gvk .Kind = gvk .Kind [:len (gvk .Kind )- 4 ]
@@ -66,7 +76,7 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
66
76
if err != nil {
67
77
return nil , err
68
78
}
69
- mapping , err := c . mapper .RESTMapping (gvk .GroupKind (), gvk .Version )
79
+ mapping , err := mapper .RESTMapping (gvk .GroupKind (), gvk .Version )
70
80
if err != nil {
71
81
return nil , err
72
82
}
@@ -75,7 +85,7 @@ func (c *clientRestResources) newResource(gvk schema.GroupVersionKind, isList, i
75
85
76
86
// getResource returns the resource meta information for the given type of object.
77
87
// 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 ) {
79
89
gvk , err := apiutil .GVKForObject (obj , c .scheme )
80
90
if err != nil {
81
91
return nil , err
@@ -86,9 +96,25 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
86
96
// It's better to do creation work twice than to not let multiple
87
97
// people make requests at once
88
98
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
90
116
if isUnstructured {
91
- resourceByType = c .unstructuredResourceByType
117
+ resourceByType = cr .unstructuredResourceByType
92
118
}
93
119
r , known := resourceByType [gvk ]
94
120
c .mu .RUnlock ()
@@ -100,7 +126,7 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
100
126
// Initialize a new Client
101
127
c .mu .Lock ()
102
128
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 )
104
130
if err != nil {
105
131
return nil , err
106
132
}
@@ -109,8 +135,8 @@ func (c *clientRestResources) getResource(obj runtime.Object) (*resourceMeta, er
109
135
}
110
136
111
137
// 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 )
114
140
if err != nil {
115
141
return nil , err
116
142
}
0 commit comments