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