Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / k8s.io / apimachinery / pkg / api / meta / restmapper.go
1 /*
2 Copyright 2014 The Kubernetes 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 // TODO: move everything in this file to pkg/api/rest
18 package meta
19
20 import (
21         "fmt"
22         "sort"
23         "strings"
24
25         "k8s.io/apimachinery/pkg/runtime"
26         "k8s.io/apimachinery/pkg/runtime/schema"
27 )
28
29 // Implements RESTScope interface
30 type restScope struct {
31         name RESTScopeName
32 }
33
34 func (r *restScope) Name() RESTScopeName {
35         return r.name
36 }
37
38 var RESTScopeNamespace = &restScope{
39         name: RESTScopeNameNamespace,
40 }
41
42 var RESTScopeRoot = &restScope{
43         name: RESTScopeNameRoot,
44 }
45
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.
49 //
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.
54 //
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
59
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
65 }
66
67 func (m *DefaultRESTMapper) String() string {
68         return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource)
69 }
70
71 var _ RESTMapper = &DefaultRESTMapper{}
72
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
85
86         return &DefaultRESTMapper{
87                 resourceToKind:       resourceToKind,
88                 kindToPluralResource: kindToPluralResource,
89                 kindToScope:          kindToScope,
90                 defaultGroupVersions: defaultGroupVersions,
91                 singularToPlural:     singularToPlural,
92                 pluralToSingular:     pluralToSingular,
93         }
94 }
95
96 func (m *DefaultRESTMapper) Add(kind schema.GroupVersionKind, scope RESTScope) {
97         plural, singular := UnsafeGuessKindToResource(kind)
98         m.AddSpecific(kind, plural, singular, scope)
99 }
100
101 func (m *DefaultRESTMapper) AddSpecific(kind schema.GroupVersionKind, plural, singular schema.GroupVersionResource, scope RESTScope) {
102         m.singularToPlural[singular] = plural
103         m.pluralToSingular[plural] = singular
104
105         m.resourceToKind[singular] = kind
106         m.resourceToKind[plural] = kind
107
108         m.kindToPluralResource[kind] = plural
109         m.kindToScope[kind] = scope
110 }
111
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{
117         "endpoints",
118 }
119
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{}
127         }
128         singularName := strings.ToLower(kindName)
129         singular := kind.GroupVersion().WithResource(singularName)
130
131         for _, skip := range unpluralizedSuffixes {
132                 if strings.HasSuffix(singularName, skip) {
133                         return singular, singular
134                 }
135         }
136
137         switch string(singularName[len(singularName)-1]) {
138         case "s":
139                 return kind.GroupVersion().WithResource(singularName + "es"), singular
140         case "y":
141                 return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular
142         }
143
144         return kind.GroupVersion().WithResource(singularName + "s"), singular
145 }
146
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)
152         if err != nil {
153                 return resourceType, err
154         }
155
156         singular := schema.GroupVersionResource{}
157         for _, curr := range resources {
158                 currSingular, ok := m.pluralToSingular[curr]
159                 if !ok {
160                         continue
161                 }
162                 if singular.Empty() {
163                         singular = currSingular
164                         continue
165                 }
166
167                 if currSingular.Resource != singular.Resource {
168                         return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType)
169                 }
170         }
171
172         if singular.Empty() {
173                 return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType)
174         }
175
176         return singular.Resource, nil
177 }
178
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 = ""
184         }
185
186         return resource
187 }
188
189 func (m *DefaultRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
190         resource := coerceResourceForMatching(input)
191
192         hasResource := len(resource.Resource) > 0
193         hasGroup := len(resource.Group) > 0
194         hasVersion := len(resource.Version) > 0
195
196         if !hasResource {
197                 return nil, fmt.Errorf("a resource must be present, got: %v", resource)
198         }
199
200         ret := []schema.GroupVersionResource{}
201         switch {
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)
207                                 break
208                         }
209                         if plural == resource {
210                                 ret = append(ret, plural)
211                                 break
212                         }
213                 }
214
215         case hasGroup:
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)
223                         }
224                         if plural.GroupResource() == requestedGroupResource {
225                                 foundExactMatch = true
226                                 ret = append(ret, plural)
227                         }
228                 }
229
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) {
235                                         continue
236                                 }
237                                 if singular.Resource == requestedGroupResource.Resource {
238                                         ret = append(ret, plural)
239                                 }
240                                 if plural.Resource == requestedGroupResource.Resource {
241                                         ret = append(ret, plural)
242                                 }
243                         }
244
245                 }
246
247         case hasVersion:
248                 for plural, singular := range m.pluralToSingular {
249                         if singular.Version == resource.Version && singular.Resource == resource.Resource {
250                                 ret = append(ret, plural)
251                         }
252                         if plural.Version == resource.Version && plural.Resource == resource.Resource {
253                                 ret = append(ret, plural)
254                         }
255                 }
256
257         default:
258                 for plural, singular := range m.pluralToSingular {
259                         if singular.Resource == resource.Resource {
260                                 ret = append(ret, plural)
261                         }
262                         if plural.Resource == resource.Resource {
263                                 ret = append(ret, plural)
264                         }
265                 }
266         }
267
268         if len(ret) == 0 {
269                 return nil, &NoResourceMatchError{PartialResource: resource}
270         }
271
272         sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions})
273         return ret, nil
274 }
275
276 func (m *DefaultRESTMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
277         resources, err := m.ResourcesFor(resource)
278         if err != nil {
279                 return schema.GroupVersionResource{}, err
280         }
281         if len(resources) == 1 {
282                 return resources[0], nil
283         }
284
285         return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources}
286 }
287
288 func (m *DefaultRESTMapper) KindsFor(input schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
289         resource := coerceResourceForMatching(input)
290
291         hasResource := len(resource.Resource) > 0
292         hasGroup := len(resource.Group) > 0
293         hasVersion := len(resource.Version) > 0
294
295         if !hasResource {
296                 return nil, fmt.Errorf("a resource must be present, got: %v", resource)
297         }
298
299         ret := []schema.GroupVersionKind{}
300         switch {
301         // fully qualified.  Find the exact match
302         case hasGroup && hasVersion:
303                 kind, exists := m.resourceToKind[resource]
304                 if exists {
305                         ret = append(ret, kind)
306                 }
307
308         case hasGroup:
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)
315                         }
316                 }
317
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) {
323                                         continue
324                                 }
325                                 if currResource.Resource == requestedGroupResource.Resource {
326                                         ret = append(ret, currKind)
327                                 }
328                         }
329
330                 }
331
332         case hasVersion:
333                 for currResource, currKind := range m.resourceToKind {
334                         if currResource.Version == resource.Version && currResource.Resource == resource.Resource {
335                                 ret = append(ret, currKind)
336                         }
337                 }
338
339         default:
340                 for currResource, currKind := range m.resourceToKind {
341                         if currResource.Resource == resource.Resource {
342                                 ret = append(ret, currKind)
343                         }
344                 }
345         }
346
347         if len(ret) == 0 {
348                 return nil, &NoResourceMatchError{PartialResource: input}
349         }
350
351         sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions})
352         return ret, nil
353 }
354
355 func (m *DefaultRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
356         kinds, err := m.KindsFor(resource)
357         if err != nil {
358                 return schema.GroupVersionKind{}, err
359         }
360         if len(kinds) == 1 {
361                 return kinds[0], nil
362         }
363
364         return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds}
365 }
366
367 type kindByPreferredGroupVersion struct {
368         list      []schema.GroupVersionKind
369         sortOrder []schema.GroupVersion
370 }
371
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 {
375         lhs := o.list[i]
376         rhs := o.list[j]
377         if lhs == rhs {
378                 return false
379         }
380
381         if lhs.GroupVersion() == rhs.GroupVersion() {
382                 return lhs.Kind < rhs.Kind
383         }
384
385         // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
386         lhsIndex := -1
387         rhsIndex := -1
388
389         for i := range o.sortOrder {
390                 if o.sortOrder[i] == lhs.GroupVersion() {
391                         lhsIndex = i
392                 }
393                 if o.sortOrder[i] == rhs.GroupVersion() {
394                         rhsIndex = i
395                 }
396         }
397
398         if rhsIndex == -1 {
399                 return true
400         }
401
402         return lhsIndex < rhsIndex
403 }
404
405 type resourceByPreferredGroupVersion struct {
406         list      []schema.GroupVersionResource
407         sortOrder []schema.GroupVersion
408 }
409
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 {
413         lhs := o.list[i]
414         rhs := o.list[j]
415         if lhs == rhs {
416                 return false
417         }
418
419         if lhs.GroupVersion() == rhs.GroupVersion() {
420                 return lhs.Resource < rhs.Resource
421         }
422
423         // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
424         lhsIndex := -1
425         rhsIndex := -1
426
427         for i := range o.sortOrder {
428                 if o.sortOrder[i] == lhs.GroupVersion() {
429                         lhsIndex = i
430                 }
431                 if o.sortOrder[i] == rhs.GroupVersion() {
432                         rhsIndex = i
433                 }
434         }
435
436         if rhsIndex == -1 {
437                 return true
438         }
439
440         return lhsIndex < rhsIndex
441 }
442
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...)
449         if err != nil {
450                 return nil, err
451         }
452         if len(mappings) == 0 {
453                 return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions}
454         }
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
459 }
460
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)
466         hadVersion := false
467
468         // Pick an appropriate version
469         for _, version := range versions {
470                 if len(version) == 0 || version == runtime.APIVersionInternal {
471                         continue
472                 }
473                 currGVK := gk.WithVersion(version)
474                 hadVersion = true
475                 if _, ok := m.kindToPluralResource[currGVK]; ok {
476                         potentialGVK = append(potentialGVK, currGVK)
477                         break
478                 }
479         }
480         // Use the default preferred versions
481         if !hadVersion && len(potentialGVK) == 0 {
482                 for _, gv := range m.defaultGroupVersions {
483                         if gv.Group != gk.Group {
484                                 continue
485                         }
486                         potentialGVK = append(potentialGVK, gk.WithVersion(gv.Version))
487                 }
488         }
489
490         if len(potentialGVK) == 0 {
491                 return nil, &NoKindMatchError{GroupKind: gk, SearchedVersions: versions}
492         }
493
494         for _, gvk := range potentialGVK {
495                 //Ensure we have a REST mapping
496                 res, ok := m.kindToPluralResource[gvk]
497                 if !ok {
498                         continue
499                 }
500
501                 // Ensure we have a REST scope
502                 scope, ok := m.kindToScope[gvk]
503                 if !ok {
504                         return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind)
505                 }
506
507                 mappings = append(mappings, &RESTMapping{
508                         Resource:         res,
509                         GroupVersionKind: gvk,
510                         Scope:            scope,
511                 })
512         }
513
514         if len(mappings) == 0 {
515                 return nil, &NoResourceMatchError{PartialResource: schema.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}}
516         }
517         return mappings, nil
518 }