...

Source file src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources.go

Documentation: k8s.io/kubectl/pkg/cmd/apiresources

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  // APIResourceOptions is the start of the data required to perform the operation.
    62  // As new fields are added, add them here instead of referencing the cmd.Flags()
    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  // groupResource contains the APIGroup and APIResource
    82  type groupResource struct {
    83  	APIGroup        string
    84  	APIGroupVersion string
    85  	APIResource     metav1.APIResource
    86  }
    87  
    88  // NewAPIResourceOptions creates the options for APIResource
    89  func NewAPIResourceOptions(ioStreams genericiooptions.IOStreams) *APIResourceOptions {
    90  	return &APIResourceOptions{
    91  		IOStreams:  ioStreams,
    92  		Namespaced: true,
    93  	}
    94  }
    95  
    96  // NewCmdAPIResources creates the `api-resources` command
    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  // Validate checks to the APIResourceOptions to see if there is sufficient information run the command
   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  // Complete adapts from the command line args and validates them
   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  // RunAPIResources does the work
   158  func (o *APIResourceOptions) RunAPIResources() error {
   159  	w := printers.GetNewTabWriter(o.Out)
   160  	defer w.Flush()
   161  
   162  	if !o.Cached {
   163  		// Always request fresh data from the server
   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  			// filter apiGroup
   188  			if o.groupChanged && o.APIGroup != gv.Group {
   189  				continue
   190  			}
   191  			// filter namespaced
   192  			if o.nsChanged && o.Namespaced != resource.Namespaced {
   193  				continue
   194  			}
   195  			// filter to resources that support the specified verbs
   196  			if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
   197  				continue
   198  			}
   199  			// filter to resources that belong to the specified categories
   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