priority.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. Copyright 2016 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. package meta
  14. import (
  15. "fmt"
  16. "k8s.io/apimachinery/pkg/runtime/schema"
  17. )
  18. const (
  19. AnyGroup = "*"
  20. AnyVersion = "*"
  21. AnyResource = "*"
  22. AnyKind = "*"
  23. )
  24. // PriorityRESTMapper is a wrapper for automatically choosing a particular Resource or Kind
  25. // when multiple matches are possible
  26. type PriorityRESTMapper struct {
  27. // Delegate is the RESTMapper to use to locate all the Kind and Resource matches
  28. Delegate RESTMapper
  29. // ResourcePriority is a list of priority patterns to apply to matching resources.
  30. // The list of all matching resources is narrowed based on the patterns until only one remains.
  31. // A pattern with no matches is skipped. A pattern with more than one match uses its
  32. // matches as the list to continue matching against.
  33. ResourcePriority []schema.GroupVersionResource
  34. // KindPriority is a list of priority patterns to apply to matching kinds.
  35. // The list of all matching kinds is narrowed based on the patterns until only one remains.
  36. // A pattern with no matches is skipped. A pattern with more than one match uses its
  37. // matches as the list to continue matching against.
  38. KindPriority []schema.GroupVersionKind
  39. }
  40. func (m PriorityRESTMapper) String() string {
  41. return fmt.Sprintf("PriorityRESTMapper{\n\t%v\n\t%v\n\t%v\n}", m.ResourcePriority, m.KindPriority, m.Delegate)
  42. }
  43. // ResourceFor finds all resources, then passes them through the ResourcePriority patterns to find a single matching hit.
  44. func (m PriorityRESTMapper) ResourceFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
  45. originalGVRs, originalErr := m.Delegate.ResourcesFor(partiallySpecifiedResource)
  46. if originalErr != nil && len(originalGVRs) == 0 {
  47. return schema.GroupVersionResource{}, originalErr
  48. }
  49. if len(originalGVRs) == 1 {
  50. return originalGVRs[0], originalErr
  51. }
  52. remainingGVRs := append([]schema.GroupVersionResource{}, originalGVRs...)
  53. for _, pattern := range m.ResourcePriority {
  54. matchedGVRs := []schema.GroupVersionResource{}
  55. for _, gvr := range remainingGVRs {
  56. if resourceMatches(pattern, gvr) {
  57. matchedGVRs = append(matchedGVRs, gvr)
  58. }
  59. }
  60. switch len(matchedGVRs) {
  61. case 0:
  62. // if you have no matches, then nothing matched this pattern just move to the next
  63. continue
  64. case 1:
  65. // one match, return
  66. return matchedGVRs[0], originalErr
  67. default:
  68. // more than one match, use the matched hits as the list moving to the next pattern.
  69. // this way you can have a series of selection criteria
  70. remainingGVRs = matchedGVRs
  71. }
  72. }
  73. return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingResources: originalGVRs}
  74. }
  75. // KindFor finds all kinds, then passes them through the KindPriority patterns to find a single matching hit.
  76. func (m PriorityRESTMapper) KindFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
  77. originalGVKs, originalErr := m.Delegate.KindsFor(partiallySpecifiedResource)
  78. if originalErr != nil && len(originalGVKs) == 0 {
  79. return schema.GroupVersionKind{}, originalErr
  80. }
  81. if len(originalGVKs) == 1 {
  82. return originalGVKs[0], originalErr
  83. }
  84. remainingGVKs := append([]schema.GroupVersionKind{}, originalGVKs...)
  85. for _, pattern := range m.KindPriority {
  86. matchedGVKs := []schema.GroupVersionKind{}
  87. for _, gvr := range remainingGVKs {
  88. if kindMatches(pattern, gvr) {
  89. matchedGVKs = append(matchedGVKs, gvr)
  90. }
  91. }
  92. switch len(matchedGVKs) {
  93. case 0:
  94. // if you have no matches, then nothing matched this pattern just move to the next
  95. continue
  96. case 1:
  97. // one match, return
  98. return matchedGVKs[0], originalErr
  99. default:
  100. // more than one match, use the matched hits as the list moving to the next pattern.
  101. // this way you can have a series of selection criteria
  102. remainingGVKs = matchedGVKs
  103. }
  104. }
  105. return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingKinds: originalGVKs}
  106. }
  107. func resourceMatches(pattern schema.GroupVersionResource, resource schema.GroupVersionResource) bool {
  108. if pattern.Group != AnyGroup && pattern.Group != resource.Group {
  109. return false
  110. }
  111. if pattern.Version != AnyVersion && pattern.Version != resource.Version {
  112. return false
  113. }
  114. if pattern.Resource != AnyResource && pattern.Resource != resource.Resource {
  115. return false
  116. }
  117. return true
  118. }
  119. func kindMatches(pattern schema.GroupVersionKind, kind schema.GroupVersionKind) bool {
  120. if pattern.Group != AnyGroup && pattern.Group != kind.Group {
  121. return false
  122. }
  123. if pattern.Version != AnyVersion && pattern.Version != kind.Version {
  124. return false
  125. }
  126. if pattern.Kind != AnyKind && pattern.Kind != kind.Kind {
  127. return false
  128. }
  129. return true
  130. }
  131. func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) {
  132. mappings, originalErr := m.Delegate.RESTMappings(gk, versions...)
  133. if originalErr != nil && len(mappings) == 0 {
  134. return nil, originalErr
  135. }
  136. // any versions the user provides take priority
  137. priorities := m.KindPriority
  138. if len(versions) > 0 {
  139. priorities = make([]schema.GroupVersionKind, 0, len(m.KindPriority)+len(versions))
  140. for _, version := range versions {
  141. gv := schema.GroupVersion{
  142. Version: version,
  143. Group: gk.Group,
  144. }
  145. priorities = append(priorities, gv.WithKind(AnyKind))
  146. }
  147. priorities = append(priorities, m.KindPriority...)
  148. }
  149. remaining := append([]*RESTMapping{}, mappings...)
  150. for _, pattern := range priorities {
  151. var matching []*RESTMapping
  152. for _, m := range remaining {
  153. if kindMatches(pattern, m.GroupVersionKind) {
  154. matching = append(matching, m)
  155. }
  156. }
  157. switch len(matching) {
  158. case 0:
  159. // if you have no matches, then nothing matched this pattern just move to the next
  160. continue
  161. case 1:
  162. // one match, return
  163. return matching[0], originalErr
  164. default:
  165. // more than one match, use the matched hits as the list moving to the next pattern.
  166. // this way you can have a series of selection criteria
  167. remaining = matching
  168. }
  169. }
  170. if len(remaining) == 1 {
  171. return remaining[0], originalErr
  172. }
  173. var kinds []schema.GroupVersionKind
  174. for _, m := range mappings {
  175. kinds = append(kinds, m.GroupVersionKind)
  176. }
  177. return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds}
  178. }
  179. func (m PriorityRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) {
  180. return m.Delegate.RESTMappings(gk, versions...)
  181. }
  182. func (m PriorityRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
  183. return m.Delegate.ResourceSingularizer(resource)
  184. }
  185. func (m PriorityRESTMapper) ResourcesFor(partiallySpecifiedResource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
  186. return m.Delegate.ResourcesFor(partiallySpecifiedResource)
  187. }
  188. func (m PriorityRESTMapper) KindsFor(partiallySpecifiedResource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) {
  189. return m.Delegate.KindsFor(partiallySpecifiedResource)
  190. }