Skip to content

Commit 8de2a35

Browse files
committed
Added DynamicRESTMapper
On-behalf-of: @SAP [email protected] Signed-off-by: Robert Vasek <[email protected]>
1 parent d992bc9 commit 8de2a35

File tree

6 files changed

+1158
-0
lines changed

6 files changed

+1158
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package dynamicrestmapper
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/api/meta"
21+
"k8s.io/apimachinery/pkg/runtime/schema"
22+
)
23+
24+
// This file adds mutable methods to our fork of upstream's DefaultRESTMapper.
25+
26+
func (m *DefaultRESTMapper) empty() bool {
27+
// If one of the maps is empty, all of the maps are empty.
28+
return len(m.resourceToKind) == 0
29+
}
30+
31+
func (m *DefaultRESTMapper) add(typeMeta typeMeta) {
32+
kind := typeMeta.groupVersionKind()
33+
singular := typeMeta.groupVersionResourceSingular()
34+
plural := typeMeta.groupVersionResourcePlural()
35+
36+
m.singularToPlural[singular] = plural
37+
m.pluralToSingular[plural] = singular
38+
39+
m.resourceToKind[singular] = kind
40+
m.resourceToKind[plural] = kind
41+
42+
m.kindToPluralResource[kind] = plural
43+
m.kindToScope[kind] = meta.RESTScopeRoot
44+
}
45+
46+
func (m *DefaultRESTMapper) remove(typeMeta typeMeta) {
47+
kind := typeMeta.groupVersionKind()
48+
singular := typeMeta.groupVersionResourceSingular()
49+
plural := typeMeta.groupVersionResourcePlural()
50+
51+
delete(m.singularToPlural, singular)
52+
delete(m.pluralToSingular, plural)
53+
54+
delete(m.resourceToKind, singular)
55+
delete(m.resourceToKind, plural)
56+
57+
delete(m.kindToPluralResource, kind)
58+
delete(m.kindToScope, kind)
59+
}
60+
61+
func (m *DefaultRESTMapper) getGVKR(gvr schema.GroupVersionResource) typeMeta {
62+
kind := m.resourceToKind[gvr]
63+
singular := m.pluralToSingular[gvr]
64+
if singular.Empty() {
65+
singular = gvr
66+
}
67+
plural := m.singularToPlural[gvr]
68+
if plural.Empty() {
69+
plural = gvr
70+
}
71+
scope := m.kindToScope[kind]
72+
73+
return newTypeMeta(
74+
kind.Group,
75+
kind.Version,
76+
kind.Kind,
77+
singular.Resource,
78+
plural.Resource,
79+
scope,
80+
)
81+
}
82+
83+
func (m *DefaultRESTMapper) getGVKRs(gr schema.GroupResource) ([]typeMeta, error) {
84+
gvrs, err := m.ResourcesFor(gr.WithVersion(""))
85+
if err != nil {
86+
return nil, err
87+
}
88+
gvkrs := make([]typeMeta, len(gvrs))
89+
for i := range gvrs {
90+
gvkrs[i] = m.getGVKR(gvrs[i])
91+
}
92+
return gvkrs, nil
93+
}
94+
95+
// Applies the two slices to the known mappings. It is assumed there is
96+
// no overlap between toRemove and toAdd.
97+
func (m *DefaultRESTMapper) apply(toRemove []typeMeta, toAdd []typeMeta) {
98+
for i := range toRemove {
99+
m.remove(toRemove[i])
100+
}
101+
102+
for i := range toAdd {
103+
m.add(toAdd[i])
104+
}
105+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2025 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package dynamicrestmapper
18+
19+
import (
20+
"sync"
21+
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
24+
"github.com/kcp-dev/logicalcluster/v3"
25+
)
26+
27+
// DynamicRESTMapper is a thread-safe RESTMapper with per-cluster GVK/GVR mappings.
28+
// The mapping data is fed from its associated Controller (in this package), triggered
29+
// on CRDs and APIBindings (and their respective APIResourceSchemas). Additionally,
30+
// mappings for built-in types (pkg/virtual/apiexport/schemas/builtin/builtin.go) are
31+
// added for each LogicalCluster by default.
32+
type DynamicRESTMapper struct {
33+
lock sync.RWMutex
34+
byCluster map[logicalcluster.Name]*DefaultRESTMapper
35+
}
36+
37+
func NewDynamicRESTMapper(defaultGroupVersions []schema.GroupVersion) *DynamicRESTMapper {
38+
return &DynamicRESTMapper{
39+
byCluster: make(map[logicalcluster.Name]*DefaultRESTMapper),
40+
}
41+
}
42+
43+
func (d *DynamicRESTMapper) deleteMappingsForCluster(clusterName logicalcluster.Name) {
44+
d.lock.Lock()
45+
defer d.lock.Unlock()
46+
delete(d.byCluster, clusterName)
47+
}
48+
49+
// ForCluster returns a RESTMapper for the specified cluster name.
50+
// The method never returns nil. If the cluster doesn't exist at the time
51+
// of calling ForCluster, or if it is deleted while holding the returned
52+
// RESTMapper instance, all RESTMapper's methods will empty matches and
53+
// NoResourceMatchError error.
54+
func (d *DynamicRESTMapper) ForCluster(clusterName logicalcluster.Name) *ForCluster {
55+
return newForCluster(clusterName, d)
56+
}

0 commit comments

Comments
 (0)