get.go 27 KB

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