2019-01-12 04:58:27 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package get
import (
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"github.com/spf13/cobra"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
kapierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions"
2019-04-07 17:07:55 +00:00
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
2019-01-12 04:58:27 +00:00
"k8s.io/client-go/rest"
watchtools "k8s.io/client-go/tools/watch"
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
utilprinters "k8s.io/kubernetes/pkg/kubectl/util/printers"
"k8s.io/kubernetes/pkg/kubectl/util/templates"
"k8s.io/kubernetes/pkg/util/interrupt"
)
// GetOptions contains the input to the get command.
type GetOptions struct {
PrintFlags * PrintFlags
ToPrinter func ( * meta . RESTMapping , bool , bool ) ( printers . ResourcePrinterFunc , error )
IsHumanReadablePrinter bool
CmdParent string
resource . FilenameOptions
Raw string
Watch bool
WatchOnly bool
ChunkSize int64
LabelSelector string
FieldSelector string
AllNamespaces bool
Namespace string
ExplicitNamespace bool
ServerPrint bool
NoHeaders bool
Sort bool
IgnoreNotFound bool
Export bool
genericclioptions . IOStreams
}
var (
getLong = templates . LongDesc ( `
Display one or many resources
Prints a table of the most important information about the specified resources .
You can filter the list using a label selector and the -- selector flag . If the
desired resource type is namespaced you will only see results in your current
namespace unless you pass -- all - namespaces .
Uninitialized objects are not shown unless -- include - uninitialized is passed .
By specifying the output as ' template ' and providing a Go template as the value
of the -- template flag , you can filter the attributes of the fetched resources . ` )
getExample = templates . Examples ( i18n . T ( `
# List all pods in ps output format .
kubectl get pods
# List all pods in ps output format with more information ( such as node name ) .
kubectl get pods - o wide
# List a single replication controller with specified NAME in ps output format .
kubectl get replicationcontroller web
# List deployments in JSON output format , in the "v1" version of the "apps" API group :
kubectl get deployments . v1 . apps - o json
# List a single pod in JSON output format .
kubectl get - o json pod web - pod - 13 je7
# List a pod identified by type and name specified in "pod.yaml" in JSON output format .
kubectl get - f pod . yaml - o json
2019-04-07 17:07:55 +00:00
# List resources from a directory with kustomization . yaml - e . g . dir / kustomization . yaml .
kubectl get - k dir /
2019-01-12 04:58:27 +00:00
# Return only the phase value of the specified pod .
kubectl get - o template pod / web - pod - 13 je7 -- template = { { . status . phase } }
2019-04-07 17:07:55 +00:00
# List resource information in custom columns .
kubectl get pod test - pod - o custom - columns = CONTAINER : . spec . containers [ 0 ] . name , IMAGE : . spec . containers [ 0 ] . image
2019-01-12 04:58:27 +00:00
# List all replication controllers and services together in ps output format .
kubectl get rc , services
# List one or more resources by their type and names .
kubectl get rc / web service / frontend pods / web - pod - 13 je7 ` ) )
)
const (
useOpenAPIPrintColumnFlagLabel = "use-openapi-print-columns"
useServerPrintColumns = "server-print"
)
// NewGetOptions returns a GetOptions with default chunk size 500.
func NewGetOptions ( parent string , streams genericclioptions . IOStreams ) * GetOptions {
return & GetOptions {
PrintFlags : NewGetPrintFlags ( ) ,
CmdParent : parent ,
IOStreams : streams ,
ChunkSize : 500 ,
ServerPrint : true ,
}
}
// NewCmdGet creates a command object for the generic "get" action, which
// retrieves one or more resources from a server.
func NewCmdGet ( parent string , f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
o := NewGetOptions ( parent , streams )
cmd := & cobra . Command {
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]" ,
DisableFlagsInUseLine : true ,
Short : i18n . T ( "Display one or many resources" ) ,
2019-04-07 17:07:55 +00:00
Long : getLong + "\n\n" + cmdutil . SuggestAPIResources ( parent ) ,
2019-01-12 04:58:27 +00:00
Example : getExample ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( cmd ) )
cmdutil . CheckErr ( o . Run ( f , cmd , args ) )
} ,
SuggestFor : [ ] string { "list" , "ps" } ,
}
o . PrintFlags . AddFlags ( cmd )
cmd . Flags ( ) . StringVar ( & o . Raw , "raw" , o . Raw , "Raw URI to request from the server. Uses the transport specified by the kubeconfig file." )
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." )
cmd . Flags ( ) . BoolVar ( & o . WatchOnly , "watch-only" , o . WatchOnly , "Watch for changes to the requested object(s), without listing/getting first." )
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." )
cmd . Flags ( ) . BoolVar ( & o . IgnoreNotFound , "ignore-not-found" , o . IgnoreNotFound , "If the requested object does not exist the command will return exit code 0." )
cmd . Flags ( ) . StringVarP ( & o . LabelSelector , "selector" , "l" , o . LabelSelector , "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)" )
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." )
2019-04-07 17:07:55 +00:00
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." )
2019-01-12 04:58:27 +00:00
cmdutil . AddIncludeUninitializedFlag ( cmd )
addOpenAPIPrintColumnFlags ( cmd , o )
addServerPrintColumnFlags ( cmd , o )
cmd . Flags ( ) . BoolVar ( & o . Export , "export" , o . Export , "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information." )
2019-04-07 17:07:55 +00:00
cmd . Flags ( ) . MarkDeprecated ( "export" , "This flag is deprecated and will be removed in future." )
2019-01-12 04:58:27 +00:00
cmdutil . AddFilenameOptionFlags ( cmd , & o . FilenameOptions , "identifying the resource to get from a server." )
return cmd
}
// Complete takes the command arguments and factory and infers any remaining options.
func ( o * GetOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
if len ( o . Raw ) > 0 {
if len ( args ) > 0 {
return fmt . Errorf ( "arguments may not be passed when --raw is specified" )
}
return nil
}
var err error
o . Namespace , o . ExplicitNamespace , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return err
}
if o . AllNamespaces {
o . ExplicitNamespace = false
}
isSorting , err := cmd . Flags ( ) . GetString ( "sort-by" )
if err != nil {
return err
}
o . Sort = len ( isSorting ) > 0
o . NoHeaders = cmdutil . GetFlagBool ( cmd , "no-headers" )
// TODO (soltysh): currently we don't support custom columns
// with server side print. So in these cases force the old behavior.
outputOption := cmd . Flags ( ) . Lookup ( "output" ) . Value . String ( )
if outputOption == "custom-columns" {
o . ServerPrint = false
}
templateArg := ""
if o . PrintFlags . TemplateFlags != nil && o . PrintFlags . TemplateFlags . TemplateArgument != nil {
templateArg = * o . PrintFlags . TemplateFlags . TemplateArgument
}
// human readable printers have special conversion rules, so we determine if we're using one.
if ( len ( * o . PrintFlags . OutputFormat ) == 0 && len ( templateArg ) == 0 ) || * o . PrintFlags . OutputFormat == "wide" {
o . IsHumanReadablePrinter = true
}
o . ToPrinter = func ( mapping * meta . RESTMapping , withNamespace bool , withKind bool ) ( printers . ResourcePrinterFunc , error ) {
// make a new copy of current flags / opts before mutating
printFlags := o . PrintFlags . Copy ( )
if mapping != nil {
printFlags . SetKind ( mapping . GroupVersionKind . GroupKind ( ) )
}
if withNamespace {
printFlags . EnsureWithNamespace ( )
}
if withKind {
printFlags . EnsureWithKind ( )
}
printer , err := printFlags . ToPrinter ( )
if err != nil {
return nil , err
}
printer = maybeWrapSortingPrinter ( printer , isSorting )
return printer . PrintObj , nil
}
switch {
case o . Watch || o . WatchOnly :
default :
2019-04-07 17:07:55 +00:00
if len ( args ) == 0 && cmdutil . IsFilenameSliceEmpty ( o . Filenames , "" ) {
fmt . Fprintf ( o . ErrOut , "You must specify the type of resource to get. %s\n\n" , cmdutil . SuggestAPIResources ( o . CmdParent ) )
2019-01-12 04:58:27 +00:00
fullCmdName := cmd . Parent ( ) . CommandPath ( )
usageString := "Required resource not specified."
if len ( fullCmdName ) > 0 && cmdutil . IsSiblingCommandExists ( cmd , "explain" ) {
usageString = fmt . Sprintf ( "%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods)." , usageString , fullCmdName )
}
return cmdutil . UsageErrorf ( cmd , usageString )
}
}
return nil
}
// Validate checks the set of flags provided by the user.
func ( o * GetOptions ) Validate ( cmd * cobra . Command ) error {
if len ( o . Raw ) > 0 {
if o . Watch || o . WatchOnly || len ( o . LabelSelector ) > 0 || o . Export {
return fmt . Errorf ( "--raw may not be specified with other flags that filter the server request or alter the output" )
}
if len ( cmdutil . GetFlagString ( cmd , "output" ) ) > 0 {
return cmdutil . UsageErrorf ( cmd , "--raw and --output are mutually exclusive" )
}
if _ , err := url . ParseRequestURI ( o . Raw ) ; err != nil {
return cmdutil . UsageErrorf ( cmd , "--raw must be a valid URL path: %v" , err )
}
}
if cmdutil . GetFlagBool ( cmd , "show-labels" ) {
outputOption := cmd . Flags ( ) . Lookup ( "output" ) . Value . String ( )
if outputOption != "" && outputOption != "wide" {
return fmt . Errorf ( "--show-labels option cannot be used with %s printer" , outputOption )
}
}
return nil
}
2019-04-07 17:07:55 +00:00
// OriginalPositioner and NopPositioner is required for swap/sort operations of data in table format
2019-01-12 04:58:27 +00:00
type OriginalPositioner interface {
OriginalPosition ( int ) int
}
2019-04-07 17:07:55 +00:00
// NopPositioner and OriginalPositioner is required for swap/sort operations of data in table format
2019-01-12 04:58:27 +00:00
type NopPositioner struct { }
2019-04-07 17:07:55 +00:00
// OriginalPosition returns the original position from NopPositioner object
2019-01-12 04:58:27 +00:00
func ( t * NopPositioner ) OriginalPosition ( ix int ) int {
return ix
}
2019-04-07 17:07:55 +00:00
// RuntimeSorter holds the required objects to perform sorting of runtime objects
2019-01-12 04:58:27 +00:00
type RuntimeSorter struct {
field string
decoder runtime . Decoder
objects [ ] runtime . Object
positioner OriginalPositioner
}
2019-04-07 17:07:55 +00:00
// Sort performs the sorting of runtime objects
2019-01-12 04:58:27 +00:00
func ( r * RuntimeSorter ) Sort ( ) error {
// a list is only considered "sorted" if there are 0 or 1 items in it
// AND (if 1 item) the item is not a Table object
if len ( r . objects ) == 0 {
return nil
}
if len ( r . objects ) == 1 {
_ , isTable := r . objects [ 0 ] . ( * metav1beta1 . Table )
if ! isTable {
return nil
}
}
includesTable := false
includesRuntimeObjs := false
for _ , obj := range r . objects {
switch t := obj . ( type ) {
case * metav1beta1 . Table :
includesTable = true
if sorter , err := NewTableSorter ( t , r . field ) ; err != nil {
return err
} else if err := sorter . Sort ( ) ; err != nil {
return err
}
default :
includesRuntimeObjs = true
}
}
// we use a NopPositioner when dealing with Table objects
// because the objects themselves are not swapped, but rather
// the rows in each object are swapped / sorted.
r . positioner = & NopPositioner { }
if includesRuntimeObjs && includesTable {
return fmt . Errorf ( "sorting is not supported on mixed Table and non-Table object lists" )
}
if includesTable {
return nil
}
// if not dealing with a Table response from the server, assume
// all objects are runtime.Object as usual, and sort using old method.
var err error
if r . positioner , err = SortObjects ( r . decoder , r . objects , r . field ) ; err != nil {
return err
}
return nil
}
2019-04-07 17:07:55 +00:00
// OriginalPosition returns the original position of a runtime object
2019-01-12 04:58:27 +00:00
func ( r * RuntimeSorter ) OriginalPosition ( ix int ) int {
if r . positioner == nil {
return 0
}
return r . positioner . OriginalPosition ( ix )
}
2019-04-07 17:07:55 +00:00
// WithDecoder allows custom decoder to be set for testing
2019-01-12 04:58:27 +00:00
func ( r * RuntimeSorter ) WithDecoder ( decoder runtime . Decoder ) * RuntimeSorter {
r . decoder = decoder
return r
}
2019-04-07 17:07:55 +00:00
// NewRuntimeSorter returns a new instance of RuntimeSorter
2019-01-12 04:58:27 +00:00
func NewRuntimeSorter ( objects [ ] runtime . Object , sortBy string ) * RuntimeSorter {
parsedField , err := RelaxedJSONPathExpression ( sortBy )
if err != nil {
parsedField = sortBy
}
return & RuntimeSorter {
field : parsedField ,
decoder : legacyscheme . Codecs . UniversalDecoder ( ) ,
objects : objects ,
}
}
// Run performs the get operation.
// TODO: remove the need to pass these arguments, like other commands.
func ( o * GetOptions ) Run ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
if len ( o . Raw ) > 0 {
return o . raw ( f )
}
if o . Watch || o . WatchOnly {
return o . watch ( f , cmd , args )
}
chunkSize := o . ChunkSize
if o . Sort {
// TODO(juanvallejo): in the future, we could have the client use chunking
// to gather all results, then sort them all at the end to reduce server load.
chunkSize = 0
}
r := f . NewBuilder ( ) .
Unstructured ( ) .
NamespaceParam ( o . Namespace ) . DefaultNamespace ( ) . AllNamespaces ( o . AllNamespaces ) .
FilenameParam ( o . ExplicitNamespace , & o . FilenameOptions ) .
LabelSelectorParam ( o . LabelSelector ) .
FieldSelectorParam ( o . FieldSelector ) .
ExportParam ( o . Export ) .
RequestChunksOf ( chunkSize ) .
ResourceTypeOrNameArgs ( true , args ... ) .
ContinueOnError ( ) .
Latest ( ) .
Flatten ( ) .
TransformRequests ( func ( req * rest . Request ) {
// We need full objects if printing with openapi columns
if ! o . ServerPrint || ! o . IsHumanReadablePrinter {
return
}
group := metav1beta1 . GroupName
version := metav1beta1 . SchemeGroupVersion . Version
tableParam := fmt . Sprintf ( "application/json;as=Table;v=%s;g=%s, application/json" , version , group )
req . SetHeader ( "Accept" , tableParam )
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
if o . Sort {
req . Param ( "includeObject" , "Object" )
}
} ) .
Do ( )
if o . IgnoreNotFound {
r . IgnoreErrors ( kapierrors . IsNotFound )
}
if err := r . Err ( ) ; err != nil {
return err
}
if ! o . IsHumanReadablePrinter {
return o . printGeneric ( r )
}
allErrs := [ ] error { }
errs := sets . NewString ( )
infos , err := r . Infos ( )
if err != nil {
allErrs = append ( allErrs , err )
}
printWithKind := multipleGVKsRequested ( infos )
objs := make ( [ ] runtime . Object , len ( infos ) )
for ix := range infos {
if o . ServerPrint {
table , err := o . decodeIntoTable ( infos [ ix ] . Object )
if err == nil {
infos [ ix ] . Object = table
} else {
// if we are unable to decode server response into a v1beta1.Table,
// fallback to client-side printing with whatever info the server returned.
klog . V ( 2 ) . Infof ( "Unable to decode server response into a Table. Falling back to hardcoded types: %v" , err )
}
}
objs [ ix ] = infos [ ix ] . Object
}
sorting , err := cmd . Flags ( ) . GetString ( "sort-by" )
if err != nil {
return err
}
var positioner OriginalPositioner
if o . Sort {
sorter := NewRuntimeSorter ( objs , sorting )
if err := sorter . Sort ( ) ; err != nil {
return err
}
positioner = sorter
}
var printer printers . ResourcePrinter
var lastMapping * meta . RESTMapping
nonEmptyObjCount := 0
w := utilprinters . GetNewTabWriter ( o . Out )
for ix := range objs {
var mapping * meta . RESTMapping
var info * resource . Info
if positioner != nil {
info = infos [ positioner . OriginalPosition ( ix ) ]
mapping = info . Mapping
} else {
info = infos [ ix ]
mapping = info . Mapping
}
// if dealing with a table that has no rows, skip remaining steps
// and avoid printing an unnecessary newline
if table , isTable := info . Object . ( * metav1beta1 . Table ) ; isTable {
if len ( table . Rows ) == 0 {
continue
}
}
nonEmptyObjCount ++
printWithNamespace := o . AllNamespaces
if mapping != nil && mapping . Scope . Name ( ) == meta . RESTScopeNameRoot {
printWithNamespace = false
}
if shouldGetNewPrinterForMapping ( printer , lastMapping , mapping ) {
w . Flush ( )
2019-04-07 17:07:55 +00:00
w . SetRememberedWidths ( nil )
2019-01-12 04:58:27 +00:00
// TODO: this doesn't belong here
// add linebreak between resource groups (if there is more than one)
// skip linebreak above first resource group
if lastMapping != nil && ! o . NoHeaders {
fmt . Fprintln ( o . ErrOut )
}
printer , err = o . ToPrinter ( mapping , printWithNamespace , printWithKind )
if err != nil {
if ! errs . Has ( err . Error ( ) ) {
errs . Insert ( err . Error ( ) )
allErrs = append ( allErrs , err )
}
continue
}
lastMapping = mapping
}
internalObj , err := legacyscheme . Scheme . ConvertToVersion ( info . Object , info . Mapping . GroupVersionKind . GroupKind ( ) . WithVersion ( runtime . APIVersionInternal ) . GroupVersion ( ) )
if err != nil {
// if there's an error, try to print what you have (mirrors old behavior).
klog . V ( 1 ) . Info ( err )
printer . PrintObj ( info . Object , w )
} else {
printer . PrintObj ( internalObj , w )
}
}
w . Flush ( )
if nonEmptyObjCount == 0 && ! o . IgnoreNotFound && len ( allErrs ) == 0 {
fmt . Fprintln ( o . ErrOut , "No resources found." )
}
return utilerrors . NewAggregate ( allErrs )
}
// raw makes a simple HTTP request to the provided path on the server using the default
// credentials.
func ( o * GetOptions ) raw ( f cmdutil . Factory ) error {
restClient , err := f . RESTClient ( )
if err != nil {
return err
}
stream , err := restClient . Get ( ) . RequestURI ( o . Raw ) . Stream ( )
if err != nil {
return err
}
defer stream . Close ( )
_ , err = io . Copy ( o . Out , stream )
if err != nil && err != io . EOF {
return err
}
return nil
}
// watch starts a client-side watch of one or more resources.
// TODO: remove the need for arguments here.
func ( o * GetOptions ) watch ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
r := f . NewBuilder ( ) .
Unstructured ( ) .
NamespaceParam ( o . Namespace ) . DefaultNamespace ( ) . AllNamespaces ( o . AllNamespaces ) .
FilenameParam ( o . ExplicitNamespace , & o . FilenameOptions ) .
LabelSelectorParam ( o . LabelSelector ) .
FieldSelectorParam ( o . FieldSelector ) .
ExportParam ( o . Export ) .
RequestChunksOf ( o . ChunkSize ) .
ResourceTypeOrNameArgs ( true , args ... ) .
SingleResourceType ( ) .
Latest ( ) .
Do ( )
if err := r . Err ( ) ; err != nil {
return err
}
infos , err := r . Infos ( )
if err != nil {
return err
}
if multipleGVKsRequested ( infos ) {
return i18n . Errorf ( "watch is only supported on individual resources and resource collections - more than 1 resources were found" )
}
info := infos [ 0 ]
mapping := info . ResourceMapping ( )
printer , err := o . ToPrinter ( mapping , o . AllNamespaces , false )
if err != nil {
return err
}
obj , err := r . Object ( )
if err != nil {
return err
}
// watching from resourceVersion 0, starts the watch at ~now and
// will return an initial watch event. Starting form ~now, rather
// the rv of the object will insure that we start the watch from
// inside the watch window, which the rv of the object might not be.
rv := "0"
isList := meta . IsListType ( obj )
if isList {
// the resourceVersion of list objects is ~now but won't return
// an initial watch event
rv , err = meta . NewAccessor ( ) . ResourceVersion ( obj )
if err != nil {
return err
}
}
2019-04-07 17:07:55 +00:00
writer := utilprinters . GetNewTabWriter ( o . Out )
2019-01-12 04:58:27 +00:00
// print the current object
if ! o . WatchOnly {
var objsToPrint [ ] runtime . Object
if isList {
objsToPrint , _ = meta . ExtractList ( obj )
} else {
objsToPrint = append ( objsToPrint , obj )
}
for _ , objToPrint := range objsToPrint {
if o . IsHumanReadablePrinter {
// printing always takes the internal version, but the watch event uses externals
internalGV := mapping . GroupVersionKind . GroupKind ( ) . WithVersion ( runtime . APIVersionInternal ) . GroupVersion ( )
objToPrint = attemptToConvertToInternal ( objToPrint , legacyscheme . Scheme , internalGV )
}
if err := printer . PrintObj ( objToPrint , writer ) ; err != nil {
return fmt . Errorf ( "unable to output the provided object: %v" , err )
}
}
writer . Flush ( )
}
// print watched changes
w , err := r . Watch ( rv )
if err != nil {
return err
}
first := true
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
intr := interrupt . New ( nil , cancel )
intr . Run ( func ( ) error {
_ , err := watchtools . UntilWithoutRetry ( ctx , w , func ( e watch . Event ) ( bool , error ) {
if ! isList && first {
// drop the initial watch event in the single resource case
first = false
return false , nil
}
// printing always takes the internal version, but the watch event uses externals
// TODO fix printing to use server-side or be version agnostic
objToPrint := e . Object
if o . IsHumanReadablePrinter {
internalGV := mapping . GroupVersionKind . GroupKind ( ) . WithVersion ( runtime . APIVersionInternal ) . GroupVersion ( )
objToPrint = attemptToConvertToInternal ( e . Object , legacyscheme . Scheme , internalGV )
}
2019-04-07 17:07:55 +00:00
if err := printer . PrintObj ( objToPrint , writer ) ; err != nil {
2019-01-12 04:58:27 +00:00
return false , err
}
2019-04-07 17:07:55 +00:00
writer . Flush ( )
2019-01-12 04:58:27 +00:00
return false , nil
} )
return err
} )
return nil
}
// attemptToConvertToInternal tries to convert to an internal type, but returns the original if it can't
func attemptToConvertToInternal ( obj runtime . Object , converter runtime . ObjectConvertor , targetVersion schema . GroupVersion ) runtime . Object {
internalObject , err := converter . ConvertToVersion ( obj , targetVersion )
if err != nil {
klog . V ( 1 ) . Infof ( "Unable to convert %T to %v: %v" , obj , targetVersion , err )
return obj
}
return internalObject
}
func ( o * GetOptions ) decodeIntoTable ( obj runtime . Object ) ( runtime . Object , error ) {
if obj . GetObjectKind ( ) . GroupVersionKind ( ) . Kind != "Table" {
return nil , fmt . Errorf ( "attempt to decode non-Table object into a v1beta1.Table" )
}
unstr , ok := obj . ( * unstructured . Unstructured )
if ! ok {
return nil , fmt . Errorf ( "attempt to decode non-Unstructured object" )
}
table := & metav1beta1 . Table { }
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( unstr . Object , table ) ; err != nil {
return nil , err
}
for i := range table . Rows {
row := & table . Rows [ i ]
if row . Object . Raw == nil || row . Object . Object != nil {
continue
}
converted , err := runtime . Decode ( unstructured . UnstructuredJSONScheme , row . Object . Raw )
if err != nil {
return nil , err
}
row . Object . Object = converted
}
return table , nil
}
func ( o * GetOptions ) printGeneric ( r * resource . Result ) error {
// we flattened the data from the builder, so we have individual items, but now we'd like to either:
// 1. if there is more than one item, combine them all into a single list
// 2. if there is a single item and that item is a list, leave it as its specific list
// 3. if there is a single item and it is not a list, leave it as a single item
var errs [ ] error
singleItemImplied := false
infos , err := r . IntoSingleItemImplied ( & singleItemImplied ) . Infos ( )
if err != nil {
if singleItemImplied {
return err
}
errs = append ( errs , err )
}
if len ( infos ) == 0 && o . IgnoreNotFound {
return utilerrors . Reduce ( utilerrors . Flatten ( utilerrors . NewAggregate ( errs ) ) )
}
printer , err := o . ToPrinter ( nil , false , false )
if err != nil {
return err
}
var obj runtime . Object
if ! singleItemImplied || len ( infos ) > 1 {
// we have more than one item, so coerce all items into a list.
// we don't want an *unstructured.Unstructured list yet, as we
// may be dealing with non-unstructured objects. Compose all items
// into an corev1.List, and then decode using an unstructured scheme.
list := corev1 . List {
TypeMeta : metav1 . TypeMeta {
Kind : "List" ,
APIVersion : "v1" ,
} ,
ListMeta : metav1 . ListMeta { } ,
}
for _ , info := range infos {
list . Items = append ( list . Items , runtime . RawExtension { Object : info . Object } )
}
listData , err := json . Marshal ( list )
if err != nil {
return err
}
converted , err := runtime . Decode ( unstructured . UnstructuredJSONScheme , listData )
if err != nil {
return err
}
obj = converted
} else {
obj = infos [ 0 ] . Object
}
isList := meta . IsListType ( obj )
if isList {
items , err := meta . ExtractList ( obj )
if err != nil {
return err
}
// take the items and create a new list for display
list := & unstructured . UnstructuredList {
Object : map [ string ] interface { } {
"kind" : "List" ,
"apiVersion" : "v1" ,
"metadata" : map [ string ] interface { } { } ,
} ,
}
if listMeta , err := meta . ListAccessor ( obj ) ; err == nil {
list . Object [ "metadata" ] = map [ string ] interface { } {
"selfLink" : listMeta . GetSelfLink ( ) ,
"resourceVersion" : listMeta . GetResourceVersion ( ) ,
}
}
for _ , item := range items {
list . Items = append ( list . Items , * item . ( * unstructured . Unstructured ) )
}
if err := printer . PrintObj ( list , o . Out ) ; err != nil {
errs = append ( errs , err )
}
return utilerrors . Reduce ( utilerrors . Flatten ( utilerrors . NewAggregate ( errs ) ) )
}
if printErr := printer . PrintObj ( obj , o . Out ) ; printErr != nil {
errs = append ( errs , printErr )
}
return utilerrors . Reduce ( utilerrors . Flatten ( utilerrors . NewAggregate ( errs ) ) )
}
func addOpenAPIPrintColumnFlags ( cmd * cobra . Command , opt * GetOptions ) {
cmd . Flags ( ) . MarkDeprecated ( useOpenAPIPrintColumnFlagLabel , "deprecated in favor of server-side printing" )
}
func addServerPrintColumnFlags ( cmd * cobra . Command , opt * GetOptions ) {
cmd . Flags ( ) . BoolVar ( & opt . ServerPrint , useServerPrintColumns , opt . ServerPrint , "If true, have the server return the appropriate table output. Supports extension APIs and CRDs." )
}
func shouldGetNewPrinterForMapping ( printer printers . ResourcePrinter , lastMapping , mapping * meta . RESTMapping ) bool {
return printer == nil || lastMapping == nil || mapping == nil || mapping . Resource != lastMapping . Resource
}
func maybeWrapSortingPrinter ( printer printers . ResourcePrinter , sortBy string ) printers . ResourcePrinter {
if len ( sortBy ) != 0 {
return & SortingPrinter {
Delegate : printer ,
SortField : fmt . Sprintf ( "%s" , sortBy ) ,
}
}
return printer
}
func multipleGVKsRequested ( infos [ ] * resource . Info ) bool {
if len ( infos ) < 2 {
return false
}
gvk := infos [ 0 ] . Mapping . GroupVersionKind
for _ , info := range infos {
if info . Mapping . GroupVersionKind != gvk {
return true
}
}
return false
}