restmapper.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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 discovery
  14. import (
  15. "fmt"
  16. "sync"
  17. "k8s.io/apimachinery/pkg/api/errors"
  18. "k8s.io/apimachinery/pkg/api/meta"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/runtime/schema"
  21. "github.com/golang/glog"
  22. )
  23. // APIGroupResources is an API group with a mapping of versions to
  24. // resources.
  25. type APIGroupResources struct {
  26. Group metav1.APIGroup
  27. // A mapping of version string to a slice of APIResources for
  28. // that version.
  29. VersionedResources map[string][]metav1.APIResource
  30. }
  31. // NewRESTMapper returns a PriorityRESTMapper based on the discovered
  32. // groups and resources passed in.
  33. func NewRESTMapper(groupResources []*APIGroupResources, versionInterfaces meta.VersionInterfacesFunc) meta.RESTMapper {
  34. unionMapper := meta.MultiRESTMapper{}
  35. var groupPriority []string
  36. // /v1 is special. It should always come first
  37. resourcePriority := []schema.GroupVersionResource{{Group: "", Version: "v1", Resource: meta.AnyResource}}
  38. kindPriority := []schema.GroupVersionKind{{Group: "", Version: "v1", Kind: meta.AnyKind}}
  39. for _, group := range groupResources {
  40. groupPriority = append(groupPriority, group.Group.Name)
  41. if len(group.Group.PreferredVersion.Version) != 0 {
  42. preferred := group.Group.PreferredVersion.Version
  43. if _, ok := group.VersionedResources[preferred]; ok {
  44. resourcePriority = append(resourcePriority, schema.GroupVersionResource{
  45. Group: group.Group.Name,
  46. Version: group.Group.PreferredVersion.Version,
  47. Resource: meta.AnyResource,
  48. })
  49. kindPriority = append(kindPriority, schema.GroupVersionKind{
  50. Group: group.Group.Name,
  51. Version: group.Group.PreferredVersion.Version,
  52. Kind: meta.AnyKind,
  53. })
  54. }
  55. }
  56. for _, discoveryVersion := range group.Group.Versions {
  57. resources, ok := group.VersionedResources[discoveryVersion.Version]
  58. if !ok {
  59. continue
  60. }
  61. gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
  62. versionMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gv}, versionInterfaces)
  63. for _, resource := range resources {
  64. scope := meta.RESTScopeNamespace
  65. if !resource.Namespaced {
  66. scope = meta.RESTScopeRoot
  67. }
  68. versionMapper.Add(gv.WithKind(resource.Kind), scope)
  69. // TODO only do this if it supports listing
  70. versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
  71. }
  72. // TODO why is this type not in discovery (at least for "v1")
  73. versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot)
  74. unionMapper = append(unionMapper, versionMapper)
  75. }
  76. }
  77. for _, group := range groupPriority {
  78. resourcePriority = append(resourcePriority, schema.GroupVersionResource{
  79. Group: group,
  80. Version: meta.AnyVersion,
  81. Resource: meta.AnyResource,
  82. })
  83. kindPriority = append(kindPriority, schema.GroupVersionKind{
  84. Group: group,
  85. Version: meta.AnyVersion,
  86. Kind: meta.AnyKind,
  87. })
  88. }
  89. return meta.PriorityRESTMapper{
  90. Delegate: unionMapper,
  91. ResourcePriority: resourcePriority,
  92. KindPriority: kindPriority,
  93. }
  94. }
  95. // GetAPIGroupResources uses the provided discovery client to gather
  96. // discovery information and populate a slice of APIGroupResources.
  97. func GetAPIGroupResources(cl DiscoveryInterface) ([]*APIGroupResources, error) {
  98. apiGroups, err := cl.ServerGroups()
  99. if err != nil {
  100. return nil, err
  101. }
  102. var result []*APIGroupResources
  103. for _, group := range apiGroups.Groups {
  104. groupResources := &APIGroupResources{
  105. Group: group,
  106. VersionedResources: make(map[string][]metav1.APIResource),
  107. }
  108. for _, version := range group.Versions {
  109. resources, err := cl.ServerResourcesForGroupVersion(version.GroupVersion)
  110. if err != nil {
  111. if errors.IsNotFound(err) {
  112. continue // ignore as this can race with deletion of 3rd party APIs
  113. }
  114. return nil, err
  115. }
  116. groupResources.VersionedResources[version.Version] = resources.APIResources
  117. }
  118. result = append(result, groupResources)
  119. }
  120. return result, nil
  121. }
  122. // DeferredDiscoveryRESTMapper is a RESTMapper that will defer
  123. // initialization of the RESTMapper until the first mapping is
  124. // requested.
  125. type DeferredDiscoveryRESTMapper struct {
  126. initMu sync.Mutex
  127. delegate meta.RESTMapper
  128. cl CachedDiscoveryInterface
  129. versionInterface meta.VersionInterfacesFunc
  130. }
  131. // NewDeferredDiscoveryRESTMapper returns a
  132. // DeferredDiscoveryRESTMapper that will lazily query the provided
  133. // client for discovery information to do REST mappings.
  134. func NewDeferredDiscoveryRESTMapper(cl CachedDiscoveryInterface, versionInterface meta.VersionInterfacesFunc) *DeferredDiscoveryRESTMapper {
  135. return &DeferredDiscoveryRESTMapper{
  136. cl: cl,
  137. versionInterface: versionInterface,
  138. }
  139. }
  140. func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
  141. d.initMu.Lock()
  142. defer d.initMu.Unlock()
  143. if d.delegate != nil {
  144. return d.delegate, nil
  145. }
  146. groupResources, err := GetAPIGroupResources(d.cl)
  147. if err != nil {
  148. return nil, err
  149. }
  150. d.delegate = NewRESTMapper(groupResources, d.versionInterface)
  151. return d.delegate, err
  152. }
  153. // Reset resets the internally cached Discovery information and will
  154. // cause the next mapping request to re-discover.
  155. func (d *DeferredDiscoveryRESTMapper) Reset() {
  156. glog.V(5).Info("Invalidating discovery information")
  157. d.initMu.Lock()
  158. defer d.initMu.Unlock()
  159. d.cl.Invalidate()
  160. d.delegate = nil
  161. }
  162. // KindFor takes a partial resource and returns back the single match.
  163. // It returns an error if there are multiple matches.
  164. func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
  165. del, err := d.getDelegate()
  166. if err != nil {
  167. return schema.GroupVersionKind{}, err
  168. }
  169. gvk, err = del.KindFor(resource)
  170. if err != nil && !d.cl.Fresh() {
  171. d.Reset()
  172. gvk, err = d.KindFor(resource)
  173. }
  174. return
  175. }
  176. // KindsFor takes a partial resource and returns back the list of
  177. // potential kinds in priority order.
  178. func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
  179. del, err := d.getDelegate()
  180. if err != nil {
  181. return nil, err
  182. }
  183. gvks, err = del.KindsFor(resource)
  184. if len(gvks) == 0 && !d.cl.Fresh() {
  185. d.Reset()
  186. gvks, err = d.KindsFor(resource)
  187. }
  188. return
  189. }
  190. // ResourceFor takes a partial resource and returns back the single
  191. // match. It returns an error if there are multiple matches.
  192. func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
  193. del, err := d.getDelegate()
  194. if err != nil {
  195. return schema.GroupVersionResource{}, err
  196. }
  197. gvr, err = del.ResourceFor(input)
  198. if err != nil && !d.cl.Fresh() {
  199. d.Reset()
  200. gvr, err = d.ResourceFor(input)
  201. }
  202. return
  203. }
  204. // ResourcesFor takes a partial resource and returns back the list of
  205. // potential resource in priority order.
  206. func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
  207. del, err := d.getDelegate()
  208. if err != nil {
  209. return nil, err
  210. }
  211. gvrs, err = del.ResourcesFor(input)
  212. if len(gvrs) == 0 && !d.cl.Fresh() {
  213. d.Reset()
  214. gvrs, err = d.ResourcesFor(input)
  215. }
  216. return
  217. }
  218. // RESTMapping identifies a preferred resource mapping for the
  219. // provided group kind.
  220. func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
  221. del, err := d.getDelegate()
  222. if err != nil {
  223. return nil, err
  224. }
  225. m, err = del.RESTMapping(gk, versions...)
  226. if err != nil && !d.cl.Fresh() {
  227. d.Reset()
  228. m, err = d.RESTMapping(gk, versions...)
  229. }
  230. return
  231. }
  232. // RESTMappings returns the RESTMappings for the provided group kind
  233. // in a rough internal preferred order. If no kind is found, it will
  234. // return a NoResourceMatchError.
  235. func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
  236. del, err := d.getDelegate()
  237. if err != nil {
  238. return nil, err
  239. }
  240. ms, err = del.RESTMappings(gk, versions...)
  241. if len(ms) == 0 && !d.cl.Fresh() {
  242. d.Reset()
  243. ms, err = d.RESTMappings(gk, versions...)
  244. }
  245. return
  246. }
  247. // AliasesForResource returns whether a resource has an alias or not.
  248. func (d *DeferredDiscoveryRESTMapper) AliasesForResource(resource string) (as []string, ok bool) {
  249. del, err := d.getDelegate()
  250. if err != nil {
  251. return nil, false
  252. }
  253. as, ok = del.AliasesForResource(resource)
  254. if len(as) == 0 && !d.cl.Fresh() {
  255. d.Reset()
  256. as, ok = d.AliasesForResource(resource)
  257. }
  258. return
  259. }
  260. // ResourceSingularizer converts a resource name from plural to
  261. // singular (e.g., from pods to pod).
  262. func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
  263. del, err := d.getDelegate()
  264. if err != nil {
  265. return resource, err
  266. }
  267. singular, err = del.ResourceSingularizer(resource)
  268. if err != nil && !d.cl.Fresh() {
  269. d.Reset()
  270. singular, err = d.ResourceSingularizer(resource)
  271. }
  272. return
  273. }
  274. func (d *DeferredDiscoveryRESTMapper) String() string {
  275. del, err := d.getDelegate()
  276. if err != nil {
  277. return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err)
  278. }
  279. return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del)
  280. }
  281. // Make sure it satisfies the interface
  282. var _ meta.RESTMapper = &DeferredDiscoveryRESTMapper{}