1
16
17 package apiresources
18
19 import (
20 "fmt"
21 "io"
22 "sort"
23 "strings"
24
25 "github.com/spf13/cobra"
26
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/util/errors"
30 "k8s.io/apimachinery/pkg/util/sets"
31 "k8s.io/cli-runtime/pkg/genericclioptions"
32 "k8s.io/cli-runtime/pkg/genericiooptions"
33 "k8s.io/cli-runtime/pkg/printers"
34 "k8s.io/client-go/discovery"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/util/i18n"
37 "k8s.io/kubectl/pkg/util/templates"
38 )
39
40 var (
41 apiresourcesExample = templates.Examples(`
42 # Print the supported API resources
43 kubectl api-resources
44
45 # Print the supported API resources with more information
46 kubectl api-resources -o wide
47
48 # Print the supported API resources sorted by a column
49 kubectl api-resources --sort-by=name
50
51 # Print the supported namespaced resources
52 kubectl api-resources --namespaced=true
53
54 # Print the supported non-namespaced resources
55 kubectl api-resources --namespaced=false
56
57 # Print the supported API resources with a specific APIGroup
58 kubectl api-resources --api-group=rbac.authorization.k8s.io`)
59 )
60
61
62
63 type APIResourceOptions struct {
64 Output string
65 SortBy string
66 APIGroup string
67 Namespaced bool
68 Verbs []string
69 NoHeaders bool
70 Cached bool
71 Categories []string
72
73 groupChanged bool
74 nsChanged bool
75
76 discoveryClient discovery.CachedDiscoveryInterface
77
78 genericiooptions.IOStreams
79 }
80
81
82 type groupResource struct {
83 APIGroup string
84 APIGroupVersion string
85 APIResource metav1.APIResource
86 }
87
88
89 func NewAPIResourceOptions(ioStreams genericiooptions.IOStreams) *APIResourceOptions {
90 return &APIResourceOptions{
91 IOStreams: ioStreams,
92 Namespaced: true,
93 }
94 }
95
96
97 func NewCmdAPIResources(restClientGetter genericclioptions.RESTClientGetter, ioStreams genericiooptions.IOStreams) *cobra.Command {
98 o := NewAPIResourceOptions(ioStreams)
99
100 cmd := &cobra.Command{
101 Use: "api-resources",
102 Short: i18n.T("Print the supported API resources on the server"),
103 Long: i18n.T("Print the supported API resources on the server."),
104 Example: apiresourcesExample,
105 Run: func(cmd *cobra.Command, args []string) {
106 cmdutil.CheckErr(o.Complete(restClientGetter, cmd, args))
107 cmdutil.CheckErr(o.Validate())
108 cmdutil.CheckErr(o.RunAPIResources())
109 },
110 }
111
112 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).")
113 cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, `Output format. One of: (wide, name).`)
114
115 cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
116 cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
117 cmd.Flags().StringSliceVar(&o.Verbs, "verbs", o.Verbs, "Limit to resources that support the specified verbs.")
118 cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort list of resources using specified field. The field can be either 'name' or 'kind'.")
119 cmd.Flags().BoolVar(&o.Cached, "cached", o.Cached, "Use the cached list of resources if available.")
120 cmd.Flags().StringSliceVar(&o.Categories, "categories", o.Categories, "Limit to resources that belong to the specified categories.")
121 return cmd
122 }
123
124
125 func (o *APIResourceOptions) Validate() error {
126 supportedOutputTypes := sets.NewString("", "wide", "name")
127 if !supportedOutputTypes.Has(o.Output) {
128 return fmt.Errorf("--output %v is not available", o.Output)
129 }
130 supportedSortTypes := sets.NewString("", "name", "kind")
131 if len(o.SortBy) > 0 {
132 if !supportedSortTypes.Has(o.SortBy) {
133 return fmt.Errorf("--sort-by accepts only name or kind")
134 }
135 }
136 return nil
137 }
138
139
140 func (o *APIResourceOptions) Complete(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, args []string) error {
141 if len(args) != 0 {
142 return cmdutil.UsageErrorf(cmd, "unexpected arguments: %v", args)
143 }
144
145 discoveryClient, err := restClientGetter.ToDiscoveryClient()
146 if err != nil {
147 return err
148 }
149 o.discoveryClient = discoveryClient
150
151 o.groupChanged = cmd.Flags().Changed("api-group")
152 o.nsChanged = cmd.Flags().Changed("namespaced")
153
154 return nil
155 }
156
157
158 func (o *APIResourceOptions) RunAPIResources() error {
159 w := printers.GetNewTabWriter(o.Out)
160 defer w.Flush()
161
162 if !o.Cached {
163
164 o.discoveryClient.Invalidate()
165 }
166
167 errs := []error{}
168 lists, err := o.discoveryClient.ServerPreferredResources()
169 if err != nil {
170 errs = append(errs, err)
171 }
172
173 resources := []groupResource{}
174
175 for _, list := range lists {
176 if len(list.APIResources) == 0 {
177 continue
178 }
179 gv, err := schema.ParseGroupVersion(list.GroupVersion)
180 if err != nil {
181 continue
182 }
183 for _, resource := range list.APIResources {
184 if len(resource.Verbs) == 0 {
185 continue
186 }
187
188 if o.groupChanged && o.APIGroup != gv.Group {
189 continue
190 }
191
192 if o.nsChanged && o.Namespaced != resource.Namespaced {
193 continue
194 }
195
196 if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
197 continue
198 }
199
200 if len(o.Categories) > 0 && !sets.NewString(resource.Categories...).HasAll(o.Categories...) {
201 continue
202 }
203 resources = append(resources, groupResource{
204 APIGroup: gv.Group,
205 APIGroupVersion: gv.String(),
206 APIResource: resource,
207 })
208 }
209 }
210
211 if o.NoHeaders == false && o.Output != "name" {
212 if err = printContextHeaders(w, o.Output); err != nil {
213 return err
214 }
215 }
216
217 sort.Stable(sortableResource{resources, o.SortBy})
218 for _, r := range resources {
219 switch o.Output {
220 case "name":
221 name := r.APIResource.Name
222 if len(r.APIGroup) > 0 {
223 name += "." + r.APIGroup
224 }
225 if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
226 errs = append(errs, err)
227 }
228 case "wide":
229 if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\t%v\n",
230 r.APIResource.Name,
231 strings.Join(r.APIResource.ShortNames, ","),
232 r.APIGroupVersion,
233 r.APIResource.Namespaced,
234 r.APIResource.Kind,
235 strings.Join(r.APIResource.Verbs, ","),
236 strings.Join(r.APIResource.Categories, ",")); err != nil {
237 errs = append(errs, err)
238 }
239 case "":
240 if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
241 r.APIResource.Name,
242 strings.Join(r.APIResource.ShortNames, ","),
243 r.APIGroupVersion,
244 r.APIResource.Namespaced,
245 r.APIResource.Kind); err != nil {
246 errs = append(errs, err)
247 }
248 }
249 }
250
251 if len(errs) > 0 {
252 return errors.NewAggregate(errs)
253 }
254 return nil
255 }
256
257 func printContextHeaders(out io.Writer, output string) error {
258 columnNames := []string{"NAME", "SHORTNAMES", "APIVERSION", "NAMESPACED", "KIND"}
259 if output == "wide" {
260 columnNames = append(columnNames, "VERBS", "CATEGORIES")
261 }
262 _, err := fmt.Fprintf(out, "%s\n", strings.Join(columnNames, "\t"))
263 return err
264 }
265
266 type sortableResource struct {
267 resources []groupResource
268 sortBy string
269 }
270
271 func (s sortableResource) Len() int { return len(s.resources) }
272 func (s sortableResource) Swap(i, j int) {
273 s.resources[i], s.resources[j] = s.resources[j], s.resources[i]
274 }
275 func (s sortableResource) Less(i, j int) bool {
276 ret := strings.Compare(s.compareValues(i, j))
277 if ret > 0 {
278 return false
279 } else if ret == 0 {
280 return strings.Compare(s.resources[i].APIResource.Name, s.resources[j].APIResource.Name) < 0
281 }
282 return true
283 }
284
285 func (s sortableResource) compareValues(i, j int) (string, string) {
286 switch s.sortBy {
287 case "name":
288 return s.resources[i].APIResource.Name, s.resources[j].APIResource.Name
289 case "kind":
290 return s.resources[i].APIResource.Kind, s.resources[j].APIResource.Kind
291 }
292 return s.resources[i].APIGroup, s.resources[j].APIGroup
293 }
294
View as plain text