1
16
17 package tableconvertor
18
19 import (
20 "bytes"
21 "context"
22 "encoding/json"
23 "fmt"
24 "io"
25 "reflect"
26
27 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28 "k8s.io/apimachinery/pkg/api/meta"
29 metatable "k8s.io/apimachinery/pkg/api/meta/table"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32 "k8s.io/apimachinery/pkg/runtime"
33 "k8s.io/apiserver/pkg/registry/rest"
34 "k8s.io/client-go/util/jsonpath"
35 )
36
37 var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
38
39
40
41 func New(crdColumns []apiextensionsv1.CustomResourceColumnDefinition) (rest.TableConvertor, error) {
42 headers := []metav1.TableColumnDefinition{
43 {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
44 }
45 c := &convertor{
46 headers: headers,
47 }
48
49 for _, col := range crdColumns {
50 path := jsonpath.New(col.Name)
51 if err := path.Parse(fmt.Sprintf("{%s}", col.JSONPath)); err != nil {
52 return c, fmt.Errorf("unrecognized column definition %q", col.JSONPath)
53 }
54 path.AllowMissingKeys(true)
55
56 desc := fmt.Sprintf("Custom resource definition column (in JSONPath format): %s", col.JSONPath)
57 if len(col.Description) > 0 {
58 desc = col.Description
59 }
60
61 c.additionalColumns = append(c.additionalColumns, path)
62 c.headers = append(c.headers, metav1.TableColumnDefinition{
63 Name: col.Name,
64 Type: col.Type,
65 Format: col.Format,
66 Description: desc,
67 Priority: col.Priority,
68 })
69 }
70
71 return c, nil
72 }
73
74 type columnPrinter interface {
75 FindResults(data interface{}) ([][]reflect.Value, error)
76 PrintResults(w io.Writer, results []reflect.Value) error
77 }
78
79 type convertor struct {
80 headers []metav1.TableColumnDefinition
81 additionalColumns []columnPrinter
82 }
83
84 func (c *convertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
85 table := &metav1.Table{}
86 opt, ok := tableOptions.(*metav1.TableOptions)
87 noHeaders := ok && opt != nil && opt.NoHeaders
88 if !noHeaders {
89 table.ColumnDefinitions = c.headers
90 }
91
92 if m, err := meta.ListAccessor(obj); err == nil {
93 table.ResourceVersion = m.GetResourceVersion()
94 table.Continue = m.GetContinue()
95 table.RemainingItemCount = m.GetRemainingItemCount()
96 } else {
97 if m, err := meta.CommonAccessor(obj); err == nil {
98 table.ResourceVersion = m.GetResourceVersion()
99 }
100 }
101
102 var err error
103 buf := &bytes.Buffer{}
104 table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
105 cells := make([]interface{}, 1, 1+len(c.additionalColumns))
106 cells[0] = name
107 customHeaders := c.headers[1:]
108 us, ok := obj.(runtime.Unstructured)
109 if !ok {
110 m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
111 if err != nil {
112 return nil, err
113 }
114 us = &unstructured.Unstructured{Object: m}
115 }
116 for i, column := range c.additionalColumns {
117 results, err := column.FindResults(us.UnstructuredContent())
118 if err != nil || len(results) == 0 || len(results[0]) == 0 {
119 cells = append(cells, nil)
120 continue
121 }
122
123
124 value := results[0][0].Interface()
125 if customHeaders[i].Type == "string" {
126 if err := column.PrintResults(buf, []reflect.Value{reflect.ValueOf(value)}); err == nil {
127 cells = append(cells, buf.String())
128 buf.Reset()
129 } else {
130 cells = append(cells, nil)
131 }
132 } else {
133 cells = append(cells, cellForJSONValue(customHeaders[i].Type, value))
134 }
135 }
136 return cells, nil
137 })
138 return table, err
139 }
140
141 func cellForJSONValue(headerType string, value interface{}) interface{} {
142 if value == nil {
143 return nil
144 }
145
146 switch headerType {
147 case "integer":
148 switch typed := value.(type) {
149 case int64:
150 return typed
151 case float64:
152 return int64(typed)
153 case json.Number:
154 if i64, err := typed.Int64(); err == nil {
155 return i64
156 }
157 }
158 case "number":
159 switch typed := value.(type) {
160 case int64:
161 return float64(typed)
162 case float64:
163 return typed
164 case json.Number:
165 if f, err := typed.Float64(); err == nil {
166 return f
167 }
168 }
169 case "boolean":
170 if b, ok := value.(bool); ok {
171 return b
172 }
173 case "string":
174 if s, ok := value.(string); ok {
175 return s
176 }
177 case "date":
178 if typed, ok := value.(string); ok {
179 var timestamp metav1.Time
180 err := timestamp.UnmarshalQueryParameter(typed)
181 if err != nil {
182 return "<invalid>"
183 }
184 return metatable.ConvertToHumanReadableDateType(timestamp)
185 }
186 }
187
188 return nil
189 }
190
View as plain text