blob: 3b07e86db8f3c97c9e392eaa4577db9df2b8affa [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2015 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 unstructured
18
19import (
20 gojson "encoding/json"
21 "fmt"
22 "io"
23 "strings"
24
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 "k8s.io/apimachinery/pkg/types"
29 "k8s.io/apimachinery/pkg/util/json"
30)
31
32// NestedFieldCopy returns a deep copy of the value of a nested field.
33// Returns false if the value is missing.
34// No error is returned for a nil field.
35func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) {
36 val, found, err := NestedFieldNoCopy(obj, fields...)
37 if !found || err != nil {
38 return nil, found, err
39 }
40 return runtime.DeepCopyJSONValue(val), true, nil
41}
42
43// NestedFieldNoCopy returns a reference to a nested field.
44// Returns false if value is not found and an error if unable
45// to traverse obj.
46func NestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) {
47 var val interface{} = obj
48
49 for i, field := range fields {
David Bainbridge86971522019-09-26 22:09:39 +000050 if val == nil {
51 return nil, false, nil
52 }
Zack Williamse940c7a2019-08-21 14:25:39 -070053 if m, ok := val.(map[string]interface{}); ok {
54 val, ok = m[field]
55 if !ok {
56 return nil, false, nil
57 }
58 } else {
59 return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields[:i+1]), val, val)
60 }
61 }
62 return val, true, nil
63}
64
65// NestedString returns the string value of a nested field.
66// Returns false if value is not found and an error if not a string.
67func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error) {
68 val, found, err := NestedFieldNoCopy(obj, fields...)
69 if !found || err != nil {
70 return "", found, err
71 }
72 s, ok := val.(string)
73 if !ok {
74 return "", false, fmt.Errorf("%v accessor error: %v is of the type %T, expected string", jsonPath(fields), val, val)
75 }
76 return s, true, nil
77}
78
79// NestedBool returns the bool value of a nested field.
80// Returns false if value is not found and an error if not a bool.
81func NestedBool(obj map[string]interface{}, fields ...string) (bool, bool, error) {
82 val, found, err := NestedFieldNoCopy(obj, fields...)
83 if !found || err != nil {
84 return false, found, err
85 }
86 b, ok := val.(bool)
87 if !ok {
88 return false, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected bool", jsonPath(fields), val, val)
89 }
90 return b, true, nil
91}
92
93// NestedFloat64 returns the float64 value of a nested field.
94// Returns false if value is not found and an error if not a float64.
95func NestedFloat64(obj map[string]interface{}, fields ...string) (float64, bool, error) {
96 val, found, err := NestedFieldNoCopy(obj, fields...)
97 if !found || err != nil {
98 return 0, found, err
99 }
100 f, ok := val.(float64)
101 if !ok {
102 return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected float64", jsonPath(fields), val, val)
103 }
104 return f, true, nil
105}
106
107// NestedInt64 returns the int64 value of a nested field.
108// Returns false if value is not found and an error if not an int64.
109func NestedInt64(obj map[string]interface{}, fields ...string) (int64, bool, error) {
110 val, found, err := NestedFieldNoCopy(obj, fields...)
111 if !found || err != nil {
112 return 0, found, err
113 }
114 i, ok := val.(int64)
115 if !ok {
116 return 0, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected int64", jsonPath(fields), val, val)
117 }
118 return i, true, nil
119}
120
121// NestedStringSlice returns a copy of []string value of a nested field.
122// Returns false if value is not found and an error if not a []interface{} or contains non-string items in the slice.
123func NestedStringSlice(obj map[string]interface{}, fields ...string) ([]string, bool, error) {
124 val, found, err := NestedFieldNoCopy(obj, fields...)
125 if !found || err != nil {
126 return nil, found, err
127 }
128 m, ok := val.([]interface{})
129 if !ok {
130 return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val)
131 }
132 strSlice := make([]string, 0, len(m))
133 for _, v := range m {
134 if str, ok := v.(string); ok {
135 strSlice = append(strSlice, str)
136 } else {
137 return nil, false, fmt.Errorf("%v accessor error: contains non-string key in the slice: %v is of the type %T, expected string", jsonPath(fields), v, v)
138 }
139 }
140 return strSlice, true, nil
141}
142
143// NestedSlice returns a deep copy of []interface{} value of a nested field.
144// Returns false if value is not found and an error if not a []interface{}.
145func NestedSlice(obj map[string]interface{}, fields ...string) ([]interface{}, bool, error) {
146 val, found, err := NestedFieldNoCopy(obj, fields...)
147 if !found || err != nil {
148 return nil, found, err
149 }
150 _, ok := val.([]interface{})
151 if !ok {
152 return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val)
153 }
154 return runtime.DeepCopyJSONValue(val).([]interface{}), true, nil
155}
156
157// NestedStringMap returns a copy of map[string]string value of a nested field.
158// Returns false if value is not found and an error if not a map[string]interface{} or contains non-string values in the map.
159func NestedStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) {
160 m, found, err := nestedMapNoCopy(obj, fields...)
161 if !found || err != nil {
162 return nil, found, err
163 }
164 strMap := make(map[string]string, len(m))
165 for k, v := range m {
166 if str, ok := v.(string); ok {
167 strMap[k] = str
168 } else {
169 return nil, false, fmt.Errorf("%v accessor error: contains non-string key in the map: %v is of the type %T, expected string", jsonPath(fields), v, v)
170 }
171 }
172 return strMap, true, nil
173}
174
175// NestedMap returns a deep copy of map[string]interface{} value of a nested field.
176// Returns false if value is not found and an error if not a map[string]interface{}.
177func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) {
178 m, found, err := nestedMapNoCopy(obj, fields...)
179 if !found || err != nil {
180 return nil, found, err
181 }
182 return runtime.DeepCopyJSON(m), true, nil
183}
184
185// nestedMapNoCopy returns a map[string]interface{} value of a nested field.
186// Returns false if value is not found and an error if not a map[string]interface{}.
187func nestedMapNoCopy(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error) {
188 val, found, err := NestedFieldNoCopy(obj, fields...)
189 if !found || err != nil {
190 return nil, found, err
191 }
192 m, ok := val.(map[string]interface{})
193 if !ok {
194 return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields), val, val)
195 }
196 return m, true, nil
197}
198
199// SetNestedField sets the value of a nested field to a deep copy of the value provided.
200// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}.
201func SetNestedField(obj map[string]interface{}, value interface{}, fields ...string) error {
202 return setNestedFieldNoCopy(obj, runtime.DeepCopyJSONValue(value), fields...)
203}
204
205func setNestedFieldNoCopy(obj map[string]interface{}, value interface{}, fields ...string) error {
206 m := obj
207
208 for i, field := range fields[:len(fields)-1] {
209 if val, ok := m[field]; ok {
210 if valMap, ok := val.(map[string]interface{}); ok {
211 m = valMap
212 } else {
213 return fmt.Errorf("value cannot be set because %v is not a map[string]interface{}", jsonPath(fields[:i+1]))
214 }
215 } else {
216 newVal := make(map[string]interface{})
217 m[field] = newVal
218 m = newVal
219 }
220 }
221 m[fields[len(fields)-1]] = value
222 return nil
223}
224
225// SetNestedStringSlice sets the string slice value of a nested field.
226// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}.
227func SetNestedStringSlice(obj map[string]interface{}, value []string, fields ...string) error {
228 m := make([]interface{}, 0, len(value)) // convert []string into []interface{}
229 for _, v := range value {
230 m = append(m, v)
231 }
232 return setNestedFieldNoCopy(obj, m, fields...)
233}
234
235// SetNestedSlice sets the slice value of a nested field.
236// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}.
237func SetNestedSlice(obj map[string]interface{}, value []interface{}, fields ...string) error {
238 return SetNestedField(obj, value, fields...)
239}
240
241// SetNestedStringMap sets the map[string]string value of a nested field.
242// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}.
243func SetNestedStringMap(obj map[string]interface{}, value map[string]string, fields ...string) error {
244 m := make(map[string]interface{}, len(value)) // convert map[string]string into map[string]interface{}
245 for k, v := range value {
246 m[k] = v
247 }
248 return setNestedFieldNoCopy(obj, m, fields...)
249}
250
251// SetNestedMap sets the map[string]interface{} value of a nested field.
252// Returns an error if value cannot be set because one of the nesting levels is not a map[string]interface{}.
253func SetNestedMap(obj map[string]interface{}, value map[string]interface{}, fields ...string) error {
254 return SetNestedField(obj, value, fields...)
255}
256
257// RemoveNestedField removes the nested field from the obj.
258func RemoveNestedField(obj map[string]interface{}, fields ...string) {
259 m := obj
260 for _, field := range fields[:len(fields)-1] {
261 if x, ok := m[field].(map[string]interface{}); ok {
262 m = x
263 } else {
264 return
265 }
266 }
267 delete(m, fields[len(fields)-1])
268}
269
270func getNestedString(obj map[string]interface{}, fields ...string) string {
271 val, found, err := NestedString(obj, fields...)
272 if !found || err != nil {
273 return ""
274 }
275 return val
276}
277
David Bainbridge86971522019-09-26 22:09:39 +0000278func getNestedInt64(obj map[string]interface{}, fields ...string) int64 {
279 val, found, err := NestedInt64(obj, fields...)
280 if !found || err != nil {
281 return 0
282 }
283 return val
284}
285
286func getNestedInt64Pointer(obj map[string]interface{}, fields ...string) *int64 {
287 val, found, err := NestedInt64(obj, fields...)
288 if !found || err != nil {
289 return nil
290 }
291 return &val
292}
293
Zack Williamse940c7a2019-08-21 14:25:39 -0700294func jsonPath(fields []string) string {
295 return "." + strings.Join(fields, ".")
296}
297
298func extractOwnerReference(v map[string]interface{}) metav1.OwnerReference {
299 // though this field is a *bool, but when decoded from JSON, it's
300 // unmarshalled as bool.
301 var controllerPtr *bool
302 if controller, found, err := NestedBool(v, "controller"); err == nil && found {
303 controllerPtr = &controller
304 }
305 var blockOwnerDeletionPtr *bool
306 if blockOwnerDeletion, found, err := NestedBool(v, "blockOwnerDeletion"); err == nil && found {
307 blockOwnerDeletionPtr = &blockOwnerDeletion
308 }
309 return metav1.OwnerReference{
310 Kind: getNestedString(v, "kind"),
311 Name: getNestedString(v, "name"),
312 APIVersion: getNestedString(v, "apiVersion"),
313 UID: types.UID(getNestedString(v, "uid")),
314 Controller: controllerPtr,
315 BlockOwnerDeletion: blockOwnerDeletionPtr,
316 }
317}
318
319// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
320// type, which can be used for generic access to objects without a predefined scheme.
321// TODO: move into serializer/json.
322var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{}
323
324type unstructuredJSONScheme struct{}
325
326func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
327 var err error
328 if obj != nil {
329 err = s.decodeInto(data, obj)
330 } else {
331 obj, err = s.decode(data)
332 }
333
334 if err != nil {
335 return nil, nil, err
336 }
337
338 gvk := obj.GetObjectKind().GroupVersionKind()
339 if len(gvk.Kind) == 0 {
340 return nil, &gvk, runtime.NewMissingKindErr(string(data))
341 }
342
343 return obj, &gvk, nil
344}
345
346func (unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error {
347 switch t := obj.(type) {
348 case *Unstructured:
349 return json.NewEncoder(w).Encode(t.Object)
350 case *UnstructuredList:
351 items := make([]interface{}, 0, len(t.Items))
352 for _, i := range t.Items {
353 items = append(items, i.Object)
354 }
355 listObj := make(map[string]interface{}, len(t.Object)+1)
356 for k, v := range t.Object { // Make a shallow copy
357 listObj[k] = v
358 }
359 listObj["items"] = items
360 return json.NewEncoder(w).Encode(listObj)
361 case *runtime.Unknown:
362 // TODO: Unstructured needs to deal with ContentType.
363 _, err := w.Write(t.Raw)
364 return err
365 default:
366 return json.NewEncoder(w).Encode(t)
367 }
368}
369
370func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) {
371 type detector struct {
372 Items gojson.RawMessage
373 }
374 var det detector
375 if err := json.Unmarshal(data, &det); err != nil {
376 return nil, err
377 }
378
379 if det.Items != nil {
380 list := &UnstructuredList{}
381 err := s.decodeToList(data, list)
382 return list, err
383 }
384
385 // No Items field, so it wasn't a list.
386 unstruct := &Unstructured{}
387 err := s.decodeToUnstructured(data, unstruct)
388 return unstruct, err
389}
390
391func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error {
392 switch x := obj.(type) {
393 case *Unstructured:
394 return s.decodeToUnstructured(data, x)
395 case *UnstructuredList:
396 return s.decodeToList(data, x)
397 case *runtime.VersionedObjects:
398 o, err := s.decode(data)
399 if err == nil {
400 x.Objects = []runtime.Object{o}
401 }
402 return err
403 default:
404 return json.Unmarshal(data, x)
405 }
406}
407
408func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error {
409 m := make(map[string]interface{})
410 if err := json.Unmarshal(data, &m); err != nil {
411 return err
412 }
413
414 unstruct.Object = m
415
416 return nil
417}
418
419func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error {
420 type decodeList struct {
421 Items []gojson.RawMessage
422 }
423
424 var dList decodeList
425 if err := json.Unmarshal(data, &dList); err != nil {
426 return err
427 }
428
429 if err := json.Unmarshal(data, &list.Object); err != nil {
430 return err
431 }
432
433 // For typed lists, e.g., a PodList, API server doesn't set each item's
434 // APIVersion and Kind. We need to set it.
435 listAPIVersion := list.GetAPIVersion()
436 listKind := list.GetKind()
437 itemKind := strings.TrimSuffix(listKind, "List")
438
439 delete(list.Object, "items")
440 list.Items = make([]Unstructured, 0, len(dList.Items))
441 for _, i := range dList.Items {
442 unstruct := &Unstructured{}
443 if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil {
444 return err
445 }
446 // This is hacky. Set the item's Kind and APIVersion to those inferred
447 // from the List.
448 if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 {
449 unstruct.SetKind(itemKind)
450 unstruct.SetAPIVersion(listAPIVersion)
451 }
452 list.Items = append(list.Items, *unstruct)
453 }
454 return nil
455}
456
457type JSONFallbackEncoder struct {
458 runtime.Encoder
459}
460
461func (c JSONFallbackEncoder) Encode(obj runtime.Object, w io.Writer) error {
462 err := c.Encoder.Encode(obj, w)
463 if runtime.IsNotRegisteredError(err) {
464 switch obj.(type) {
465 case *Unstructured, *UnstructuredList:
466 return UnstructuredJSONScheme.Encode(obj, w)
467 }
468 }
469 return err
470}