restmapper.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // TODO: move everything in this file to pkg/api/rest
  14. package meta
  15. import (
  16. "fmt"
  17. "sort"
  18. "strings"
  19. "k8s.io/kubernetes/pkg/api/unversioned"
  20. "k8s.io/kubernetes/pkg/runtime"
  21. )
  22. // Implements RESTScope interface
  23. type restScope struct {
  24. name RESTScopeName
  25. paramName string
  26. argumentName string
  27. paramDescription string
  28. }
  29. func (r *restScope) Name() RESTScopeName {
  30. return r.name
  31. }
  32. func (r *restScope) ParamName() string {
  33. return r.paramName
  34. }
  35. func (r *restScope) ArgumentName() string {
  36. return r.argumentName
  37. }
  38. func (r *restScope) ParamDescription() string {
  39. return r.paramDescription
  40. }
  41. var RESTScopeNamespace = &restScope{
  42. name: RESTScopeNameNamespace,
  43. paramName: "namespaces",
  44. argumentName: "namespace",
  45. paramDescription: "object name and auth scope, such as for teams and projects",
  46. }
  47. var RESTScopeRoot = &restScope{
  48. name: RESTScopeNameRoot,
  49. }
  50. // DefaultRESTMapper exposes mappings between the types defined in a
  51. // runtime.Scheme. It assumes that all types defined the provided scheme
  52. // can be mapped with the provided MetadataAccessor and Codec interfaces.
  53. //
  54. // The resource name of a Kind is defined as the lowercase,
  55. // English-plural version of the Kind string.
  56. // When converting from resource to Kind, the singular version of the
  57. // resource name is also accepted for convenience.
  58. //
  59. // TODO: Only accept plural for some operations for increased control?
  60. // (`get pod bar` vs `get pods bar`)
  61. type DefaultRESTMapper struct {
  62. defaultGroupVersions []unversioned.GroupVersion
  63. resourceToKind map[unversioned.GroupVersionResource]unversioned.GroupVersionKind
  64. kindToPluralResource map[unversioned.GroupVersionKind]unversioned.GroupVersionResource
  65. kindToScope map[unversioned.GroupVersionKind]RESTScope
  66. singularToPlural map[unversioned.GroupVersionResource]unversioned.GroupVersionResource
  67. pluralToSingular map[unversioned.GroupVersionResource]unversioned.GroupVersionResource
  68. interfacesFunc VersionInterfacesFunc
  69. // aliasToResource is used for mapping aliases to resources
  70. aliasToResource map[string][]string
  71. }
  72. func (m *DefaultRESTMapper) String() string {
  73. return fmt.Sprintf("DefaultRESTMapper{kindToPluralResource=%v}", m.kindToPluralResource)
  74. }
  75. var _ RESTMapper = &DefaultRESTMapper{}
  76. // VersionInterfacesFunc returns the appropriate typer, and metadata accessor for a
  77. // given api version, or an error if no such api version exists.
  78. type VersionInterfacesFunc func(version unversioned.GroupVersion) (*VersionInterfaces, error)
  79. // NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
  80. // to a resource name and back based on the objects in a runtime.Scheme
  81. // and the Kubernetes API conventions. Takes a group name, a priority list of the versions
  82. // to search when an object has no default version (set empty to return an error),
  83. // and a function that retrieves the correct metadata for a given version.
  84. func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper {
  85. resourceToKind := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionKind)
  86. kindToPluralResource := make(map[unversioned.GroupVersionKind]unversioned.GroupVersionResource)
  87. kindToScope := make(map[unversioned.GroupVersionKind]RESTScope)
  88. singularToPlural := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource)
  89. pluralToSingular := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource)
  90. aliasToResource := make(map[string][]string)
  91. // TODO: verify name mappings work correctly when versions differ
  92. return &DefaultRESTMapper{
  93. resourceToKind: resourceToKind,
  94. kindToPluralResource: kindToPluralResource,
  95. kindToScope: kindToScope,
  96. defaultGroupVersions: defaultGroupVersions,
  97. singularToPlural: singularToPlural,
  98. pluralToSingular: pluralToSingular,
  99. aliasToResource: aliasToResource,
  100. interfacesFunc: f,
  101. }
  102. }
  103. func (m *DefaultRESTMapper) Add(kind unversioned.GroupVersionKind, scope RESTScope) {
  104. plural, singular := KindToResource(kind)
  105. m.singularToPlural[singular] = plural
  106. m.pluralToSingular[plural] = singular
  107. m.resourceToKind[singular] = kind
  108. m.resourceToKind[plural] = kind
  109. m.kindToPluralResource[kind] = plural
  110. m.kindToScope[kind] = scope
  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. // KindToResource converts Kind to a resource name.
  120. // Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match
  121. // and they aren't guaranteed to do so.
  122. func KindToResource(kind unversioned.GroupVersionKind) ( /*plural*/ unversioned.GroupVersionResource /*singular*/, unversioned.GroupVersionResource) {
  123. kindName := kind.Kind
  124. if len(kindName) == 0 {
  125. return unversioned.GroupVersionResource{}, unversioned.GroupVersionResource{}
  126. }
  127. singularName := strings.ToLower(kindName)
  128. singular := kind.GroupVersion().WithResource(singularName)
  129. for _, skip := range unpluralizedSuffixes {
  130. if strings.HasSuffix(singularName, skip) {
  131. return singular, singular
  132. }
  133. }
  134. switch string(singularName[len(singularName)-1]) {
  135. case "s":
  136. return kind.GroupVersion().WithResource(singularName + "es"), singular
  137. case "y":
  138. return kind.GroupVersion().WithResource(strings.TrimSuffix(singularName, "y") + "ies"), singular
  139. }
  140. return kind.GroupVersion().WithResource(singularName + "s"), singular
  141. }
  142. // ResourceSingularizer implements RESTMapper
  143. // It converts a resource name from plural to singular (e.g., from pods to pod)
  144. func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) {
  145. partialResource := unversioned.GroupVersionResource{Resource: resourceType}
  146. resources, err := m.ResourcesFor(partialResource)
  147. if err != nil {
  148. return resourceType, err
  149. }
  150. singular := unversioned.GroupVersionResource{}
  151. for _, curr := range resources {
  152. currSingular, ok := m.pluralToSingular[curr]
  153. if !ok {
  154. continue
  155. }
  156. if singular.Empty() {
  157. singular = currSingular
  158. continue
  159. }
  160. if currSingular.Resource != singular.Resource {
  161. return resourceType, fmt.Errorf("multiple possible singular resources (%v) found for %v", resources, resourceType)
  162. }
  163. }
  164. if singular.Empty() {
  165. return resourceType, fmt.Errorf("no singular of resource %v has been defined", resourceType)
  166. }
  167. return singular.Resource, nil
  168. }
  169. // coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior)
  170. func coerceResourceForMatching(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource {
  171. resource.Resource = strings.ToLower(resource.Resource)
  172. if resource.Version == runtime.APIVersionInternal {
  173. resource.Version = ""
  174. }
  175. return resource
  176. }
  177. func (m *DefaultRESTMapper) ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) {
  178. resource := coerceResourceForMatching(input)
  179. hasResource := len(resource.Resource) > 0
  180. hasGroup := len(resource.Group) > 0
  181. hasVersion := len(resource.Version) > 0
  182. if !hasResource {
  183. return nil, fmt.Errorf("a resource must be present, got: %v", resource)
  184. }
  185. ret := []unversioned.GroupVersionResource{}
  186. switch {
  187. // fully qualified. Find the exact match
  188. case hasGroup && hasVersion:
  189. for plural, singular := range m.pluralToSingular {
  190. if singular == resource {
  191. ret = append(ret, plural)
  192. break
  193. }
  194. if plural == resource {
  195. ret = append(ret, plural)
  196. break
  197. }
  198. }
  199. case hasGroup:
  200. requestedGroupResource := resource.GroupResource()
  201. for plural, singular := range m.pluralToSingular {
  202. if singular.GroupResource() == requestedGroupResource {
  203. ret = append(ret, plural)
  204. }
  205. if plural.GroupResource() == requestedGroupResource {
  206. ret = append(ret, plural)
  207. }
  208. }
  209. case hasVersion:
  210. for plural, singular := range m.pluralToSingular {
  211. if singular.Version == resource.Version && singular.Resource == resource.Resource {
  212. ret = append(ret, plural)
  213. }
  214. if plural.Version == resource.Version && plural.Resource == resource.Resource {
  215. ret = append(ret, plural)
  216. }
  217. }
  218. default:
  219. for plural, singular := range m.pluralToSingular {
  220. if singular.Resource == resource.Resource {
  221. ret = append(ret, plural)
  222. }
  223. if plural.Resource == resource.Resource {
  224. ret = append(ret, plural)
  225. }
  226. }
  227. }
  228. if len(ret) == 0 {
  229. return nil, &NoResourceMatchError{PartialResource: resource}
  230. }
  231. sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions})
  232. return ret, nil
  233. }
  234. func (m *DefaultRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) {
  235. resources, err := m.ResourcesFor(resource)
  236. if err != nil {
  237. return unversioned.GroupVersionResource{}, err
  238. }
  239. if len(resources) == 1 {
  240. return resources[0], nil
  241. }
  242. return unversioned.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources}
  243. }
  244. func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) {
  245. resource := coerceResourceForMatching(input)
  246. hasResource := len(resource.Resource) > 0
  247. hasGroup := len(resource.Group) > 0
  248. hasVersion := len(resource.Version) > 0
  249. if !hasResource {
  250. return nil, fmt.Errorf("a resource must be present, got: %v", resource)
  251. }
  252. ret := []unversioned.GroupVersionKind{}
  253. switch {
  254. // fully qualified. Find the exact match
  255. case hasGroup && hasVersion:
  256. kind, exists := m.resourceToKind[resource]
  257. if exists {
  258. ret = append(ret, kind)
  259. }
  260. case hasGroup:
  261. requestedGroupResource := resource.GroupResource()
  262. for currResource, currKind := range m.resourceToKind {
  263. if currResource.GroupResource() == requestedGroupResource {
  264. ret = append(ret, currKind)
  265. }
  266. }
  267. case hasVersion:
  268. for currResource, currKind := range m.resourceToKind {
  269. if currResource.Version == resource.Version && currResource.Resource == resource.Resource {
  270. ret = append(ret, currKind)
  271. }
  272. }
  273. default:
  274. for currResource, currKind := range m.resourceToKind {
  275. if currResource.Resource == resource.Resource {
  276. ret = append(ret, currKind)
  277. }
  278. }
  279. }
  280. if len(ret) == 0 {
  281. return nil, &NoResourceMatchError{PartialResource: input}
  282. }
  283. sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions})
  284. return ret, nil
  285. }
  286. func (m *DefaultRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) {
  287. kinds, err := m.KindsFor(resource)
  288. if err != nil {
  289. return unversioned.GroupVersionKind{}, err
  290. }
  291. if len(kinds) == 1 {
  292. return kinds[0], nil
  293. }
  294. return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds}
  295. }
  296. type kindByPreferredGroupVersion struct {
  297. list []unversioned.GroupVersionKind
  298. sortOrder []unversioned.GroupVersion
  299. }
  300. func (o kindByPreferredGroupVersion) Len() int { return len(o.list) }
  301. func (o kindByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
  302. func (o kindByPreferredGroupVersion) Less(i, j int) bool {
  303. lhs := o.list[i]
  304. rhs := o.list[j]
  305. if lhs == rhs {
  306. return false
  307. }
  308. if lhs.GroupVersion() == rhs.GroupVersion() {
  309. return lhs.Kind < rhs.Kind
  310. }
  311. // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
  312. lhsIndex := -1
  313. rhsIndex := -1
  314. for i := range o.sortOrder {
  315. if o.sortOrder[i] == lhs.GroupVersion() {
  316. lhsIndex = i
  317. }
  318. if o.sortOrder[i] == rhs.GroupVersion() {
  319. rhsIndex = i
  320. }
  321. }
  322. if rhsIndex == -1 {
  323. return true
  324. }
  325. return lhsIndex < rhsIndex
  326. }
  327. type resourceByPreferredGroupVersion struct {
  328. list []unversioned.GroupVersionResource
  329. sortOrder []unversioned.GroupVersion
  330. }
  331. func (o resourceByPreferredGroupVersion) Len() int { return len(o.list) }
  332. func (o resourceByPreferredGroupVersion) Swap(i, j int) { o.list[i], o.list[j] = o.list[j], o.list[i] }
  333. func (o resourceByPreferredGroupVersion) Less(i, j int) bool {
  334. lhs := o.list[i]
  335. rhs := o.list[j]
  336. if lhs == rhs {
  337. return false
  338. }
  339. if lhs.GroupVersion() == rhs.GroupVersion() {
  340. return lhs.Resource < rhs.Resource
  341. }
  342. // otherwise, the difference is in the GroupVersion, so we need to sort with respect to the preferred order
  343. lhsIndex := -1
  344. rhsIndex := -1
  345. for i := range o.sortOrder {
  346. if o.sortOrder[i] == lhs.GroupVersion() {
  347. lhsIndex = i
  348. }
  349. if o.sortOrder[i] == rhs.GroupVersion() {
  350. rhsIndex = i
  351. }
  352. }
  353. if rhsIndex == -1 {
  354. return true
  355. }
  356. return lhsIndex < rhsIndex
  357. }
  358. // RESTMapping returns a struct representing the resource path and conversion interfaces a
  359. // RESTClient should use to operate on the provided group/kind in order of versions. If a version search
  360. // order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
  361. // version should be used to access the named group/kind.
  362. // TODO: consider refactoring to use RESTMappings in a way that preserves version ordering and preference
  363. func (m *DefaultRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*RESTMapping, error) {
  364. // Pick an appropriate version
  365. var gvk *unversioned.GroupVersionKind
  366. hadVersion := false
  367. for _, version := range versions {
  368. if len(version) == 0 || version == runtime.APIVersionInternal {
  369. continue
  370. }
  371. currGVK := gk.WithVersion(version)
  372. hadVersion = true
  373. if _, ok := m.kindToPluralResource[currGVK]; ok {
  374. gvk = &currGVK
  375. break
  376. }
  377. }
  378. // Use the default preferred versions
  379. if !hadVersion && (gvk == nil) {
  380. for _, gv := range m.defaultGroupVersions {
  381. if gv.Group != gk.Group {
  382. continue
  383. }
  384. currGVK := gk.WithVersion(gv.Version)
  385. if _, ok := m.kindToPluralResource[currGVK]; ok {
  386. gvk = &currGVK
  387. break
  388. }
  389. }
  390. }
  391. if gvk == nil {
  392. return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")}
  393. }
  394. // Ensure we have a REST mapping
  395. resource, ok := m.kindToPluralResource[*gvk]
  396. if !ok {
  397. found := []unversioned.GroupVersion{}
  398. for _, gv := range m.defaultGroupVersions {
  399. if _, ok := m.kindToPluralResource[*gvk]; ok {
  400. found = append(found, gv)
  401. }
  402. }
  403. if len(found) > 0 {
  404. return nil, fmt.Errorf("object with kind %q exists in versions %v, not %v", gvk.Kind, found, gvk.GroupVersion().String())
  405. }
  406. return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", gvk.GroupVersion().String(), gvk.Kind)
  407. }
  408. // Ensure we have a REST scope
  409. scope, ok := m.kindToScope[*gvk]
  410. if !ok {
  411. return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
  412. }
  413. interfaces, err := m.interfacesFunc(gvk.GroupVersion())
  414. if err != nil {
  415. return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
  416. }
  417. retVal := &RESTMapping{
  418. Resource: resource.Resource,
  419. GroupVersionKind: *gvk,
  420. Scope: scope,
  421. ObjectConvertor: interfaces.ObjectConvertor,
  422. MetadataAccessor: interfaces.MetadataAccessor,
  423. }
  424. return retVal, nil
  425. }
  426. // RESTMappings returns the RESTMappings for the provided group kind in a rough internal preferred order. If no
  427. // kind is found it will return a NoResourceMatchError.
  428. func (m *DefaultRESTMapper) RESTMappings(gk unversioned.GroupKind) ([]*RESTMapping, error) {
  429. // Use the default preferred versions
  430. var mappings []*RESTMapping
  431. for _, gv := range m.defaultGroupVersions {
  432. if gv.Group != gk.Group {
  433. continue
  434. }
  435. gvk := gk.WithVersion(gv.Version)
  436. gvr, ok := m.kindToPluralResource[gvk]
  437. if !ok {
  438. continue
  439. }
  440. // Ensure we have a REST scope
  441. scope, ok := m.kindToScope[gvk]
  442. if !ok {
  443. return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion(), gvk.Kind)
  444. }
  445. interfaces, err := m.interfacesFunc(gvk.GroupVersion())
  446. if err != nil {
  447. return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
  448. }
  449. mappings = append(mappings, &RESTMapping{
  450. Resource: gvr.Resource,
  451. GroupVersionKind: gvk,
  452. Scope: scope,
  453. ObjectConvertor: interfaces.ObjectConvertor,
  454. MetadataAccessor: interfaces.MetadataAccessor,
  455. })
  456. }
  457. if len(mappings) == 0 {
  458. return nil, &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Group: gk.Group, Resource: gk.Kind}}
  459. }
  460. return mappings, nil
  461. }
  462. // AddResourceAlias maps aliases to resources
  463. func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) {
  464. if len(resources) == 0 {
  465. return
  466. }
  467. m.aliasToResource[alias] = resources
  468. }
  469. // AliasesForResource returns whether a resource has an alias or not
  470. func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) {
  471. if res, ok := m.aliasToResource[alias]; ok {
  472. return res, true
  473. }
  474. return nil, false
  475. }