priority.go 7.6 KB

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