2 Copyright 2014 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.
17 // TODO: move everything in this file to pkg/api/rest
25 "k8s.io/apimachinery/pkg/runtime"
26 "k8s.io/apimachinery/pkg/runtime/schema"
29 // Implements RESTScope interface
30 type restScope struct {
34 func (r *restScope) Name() RESTScopeName {
38 var RESTScopeNamespace = &restScope{
39 name: RESTScopeNameNamespace,
42 var RESTScopeRoot = &restScope{
43 name: RESTScopeNameRoot,
46 // DefaultRESTMapper exposes mappings between the types defined in a
47 // runtime.Scheme. It assumes that all types defined the provided scheme
48 // can be mapped with the provided MetadataAccessor and Codec interfaces.
50 // The resource name of a Kind is defined as the lowercase,
51 // English-plural version of the Kind string.
52 // When converting from resource to Kind, the singular version of the
53 // resource name is also accepted for convenience.
55 // TODO: Only accept plural for some operations for increased control?
56 // (`get pod bar` vs `get pods bar`)
57 type DefaultRESTMapper struct {
58 defaultGroupVersions []schema.GroupVersion
60 resourceToKind map[schema.GroupVersionResource]schema.GroupVersionKind
61 kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource
62 kindToScope map[schema.GroupVersionKind]RESTScope
63 singularToPlural map[schema.GroupVersionResource]schema.GroupVersionResource
64 pluralToSingular map[schema.GroupVersionResource]schema.GroupVersionResource
67 func (m *DefaultRESTMapper) String() string {
68 return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource)
71 var _ RESTMapper = &DefaultRESTMapper{}
73 // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
74 // to a resource name and back based on the objects in a runtime.Scheme
75 // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
76 // to search when an object has no default version (set empty to return an error),
77 // and a function that retrieves the correct metadata for a given version.
78 func NewDefaultRESTMapper(defaultGroupVersions []schema.GroupVersion) *DefaultRESTMapper {
79 resourceToKind := make(map[schema.GroupVersionResource]schema.GroupVersionKind)
80 kindToPluralResource := make(map[schema.GroupVersionKind]schema.GroupVersionResource)
81 kindToScope := make(map[schema.GroupVersionKind]RESTScope)
82 singularToPlural := make(map[schema.GroupVersionResource]schema.GroupVersionResource)
83 pluralToSingular := make(map[schema.GroupVersionResource]schema.GroupVersionResource)
84 // TODO: verify name mappings work correctly when versions differ
86 return &DefaultRESTMapper{
87 resourceToKind: resourceToKind,
88 kindToPluralResource: kindToPluralResource,
89 kindToScope: kindToScope,
90 defaultGroupVersions: defaultGroupVersions,
91 singularToPlural: singularToPlural,
92 pluralToSingular: pluralToSingular,
96 func (m *DefaultRESTMapper) Add(kind schema.GroupVersionKind, scope RESTScope) {
97 plural, singular := UnsafeGuessKindToResource(kind)
98 m.AddSpecific(kind, plural, singular, scope)
101 func (m *DefaultRESTMapper) AddSpecific(kind schema.GroupVersionKind, plural, singular schema.GroupVersionResource, scope RESTScope) {
102 m.singularToPlural[singular] = plural
103 m.pluralToSingular[plural] = singular
105 m.resourceToKind[singular] = kind
106 m.resourceToKind[plural] = kind
108 m.kindToPluralResource[kind] = plural
109 m.kindToScope[kind] = scope
112 // unpluralizedSuffixes is a list of resource suffixes that are the same plural and singular
113 // This is only is only necessary because some bits of code are lazy and don't actually use the RESTMapper like they should.
114 // TODO eliminate this so that different callers can correctly map to resources. This probably means updating all
115 // callers to use the RESTMapper they mean.
116 var unpluralizedSuffixes = []string{
120 // UnsafeGuessKindToResource converts Kind to a resource name.
121 // Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match
122 // and they aren't guaranteed to do so.
123 func UnsafeGuessKindToResource(kind schema.GroupVersionKind) ( /*plural*/ schema.GroupVersionResource /*singular*/, schema.GroupVersionResource) {
124 kindName := kind.Kind
125 if len(kindName) == 0 {
126 return schema.GroupVersionResource{}, schema.GroupVersionResource{}
128 singularName := strings.ToLower(kindName)
129 singular := kind.GroupVersion().WithResource(singularName)
131 for _, skip := range unpluralizedSuffixes {
132 if strings.HasSuffix(singularName, skip) {
133 return singular, singular
137 switch string(singularName[len(singularName)-1]) {
139 return kind.GroupVersion().WithResource(singularName + "es"), singular
141 return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular
144 return kind.GroupVersion().WithResource(singularName + "s"), singular
147 // ResourceSingularizer implements RESTMapper
148 // It converts a resource name from plural to singular (e.g., from pods to pod)
149 func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) {
150 partialResource := schema.GroupVersionResource{Resource: resourceType}
151 resources, err := m.ResourcesFor(partialResource)
153 return resourceType, err
156 singular := schema.GroupVersionResource{}
157 for _, curr := range resources {
158 currSingular, ok := m.pluralToSingular[curr]
162 if singular.Empty() {
163 singular = currSingular
167 if currSingular.Resource != singular.Resource {
168 return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType)
172 if singular.Empty() {
173 return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType)
176 return singular.Resource, nil
179 // coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior)
180 func coerceResourceForMatching(resource schema.GroupVersionResource) schema.GroupVersionResource {
181 resource.Resource = strings.ToLower(resource.Resource)
182 if resource.Version == runtime.APIVersionInternal {
183 resource.Version = ""
189 func (m *DefaultRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
190 resource := coerceResourceForMatching(input)
192 hasResource := len(resource.Resource) > 0
193 hasGroup := len(resource.Group) > 0
194 hasVersion := len(resource.Version) > 0
197 return nil, fmt.Errorf("a resource must be present, got: %v", resource)
200 ret := []schema.GroupVersionResource{}
202 case hasGroup && hasVersion:
203 // fully qualified. Find the exact match
204 for plural, singular := range m.pluralToSingular {
205 if singular == resource {
206 ret = append(ret, plural)
209 if plural == resource {
210 ret = append(ret, plural)
216 // given a group, prefer an exact match. If you don't find one, resort to a prefix match on group
217 foundExactMatch := false
218 requestedGroupResource := resource.GroupResource()
219 for plural, singular := range m.pluralToSingular {
220 if singular.GroupResource() == requestedGroupResource {
221 foundExactMatch = true
222 ret = append(ret, plural)
224 if plural.GroupResource() == requestedGroupResource {
225 foundExactMatch = true
226 ret = append(ret, plural)
230 // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match
231 // storageclass.storage.k8s.io
232 if !foundExactMatch {
233 for plural, singular := range m.pluralToSingular {
234 if !strings.HasPrefix(plural.Group, requestedGroupResource.Group) {
237 if singular.Resource == requestedGroupResource.Resource {
238 ret = append(ret, plural)
240 if plural.Resource == requestedGroupResource.Resource {
241 ret = append(ret, plural)
248 for plural, singular := range m.pluralToSingular {
249 if singular.Version == resource.Version && singular.Resource == resource.Resource {
250 ret = append(ret, plural)
252 if plural.Version == resource.Version && plural.Resource == resource.Resource {
253 ret = append(ret, plural)
258 for plural, singular := range m.pluralToSingular {
259 if singular.Resource == resource.Resource {
260 ret = append(ret, plural)
262 if plural.Resource == resource.Resource {
263 ret = append(ret, plural)
269 return nil, &NoResourceMatchError{PartialResource: resource}
272 sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions})
276 func (m *DefaultRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
277 resources, err := m.ResourcesFor(resource)
279 return schema.GroupVersionResource{}, err
281 if len(resources) == 1 {
282 return resources[0], nil
285 return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources}
288 func (m *DefaultRESTMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
289 resource := coerceResourceForMatching(input)
291 hasResource := len(resource.Resource) > 0
292 hasGroup := len(resource.Group) > 0
293 hasVersion := len(resource.Version) > 0
296 return nil, fmt.Errorf("a resource must be present, got: %v", resource)
299 ret := []schema.GroupVersionKind{}
301 // fully qualified. Find the exact match
302 case hasGroup && hasVersion:
303 kind, exists := m.resourceToKind[resource]
305 ret = append(ret, kind)
309 foundExactMatch := false
310 requestedGroupResource := resource.GroupResource()
311 for currResource, currKind := range m.resourceToKind {
312 if currResource.GroupResource() == requestedGroupResource {
313 foundExactMatch = true
314 ret = append(ret, currKind)
318 // if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match
319 // storageclass.storage.k8s.io
320 if !foundExactMatch {
321 for currResource, currKind := range m.resourceToKind {
322 if !strings.HasPrefix(currResource.Group, requestedGroupResource.Group) {
325 if currResource.Resource == requestedGroupResource.Resource {
326 ret = append(ret, currKind)
333 for currResource, currKind := range m.resourceToKind {
334 if currResource.Version == resource.Version && currResource.Resource == resource.Resource {
335 ret = append(ret, currKind)
340 for currResource, currKind := range m.resourceToKind {
341 if currResource.Resource == resource.Resource {
342 ret = append(ret, currKind)
348 return nil, &NoResourceMatchError{PartialResource: input}
351 sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions})
355 func (m *DefaultRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
356 kinds, err := m.KindsFor(resource)
358 return schema.GroupVersionKind{}, err
364 return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds}
367 type kindByPreferredGroupVersion struct {
368 list []schema.GroupVersionKind
369 sortOrder []schema.GroupVersion
372 func (o kindByPreferredGroupVersion) Len() int { return len(o.list) }
373 func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
374 func (o kindByPreferredGroupVersion) Less(i, j int) bool {
381 if lhs.GroupVersion() == rhs.GroupVersion() {
382 return lhs.Kind < rhs.Kind
385 // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
389 for i := range o.sortOrder {
390 if o.sortOrder[i] == lhs.GroupVersion() {
393 if o.sortOrder[i] == rhs.GroupVersion() {
402 return lhsIndex < rhsIndex
405 type resourceByPreferredGroupVersion struct {
406 list []schema.GroupVersionResource
407 sortOrder []schema.GroupVersion
410 func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) }
411 func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
412 func (o resourceByPreferredGroupVersion) Less(i, j int) bool {
419 if lhs.GroupVersion() == rhs.GroupVersion() {
420 return lhs.Resource < rhs.Resource
423 // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
427 for i := range o.sortOrder {
428 if o.sortOrder[i] == lhs.GroupVersion() {
431 if o.sortOrder[i] == rhs.GroupVersion() {
440 return lhsIndex < rhsIndex
443 // RESTMapping returns a struct representing the resource path and conversion interfaces a
444 // RESTClient should use to operate on the provided group/kind in order of versions. If a version search
445 // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
446 // version should be used to access the named group/kind.
447 func (m *DefaultRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) {
448 mappings, err := m.RESTMappings(gk, versions...)
452 if len(mappings) == 0 {
453 return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions}
455 // since we rely on RESTMappings method
456 // take the first match and return to the caller
457 // as this was the existing behavior.
458 return mappings[0], nil
461 // RESTMappings returns the RESTMappings for the provided group kind. If a version search order
462 // is not provided, the search order provided to DefaultRESTMapper will be used.
463 func (m *DefaultRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) {
464 mappings := make([]*RESTMapping, 0)
465 potentialGVK := make([]schema.GroupVersionKind, 0)
468 // Pick an appropriate version
469 for _, version := range versions {
470 if len(version) == 0 || version == runtime.APIVersionInternal {
473 currGVK := gk.WithVersion(version)
475 if _, ok := m.kindToPluralResource[currGVK]; ok {
476 potentialGVK = append(potentialGVK, currGVK)
480 // Use the default preferred versions
481 if !hadVersion && len(potentialGVK) == 0 {
482 for _, gv := range m.defaultGroupVersions {
483 if gv.Group != gk.Group {
486 potentialGVK = append(potentialGVK, gk.WithVersion(gv.Version))
490 if len(potentialGVK) == 0 {
491 return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions}
494 for _, gvk := range potentialGVK {
495 //Ensure we have a REST mapping
496 res, ok := m.kindToPluralResource[gvk]
501 // Ensure we have a REST scope
502 scope, ok := m.kindToScope[gvk]
504 return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind)
507 mappings = append(mappings, &RESTMapping{
509 GroupVersionKind: gvk,
514 if len(mappings) == 0 {
515 return nil, &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}}