2 Copyright 2016 The Kubernetes Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
24 "k8s.io/apimachinery/pkg/api/meta"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime/schema"
27 "k8s.io/client-go/discovery"
32 // APIGroupResources is an API group with a mapping of versions to
34 type APIGroupResources struct {
36 // A mapping of version string to a slice of APIResources for
38 VersionedResources map[string][]metav1.APIResource
41 // NewDiscoveryRESTMapper returns a PriorityRESTMapper based on the discovered
42 // groups and resources passed in.
43 func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper {
44 unionMapper := meta.MultiRESTMapper{}
46 var groupPriority []string
47 // /v1 is special. It should always come first
48 resourcePriority := []schema.GroupVersionResource{{Group: "", Version: "v1", Resource: meta.AnyResource}}
49 kindPriority := []schema.GroupVersionKind{{Group: "", Version: "v1", Kind: meta.AnyKind}}
51 for _, group := range groupResources {
52 groupPriority = append(groupPriority, group.Group.Name)
54 // Make sure the preferred version comes first
55 if len(group.Group.PreferredVersion.Version) != 0 {
56 preferred := group.Group.PreferredVersion.Version
57 if _, ok := group.VersionedResources[preferred]; ok {
58 resourcePriority = append(resourcePriority, schema.GroupVersionResource{
59 Group: group.Group.Name,
60 Version: group.Group.PreferredVersion.Version,
61 Resource: meta.AnyResource,
64 kindPriority = append(kindPriority, schema.GroupVersionKind{
65 Group: group.Group.Name,
66 Version: group.Group.PreferredVersion.Version,
72 for _, discoveryVersion := range group.Group.Versions {
73 resources, ok := group.VersionedResources[discoveryVersion.Version]
78 // Add non-preferred versions after the preferred version, in case there are resources that only exist in those versions
79 if discoveryVersion.Version != group.Group.PreferredVersion.Version {
80 resourcePriority = append(resourcePriority, schema.GroupVersionResource{
81 Group: group.Group.Name,
82 Version: discoveryVersion.Version,
83 Resource: meta.AnyResource,
86 kindPriority = append(kindPriority, schema.GroupVersionKind{
87 Group: group.Group.Name,
88 Version: discoveryVersion.Version,
93 gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
94 versionMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gv})
96 for _, resource := range resources {
97 scope := meta.RESTScopeNamespace
98 if !resource.Namespaced {
99 scope = meta.RESTScopeRoot
102 // if we have a slash, then this is a subresource and we shouldn't create mappings for those.
103 if strings.Contains(resource.Name, "/") {
107 plural := gv.WithResource(resource.Name)
108 singular := gv.WithResource(resource.SingularName)
109 // this is for legacy resources and servers which don't list singular forms. For those we must still guess.
110 if len(resource.SingularName) == 0 {
111 _, singular = meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind))
114 versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope)
115 versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope)
116 // TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior
117 versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
119 // TODO why is this type not in discovery (at least for "v1")
120 versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot)
121 unionMapper = append(unionMapper, versionMapper)
125 for _, group := range groupPriority {
126 resourcePriority = append(resourcePriority, schema.GroupVersionResource{
128 Version: meta.AnyVersion,
129 Resource: meta.AnyResource,
131 kindPriority = append(kindPriority, schema.GroupVersionKind{
133 Version: meta.AnyVersion,
138 return meta.PriorityRESTMapper{
139 Delegate: unionMapper,
140 ResourcePriority: resourcePriority,
141 KindPriority: kindPriority,
145 // GetAPIGroupResources uses the provided discovery client to gather
146 // discovery information and populate a slice of APIGroupResources.
147 func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources, error) {
148 apiGroups, err := cl.ServerGroups()
150 if apiGroups == nil || len(apiGroups.Groups) == 0 {
153 // TODO track the errors and update callers to handle partial errors.
155 var result []*APIGroupResources
156 for _, group := range apiGroups.Groups {
157 groupResources := &APIGroupResources{
159 VersionedResources: make(map[string][]metav1.APIResource),
161 for _, version := range group.Versions {
162 resources, err := cl.ServerResourcesForGroupVersion(version.GroupVersion)
164 // continue as best we can
165 // TODO track the errors and update callers to handle partial errors.
166 if resources == nil || len(resources.APIResources) == 0 {
170 groupResources.VersionedResources[version.Version] = resources.APIResources
172 result = append(result, groupResources)
177 // DeferredDiscoveryRESTMapper is a RESTMapper that will defer
178 // initialization of the RESTMapper until the first mapping is
180 type DeferredDiscoveryRESTMapper struct {
182 delegate meta.RESTMapper
183 cl discovery.CachedDiscoveryInterface
186 // NewDeferredDiscoveryRESTMapper returns a
187 // DeferredDiscoveryRESTMapper that will lazily query the provided
188 // client for discovery information to do REST mappings.
189 func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
190 return &DeferredDiscoveryRESTMapper{
195 func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
197 defer d.initMu.Unlock()
199 if d.delegate != nil {
200 return d.delegate, nil
203 groupResources, err := GetAPIGroupResources(d.cl)
208 d.delegate = NewDiscoveryRESTMapper(groupResources)
209 return d.delegate, err
212 // Reset resets the internally cached Discovery information and will
213 // cause the next mapping request to re-discover.
214 func (d *DeferredDiscoveryRESTMapper) Reset() {
215 klog.V(5).Info("Invalidating discovery information")
218 defer d.initMu.Unlock()
224 // KindFor takes a partial resource and returns back the single match.
225 // It returns an error if there are multiple matches.
226 func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
227 del, err := d.getDelegate()
229 return schema.GroupVersionKind{}, err
231 gvk, err = del.KindFor(resource)
232 if err != nil && !d.cl.Fresh() {
234 gvk, err = d.KindFor(resource)
239 // KindsFor takes a partial resource and returns back the list of
240 // potential kinds in priority order.
241 func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
242 del, err := d.getDelegate()
246 gvks, err = del.KindsFor(resource)
247 if len(gvks) == 0 && !d.cl.Fresh() {
249 gvks, err = d.KindsFor(resource)
254 // ResourceFor takes a partial resource and returns back the single
255 // match. It returns an error if there are multiple matches.
256 func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
257 del, err := d.getDelegate()
259 return schema.GroupVersionResource{}, err
261 gvr, err = del.ResourceFor(input)
262 if err != nil && !d.cl.Fresh() {
264 gvr, err = d.ResourceFor(input)
269 // ResourcesFor takes a partial resource and returns back the list of
270 // potential resource in priority order.
271 func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
272 del, err := d.getDelegate()
276 gvrs, err = del.ResourcesFor(input)
277 if len(gvrs) == 0 && !d.cl.Fresh() {
279 gvrs, err = d.ResourcesFor(input)
284 // RESTMapping identifies a preferred resource mapping for the
285 // provided group kind.
286 func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
287 del, err := d.getDelegate()
291 m, err = del.RESTMapping(gk, versions...)
292 if err != nil && !d.cl.Fresh() {
294 m, err = d.RESTMapping(gk, versions...)
299 // RESTMappings returns the RESTMappings for the provided group kind
300 // in a rough internal preferred order. If no kind is found, it will
301 // return a NoResourceMatchError.
302 func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
303 del, err := d.getDelegate()
307 ms, err = del.RESTMappings(gk, versions...)
308 if len(ms) == 0 && !d.cl.Fresh() {
310 ms, err = d.RESTMappings(gk, versions...)
315 // ResourceSingularizer converts a resource name from plural to
316 // singular (e.g., from pods to pod).
317 func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
318 del, err := d.getDelegate()
322 singular, err = del.ResourceSingularizer(resource)
323 if err != nil && !d.cl.Fresh() {
325 singular, err = d.ResourceSingularizer(resource)
330 func (d *DeferredDiscoveryRESTMapper) String() string {
331 del, err := d.getDelegate()
333 return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err)
335 return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del)
338 // Make sure it satisfies the interface
339 var _ meta.RESTMapper = &DeferredDiscoveryRESTMapper{}