...

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

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

     1  /*
     2  Copyright 2014 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 get
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"net/url"
    25  	"strings"
    26  
    27  	"github.com/spf13/cobra"
    28  
    29  	corev1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/meta"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    37  	"k8s.io/apimachinery/pkg/util/sets"
    38  	"k8s.io/apimachinery/pkg/watch"
    39  	"k8s.io/cli-runtime/pkg/genericiooptions"
    40  	"k8s.io/cli-runtime/pkg/printers"
    41  	"k8s.io/cli-runtime/pkg/resource"
    42  	kubernetesscheme "k8s.io/client-go/kubernetes/scheme"
    43  	"k8s.io/client-go/rest"
    44  	watchtools "k8s.io/client-go/tools/watch"
    45  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    46  	"k8s.io/kubectl/pkg/rawhttp"
    47  	"k8s.io/kubectl/pkg/scheme"
    48  	"k8s.io/kubectl/pkg/util/i18n"
    49  	"k8s.io/kubectl/pkg/util/interrupt"
    50  	"k8s.io/kubectl/pkg/util/slice"
    51  	"k8s.io/kubectl/pkg/util/templates"
    52  	utilpointer "k8s.io/utils/pointer"
    53  )
    54  
    55  // GetOptions contains the input to the get command.
    56  type GetOptions struct {
    57  	PrintFlags             *PrintFlags
    58  	ToPrinter              func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error)
    59  	IsHumanReadablePrinter bool
    60  
    61  	CmdParent string
    62  
    63  	resource.FilenameOptions
    64  
    65  	Raw       string
    66  	Watch     bool
    67  	WatchOnly bool
    68  	ChunkSize int64
    69  
    70  	OutputWatchEvents bool
    71  
    72  	LabelSelector     string
    73  	FieldSelector     string
    74  	AllNamespaces     bool
    75  	Namespace         string
    76  	ExplicitNamespace bool
    77  	Subresource       string
    78  	SortBy            string
    79  
    80  	ServerPrint bool
    81  
    82  	NoHeaders      bool
    83  	IgnoreNotFound bool
    84  
    85  	genericiooptions.IOStreams
    86  }
    87  
    88  var (
    89  	getLong = templates.LongDesc(i18n.T(`
    90  		Display one or many resources.
    91  
    92  		Prints a table of the most important information about the specified resources.
    93  		You can filter the list using a label selector and the --selector flag. If the
    94  		desired resource type is namespaced you will only see results in your current
    95  		namespace unless you pass --all-namespaces.
    96  
    97  		By specifying the output as 'template' and providing a Go template as the value
    98  		of the --template flag, you can filter the attributes of the fetched resources.`))
    99  
   100  	getExample = templates.Examples(i18n.T(`
   101  		# List all pods in ps output format
   102  		kubectl get pods
   103  
   104  		# List all pods in ps output format with more information (such as node name)
   105  		kubectl get pods -o wide
   106  
   107  		# List a single replication controller with specified NAME in ps output format
   108  		kubectl get replicationcontroller web
   109  
   110  		# List deployments in JSON output format, in the "v1" version of the "apps" API group
   111  		kubectl get deployments.v1.apps -o json
   112  
   113  		# List a single pod in JSON output format
   114  		kubectl get -o json pod web-pod-13je7
   115  
   116  		# List a pod identified by type and name specified in "pod.yaml" in JSON output format
   117  		kubectl get -f pod.yaml -o json
   118  
   119  		# List resources from a directory with kustomization.yaml - e.g. dir/kustomization.yaml
   120  		kubectl get -k dir/
   121  
   122  		# Return only the phase value of the specified pod
   123  		kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}
   124  
   125  		# List resource information in custom columns
   126  		kubectl get pod test-pod -o custom-columns=CONTAINER:.spec.containers[0].name,IMAGE:.spec.containers[0].image
   127  
   128  		# List all replication controllers and services together in ps output format
   129  		kubectl get rc,services
   130  
   131  		# List one or more resources by their type and names
   132  		kubectl get rc/web service/frontend pods/web-pod-13je7
   133  
   134  		# List the 'status' subresource for a single pod
   135  		kubectl get pod web-pod-13je7 --subresource status`))
   136  )
   137  
   138  const (
   139  	useServerPrintColumns = "server-print"
   140  )
   141  
   142  var supportedSubresources = []string{"status", "scale"}
   143  
   144  // NewGetOptions returns a GetOptions with default chunk size 500.
   145  func NewGetOptions(parent string, streams genericiooptions.IOStreams) *GetOptions {
   146  	return &GetOptions{
   147  		PrintFlags: NewGetPrintFlags(),
   148  		CmdParent:  parent,
   149  
   150  		IOStreams:   streams,
   151  		ChunkSize:   cmdutil.DefaultChunkSize,
   152  		ServerPrint: true,
   153  	}
   154  }
   155  
   156  // NewCmdGet creates a command object for the generic "get" action, which
   157  // retrieves one or more resources from a server.
   158  func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   159  	o := NewGetOptions(parent, streams)
   160  
   161  	cmd := &cobra.Command{
   162  		Use:                   fmt.Sprintf("get [(-o|--output=)%s] (TYPE[.VERSION][.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags]", strings.Join(o.PrintFlags.AllowedFormats(), "|")),
   163  		DisableFlagsInUseLine: true,
   164  		Short:                 i18n.T("Display one or many resources"),
   165  		Long:                  getLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
   166  		Example:               getExample,
   167  		// ValidArgsFunction is set when this function is called so that we have access to the util package
   168  		Run: func(cmd *cobra.Command, args []string) {
   169  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   170  			cmdutil.CheckErr(o.Validate())
   171  			cmdutil.CheckErr(o.Run(f, args))
   172  		},
   173  		SuggestFor: []string{"list", "ps"},
   174  	}
   175  
   176  	o.PrintFlags.AddFlags(cmd)
   177  
   178  	cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to request from the server.  Uses the transport specified by the kubeconfig file.")
   179  	cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.")
   180  	cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.")
   181  	cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.")
   182  	cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
   183  	cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
   184  	cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
   185  	addServerPrintColumnFlags(cmd, o)
   186  	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.")
   187  	cmdutil.AddChunkSizeFlag(cmd, &o.ChunkSize)
   188  	cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector)
   189  	cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, gets the subresource of the requested object.", supportedSubresources...)
   190  	return cmd
   191  }
   192  
   193  // Complete takes the command arguments and factory and infers any remaining options.
   194  func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   195  	if len(o.Raw) > 0 {
   196  		if len(args) > 0 {
   197  			return fmt.Errorf("arguments may not be passed when --raw is specified")
   198  		}
   199  		return nil
   200  	}
   201  
   202  	var err error
   203  	o.Namespace, o.ExplicitNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   204  	if err != nil {
   205  		return err
   206  	}
   207  	if o.AllNamespaces {
   208  		o.ExplicitNamespace = false
   209  	}
   210  
   211  	if o.PrintFlags.HumanReadableFlags.SortBy != nil {
   212  		o.SortBy = *o.PrintFlags.HumanReadableFlags.SortBy
   213  	}
   214  
   215  	o.NoHeaders = cmdutil.GetFlagBool(cmd, "no-headers")
   216  
   217  	// TODO (soltysh): currently we don't support custom columns
   218  	// with server side print. So in these cases force the old behavior.
   219  	outputOption := cmd.Flags().Lookup("output").Value.String()
   220  	if strings.Contains(outputOption, "custom-columns") || outputOption == "yaml" || strings.Contains(outputOption, "json") {
   221  		o.ServerPrint = false
   222  	}
   223  
   224  	templateArg := ""
   225  	if o.PrintFlags.TemplateFlags != nil && o.PrintFlags.TemplateFlags.TemplateArgument != nil {
   226  		templateArg = *o.PrintFlags.TemplateFlags.TemplateArgument
   227  	}
   228  
   229  	// human readable printers have special conversion rules, so we determine if we're using one.
   230  	if (len(*o.PrintFlags.OutputFormat) == 0 && len(templateArg) == 0) || *o.PrintFlags.OutputFormat == "wide" {
   231  		o.IsHumanReadablePrinter = true
   232  	}
   233  
   234  	o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
   235  		// make a new copy of current flags / opts before mutating
   236  		printFlags := o.PrintFlags.Copy()
   237  
   238  		if mapping != nil {
   239  			printFlags.SetKind(mapping.GroupVersionKind.GroupKind())
   240  		}
   241  		if withNamespace {
   242  			printFlags.EnsureWithNamespace()
   243  		}
   244  		if withKind {
   245  			printFlags.EnsureWithKind()
   246  		}
   247  
   248  		printer, err := printFlags.ToPrinter()
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  		printer, err = printers.NewTypeSetter(scheme.Scheme).WrapToPrinter(printer, nil)
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  
   257  		if len(o.SortBy) > 0 {
   258  			printer = &SortingPrinter{Delegate: printer, SortField: o.SortBy}
   259  		}
   260  		if outputObjects != nil {
   261  			printer = &skipPrinter{delegate: printer, output: outputObjects}
   262  		}
   263  		if o.ServerPrint {
   264  			printer = &TablePrinter{Delegate: printer}
   265  		}
   266  		return printer.PrintObj, nil
   267  	}
   268  
   269  	switch {
   270  	case o.Watch || o.WatchOnly:
   271  		if len(o.SortBy) > 0 {
   272  			fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch or --watch-only requested, --sort-by will be ignored\n")
   273  		}
   274  	default:
   275  		if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
   276  			fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent))
   277  			fullCmdName := cmd.Parent().CommandPath()
   278  			usageString := "Required resource not specified."
   279  			if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") {
   280  				usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName)
   281  			}
   282  
   283  			return cmdutil.UsageErrorf(cmd, usageString)
   284  		}
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // Validate checks the set of flags provided by the user.
   291  func (o *GetOptions) Validate() error {
   292  	if len(o.Raw) > 0 {
   293  		if o.Watch || o.WatchOnly || len(o.LabelSelector) > 0 {
   294  			return fmt.Errorf("--raw may not be specified with other flags that filter the server request or alter the output")
   295  		}
   296  		if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 {
   297  			return fmt.Errorf("--raw and --output are mutually exclusive")
   298  		}
   299  		if _, err := url.ParseRequestURI(o.Raw); err != nil {
   300  			return fmt.Errorf("--raw must be a valid URL path: %v", err)
   301  		}
   302  	}
   303  	if o.PrintFlags.HumanReadableFlags.ShowLabels != nil && *o.PrintFlags.HumanReadableFlags.ShowLabels && o.PrintFlags.OutputFormat != nil {
   304  		outputOption := *o.PrintFlags.OutputFormat
   305  		if outputOption != "" && outputOption != "wide" {
   306  			return fmt.Errorf("--show-labels option cannot be used with %s printer", outputOption)
   307  		}
   308  	}
   309  	if o.OutputWatchEvents && !(o.Watch || o.WatchOnly) {
   310  		return fmt.Errorf("--output-watch-events option can only be used with --watch or --watch-only")
   311  	}
   312  	if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
   313  		return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
   314  	}
   315  	return nil
   316  }
   317  
   318  // OriginalPositioner and NopPositioner is required for swap/sort operations of data in table format
   319  type OriginalPositioner interface {
   320  	OriginalPosition(int) int
   321  }
   322  
   323  // NopPositioner and OriginalPositioner is required for swap/sort operations of data in table format
   324  type NopPositioner struct{}
   325  
   326  // OriginalPosition returns the original position from NopPositioner object
   327  func (t *NopPositioner) OriginalPosition(ix int) int {
   328  	return ix
   329  }
   330  
   331  // RuntimeSorter holds the required objects to perform sorting of runtime objects
   332  type RuntimeSorter struct {
   333  	field      string
   334  	decoder    runtime.Decoder
   335  	objects    []runtime.Object
   336  	positioner OriginalPositioner
   337  }
   338  
   339  // Sort performs the sorting of runtime objects
   340  func (r *RuntimeSorter) Sort() error {
   341  	// a list is only considered "sorted" if there are 0 or 1 items in it
   342  	// AND (if 1 item) the item is not a Table object
   343  	if len(r.objects) == 0 {
   344  		return nil
   345  	}
   346  	if len(r.objects) == 1 {
   347  		_, isTable := r.objects[0].(*metav1.Table)
   348  		if !isTable {
   349  			return nil
   350  		}
   351  	}
   352  
   353  	includesTable := false
   354  	includesRuntimeObjs := false
   355  
   356  	for _, obj := range r.objects {
   357  		switch t := obj.(type) {
   358  		case *metav1.Table:
   359  			includesTable = true
   360  
   361  			if sorter, err := NewTableSorter(t, r.field); err != nil {
   362  				return err
   363  			} else if err := sorter.Sort(); err != nil {
   364  				return err
   365  			}
   366  		default:
   367  			includesRuntimeObjs = true
   368  		}
   369  	}
   370  
   371  	// we use a NopPositioner when dealing with Table objects
   372  	// because the objects themselves are not swapped, but rather
   373  	// the rows in each object are swapped / sorted.
   374  	r.positioner = &NopPositioner{}
   375  
   376  	if includesRuntimeObjs && includesTable {
   377  		return fmt.Errorf("sorting is not supported on mixed Table and non-Table object lists")
   378  	}
   379  	if includesTable {
   380  		return nil
   381  	}
   382  
   383  	// if not dealing with a Table response from the server, assume
   384  	// all objects are runtime.Object as usual, and sort using old method.
   385  	var err error
   386  	if r.positioner, err = SortObjects(r.decoder, r.objects, r.field); err != nil {
   387  		return err
   388  	}
   389  	return nil
   390  }
   391  
   392  // OriginalPosition returns the original position of a runtime object
   393  func (r *RuntimeSorter) OriginalPosition(ix int) int {
   394  	if r.positioner == nil {
   395  		return 0
   396  	}
   397  	return r.positioner.OriginalPosition(ix)
   398  }
   399  
   400  // WithDecoder allows custom decoder to be set for testing
   401  func (r *RuntimeSorter) WithDecoder(decoder runtime.Decoder) *RuntimeSorter {
   402  	r.decoder = decoder
   403  	return r
   404  }
   405  
   406  // NewRuntimeSorter returns a new instance of RuntimeSorter
   407  func NewRuntimeSorter(objects []runtime.Object, sortBy string) *RuntimeSorter {
   408  	parsedField, err := RelaxedJSONPathExpression(sortBy)
   409  	if err != nil {
   410  		parsedField = sortBy
   411  	}
   412  
   413  	return &RuntimeSorter{
   414  		field:   parsedField,
   415  		decoder: kubernetesscheme.Codecs.UniversalDecoder(),
   416  		objects: objects,
   417  	}
   418  }
   419  
   420  func (o *GetOptions) transformRequests(req *rest.Request) {
   421  	if !o.ServerPrint || !o.IsHumanReadablePrinter {
   422  		return
   423  	}
   424  
   425  	req.SetHeader("Accept", strings.Join([]string{
   426  		fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
   427  		fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName),
   428  		"application/json",
   429  	}, ","))
   430  
   431  	// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
   432  	if len(o.SortBy) > 0 {
   433  		req.Param("includeObject", "Object")
   434  	}
   435  }
   436  
   437  // Run performs the get operation.
   438  // TODO: remove the need to pass these arguments, like other commands.
   439  func (o *GetOptions) Run(f cmdutil.Factory, args []string) error {
   440  	if len(o.Raw) > 0 {
   441  		restClient, err := f.RESTClient()
   442  		if err != nil {
   443  			return err
   444  		}
   445  		return rawhttp.RawGet(restClient, o.IOStreams, o.Raw)
   446  	}
   447  	if o.Watch || o.WatchOnly {
   448  		return o.watch(f, args)
   449  	}
   450  
   451  	chunkSize := o.ChunkSize
   452  	if len(o.SortBy) > 0 {
   453  		// TODO(juanvallejo): in the future, we could have the client use chunking
   454  		// to gather all results, then sort them all at the end to reduce server load.
   455  		chunkSize = 0
   456  	}
   457  
   458  	r := f.NewBuilder().
   459  		Unstructured().
   460  		NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
   461  		FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
   462  		LabelSelectorParam(o.LabelSelector).
   463  		FieldSelectorParam(o.FieldSelector).
   464  		Subresource(o.Subresource).
   465  		RequestChunksOf(chunkSize).
   466  		ResourceTypeOrNameArgs(true, args...).
   467  		ContinueOnError().
   468  		Latest().
   469  		Flatten().
   470  		TransformRequests(o.transformRequests).
   471  		Do()
   472  
   473  	if o.IgnoreNotFound {
   474  		r.IgnoreErrors(apierrors.IsNotFound)
   475  	}
   476  	if err := r.Err(); err != nil {
   477  		return err
   478  	}
   479  
   480  	if !o.IsHumanReadablePrinter {
   481  		return o.printGeneric(r)
   482  	}
   483  
   484  	allErrs := []error{}
   485  	errs := sets.NewString()
   486  	infos, err := r.Infos()
   487  	if err != nil {
   488  		allErrs = append(allErrs, err)
   489  	}
   490  	printWithKind := multipleGVKsRequested(infos)
   491  
   492  	objs := make([]runtime.Object, len(infos))
   493  	for ix := range infos {
   494  		objs[ix] = infos[ix].Object
   495  	}
   496  
   497  	var positioner OriginalPositioner
   498  	if len(o.SortBy) > 0 {
   499  		sorter := NewRuntimeSorter(objs, o.SortBy)
   500  		if err := sorter.Sort(); err != nil {
   501  			return err
   502  		}
   503  		positioner = sorter
   504  	}
   505  
   506  	var printer printers.ResourcePrinter
   507  	var lastMapping *meta.RESTMapping
   508  
   509  	// track if we write any output
   510  	trackingWriter := &trackingWriterWrapper{Delegate: o.Out}
   511  	// output an empty line separating output
   512  	separatorWriter := &separatorWriterWrapper{Delegate: trackingWriter}
   513  
   514  	w := printers.GetNewTabWriter(separatorWriter)
   515  	allResourcesNamespaced := !o.AllNamespaces
   516  	for ix := range objs {
   517  		var mapping *meta.RESTMapping
   518  		var info *resource.Info
   519  		if positioner != nil {
   520  			info = infos[positioner.OriginalPosition(ix)]
   521  			mapping = info.Mapping
   522  		} else {
   523  			info = infos[ix]
   524  			mapping = info.Mapping
   525  		}
   526  
   527  		allResourcesNamespaced = allResourcesNamespaced && info.Namespaced()
   528  		printWithNamespace := o.AllNamespaces
   529  
   530  		if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot {
   531  			printWithNamespace = false
   532  		}
   533  
   534  		if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) {
   535  			w.Flush()
   536  			w.SetRememberedWidths(nil)
   537  
   538  			// add linebreaks between resource groups (if there is more than one)
   539  			// when it satisfies all following 3 conditions:
   540  			// 1) it's not the first resource group
   541  			// 2) it has row header
   542  			// 3) we've written output since the last time we started a new set of headers
   543  			if lastMapping != nil && !o.NoHeaders && trackingWriter.Written > 0 {
   544  				separatorWriter.SetReady(true)
   545  			}
   546  
   547  			printer, err = o.ToPrinter(mapping, nil, printWithNamespace, printWithKind)
   548  			if err != nil {
   549  				if !errs.Has(err.Error()) {
   550  					errs.Insert(err.Error())
   551  					allErrs = append(allErrs, err)
   552  				}
   553  				continue
   554  			}
   555  
   556  			lastMapping = mapping
   557  		}
   558  
   559  		printer.PrintObj(info.Object, w)
   560  	}
   561  	w.Flush()
   562  	if trackingWriter.Written == 0 && !o.IgnoreNotFound && len(allErrs) == 0 {
   563  		// if we wrote no output, and had no errors, and are not ignoring NotFound, be sure we output something
   564  		if allResourcesNamespaced {
   565  			fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
   566  		} else {
   567  			fmt.Fprintln(o.ErrOut, "No resources found")
   568  		}
   569  	}
   570  	return utilerrors.NewAggregate(allErrs)
   571  }
   572  
   573  type trackingWriterWrapper struct {
   574  	Delegate io.Writer
   575  	Written  int
   576  }
   577  
   578  func (t *trackingWriterWrapper) Write(p []byte) (n int, err error) {
   579  	t.Written += len(p)
   580  	return t.Delegate.Write(p)
   581  }
   582  
   583  type separatorWriterWrapper struct {
   584  	Delegate io.Writer
   585  	Ready    bool
   586  }
   587  
   588  func (s *separatorWriterWrapper) Write(p []byte) (n int, err error) {
   589  	// If we're about to write non-empty bytes and `s` is ready,
   590  	// we prepend an empty line to `p` and reset `s.Read`.
   591  	if len(p) != 0 && s.Ready {
   592  		fmt.Fprintln(s.Delegate)
   593  		s.Ready = false
   594  	}
   595  	return s.Delegate.Write(p)
   596  }
   597  
   598  func (s *separatorWriterWrapper) SetReady(state bool) {
   599  	s.Ready = state
   600  }
   601  
   602  // watch starts a client-side watch of one or more resources.
   603  // TODO: remove the need for arguments here.
   604  func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
   605  	r := f.NewBuilder().
   606  		Unstructured().
   607  		NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
   608  		FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
   609  		LabelSelectorParam(o.LabelSelector).
   610  		FieldSelectorParam(o.FieldSelector).
   611  		RequestChunksOf(o.ChunkSize).
   612  		ResourceTypeOrNameArgs(true, args...).
   613  		SingleResourceType().
   614  		Latest().
   615  		TransformRequests(o.transformRequests).
   616  		Do()
   617  	if err := r.Err(); err != nil {
   618  		return err
   619  	}
   620  	infos, err := r.Infos()
   621  	if err != nil {
   622  		return err
   623  	}
   624  	if multipleGVKsRequested(infos) {
   625  		return i18n.Errorf("watch is only supported on individual resources and resource collections - more than 1 resource was found")
   626  	}
   627  
   628  	info := infos[0]
   629  	mapping := info.ResourceMapping()
   630  	outputObjects := utilpointer.BoolPtr(!o.WatchOnly)
   631  	printer, err := o.ToPrinter(mapping, outputObjects, o.AllNamespaces, false)
   632  	if err != nil {
   633  		return err
   634  	}
   635  	obj, err := r.Object()
   636  	if err != nil {
   637  		return err
   638  	}
   639  
   640  	// watching from resourceVersion 0, starts the watch at ~now and
   641  	// will return an initial watch event.  Starting form ~now, rather
   642  	// the rv of the object will insure that we start the watch from
   643  	// inside the watch window, which the rv of the object might not be.
   644  	rv := "0"
   645  	isList := meta.IsListType(obj)
   646  	if isList {
   647  		// the resourceVersion of list objects is ~now but won't return
   648  		// an initial watch event
   649  		rv, err = meta.NewAccessor().ResourceVersion(obj)
   650  		if err != nil {
   651  			return err
   652  		}
   653  	}
   654  
   655  	writer := printers.GetNewTabWriter(o.Out)
   656  
   657  	// print the current object
   658  	var objsToPrint []runtime.Object
   659  	if isList {
   660  		objsToPrint, _ = meta.ExtractList(obj)
   661  	} else {
   662  		objsToPrint = append(objsToPrint, obj)
   663  	}
   664  	for _, objToPrint := range objsToPrint {
   665  		if o.OutputWatchEvents {
   666  			objToPrint = &metav1.WatchEvent{Type: string(watch.Added), Object: runtime.RawExtension{Object: objToPrint}}
   667  		}
   668  		if err := printer.PrintObj(objToPrint, writer); err != nil {
   669  			return fmt.Errorf("unable to output the provided object: %v", err)
   670  		}
   671  	}
   672  	writer.Flush()
   673  	if isList {
   674  		// we can start outputting objects now, watches started from lists don't emit synthetic added events
   675  		*outputObjects = true
   676  	} else {
   677  		// suppress output, since watches started for individual items emit a synthetic ADDED event first
   678  		*outputObjects = false
   679  	}
   680  
   681  	// print watched changes
   682  	w, err := r.Watch(rv)
   683  	if err != nil {
   684  		return err
   685  	}
   686  
   687  	ctx, cancel := context.WithCancel(context.Background())
   688  	defer cancel()
   689  	intr := interrupt.New(nil, cancel)
   690  	intr.Run(func() error {
   691  		_, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
   692  			objToPrint := e.Object
   693  			if o.OutputWatchEvents {
   694  				objToPrint = &metav1.WatchEvent{Type: string(e.Type), Object: runtime.RawExtension{Object: objToPrint}}
   695  			}
   696  			if err := printer.PrintObj(objToPrint, writer); err != nil {
   697  				return false, err
   698  			}
   699  			writer.Flush()
   700  			// after processing at least one event, start outputting objects
   701  			*outputObjects = true
   702  			return false, nil
   703  		})
   704  		return err
   705  	})
   706  	return nil
   707  }
   708  
   709  func (o *GetOptions) printGeneric(r *resource.Result) error {
   710  	// we flattened the data from the builder, so we have individual items, but now we'd like to either:
   711  	// 1. if there is more than one item, combine them all into a single list
   712  	// 2. if there is a single item and that item is a list, leave it as its specific list
   713  	// 3. if there is a single item and it is not a list, leave it as a single item
   714  	var errs []error
   715  	singleItemImplied := false
   716  	infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
   717  	if err != nil {
   718  		if singleItemImplied {
   719  			return err
   720  		}
   721  		errs = append(errs, err)
   722  	}
   723  
   724  	if len(infos) == 0 && o.IgnoreNotFound {
   725  		return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
   726  	}
   727  
   728  	printer, err := o.ToPrinter(nil, nil, false, false)
   729  	if err != nil {
   730  		return err
   731  	}
   732  
   733  	var obj runtime.Object
   734  	if !singleItemImplied || len(infos) != 1 {
   735  		// we have zero or multple items, so coerce all items into a list.
   736  		// we don't want an *unstructured.Unstructured list yet, as we
   737  		// may be dealing with non-unstructured objects. Compose all items
   738  		// into an corev1.List, and then decode using an unstructured scheme.
   739  		list := corev1.List{
   740  			TypeMeta: metav1.TypeMeta{
   741  				Kind:       "List",
   742  				APIVersion: "v1",
   743  			},
   744  			ListMeta: metav1.ListMeta{},
   745  		}
   746  		for _, info := range infos {
   747  			list.Items = append(list.Items, runtime.RawExtension{Object: info.Object})
   748  		}
   749  
   750  		listData, err := json.Marshal(list)
   751  		if err != nil {
   752  			return err
   753  		}
   754  
   755  		converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, listData)
   756  		if err != nil {
   757  			return err
   758  		}
   759  
   760  		obj = converted
   761  	} else {
   762  		obj = infos[0].Object
   763  	}
   764  
   765  	isList := meta.IsListType(obj)
   766  	if isList {
   767  		items, err := meta.ExtractList(obj)
   768  		if err != nil {
   769  			return err
   770  		}
   771  
   772  		// take the items and create a new list for display
   773  		list := &unstructured.UnstructuredList{
   774  			Object: map[string]interface{}{
   775  				"kind":       "List",
   776  				"apiVersion": "v1",
   777  				"metadata":   map[string]interface{}{},
   778  			},
   779  		}
   780  		if listMeta, err := meta.ListAccessor(obj); err == nil {
   781  			list.Object["metadata"] = map[string]interface{}{
   782  				"resourceVersion": listMeta.GetResourceVersion(),
   783  			}
   784  		}
   785  
   786  		for _, item := range items {
   787  			list.Items = append(list.Items, *item.(*unstructured.Unstructured))
   788  		}
   789  		if err := printer.PrintObj(list, o.Out); err != nil {
   790  			errs = append(errs, err)
   791  		}
   792  		return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
   793  	}
   794  
   795  	if printErr := printer.PrintObj(obj, o.Out); printErr != nil {
   796  		errs = append(errs, printErr)
   797  	}
   798  
   799  	return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
   800  }
   801  
   802  func addServerPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
   803  	cmd.Flags().BoolVar(&opt.ServerPrint, useServerPrintColumns, opt.ServerPrint, "If true, have the server return the appropriate table output. Supports extension APIs and CRDs.")
   804  }
   805  
   806  func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool {
   807  	return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
   808  }
   809  
   810  func multipleGVKsRequested(infos []*resource.Info) bool {
   811  	if len(infos) < 2 {
   812  		return false
   813  	}
   814  	gvk := infos[0].Mapping.GroupVersionKind
   815  	for _, info := range infos {
   816  		if info.Mapping.GroupVersionKind != gvk {
   817  			return true
   818  		}
   819  	}
   820  	return false
   821  }
   822  

View as plain text