| /* |
| Copyright 2017 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 printers |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io" |
| "reflect" |
| |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/client-go/util/jsonpath" |
| ) |
| |
| // exists returns true if it would be possible to call the index function |
| // with these arguments. |
| // |
| // TODO: how to document this for users? |
| // |
| // index returns the result of indexing its first argument by the following |
| // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each |
| // indexed item must be a map, slice, or array. |
| func exists(item interface{}, indices ...interface{}) bool { |
| v := reflect.ValueOf(item) |
| for _, i := range indices { |
| index := reflect.ValueOf(i) |
| var isNil bool |
| if v, isNil = indirect(v); isNil { |
| return false |
| } |
| switch v.Kind() { |
| case reflect.Array, reflect.Slice, reflect.String: |
| var x int64 |
| switch index.Kind() { |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| x = index.Int() |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| x = int64(index.Uint()) |
| default: |
| return false |
| } |
| if x < 0 || x >= int64(v.Len()) { |
| return false |
| } |
| v = v.Index(int(x)) |
| case reflect.Map: |
| if !index.IsValid() { |
| index = reflect.Zero(v.Type().Key()) |
| } |
| if !index.Type().AssignableTo(v.Type().Key()) { |
| return false |
| } |
| if x := v.MapIndex(index); x.IsValid() { |
| v = x |
| } else { |
| v = reflect.Zero(v.Type().Elem()) |
| } |
| default: |
| return false |
| } |
| } |
| if _, isNil := indirect(v); isNil { |
| return false |
| } |
| return true |
| } |
| |
| // stolen from text/template |
| // indirect returns the item at the end of indirection, and a bool to indicate if it's nil. |
| // We indirect through pointers and empty interfaces (only) because |
| // non-empty interfaces have methods we might need. |
| func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { |
| for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { |
| if v.IsNil() { |
| return v, true |
| } |
| if v.Kind() == reflect.Interface && v.NumMethod() > 0 { |
| break |
| } |
| } |
| return v, false |
| } |
| |
| // JSONPathPrinter is an implementation of ResourcePrinter which formats data with jsonpath expression. |
| type JSONPathPrinter struct { |
| rawTemplate string |
| *jsonpath.JSONPath |
| } |
| |
| func NewJSONPathPrinter(tmpl string) (*JSONPathPrinter, error) { |
| j := jsonpath.New("out") |
| if err := j.Parse(tmpl); err != nil { |
| return nil, err |
| } |
| return &JSONPathPrinter{ |
| rawTemplate: tmpl, |
| JSONPath: j, |
| }, nil |
| } |
| |
| // PrintObj formats the obj with the JSONPath Template. |
| func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error { |
| // 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. |
| if InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) { |
| return fmt.Errorf(InternalObjectPrinterErr) |
| } |
| |
| var queryObj interface{} = obj |
| if unstructured, ok := obj.(runtime.Unstructured); ok { |
| queryObj = unstructured.UnstructuredContent() |
| } else { |
| data, err := json.Marshal(obj) |
| if err != nil { |
| return err |
| } |
| queryObj = map[string]interface{}{} |
| if err := json.Unmarshal(data, &queryObj); err != nil { |
| return err |
| } |
| } |
| |
| if err := j.JSONPath.Execute(w, queryObj); err != nil { |
| buf := bytes.NewBuffer(nil) |
| fmt.Fprintf(buf, "Error executing template: %v. Printing more information for debugging the template:\n", err) |
| fmt.Fprintf(buf, "\ttemplate was:\n\t\t%v\n", j.rawTemplate) |
| fmt.Fprintf(buf, "\tobject given to jsonpath engine was:\n\t\t%#v\n\n", queryObj) |
| return fmt.Errorf("error executing jsonpath %q: %v\n", j.rawTemplate, buf.String()) |
| } |
| return nil |
| } |