1
16
17 package get
18
19 import (
20 "bufio"
21 "bytes"
22 "fmt"
23 "io"
24 "reflect"
25 "regexp"
26 "strings"
27
28 "github.com/liggitt/tabwriter"
29
30 "k8s.io/apimachinery/pkg/api/meta"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33 "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/cli-runtime/pkg/printers"
35 "k8s.io/client-go/util/jsonpath"
36 )
37
38 var jsonRegexp = regexp.MustCompile(`^\{\.?([^{}]+)\}$|^\.?([^{}]+)$`)
39
40
41
42
43
44
45
46
47
48
49 func RelaxedJSONPathExpression(pathExpression string) (string, error) {
50 if len(pathExpression) == 0 {
51 return pathExpression, nil
52 }
53 submatches := jsonRegexp.FindStringSubmatch(pathExpression)
54 if submatches == nil {
55 return "", fmt.Errorf("unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'")
56 }
57 if len(submatches) != 3 {
58 return "", fmt.Errorf("unexpected submatch list: %v", submatches)
59 }
60 var fieldSpec string
61 if len(submatches[1]) != 0 {
62 fieldSpec = submatches[1]
63 } else {
64 fieldSpec = submatches[2]
65 }
66 return fmt.Sprintf("{.%s}", fieldSpec), nil
67 }
68
69
70
71
72
73
74 func NewCustomColumnsPrinterFromSpec(spec string, decoder runtime.Decoder, noHeaders bool) (*CustomColumnsPrinter, error) {
75 if len(spec) == 0 {
76 return nil, fmt.Errorf("custom-columns format specified but no custom columns given")
77 }
78 parts := strings.Split(spec, ",")
79 columns := make([]Column, len(parts))
80 for ix := range parts {
81 colSpec := strings.SplitN(parts[ix], ":", 2)
82 if len(colSpec) != 2 {
83 return nil, fmt.Errorf("unexpected custom-columns spec: %s, expected <header>:<json-path-expr>", parts[ix])
84 }
85 spec, err := RelaxedJSONPathExpression(colSpec[1])
86 if err != nil {
87 return nil, err
88 }
89 columns[ix] = Column{Header: colSpec[0], FieldSpec: spec}
90 }
91 return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: noHeaders}, nil
92 }
93
94 func splitOnWhitespace(line string) []string {
95 lineScanner := bufio.NewScanner(bytes.NewBufferString(line))
96 lineScanner.Split(bufio.ScanWords)
97 result := []string{}
98 for lineScanner.Scan() {
99 result = append(result, lineScanner.Text())
100 }
101 return result
102 }
103
104
105
106
107
108
109 func NewCustomColumnsPrinterFromTemplate(templateReader io.Reader, decoder runtime.Decoder) (*CustomColumnsPrinter, error) {
110 scanner := bufio.NewScanner(templateReader)
111 if !scanner.Scan() {
112 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.")
113 }
114 headers := splitOnWhitespace(scanner.Text())
115
116 if !scanner.Scan() {
117 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.")
118 }
119 specs := splitOnWhitespace(scanner.Text())
120
121 if len(headers) != len(specs) {
122 return nil, fmt.Errorf("number of headers (%d) and field specifications (%d) don't match", len(headers), len(specs))
123 }
124
125 columns := make([]Column, len(headers))
126 for ix := range headers {
127 spec, err := RelaxedJSONPathExpression(specs[ix])
128 if err != nil {
129 return nil, err
130 }
131 columns[ix] = Column{
132 Header: headers[ix],
133 FieldSpec: spec,
134 }
135 }
136 return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: false}, nil
137 }
138
139
140 type Column struct {
141
142 Header string
143
144
145 FieldSpec string
146 }
147
148
149
150 type CustomColumnsPrinter struct {
151 Columns []Column
152 Decoder runtime.Decoder
153 NoHeaders bool
154
155
156 lastType reflect.Type
157 }
158
159 func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
160
161
162
163 if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
164 return fmt.Errorf(printers.InternalObjectPrinterErr)
165 }
166
167 if _, found := out.(*tabwriter.Writer); !found {
168 w := printers.GetNewTabWriter(out)
169 out = w
170 defer w.Flush()
171 }
172
173 t := reflect.TypeOf(obj)
174 if !s.NoHeaders && t != s.lastType {
175 headers := make([]string, len(s.Columns))
176 for ix := range s.Columns {
177 headers[ix] = s.Columns[ix].Header
178 }
179 fmt.Fprintln(out, strings.Join(headers, "\t"))
180 s.lastType = t
181 }
182 parsers := make([]*jsonpath.JSONPath, len(s.Columns))
183 for ix := range s.Columns {
184 parsers[ix] = jsonpath.New(fmt.Sprintf("column%d", ix)).AllowMissingKeys(true)
185 if err := parsers[ix].Parse(s.Columns[ix].FieldSpec); err != nil {
186 return err
187 }
188 }
189
190 if meta.IsListType(obj) {
191 objs, err := meta.ExtractList(obj)
192 if err != nil {
193 return err
194 }
195 for ix := range objs {
196 if err := s.printOneObject(objs[ix], parsers, out); err != nil {
197 return err
198 }
199 }
200 } else {
201 if err := s.printOneObject(obj, parsers, out); err != nil {
202 return err
203 }
204 }
205 return nil
206 }
207
208 func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jsonpath.JSONPath, out io.Writer) error {
209 columns := make([]string, len(parsers))
210 switch u := obj.(type) {
211 case *metav1.WatchEvent:
212 if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(u.Object.Object)).Type().PkgPath()) {
213 return fmt.Errorf(printers.InternalObjectPrinterErr)
214 }
215 unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(u.Object.Object)
216 if err != nil {
217 return err
218 }
219 obj = &unstructured.Unstructured{
220 Object: map[string]interface{}{
221 "type": u.Type,
222 "object": unstructuredObject,
223 },
224 }
225
226 case *runtime.Unknown:
227 if len(u.Raw) > 0 {
228 var err error
229 if obj, err = runtime.Decode(s.Decoder, u.Raw); err != nil {
230 return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.Raw)
231 }
232 }
233 }
234
235 for ix := range parsers {
236 parser := parsers[ix]
237
238 var values [][]reflect.Value
239 var err error
240 if unstructured, ok := obj.(runtime.Unstructured); ok {
241 values, err = parser.FindResults(unstructured.UnstructuredContent())
242 } else {
243 values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
244 }
245
246 if err != nil {
247 return err
248 }
249 valueStrings := []string{}
250 if len(values) == 0 || len(values[0]) == 0 {
251 valueStrings = append(valueStrings, "<none>")
252 }
253 for arrIx := range values {
254 for valIx := range values[arrIx] {
255 valueStrings = append(valueStrings, printers.EscapeTerminal(fmt.Sprint(values[arrIx][valIx].Interface())))
256 }
257 }
258 columns[ix] = strings.Join(valueStrings, ",")
259 }
260 fmt.Fprintln(out, strings.Join(columns, "\t"))
261 return nil
262 }
263
View as plain text