1/*
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	"bytes"
21	gojson "encoding/json"
22	"errors"
23	"fmt"
24	"io"
25	"strings"
26
27	"github.com/golang/glog"
28
29	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30	"k8s.io/apimachinery/pkg/runtime"
31	"k8s.io/apimachinery/pkg/runtime/schema"
32	"k8s.io/apimachinery/pkg/types"
33	"k8s.io/apimachinery/pkg/util/json"
34)
35
36// Unstructured allows objects that do not have Golang structs registered to be manipulated
37// generically. This can be used to deal with the API objects from a plug-in. Unstructured
38// objects still have functioning TypeMeta features-- kind, version, etc.
39//
40// WARNING: This object has accessors for the v1 standard metadata. You *MUST NOT* use this
41// type if you are dealing with objects that are not in the server meta v1 schema.
42//
43// TODO: make the serialization part of this type distinct from the field accessors.
44type Unstructured struct {
45	// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
46	// map[string]interface{}
47	// children.
48	Object map[string]interface{}
49}
50
51var _ metav1.Object = &Unstructured{}
52var _ runtime.Unstructured = &Unstructured{}
53var _ runtime.Unstructured = &UnstructuredList{}
54
55func (obj *Unstructured) GetObjectKind() schema.ObjectKind     { return obj }
56func (obj *UnstructuredList) GetObjectKind() schema.ObjectKind { return obj }
57
58func (obj *Unstructured) IsUnstructuredObject()     {}
59func (obj *UnstructuredList) IsUnstructuredObject() {}
60
61func (obj *Unstructured) IsList() bool {
62	if obj.Object != nil {
63		_, ok := obj.Object["items"]
64		return ok
65	}
66	return false
67}
68func (obj *UnstructuredList) IsList() bool { return true }
69
70func (obj *Unstructured) UnstructuredContent() map[string]interface{} {
71	if obj.Object == nil {
72		obj.Object = make(map[string]interface{})
73	}
74	return obj.Object
75}
76
77// UnstructuredContent returns a map contain an overlay of the Items field onto
78// the Object field. Items always overwrites overlay. Changing "items" in the
79// returned object will affect items in the underlying Items field, but changing
80// the "items" slice itself will have no effect.
81// TODO: expose SetUnstructuredContent on runtime.Unstructured that allows
82// items to be changed.
83func (obj *UnstructuredList) UnstructuredContent() map[string]interface{} {
84	out := obj.Object
85	if out == nil {
86		out = make(map[string]interface{})
87	}
88	items := make([]interface{}, len(obj.Items))
89	for i, item := range obj.Items {
90		items[i] = item.Object
91	}
92	out["items"] = items
93	return out
94}
95
96// MarshalJSON ensures that the unstructured object produces proper
97// JSON when passed to Go's standard JSON library.
98func (u *Unstructured) MarshalJSON() ([]byte, error) {
99	var buf bytes.Buffer
100	err := UnstructuredJSONScheme.Encode(u, &buf)
101	return buf.Bytes(), err
102}
103
104// UnmarshalJSON ensures that the unstructured object properly decodes
105// JSON when passed to Go's standard JSON library.
106func (u *Unstructured) UnmarshalJSON(b []byte) error {
107	_, _, err := UnstructuredJSONScheme.Decode(b, nil, u)
108	return err
109}
110
111func getNestedField(obj map[string]interface{}, fields ...string) interface{} {
112	var val interface{} = obj
113	for _, field := range fields {
114		if _, ok := val.(map[string]interface{}); !ok {
115			return nil
116		}
117		val = val.(map[string]interface{})[field]
118	}
119	return val
120}
121
122func getNestedString(obj map[string]interface{}, fields ...string) string {
123	if str, ok := getNestedField(obj, fields...).(string); ok {
124		return str
125	}
126	return ""
127}
128
129func getNestedSlice(obj map[string]interface{}, fields ...string) []string {
130	if m, ok := getNestedField(obj, fields...).([]interface{}); ok {
131		strSlice := make([]string, 0, len(m))
132		for _, v := range m {
133			if str, ok := v.(string); ok {
134				strSlice = append(strSlice, str)
135			}
136		}
137		return strSlice
138	}
139	return nil
140}
141
142func getNestedMap(obj map[string]interface{}, fields ...string) map[string]string {
143	if m, ok := getNestedField(obj, fields...).(map[string]interface{}); ok {
144		strMap := make(map[string]string, len(m))
145		for k, v := range m {
146			if str, ok := v.(string); ok {
147				strMap[k] = str
148			}
149		}
150		return strMap
151	}
152	return nil
153}
154
155func setNestedField(obj map[string]interface{}, value interface{}, fields ...string) {
156	m := obj
157	if len(fields) > 1 {
158		for _, field := range fields[0 : len(fields)-1] {
159			if _, ok := m[field].(map[string]interface{}); !ok {
160				m[field] = make(map[string]interface{})
161			}
162			m = m[field].(map[string]interface{})
163		}
164	}
165	m[fields[len(fields)-1]] = value
166}
167
168func setNestedSlice(obj map[string]interface{}, value []string, fields ...string) {
169	m := make([]interface{}, 0, len(value))
170	for _, v := range value {
171		m = append(m, v)
172	}
173	setNestedField(obj, m, fields...)
174}
175
176func setNestedMap(obj map[string]interface{}, value map[string]string, fields ...string) {
177	m := make(map[string]interface{}, len(value))
178	for k, v := range value {
179		m[k] = v
180	}
181	setNestedField(obj, m, fields...)
182}
183
184func (u *Unstructured) setNestedField(value interface{}, fields ...string) {
185	if u.Object == nil {
186		u.Object = make(map[string]interface{})
187	}
188	setNestedField(u.Object, value, fields...)
189}
190
191func (u *Unstructured) setNestedSlice(value []string, fields ...string) {
192	if u.Object == nil {
193		u.Object = make(map[string]interface{})
194	}
195	setNestedSlice(u.Object, value, fields...)
196}
197
198func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) {
199	if u.Object == nil {
200		u.Object = make(map[string]interface{})
201	}
202	setNestedMap(u.Object, value, fields...)
203}
204
205func extractOwnerReference(src interface{}) metav1.OwnerReference {
206	v := src.(map[string]interface{})
207	// though this field is a *bool, but when decoded from JSON, it's
208	// unmarshalled as bool.
209	var controllerPtr *bool
210	controller, ok := (getNestedField(v, "controller")).(bool)
211	if !ok {
212		controllerPtr = nil
213	} else {
214		controllerCopy := controller
215		controllerPtr = &controllerCopy
216	}
217	var blockOwnerDeletionPtr *bool
218	blockOwnerDeletion, ok := (getNestedField(v, "blockOwnerDeletion")).(bool)
219	if !ok {
220		blockOwnerDeletionPtr = nil
221	} else {
222		blockOwnerDeletionCopy := blockOwnerDeletion
223		blockOwnerDeletionPtr = &blockOwnerDeletionCopy
224	}
225	return metav1.OwnerReference{
226		Kind:               getNestedString(v, "kind"),
227		Name:               getNestedString(v, "name"),
228		APIVersion:         getNestedString(v, "apiVersion"),
229		UID:                (types.UID)(getNestedString(v, "uid")),
230		Controller:         controllerPtr,
231		BlockOwnerDeletion: blockOwnerDeletionPtr,
232	}
233}
234
235func setOwnerReference(src metav1.OwnerReference) map[string]interface{} {
236	ret := make(map[string]interface{})
237	controllerPtr := src.Controller
238	if controllerPtr != nil {
239		controller := *controllerPtr
240		controllerPtr = &controller
241	}
242	blockOwnerDeletionPtr := src.BlockOwnerDeletion
243	if blockOwnerDeletionPtr != nil {
244		blockOwnerDeletion := *blockOwnerDeletionPtr
245		blockOwnerDeletionPtr = &blockOwnerDeletion
246	}
247	setNestedField(ret, src.Kind, "kind")
248	setNestedField(ret, src.Name, "name")
249	setNestedField(ret, src.APIVersion, "apiVersion")
250	setNestedField(ret, string(src.UID), "uid")
251	setNestedField(ret, controllerPtr, "controller")
252	setNestedField(ret, blockOwnerDeletionPtr, "blockOwnerDeletion")
253	return ret
254}
255
256func getOwnerReferences(object map[string]interface{}) ([]map[string]interface{}, error) {
257	field := getNestedField(object, "metadata", "ownerReferences")
258	if field == nil {
259		return nil, fmt.Errorf("cannot find field metadata.ownerReferences in %v", object)
260	}
261	ownerReferences, ok := field.([]map[string]interface{})
262	if ok {
263		return ownerReferences, nil
264	}
265	// TODO: This is hacky...
266	interfaces, ok := field.([]interface{})
267	if !ok {
268		return nil, fmt.Errorf("expect metadata.ownerReferences to be a slice in %#v", object)
269	}
270	ownerReferences = make([]map[string]interface{}, 0, len(interfaces))
271	for i := 0; i < len(interfaces); i++ {
272		r, ok := interfaces[i].(map[string]interface{})
273		if !ok {
274			return nil, fmt.Errorf("expect element metadata.ownerReferences to be a map[string]interface{} in %#v", object)
275		}
276		ownerReferences = append(ownerReferences, r)
277	}
278	return ownerReferences, nil
279}
280
281func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference {
282	original, err := getOwnerReferences(u.Object)
283	if err != nil {
284		glog.V(6).Info(err)
285		return nil
286	}
287	ret := make([]metav1.OwnerReference, 0, len(original))
288	for i := 0; i < len(original); i++ {
289		ret = append(ret, extractOwnerReference(original[i]))
290	}
291	return ret
292}
293
294func (u *Unstructured) SetOwnerReferences(references []metav1.OwnerReference) {
295	var newReferences = make([]map[string]interface{}, 0, len(references))
296	for i := 0; i < len(references); i++ {
297		newReferences = append(newReferences, setOwnerReference(references[i]))
298	}
299	u.setNestedField(newReferences, "metadata", "ownerReferences")
300}
301
302func (u *Unstructured) GetAPIVersion() string {
303	return getNestedString(u.Object, "apiVersion")
304}
305
306func (u *Unstructured) SetAPIVersion(version string) {
307	u.setNestedField(version, "apiVersion")
308}
309
310func (u *Unstructured) GetKind() string {
311	return getNestedString(u.Object, "kind")
312}
313
314func (u *Unstructured) SetKind(kind string) {
315	u.setNestedField(kind, "kind")
316}
317
318func (u *Unstructured) GetNamespace() string {
319	return getNestedString(u.Object, "metadata", "namespace")
320}
321
322func (u *Unstructured) SetNamespace(namespace string) {
323	u.setNestedField(namespace, "metadata", "namespace")
324}
325
326func (u *Unstructured) GetName() string {
327	return getNestedString(u.Object, "metadata", "name")
328}
329
330func (u *Unstructured) SetName(name string) {
331	u.setNestedField(name, "metadata", "name")
332}
333
334func (u *Unstructured) GetGenerateName() string {
335	return getNestedString(u.Object, "metadata", "generateName")
336}
337
338func (u *Unstructured) SetGenerateName(name string) {
339	u.setNestedField(name, "metadata", "generateName")
340}
341
342func (u *Unstructured) GetUID() types.UID {
343	return types.UID(getNestedString(u.Object, "metadata", "uid"))
344}
345
346func (u *Unstructured) SetUID(uid types.UID) {
347	u.setNestedField(string(uid), "metadata", "uid")
348}
349
350func (u *Unstructured) GetResourceVersion() string {
351	return getNestedString(u.Object, "metadata", "resourceVersion")
352}
353
354func (u *Unstructured) SetResourceVersion(version string) {
355	u.setNestedField(version, "metadata", "resourceVersion")
356}
357
358func (u *Unstructured) GetSelfLink() string {
359	return getNestedString(u.Object, "metadata", "selfLink")
360}
361
362func (u *Unstructured) SetSelfLink(selfLink string) {
363	u.setNestedField(selfLink, "metadata", "selfLink")
364}
365
366func (u *Unstructured) GetCreationTimestamp() metav1.Time {
367	var timestamp metav1.Time
368	timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "creationTimestamp"))
369	return timestamp
370}
371
372func (u *Unstructured) SetCreationTimestamp(timestamp metav1.Time) {
373	ts, _ := timestamp.MarshalQueryParameter()
374	u.setNestedField(ts, "metadata", "creationTimestamp")
375}
376
377func (u *Unstructured) GetDeletionTimestamp() *metav1.Time {
378	var timestamp metav1.Time
379	timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "deletionTimestamp"))
380	if timestamp.IsZero() {
381		return nil
382	}
383	return &timestamp
384}
385
386func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) {
387	ts, _ := timestamp.MarshalQueryParameter()
388	u.setNestedField(ts, "metadata", "deletionTimestamp")
389}
390
391func (u *Unstructured) GetLabels() map[string]string {
392	return getNestedMap(u.Object, "metadata", "labels")
393}
394
395func (u *Unstructured) SetLabels(labels map[string]string) {
396	u.setNestedMap(labels, "metadata", "labels")
397}
398
399func (u *Unstructured) GetAnnotations() map[string]string {
400	return getNestedMap(u.Object, "metadata", "annotations")
401}
402
403func (u *Unstructured) SetAnnotations(annotations map[string]string) {
404	u.setNestedMap(annotations, "metadata", "annotations")
405}
406
407func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) {
408	u.SetAPIVersion(gvk.GroupVersion().String())
409	u.SetKind(gvk.Kind)
410}
411
412func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind {
413	gv, err := schema.ParseGroupVersion(u.GetAPIVersion())
414	if err != nil {
415		return schema.GroupVersionKind{}
416	}
417	gvk := gv.WithKind(u.GetKind())
418	return gvk
419}
420
421func (u *Unstructured) GetFinalizers() []string {
422	return getNestedSlice(u.Object, "metadata", "finalizers")
423}
424
425func (u *Unstructured) SetFinalizers(finalizers []string) {
426	u.setNestedSlice(finalizers, "metadata", "finalizers")
427}
428
429func (u *Unstructured) GetClusterName() string {
430	return getNestedString(u.Object, "metadata", "clusterName")
431}
432
433func (u *Unstructured) SetClusterName(clusterName string) {
434	u.setNestedField(clusterName, "metadata", "clusterName")
435}
436
437// UnstructuredList allows lists that do not have Golang structs
438// registered to be manipulated generically. This can be used to deal
439// with the API lists from a plug-in.
440type UnstructuredList struct {
441	Object map[string]interface{}
442
443	// Items is a list of unstructured objects.
444	Items []*Unstructured `json:"items"`
445}
446
447// MarshalJSON ensures that the unstructured list object produces proper
448// JSON when passed to Go's standard JSON library.
449func (u *UnstructuredList) MarshalJSON() ([]byte, error) {
450	var buf bytes.Buffer
451	err := UnstructuredJSONScheme.Encode(u, &buf)
452	return buf.Bytes(), err
453}
454
455// UnmarshalJSON ensures that the unstructured list object properly
456// decodes JSON when passed to Go's standard JSON library.
457func (u *UnstructuredList) UnmarshalJSON(b []byte) error {
458	_, _, err := UnstructuredJSONScheme.Decode(b, nil, u)
459	return err
460}
461
462func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) {
463	if u.Object == nil {
464		u.Object = make(map[string]interface{})
465	}
466	setNestedField(u.Object, value, fields...)
467}
468
469func (u *UnstructuredList) GetAPIVersion() string {
470	return getNestedString(u.Object, "apiVersion")
471}
472
473func (u *UnstructuredList) SetAPIVersion(version string) {
474	u.setNestedField(version, "apiVersion")
475}
476
477func (u *UnstructuredList) GetKind() string {
478	return getNestedString(u.Object, "kind")
479}
480
481func (u *UnstructuredList) SetKind(kind string) {
482	u.setNestedField(kind, "kind")
483}
484
485func (u *UnstructuredList) GetResourceVersion() string {
486	return getNestedString(u.Object, "metadata", "resourceVersion")
487}
488
489func (u *UnstructuredList) SetResourceVersion(version string) {
490	u.setNestedField(version, "metadata", "resourceVersion")
491}
492
493func (u *UnstructuredList) GetSelfLink() string {
494	return getNestedString(u.Object, "metadata", "selfLink")
495}
496
497func (u *UnstructuredList) SetSelfLink(selfLink string) {
498	u.setNestedField(selfLink, "metadata", "selfLink")
499}
500
501func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) {
502	u.SetAPIVersion(gvk.GroupVersion().String())
503	u.SetKind(gvk.Kind)
504}
505
506func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind {
507	gv, err := schema.ParseGroupVersion(u.GetAPIVersion())
508	if err != nil {
509		return schema.GroupVersionKind{}
510	}
511	gvk := gv.WithKind(u.GetKind())
512	return gvk
513}
514
515// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
516// type, which can be used for generic access to objects without a predefined scheme.
517// TODO: move into serializer/json.
518var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{}
519
520type unstructuredJSONScheme struct{}
521
522func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
523	var err error
524	if obj != nil {
525		err = s.decodeInto(data, obj)
526	} else {
527		obj, err = s.decode(data)
528	}
529
530	if err != nil {
531		return nil, nil, err
532	}
533
534	gvk := obj.GetObjectKind().GroupVersionKind()
535	if len(gvk.Kind) == 0 {
536		return nil, &gvk, runtime.NewMissingKindErr(string(data))
537	}
538
539	return obj, &gvk, nil
540}
541
542func (unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error {
543	switch t := obj.(type) {
544	case *Unstructured:
545		return json.NewEncoder(w).Encode(t.Object)
546	case *UnstructuredList:
547		items := make([]map[string]interface{}, 0, len(t.Items))
548		for _, i := range t.Items {
549			items = append(items, i.Object)
550		}
551		t.Object["items"] = items
552		defer func() { delete(t.Object, "items") }()
553		return json.NewEncoder(w).Encode(t.Object)
554	case *runtime.Unknown:
555		// TODO: Unstructured needs to deal with ContentType.
556		_, err := w.Write(t.Raw)
557		return err
558	default:
559		return json.NewEncoder(w).Encode(t)
560	}
561}
562
563func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) {
564	type detector struct {
565		Items gojson.RawMessage
566	}
567	var det detector
568	if err := json.Unmarshal(data, &det); err != nil {
569		return nil, err
570	}
571
572	if det.Items != nil {
573		list := &UnstructuredList{}
574		err := s.decodeToList(data, list)
575		return list, err
576	}
577
578	// No Items field, so it wasn't a list.
579	unstruct := &Unstructured{}
580	err := s.decodeToUnstructured(data, unstruct)
581	return unstruct, err
582}
583
584func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error {
585	switch x := obj.(type) {
586	case *Unstructured:
587		return s.decodeToUnstructured(data, x)
588	case *UnstructuredList:
589		return s.decodeToList(data, x)
590	case *runtime.VersionedObjects:
591		o, err := s.decode(data)
592		if err == nil {
593			x.Objects = []runtime.Object{o}
594		}
595		return err
596	default:
597		return json.Unmarshal(data, x)
598	}
599}
600
601func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error {
602	m := make(map[string]interface{})
603	if err := json.Unmarshal(data, &m); err != nil {
604		return err
605	}
606
607	unstruct.Object = m
608
609	return nil
610}
611
612func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error {
613	type decodeList struct {
614		Items []gojson.RawMessage
615	}
616
617	var dList decodeList
618	if err := json.Unmarshal(data, &dList); err != nil {
619		return err
620	}
621
622	if err := json.Unmarshal(data, &list.Object); err != nil {
623		return err
624	}
625
626	// For typed lists, e.g., a PodList, API server doesn't set each item's
627	// APIVersion and Kind. We need to set it.
628	listAPIVersion := list.GetAPIVersion()
629	listKind := list.GetKind()
630	itemKind := strings.TrimSuffix(listKind, "List")
631
632	delete(list.Object, "items")
633	list.Items = nil
634	for _, i := range dList.Items {
635		unstruct := &Unstructured{}
636		if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil {
637			return err
638		}
639		// This is hacky. Set the item's Kind and APIVersion to those inferred
640		// from the List.
641		if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 {
642			unstruct.SetKind(itemKind)
643			unstruct.SetAPIVersion(listAPIVersion)
644		}
645		list.Items = append(list.Items, unstruct)
646	}
647	return nil
648}
649
650// UnstructuredObjectConverter is an ObjectConverter for use with
651// Unstructured objects. Since it has no schema or type information,
652// it will only succeed for no-op conversions. This is provided as a
653// sane implementation for APIs that require an object converter.
654type UnstructuredObjectConverter struct{}
655
656func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error {
657	unstructIn, ok := in.(*Unstructured)
658	if !ok {
659		return fmt.Errorf("input type %T in not valid for unstructured conversion", in)
660	}
661
662	unstructOut, ok := out.(*Unstructured)
663	if !ok {
664		return fmt.Errorf("output type %T in not valid for unstructured conversion", out)
665	}
666
667	// maybe deep copy the map? It is documented in the
668	// ObjectConverter interface that this function is not
669	// guaranteeed to not mutate the input. Or maybe set the input
670	// object to nil.
671	unstructOut.Object = unstructIn.Object
672	return nil
673}
674
675func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
676	if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() {
677		gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind})
678		if !ok {
679			// TODO: should this be a typed error?
680			return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target)
681		}
682		in.GetObjectKind().SetGroupVersionKind(gvk)
683	}
684	return in, nil
685}
686
687func (UnstructuredObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
688	return "", "", errors.New("unstructured cannot convert field labels")
689}
690