aggregated_discovery.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /*
  2. Copyright 2022 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. apidiscovery "k8s.io/api/apidiscovery/v2"
  17. apidiscoveryv2beta1 "k8s.io/api/apidiscovery/v2beta1"
  18. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  19. "k8s.io/apimachinery/pkg/runtime/schema"
  20. )
  21. // StaleGroupVersionError encasulates failed GroupVersion marked "stale"
  22. // in the returned AggregatedDiscovery format.
  23. type StaleGroupVersionError struct {
  24. gv schema.GroupVersion
  25. }
  26. func (s StaleGroupVersionError) Error() string {
  27. return fmt.Sprintf("stale GroupVersion discovery: %v", s.gv)
  28. }
  29. // SplitGroupsAndResources transforms "aggregated" discovery top-level structure into
  30. // the previous "unaggregated" discovery groups and resources.
  31. func SplitGroupsAndResources(aggregatedGroups apidiscovery.APIGroupDiscoveryList) (
  32. *metav1.APIGroupList,
  33. map[schema.GroupVersion]*metav1.APIResourceList,
  34. map[schema.GroupVersion]error) {
  35. // Aggregated group list will contain the entirety of discovery, including
  36. // groups, versions, and resources. GroupVersions marked "stale" are failed.
  37. groups := []*metav1.APIGroup{}
  38. failedGVs := map[schema.GroupVersion]error{}
  39. resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{}
  40. for _, aggGroup := range aggregatedGroups.Items {
  41. group, resources, failed := convertAPIGroup(aggGroup)
  42. groups = append(groups, group)
  43. for gv, resourceList := range resources {
  44. resourcesByGV[gv] = resourceList
  45. }
  46. for gv, err := range failed {
  47. failedGVs[gv] = err
  48. }
  49. }
  50. // Transform slice of groups to group list before returning.
  51. groupList := &metav1.APIGroupList{}
  52. groupList.Groups = make([]metav1.APIGroup, 0, len(groups))
  53. for _, group := range groups {
  54. groupList.Groups = append(groupList.Groups, *group)
  55. }
  56. return groupList, resourcesByGV, failedGVs
  57. }
  58. // convertAPIGroup tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup,
  59. // also returning the map of APIResourceList for resources within GroupVersions.
  60. func convertAPIGroup(g apidiscovery.APIGroupDiscovery) (
  61. *metav1.APIGroup,
  62. map[schema.GroupVersion]*metav1.APIResourceList,
  63. map[schema.GroupVersion]error) {
  64. // Iterate through versions to convert to group and resources.
  65. group := &metav1.APIGroup{}
  66. gvResources := map[schema.GroupVersion]*metav1.APIResourceList{}
  67. failedGVs := map[schema.GroupVersion]error{}
  68. group.Name = g.ObjectMeta.Name
  69. for _, v := range g.Versions {
  70. gv := schema.GroupVersion{Group: g.Name, Version: v.Version}
  71. if v.Freshness == apidiscovery.DiscoveryFreshnessStale {
  72. failedGVs[gv] = StaleGroupVersionError{gv: gv}
  73. continue
  74. }
  75. version := metav1.GroupVersionForDiscovery{}
  76. version.GroupVersion = gv.String()
  77. version.Version = v.Version
  78. group.Versions = append(group.Versions, version)
  79. // PreferredVersion is first non-stale Version
  80. if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) {
  81. group.PreferredVersion = version
  82. }
  83. resourceList := &metav1.APIResourceList{}
  84. resourceList.GroupVersion = gv.String()
  85. for _, r := range v.Resources {
  86. resource, err := convertAPIResource(r)
  87. if err == nil {
  88. resourceList.APIResources = append(resourceList.APIResources, resource)
  89. }
  90. // Subresources field in new format get transformed into full APIResources.
  91. // It is possible a partial result with an error was returned to be used
  92. // as the parent resource for the subresource.
  93. for _, subresource := range r.Subresources {
  94. sr, err := convertAPISubresource(resource, subresource)
  95. if err == nil {
  96. resourceList.APIResources = append(resourceList.APIResources, sr)
  97. }
  98. }
  99. }
  100. gvResources[gv] = resourceList
  101. }
  102. return group, gvResources, failedGVs
  103. }
  104. var emptyKind = metav1.GroupVersionKind{}
  105. // convertAPIResource tranforms a APIResourceDiscovery to an APIResource. We are
  106. // resilient to missing GVK, since this resource might be the parent resource
  107. // for a subresource. If the parent is missing a GVK, it is not returned in
  108. // discovery, and the subresource MUST have the GVK.
  109. func convertAPIResource(in apidiscovery.APIResourceDiscovery) (metav1.APIResource, error) {
  110. result := metav1.APIResource{
  111. Name: in.Resource,
  112. SingularName: in.SingularResource,
  113. Namespaced: in.Scope == apidiscovery.ScopeNamespace,
  114. Verbs: in.Verbs,
  115. ShortNames: in.ShortNames,
  116. Categories: in.Categories,
  117. }
  118. var err error
  119. if in.ResponseKind != nil && (*in.ResponseKind) != emptyKind {
  120. result.Group = in.ResponseKind.Group
  121. result.Version = in.ResponseKind.Version
  122. result.Kind = in.ResponseKind.Kind
  123. } else {
  124. err = fmt.Errorf("discovery resource %s missing GVK", in.Resource)
  125. }
  126. // Can return partial result with error, which can be the parent for a
  127. // subresource. Do not add this result to the returned discovery resources.
  128. return result, err
  129. }
  130. // convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource.
  131. func convertAPISubresource(parent metav1.APIResource, in apidiscovery.APISubresourceDiscovery) (metav1.APIResource, error) {
  132. result := metav1.APIResource{}
  133. if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
  134. return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource)
  135. }
  136. result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource)
  137. result.SingularName = parent.SingularName
  138. result.Namespaced = parent.Namespaced
  139. result.Group = in.ResponseKind.Group
  140. result.Version = in.ResponseKind.Version
  141. result.Kind = in.ResponseKind.Kind
  142. result.Verbs = in.Verbs
  143. return result, nil
  144. }
  145. // Please note the functions below will be removed in v1.35. They facilitate conversion
  146. // between the deprecated type apidiscoveryv2beta1.APIGroupDiscoveryList.
  147. // SplitGroupsAndResourcesV2Beta1 transforms "aggregated" discovery top-level structure into
  148. // the previous "unaggregated" discovery groups and resources.
  149. // Deprecated: Please use SplitGroupsAndResources
  150. func SplitGroupsAndResourcesV2Beta1(aggregatedGroups apidiscoveryv2beta1.APIGroupDiscoveryList) (
  151. *metav1.APIGroupList,
  152. map[schema.GroupVersion]*metav1.APIResourceList,
  153. map[schema.GroupVersion]error) {
  154. // Aggregated group list will contain the entirety of discovery, including
  155. // groups, versions, and resources. GroupVersions marked "stale" are failed.
  156. groups := []*metav1.APIGroup{}
  157. failedGVs := map[schema.GroupVersion]error{}
  158. resourcesByGV := map[schema.GroupVersion]*metav1.APIResourceList{}
  159. for _, aggGroup := range aggregatedGroups.Items {
  160. group, resources, failed := convertAPIGroupv2beta1(aggGroup)
  161. groups = append(groups, group)
  162. for gv, resourceList := range resources {
  163. resourcesByGV[gv] = resourceList
  164. }
  165. for gv, err := range failed {
  166. failedGVs[gv] = err
  167. }
  168. }
  169. // Transform slice of groups to group list before returning.
  170. groupList := &metav1.APIGroupList{}
  171. groupList.Groups = make([]metav1.APIGroup, 0, len(groups))
  172. for _, group := range groups {
  173. groupList.Groups = append(groupList.Groups, *group)
  174. }
  175. return groupList, resourcesByGV, failedGVs
  176. }
  177. // convertAPIGroupv2beta1 tranforms an "aggregated" APIGroupDiscovery to an "legacy" APIGroup,
  178. // also returning the map of APIResourceList for resources within GroupVersions.
  179. func convertAPIGroupv2beta1(g apidiscoveryv2beta1.APIGroupDiscovery) (
  180. *metav1.APIGroup,
  181. map[schema.GroupVersion]*metav1.APIResourceList,
  182. map[schema.GroupVersion]error) {
  183. // Iterate through versions to convert to group and resources.
  184. group := &metav1.APIGroup{}
  185. gvResources := map[schema.GroupVersion]*metav1.APIResourceList{}
  186. failedGVs := map[schema.GroupVersion]error{}
  187. group.Name = g.ObjectMeta.Name
  188. for _, v := range g.Versions {
  189. gv := schema.GroupVersion{Group: g.Name, Version: v.Version}
  190. if v.Freshness == apidiscoveryv2beta1.DiscoveryFreshnessStale {
  191. failedGVs[gv] = StaleGroupVersionError{gv: gv}
  192. continue
  193. }
  194. version := metav1.GroupVersionForDiscovery{}
  195. version.GroupVersion = gv.String()
  196. version.Version = v.Version
  197. group.Versions = append(group.Versions, version)
  198. // PreferredVersion is first non-stale Version
  199. if group.PreferredVersion == (metav1.GroupVersionForDiscovery{}) {
  200. group.PreferredVersion = version
  201. }
  202. resourceList := &metav1.APIResourceList{}
  203. resourceList.GroupVersion = gv.String()
  204. for _, r := range v.Resources {
  205. resource, err := convertAPIResourcev2beta1(r)
  206. if err == nil {
  207. resourceList.APIResources = append(resourceList.APIResources, resource)
  208. }
  209. // Subresources field in new format get transformed into full APIResources.
  210. // It is possible a partial result with an error was returned to be used
  211. // as the parent resource for the subresource.
  212. for _, subresource := range r.Subresources {
  213. sr, err := convertAPISubresourcev2beta1(resource, subresource)
  214. if err == nil {
  215. resourceList.APIResources = append(resourceList.APIResources, sr)
  216. }
  217. }
  218. }
  219. gvResources[gv] = resourceList
  220. }
  221. return group, gvResources, failedGVs
  222. }
  223. // convertAPIResource tranforms a APIResourceDiscovery to an APIResource. We are
  224. // resilient to missing GVK, since this resource might be the parent resource
  225. // for a subresource. If the parent is missing a GVK, it is not returned in
  226. // discovery, and the subresource MUST have the GVK.
  227. func convertAPIResourcev2beta1(in apidiscoveryv2beta1.APIResourceDiscovery) (metav1.APIResource, error) {
  228. result := metav1.APIResource{
  229. Name: in.Resource,
  230. SingularName: in.SingularResource,
  231. Namespaced: in.Scope == apidiscoveryv2beta1.ScopeNamespace,
  232. Verbs: in.Verbs,
  233. ShortNames: in.ShortNames,
  234. Categories: in.Categories,
  235. }
  236. // Can return partial result with error, which can be the parent for a
  237. // subresource. Do not add this result to the returned discovery resources.
  238. if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
  239. return result, fmt.Errorf("discovery resource %s missing GVK", in.Resource)
  240. }
  241. result.Group = in.ResponseKind.Group
  242. result.Version = in.ResponseKind.Version
  243. result.Kind = in.ResponseKind.Kind
  244. return result, nil
  245. }
  246. // convertAPISubresource tranforms a APISubresourceDiscovery to an APIResource.
  247. func convertAPISubresourcev2beta1(parent metav1.APIResource, in apidiscoveryv2beta1.APISubresourceDiscovery) (metav1.APIResource, error) {
  248. result := metav1.APIResource{}
  249. if in.ResponseKind == nil || (*in.ResponseKind) == emptyKind {
  250. return result, fmt.Errorf("subresource %s/%s missing GVK", parent.Name, in.Subresource)
  251. }
  252. result.Name = fmt.Sprintf("%s/%s", parent.Name, in.Subresource)
  253. result.SingularName = parent.SingularName
  254. result.Namespaced = parent.Namespaced
  255. result.Group = in.ResponseKind.Group
  256. result.Version = in.ResponseKind.Version
  257. result.Kind = in.ResponseKind.Kind
  258. result.Verbs = in.Verbs
  259. return result, nil
  260. }