Skip to content

Commit bb432bc

Browse files
committed
restmapper: Ensure that we only access cached fields from a single method
Also ensure this access is always guarded with a mutex.
1 parent f9d7ab3 commit bb432bc

File tree

3 files changed

+195
-115
lines changed

3 files changed

+195
-115
lines changed

pkg/restmapper/caching.go

Lines changed: 115 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,124 +17,157 @@ import (
1717

1818
// cache is our cache of schema information.
1919
type cache struct {
20-
mutex sync.Mutex
21-
groups map[string]metav1.APIGroup
22-
groupVersions map[schema.GroupVersion]*cachedGroupVersion
20+
mutex sync.Mutex
21+
cachedAllGroups map[string]metav1.APIGroup
22+
cachedGroupVersions map[schema.GroupVersion]*cachedGroupVersion
2323
}
2424

2525
// newCache is the constructor for a cache.
2626
func newCache() *cache {
2727
return &cache{
28-
groupVersions: make(map[schema.GroupVersion]*cachedGroupVersion),
28+
cachedGroupVersions: make(map[schema.GroupVersion]*cachedGroupVersion),
2929
}
3030
}
3131

32-
// findGroupInfo returns the APIGroup for the specified group, querying discovery if not cached.
32+
// fetchAllGroups returns the APIGroup for the specified group, querying discovery if not cached.
3333
// If not found, returns APIGroup{}, false, nil
34-
func (c *cache) findGroupInfo(ctx context.Context, discovery discovery.DiscoveryInterface, groupName string) (metav1.APIGroup, bool, error) {
34+
func (c *cache) fetchAllGroups(ctx context.Context, discovery discovery.DiscoveryInterface) (map[string]metav1.APIGroup, error) {
3535
log := log.FromContext(ctx)
3636

3737
c.mutex.Lock()
3838
defer c.mutex.Unlock()
3939

40-
if c.groups == nil {
40+
if c.cachedAllGroups == nil {
4141
log.Info("discovering server groups")
4242
serverGroups, err := discovery.ServerGroups()
4343
if err != nil {
4444
klog.Infof("unexpected error from ServerGroups: %v", err)
45-
return metav1.APIGroup{}, false, fmt.Errorf("error from ServerGroups: %w", err)
45+
return nil, fmt.Errorf("error from ServerGroups: %w", err)
4646
}
4747

4848
groups := make(map[string]metav1.APIGroup)
4949
for i := range serverGroups.Groups {
5050
group := &serverGroups.Groups[i]
5151
groups[group.Name] = *group
5252
}
53-
c.groups = groups
53+
c.cachedAllGroups = groups
5454
}
5555

56-
group, found := c.groups[groupName]
57-
return group, found, nil
56+
return c.cachedAllGroups, nil
5857
}
5958

6059
// cachedGroupVersion caches (all) the resource information for a particular groupversion.
6160
type cachedGroupVersion struct {
62-
gv schema.GroupVersion
63-
mutex sync.Mutex
64-
kinds map[string]cachedGVR
65-
// resource to kind
66-
toKind map[string]string
61+
gv schema.GroupVersion
62+
mutex sync.Mutex
63+
cachedServerResources map[string]cachedResource
6764
}
6865

69-
// cachedGVR caches the information for a particular resource.
70-
type cachedGVR struct {
71-
Resource string
72-
Scope meta.RESTScope
66+
// cachedResource caches the information for a particular resource.
67+
type cachedResource struct {
68+
resource string
69+
scope meta.RESTScope
70+
gvk schema.GroupVersionKind
7371
}
7472

75-
// KindFor finds out the Kind from the GVR in the cache. If the GVR version is not given, we will iterate all the matching
73+
func (r *cachedResource) GVR() schema.GroupVersionResource {
74+
return r.gvk.GroupVersion().WithResource(r.resource)
75+
}
76+
77+
func (r *cachedResource) GVK() schema.GroupVersionKind {
78+
return r.gvk
79+
}
80+
81+
func (r *cachedResource) RESTMapping() *meta.RESTMapping {
82+
return &meta.RESTMapping{
83+
Resource: r.GVR(),
84+
GroupVersionKind: r.GVK(),
85+
Scope: r.scope,
86+
}
87+
}
88+
89+
// KindsFor finds out the Kind from the GVR in the cache. If the GVR version is not given, we will iterate all the matching
7690
// GR in the cache and return the first matching one.
77-
func (c *cache) KindsFor(gvr schema.GroupVersionResource) []string {
91+
func (c *cache) KindsFor(ctx context.Context, gvr schema.GroupVersionResource, discovery discovery.DiscoveryInterface) ([]schema.GroupVersionKind, error) {
92+
var matches []schema.GroupVersionKind
7893
if gvr.Version != "" {
79-
cachedgvr, ok := c.groupVersions[gvr.GroupVersion()]
80-
if !ok {
81-
return nil
94+
gv := gvr.GroupVersion()
95+
cachedGV := c.cacheForGroupVersion(gv)
96+
97+
all, err := cachedGV.KindsFor(ctx, gvr, discovery)
98+
if err != nil {
99+
return nil, err
82100
}
83-
return []string{cachedgvr.toKind[gvr.Resource]}
101+
102+
matches = append(matches, all...)
103+
104+
return matches, nil
84105
}
85-
var kinds []string
86106

87-
for keyGVR, cachedgvr := range c.groupVersions {
88-
if keyGVR.Group != gvr.Group {
107+
allGroups, err := c.fetchAllGroups(ctx, discovery)
108+
if err != nil {
109+
return nil, err
110+
}
111+
for groupName, group := range allGroups {
112+
if groupName != gvr.Group {
89113
continue
90114
}
91-
kind, ok := cachedgvr.toKind[gvr.Resource]
92-
if ok && kind != "" {
93-
kinds = append(kinds, kind)
115+
for _, version := range group.Versions {
116+
gv := schema.GroupVersion{Group: groupName, Version: version.Version}
117+
cachedGV := c.cacheForGroupVersion(gv)
118+
119+
all, err := cachedGV.KindsFor(ctx, gvr, discovery)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
matches = append(matches, all...)
94125
}
95126
}
96-
return kinds
127+
return matches, nil
97128
}
98129

99-
// findRESTMapping returns the RESTMapping for the specified GVK, querying discovery if not cached.
100-
func (c *cache) findRESTMapping(ctx context.Context, discovery discovery.DiscoveryInterface, gv schema.GroupVersion, kind string) (*meta.RESTMapping, error) {
130+
func (c *cache) cacheForGroupVersion(gv schema.GroupVersion) *cachedGroupVersion {
101131
c.mutex.Lock()
102-
cached := c.groupVersions[gv]
132+
cached := c.cachedGroupVersions[gv]
103133
if cached == nil {
104-
cached = &cachedGroupVersion{gv: gv, toKind: make(map[string]string)}
105-
c.groupVersions[gv] = cached
134+
cached = &cachedGroupVersion{gv: gv}
135+
c.cachedGroupVersions[gv] = cached
106136
}
107137
c.mutex.Unlock()
108-
return cached.findRESTMapping(ctx, discovery, kind)
138+
return cached
139+
}
140+
141+
// findRESTMapping returns the RESTMapping for the specified GVK, querying discovery if not cached.
142+
func (c *cache) findRESTMapping(ctx context.Context, discovery discovery.DiscoveryInterface, gv schema.GroupVersion, kind string) (*meta.RESTMapping, error) {
143+
cachedGV := c.cacheForGroupVersion(gv)
144+
return cachedGV.findRESTMapping(ctx, discovery, kind)
109145
}
110146

111147
// findRESTMapping returns the RESTMapping for the specified GVK, querying discovery if not cached.
112148
func (c *cachedGroupVersion) findRESTMapping(ctx context.Context, discovery discovery.DiscoveryInterface, kind string) (*meta.RESTMapping, error) {
113-
kinds, err := c.fetch(ctx, discovery)
149+
resources, err := c.fetchServerResources(ctx, discovery)
114150
if err != nil {
115151
return nil, err
116152
}
117153

118-
cached, found := kinds[kind]
119-
if !found {
120-
return nil, nil
154+
for _, resource := range resources {
155+
if resource.GVK().Kind == kind {
156+
return resource.RESTMapping(), nil
157+
}
121158
}
122-
return &meta.RESTMapping{
123-
Resource: c.gv.WithResource(cached.Resource),
124-
GroupVersionKind: c.gv.WithKind(kind),
125-
Scope: cached.Scope,
126-
}, nil
159+
return nil, nil
127160
}
128161

129162
// fetch returns the metadata, fetching it if not cached.
130-
func (c *cachedGroupVersion) fetch(ctx context.Context, discovery discovery.DiscoveryInterface) (map[string]cachedGVR, error) {
163+
func (c *cachedGroupVersion) fetchServerResources(ctx context.Context, discovery discovery.DiscoveryInterface) (map[string]cachedResource, error) {
131164
log := log.FromContext(ctx)
132165

133166
c.mutex.Lock()
134167
defer c.mutex.Unlock()
135168

136-
if c.kinds != nil {
137-
return c.kinds, nil
169+
if c.cachedServerResources != nil {
170+
return c.cachedServerResources, nil
138171
}
139172

140173
log.Info("discovering server resources for group/version", "gv", c.gv.String())
@@ -149,7 +182,7 @@ func (c *cachedGroupVersion) fetch(ctx context.Context, discovery discovery.Disc
149182
}
150183
}
151184

152-
kinds := make(map[string]cachedGVR)
185+
result := make(map[string]cachedResource)
153186
for i := range resourceList.APIResources {
154187
resource := resourceList.APIResources[i]
155188

@@ -162,12 +195,36 @@ func (c *cachedGroupVersion) fetch(ctx context.Context, discovery discovery.Disc
162195
if resource.Namespaced {
163196
scope = meta.RESTScopeNamespace
164197
}
165-
kinds[resource.Kind] = cachedGVR{
166-
Resource: resource.Name,
167-
Scope: scope,
198+
result[resource.Name] = cachedResource{
199+
resource: resource.Name,
200+
scope: scope,
201+
gvk: c.gv.WithKind(resource.Kind),
168202
}
169-
c.toKind[resource.Name] = resource.Kind
170203
}
171-
c.kinds = kinds
172-
return kinds, nil
204+
c.cachedServerResources = result
205+
return result, nil
206+
}
207+
208+
func (c *cachedGroupVersion) KindsFor(ctx context.Context, filterGVR schema.GroupVersionResource, discovery discovery.DiscoveryInterface) ([]schema.GroupVersionKind, error) {
209+
serverResources, err := c.fetchServerResources(ctx, discovery)
210+
if err != nil {
211+
return nil, err
212+
}
213+
var matches []schema.GroupVersionKind
214+
for _, resource := range serverResources {
215+
resourceGVR := resource.GVR()
216+
217+
if resourceGVR.Group != filterGVR.Group {
218+
continue
219+
}
220+
if filterGVR.Version != "" && resourceGVR.Version != filterGVR.Version {
221+
continue
222+
}
223+
if filterGVR.Resource != "" && resourceGVR.Resource != filterGVR.Resource {
224+
continue
225+
}
226+
matches = append(matches, resource.GVK())
227+
}
228+
229+
return matches, nil
173230
}

0 commit comments

Comments
 (0)