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