1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package printers 18 19import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "io" 24 "reflect" 25 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/client-go/util/jsonpath" 28) 29 30// exists returns true if it would be possible to call the index function 31// with these arguments. 32// 33// TODO: how to document this for users? 34// 35// index returns the result of indexing its first argument by the following 36// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each 37// indexed item must be a map, slice, or array. 38func exists(item interface{}, indices ...interface{}) bool { 39 v := reflect.ValueOf(item) 40 for _, i := range indices { 41 index := reflect.ValueOf(i) 42 var isNil bool 43 if v, isNil = indirect(v); isNil { 44 return false 45 } 46 switch v.Kind() { 47 case reflect.Array, reflect.Slice, reflect.String: 48 var x int64 49 switch index.Kind() { 50 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 51 x = index.Int() 52 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 53 x = int64(index.Uint()) 54 default: 55 return false 56 } 57 if x < 0 || x >= int64(v.Len()) { 58 return false 59 } 60 v = v.Index(int(x)) 61 case reflect.Map: 62 if !index.IsValid() { 63 index = reflect.Zero(v.Type().Key()) 64 } 65 if !index.Type().AssignableTo(v.Type().Key()) { 66 return false 67 } 68 if x := v.MapIndex(index); x.IsValid() { 69 v = x 70 } else { 71 v = reflect.Zero(v.Type().Elem()) 72 } 73 default: 74 return false 75 } 76 } 77 if _, isNil := indirect(v); isNil { 78 return false 79 } 80 return true 81} 82 83// stolen from text/template 84// indirect returns the item at the end of indirection, and a bool to indicate if it's nil. 85// We indirect through pointers and empty interfaces (only) because 86// non-empty interfaces have methods we might need. 87func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { 88 for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { 89 if v.IsNil() { 90 return v, true 91 } 92 if v.Kind() == reflect.Interface && v.NumMethod() > 0 { 93 break 94 } 95 } 96 return v, false 97} 98 99// JSONPathPrinter is an implementation of ResourcePrinter which formats data with jsonpath expression. 100type JSONPathPrinter struct { 101 rawTemplate string 102 *jsonpath.JSONPath 103} 104 105func NewJSONPathPrinter(tmpl string) (*JSONPathPrinter, error) { 106 j := jsonpath.New("out") 107 if err := j.Parse(tmpl); err != nil { 108 return nil, err 109 } 110 return &JSONPathPrinter{ 111 rawTemplate: tmpl, 112 JSONPath: j, 113 }, nil 114} 115 116// PrintObj formats the obj with the JSONPath Template. 117func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error { 118 // we use reflect.Indirect here in order to obtain the actual value from a pointer. 119 // we need an actual value in order to retrieve the package path for an object. 120 // using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers. 121 if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { 122 return fmt.Errorf(InternalObjectPrinterErr) 123 } 124 125 var queryObj interface{} = obj 126 if unstructured, ok := obj.(runtime.Unstructured); ok { 127 queryObj = unstructured.UnstructuredContent() 128 } else { 129 data, err := json.Marshal(obj) 130 if err != nil { 131 return err 132 } 133 queryObj = map[string]interface{}{} 134 if err := json.Unmarshal(data, &queryObj); err != nil { 135 return err 136 } 137 } 138 139 if err := j.JSONPath.Execute(w, queryObj); err != nil { 140 buf := bytes.NewBuffer(nil) 141 fmt.Fprintf(buf, "Error executing template: %v. Printing more information for debugging the template:\n", err) 142 fmt.Fprintf(buf, "\ttemplate was:\n\t\t%v\n", j.rawTemplate) 143 fmt.Fprintf(buf, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n", queryObj) 144 return fmt.Errorf("error executing jsonpath %q: %v\n", j.rawTemplate, buf.String()) 145 } 146 return nil 147} 148