apiresources.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. Copyright 2018 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 apiresources
  14. import (
  15. "fmt"
  16. "io"
  17. "sort"
  18. "strings"
  19. "github.com/spf13/cobra"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime/schema"
  22. "k8s.io/apimachinery/pkg/util/errors"
  23. "k8s.io/apimachinery/pkg/util/sets"
  24. "k8s.io/cli-runtime/pkg/genericclioptions"
  25. cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
  26. "k8s.io/kubernetes/pkg/kubectl/util/printers"
  27. "k8s.io/kubernetes/pkg/kubectl/util/templates"
  28. )
  29. var (
  30. apiresourcesExample = templates.Examples(`
  31. # Print the supported API Resources
  32. kubectl api-resources
  33. # Print the supported API Resources with more information
  34. kubectl api-resources -o wide
  35. # Print the supported namespaced resources
  36. kubectl api-resources --namespaced=true
  37. # Print the supported non-namespaced resources
  38. kubectl api-resources --namespaced=false
  39. # Print the supported API Resources with specific APIGroup
  40. kubectl api-resources --api-group=extensions`)
  41. )
  42. // APIResourceOptions is the start of the data required to perform the operation.
  43. // As new fields are added, add them here instead of referencing the cmd.Flags()
  44. type APIResourceOptions struct {
  45. Output string
  46. APIGroup string
  47. Namespaced bool
  48. Verbs []string
  49. NoHeaders bool
  50. Cached bool
  51. genericclioptions.IOStreams
  52. }
  53. // groupResource contains the APIGroup and APIResource
  54. type groupResource struct {
  55. APIGroup string
  56. APIResource metav1.APIResource
  57. }
  58. // NewAPIResourceOptions creates the options for APIResource
  59. func NewAPIResourceOptions(ioStreams genericclioptions.IOStreams) *APIResourceOptions {
  60. return &APIResourceOptions{
  61. IOStreams: ioStreams,
  62. Namespaced: true,
  63. }
  64. }
  65. // NewCmdAPIResources creates the `api-resources` command
  66. func NewCmdAPIResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
  67. o := NewAPIResourceOptions(ioStreams)
  68. cmd := &cobra.Command{
  69. Use: "api-resources",
  70. Short: "Print the supported API resources on the server",
  71. Long: "Print the supported API resources on the server",
  72. Example: apiresourcesExample,
  73. Run: func(cmd *cobra.Command, args []string) {
  74. cmdutil.CheckErr(o.Complete(cmd, args))
  75. cmdutil.CheckErr(o.Validate())
  76. cmdutil.CheckErr(o.RunAPIResources(cmd, f))
  77. },
  78. }
  79. cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
  80. cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: wide|name.")
  81. cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
  82. cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
  83. cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.")
  84. cmd.Flags().BoolVar(&o.Cached, "cached", o.Cached, "Use the cached list of resources if available.")
  85. return cmd
  86. }
  87. // Validate checks to the APIResourceOptions to see if there is sufficient information run the command
  88. func (o *APIResourceOptions) Validate() error {
  89. supportedOutputTypes := sets.NewString("", "wide", "name")
  90. if !supportedOutputTypes.Has(o.Output) {
  91. return fmt.Errorf("--output %v is not available", o.Output)
  92. }
  93. return nil
  94. }
  95. // Complete adapts from the command line args and validates them
  96. func (o *APIResourceOptions) Complete(cmd *cobra.Command, args []string) error {
  97. if len(args) != 0 {
  98. return cmdutil.UsageErrorf(cmd, "unexpected arguments: %v", args)
  99. }
  100. return nil
  101. }
  102. // RunAPIResources does the work
  103. func (o *APIResourceOptions) RunAPIResources(cmd *cobra.Command, f cmdutil.Factory) error {
  104. w := printers.GetNewTabWriter(o.Out)
  105. defer w.Flush()
  106. discoveryclient, err := f.ToDiscoveryClient()
  107. if err != nil {
  108. return err
  109. }
  110. if !o.Cached {
  111. // Always request fresh data from the server
  112. discoveryclient.Invalidate()
  113. }
  114. errs := []error{}
  115. lists, err := discoveryclient.ServerPreferredResources()
  116. if err != nil {
  117. errs = append(errs, err)
  118. }
  119. resources := []groupResource{}
  120. groupChanged := cmd.Flags().Changed("api-group")
  121. nsChanged := cmd.Flags().Changed("namespaced")
  122. for _, list := range lists {
  123. if len(list.APIResources) == 0 {
  124. continue
  125. }
  126. gv, err := schema.ParseGroupVersion(list.GroupVersion)
  127. if err != nil {
  128. continue
  129. }
  130. for _, resource := range list.APIResources {
  131. if len(resource.Verbs) == 0 {
  132. continue
  133. }
  134. // filter apiGroup
  135. if groupChanged && o.APIGroup != gv.Group {
  136. continue
  137. }
  138. // filter namespaced
  139. if nsChanged && o.Namespaced != resource.Namespaced {
  140. continue
  141. }
  142. // filter to resources that support the specified verbs
  143. if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
  144. continue
  145. }
  146. resources = append(resources, groupResource{
  147. APIGroup: gv.Group,
  148. APIResource: resource,
  149. })
  150. }
  151. }
  152. if o.NoHeaders == false && o.Output != "name" {
  153. if err = printContextHeaders(w, o.Output); err != nil {
  154. return err
  155. }
  156. }
  157. sort.Stable(sortableGroupResource(resources))
  158. for _, r := range resources {
  159. switch o.Output {
  160. case "name":
  161. name := r.APIResource.Name
  162. if len(r.APIGroup) > 0 {
  163. name += "." + r.APIGroup
  164. }
  165. if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
  166. errs = append(errs, err)
  167. }
  168. case "wide":
  169. if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\n",
  170. r.APIResource.Name,
  171. strings.Join(r.APIResource.ShortNames, ","),
  172. r.APIGroup,
  173. r.APIResource.Namespaced,
  174. r.APIResource.Kind,
  175. r.APIResource.Verbs); err != nil {
  176. errs = append(errs, err)
  177. }
  178. case "":
  179. if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
  180. r.APIResource.Name,
  181. strings.Join(r.APIResource.ShortNames, ","),
  182. r.APIGroup,
  183. r.APIResource.Namespaced,
  184. r.APIResource.Kind); err != nil {
  185. errs = append(errs, err)
  186. }
  187. }
  188. }
  189. if len(errs) > 0 {
  190. return errors.NewAggregate(errs)
  191. }
  192. return nil
  193. }
  194. func printContextHeaders(out io.Writer, output string) error {
  195. columnNames := []string{"NAME", "SHORTNAMES", "APIGROUP", "NAMESPACED", "KIND"}
  196. if output == "wide" {
  197. columnNames = append(columnNames, "VERBS")
  198. }
  199. _, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
  200. return err
  201. }
  202. type sortableGroupResource []groupResource
  203. func (s sortableGroupResource) Len() int { return len(s) }
  204. func (s sortableGroupResource) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  205. func (s sortableGroupResource) Less(i, j int) bool {
  206. ret := strings.Compare(s[i].APIGroup, s[j].APIGroup)
  207. if ret > 0 {
  208. return false
  209. } else if ret == 0 {
  210. return strings.Compare(s[i].APIResource.Name, s[j].APIResource.Name) < 0
  211. }
  212. return true
  213. }