2015-08-13 21:11:23 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-08-13 21:11:23 +00:00
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 .
* /
2018-11-01 22:45:38 +00:00
package get
2015-08-13 21:11:23 +00:00
import (
2015-09-03 04:06:48 +00:00
"bufio"
"bytes"
2015-08-13 21:11:23 +00:00
"fmt"
"io"
"reflect"
2015-09-09 01:28:58 +00:00
"regexp"
2015-08-13 21:11:23 +00:00
"strings"
2018-12-29 04:10:28 +00:00
"github.com/liggitt/tabwriter"
2015-08-13 21:11:23 +00:00
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
2019-03-06 14:54:25 +00:00
"k8s.io/cli-runtime/pkg/printers"
2017-01-23 18:37:22 +00:00
"k8s.io/client-go/util/jsonpath"
2018-11-01 22:45:38 +00:00
utilprinters "k8s.io/kubernetes/pkg/kubectl/util/printers"
2015-08-13 21:11:23 +00:00
)
2015-09-09 01:28:58 +00:00
var jsonRegexp = regexp . MustCompile ( "^\\{\\.?([^{}]+)\\}$|^\\.?([^{}]+)$" )
2017-02-19 22:37:24 +00:00
// RelaxedJSONPathExpression attempts to be flexible with JSONPath expressions, it accepts:
2018-06-22 02:54:41 +00:00
// * metadata.name (no leading '.' or curly braces '{...}'
2015-09-03 04:06:48 +00:00
// * {metadata.name} (no leading '.')
// * .metadata.name (no curly braces '{...}')
// * {.metadata.name} (complete expression)
2017-02-19 22:37:24 +00:00
// And transforms them all into a valid jsonpath expression:
2015-09-03 04:06:48 +00:00
// {.metadata.name}
2017-02-19 22:37:24 +00:00
func RelaxedJSONPathExpression ( pathExpression string ) ( string , error ) {
2015-09-03 04:06:48 +00:00
if len ( pathExpression ) == 0 {
2015-09-09 01:28:58 +00:00
return pathExpression , nil
2015-09-03 04:06:48 +00:00
}
2015-09-09 01:28:58 +00:00
submatches := jsonRegexp . FindStringSubmatch ( pathExpression )
if submatches == nil {
return "" , fmt . Errorf ( "unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'" )
}
if len ( submatches ) != 3 {
return "" , fmt . Errorf ( "unexpected submatch list: %v" , submatches )
}
var fieldSpec string
if len ( submatches [ 1 ] ) != 0 {
fieldSpec = submatches [ 1 ]
2015-09-03 04:06:48 +00:00
} else {
2015-09-09 01:28:58 +00:00
fieldSpec = submatches [ 2 ]
2015-09-03 04:06:48 +00:00
}
2015-09-09 01:28:58 +00:00
return fmt . Sprintf ( "{.%s}" , fieldSpec ) , nil
2015-09-03 04:06:48 +00:00
}
// NewCustomColumnsPrinterFromSpec creates a custom columns printer from a comma separated list of <header>:<jsonpath-field-spec> pairs.
// e.g. NAME:metadata.name,API_VERSION:apiVersion creates a printer that prints:
//
// NAME API_VERSION
// foo bar
2016-06-01 12:50:29 +00:00
func NewCustomColumnsPrinterFromSpec ( spec string , decoder runtime . Decoder , noHeaders bool ) ( * CustomColumnsPrinter , error ) {
2015-09-10 20:07:13 +00:00
if len ( spec ) == 0 {
2015-09-03 04:06:48 +00:00
return nil , fmt . Errorf ( "custom-columns format specified but no custom columns given" )
}
2015-09-10 20:07:13 +00:00
parts := strings . Split ( spec , "," )
2015-09-03 04:06:48 +00:00
columns := make ( [ ] Column , len ( parts ) )
for ix := range parts {
2019-01-18 06:53:18 +00:00
colSpec := strings . SplitN ( parts [ ix ] , ":" , 2 )
2015-09-03 04:06:48 +00:00
if len ( colSpec ) != 2 {
return nil , fmt . Errorf ( "unexpected custom-columns spec: %s, expected <header>:<json-path-expr>" , parts [ ix ] )
}
2017-02-19 22:37:24 +00:00
spec , err := RelaxedJSONPathExpression ( colSpec [ 1 ] )
2015-09-09 01:28:58 +00:00
if err != nil {
return nil , err
}
columns [ ix ] = Column { Header : colSpec [ 0 ] , FieldSpec : spec }
2015-09-03 04:06:48 +00:00
}
2016-06-01 12:50:29 +00:00
return & CustomColumnsPrinter { Columns : columns , Decoder : decoder , NoHeaders : noHeaders } , nil
2015-09-03 04:06:48 +00:00
}
func splitOnWhitespace ( line string ) [ ] string {
lineScanner := bufio . NewScanner ( bytes . NewBufferString ( line ) )
lineScanner . Split ( bufio . ScanWords )
result := [ ] string { }
for lineScanner . Scan ( ) {
result = append ( result , lineScanner . Text ( ) )
}
return result
}
// NewCustomColumnsPrinterFromTemplate creates a custom columns printer from a template stream. The template is expected
// to consist of two lines, whitespace separated. The first line is the header line, the second line is the jsonpath field spec
2016-08-02 16:51:51 +00:00
// For example, the template below:
2015-09-03 04:06:48 +00:00
// NAME API_VERSION
// {metadata.name} {apiVersion}
2016-01-27 19:14:27 +00:00
func NewCustomColumnsPrinterFromTemplate ( templateReader io . Reader , decoder runtime . Decoder ) ( * CustomColumnsPrinter , error ) {
2015-09-03 04:06:48 +00:00
scanner := bufio . NewScanner ( templateReader )
if ! scanner . Scan ( ) {
2015-09-09 01:28:58 +00:00
return nil , fmt . Errorf ( "invalid template, missing header line. Expected format is one line of space separated headers, one line of space separated column specs." )
2015-09-03 04:06:48 +00:00
}
headers := splitOnWhitespace ( scanner . Text ( ) )
if ! scanner . Scan ( ) {
2015-09-09 01:28:58 +00:00
return nil , fmt . Errorf ( "invalid template, missing spec line. Expected format is one line of space separated headers, one line of space separated column specs." )
2015-09-03 04:06:48 +00:00
}
specs := splitOnWhitespace ( scanner . Text ( ) )
if len ( headers ) != len ( specs ) {
return nil , fmt . Errorf ( "number of headers (%d) and field specifications (%d) don't match" , len ( headers ) , len ( specs ) )
}
columns := make ( [ ] Column , len ( headers ) )
for ix := range headers {
2017-02-19 22:37:24 +00:00
spec , err := RelaxedJSONPathExpression ( specs [ ix ] )
2015-09-09 01:28:58 +00:00
if err != nil {
return nil , err
}
2015-09-03 04:06:48 +00:00
columns [ ix ] = Column {
Header : headers [ ix ] ,
2015-09-09 01:28:58 +00:00
FieldSpec : spec ,
2015-09-03 04:06:48 +00:00
}
}
2016-06-01 12:50:29 +00:00
return & CustomColumnsPrinter { Columns : columns , Decoder : decoder , NoHeaders : false } , nil
2015-09-03 04:06:48 +00:00
}
2015-08-13 21:11:23 +00:00
// Column represents a user specified column
type Column struct {
// The header to print above the column, general style is ALL_CAPS
Header string
// The pointer to the field in the object to print in JSONPath form
// e.g. {.ObjectMeta.Name}, see pkg/util/jsonpath for more details.
FieldSpec string
}
// CustomColumnPrinter is a printer that knows how to print arbitrary columns
// of data from templates specified in the `Columns` array
type CustomColumnsPrinter struct {
2016-06-01 12:50:29 +00:00
Columns [ ] Column
Decoder runtime . Decoder
NoHeaders bool
2017-05-19 21:24:39 +00:00
// lastType records type of resource printed last so that we don't repeat
// header while printing same type of resources.
lastType reflect . Type
2015-08-13 21:11:23 +00:00
}
func ( s * CustomColumnsPrinter ) PrintObj ( obj runtime . Object , out io . Writer ) error {
2018-05-02 15:27:45 +00:00
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
// we need an actual value in order to retrieve the package path for an object.
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
2018-05-02 19:15:47 +00:00
if printers . InternalObjectPreventer . IsForbidden ( reflect . Indirect ( reflect . ValueOf ( obj ) ) . Type ( ) . PkgPath ( ) ) {
return fmt . Errorf ( printers . InternalObjectPrinterErr )
2018-05-02 15:27:45 +00:00
}
2017-11-30 08:58:24 +00:00
if w , found := out . ( * tabwriter . Writer ) ; ! found {
2018-11-01 22:45:38 +00:00
w = utilprinters . GetNewTabWriter ( out )
2017-11-30 08:58:24 +00:00
out = w
defer w . Flush ( )
}
2016-06-01 12:50:29 +00:00
2017-05-19 21:24:39 +00:00
t := reflect . TypeOf ( obj )
if ! s . NoHeaders && t != s . lastType {
2016-06-01 12:50:29 +00:00
headers := make ( [ ] string , len ( s . Columns ) )
for ix := range s . Columns {
headers [ ix ] = s . Columns [ ix ] . Header
}
2017-11-30 08:58:24 +00:00
fmt . Fprintln ( out , strings . Join ( headers , "\t" ) )
2017-05-19 21:24:39 +00:00
s . lastType = t
2015-08-13 21:11:23 +00:00
}
parsers := make ( [ ] * jsonpath . JSONPath , len ( s . Columns ) )
for ix := range s . Columns {
2017-08-24 23:14:17 +00:00
parsers [ ix ] = jsonpath . New ( fmt . Sprintf ( "column%d" , ix ) ) . AllowMissingKeys ( true )
2015-08-13 21:11:23 +00:00
if err := parsers [ ix ] . Parse ( s . Columns [ ix ] . FieldSpec ) ; err != nil {
return err
}
}
2015-11-12 10:45:42 +00:00
if meta . IsListType ( obj ) {
objs , err := meta . ExtractList ( obj )
2015-08-13 21:11:23 +00:00
if err != nil {
return err
}
for ix := range objs {
2017-11-30 08:58:24 +00:00
if err := s . printOneObject ( objs [ ix ] , parsers , out ) ; err != nil {
2015-08-13 21:11:23 +00:00
return err
}
}
} else {
2017-11-30 08:58:24 +00:00
if err := s . printOneObject ( obj , parsers , out ) ; err != nil {
2015-08-13 21:11:23 +00:00
return err
}
}
2017-11-30 08:58:24 +00:00
return nil
2015-08-13 21:11:23 +00:00
}
func ( s * CustomColumnsPrinter ) printOneObject ( obj runtime . Object , parsers [ ] * jsonpath . JSONPath , out io . Writer ) error {
columns := make ( [ ] string , len ( parsers ) )
2015-10-07 23:00:00 +00:00
switch u := obj . ( type ) {
case * runtime . Unknown :
2016-03-16 08:03:33 +00:00
if len ( u . Raw ) > 0 {
2016-01-27 19:14:27 +00:00
var err error
2016-03-16 08:03:33 +00:00
if obj , err = runtime . Decode ( s . Decoder , u . Raw ) ; err != nil {
return fmt . Errorf ( "can't decode object for printing: %v (%s)" , err , u . Raw )
2016-01-27 19:14:27 +00:00
}
2015-10-07 23:00:00 +00:00
}
}
2016-11-02 16:29:01 +00:00
2015-08-13 21:11:23 +00:00
for ix := range parsers {
parser := parsers [ ix ]
2016-11-02 16:29:01 +00:00
var values [ ] [ ] reflect . Value
var err error
2016-12-04 04:30:51 +00:00
if unstructured , ok := obj . ( runtime . Unstructured ) ; ok {
values , err = parser . FindResults ( unstructured . UnstructuredContent ( ) )
2016-11-02 16:29:01 +00:00
} else {
values , err = parser . FindResults ( reflect . ValueOf ( obj ) . Elem ( ) . Interface ( ) )
}
2015-08-13 21:11:23 +00:00
if err != nil {
return err
}
2017-08-24 23:14:17 +00:00
valueStrings := [ ] string { }
2015-08-13 21:11:23 +00:00
if len ( values ) == 0 || len ( values [ 0 ] ) == 0 {
2017-08-24 23:14:17 +00:00
valueStrings = append ( valueStrings , "<none>" )
2015-08-13 21:11:23 +00:00
}
for arrIx := range values {
for valIx := range values [ arrIx ] {
valueStrings = append ( valueStrings , fmt . Sprintf ( "%v" , values [ arrIx ] [ valIx ] . Interface ( ) ) )
}
}
columns [ ix ] = strings . Join ( valueStrings , "," )
}
fmt . Fprintln ( out , strings . Join ( columns , "\t" ) )
return nil
}