/
helpers.go
158 lines (148 loc) · 5.23 KB
/
helpers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package framework
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
)
// ReadValue (element, array or section) from an unstructured object
//
// Requires a path consisting of nested element names e.g. "metadata", "labels", "some_label". Returns an error if
// element is not found or if a nesting element is not a section. By "section" I mean map[string]interface{} i.e. map
// capable of nesting elements. No support for addressing individual elements in an array (slice).
func ReadValue(obj *unstructured.Unstructured, path ...string) (interface{}, error) {
if len(path) == 0 {
return nil, fmt.Errorf("empty path")
}
errpath := ""
errjson, _ := obj.MarshalJSON()
section := obj.UnstructuredContent()
for i, v := range path {
errpath += "[" + v + "]"
if i+1 == len(path) {
result, ok := section[v]
if !ok {
return nil, fmt.Errorf("could not find value %s in:\n%s", errpath, string(errjson))
} else {
return result, nil
}
} else {
tmp, ok := section[v]
if !ok {
return nil, fmt.Errorf("could not find section %s in:\n%s", errpath, string(errjson))
}
section, ok = tmp.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("found %s value, but section was expected in:\n%s", errpath, string(errjson))
}
}
}
panic("Unreachable code in ReadValue")
}
// WriteValue (element, array or section) from an unstructured object
//
// Requires a path consisting of nested element names e.g. "metadata", "labels", "some_label". Returns an error if
// nesting element is not found or is not a section. By "section" I mean map[string]interface{} i.e. map
// capable of nesting elements. No support for addressing individual elements in an array (slice).
func WriteValue(obj *unstructured.Unstructured, value interface{}, path ...string) error {
if len(path) == 0 {
return fmt.Errorf("empty path")
}
errpath := ""
errjson, _ := obj.MarshalJSON()
section := obj.UnstructuredContent()
for i, v := range path {
errpath += "[" + v + "]"
if i+1 == len(path) {
section[v] = value
return nil
} else {
tmp, ok := section[v]
if !ok {
return fmt.Errorf("could not find section %s in:\n%s", errpath, string(errjson))
}
section, ok = tmp.(map[string]interface{})
if !ok {
return fmt.Errorf("found %s value, but section was expected in:\n%s", errpath, string(errjson))
}
}
}
panic("Unreachable code in WriteValue")
}
// DeleteValue (element, array or section) from the unstructured object
//
// Requires a path consisting of nested element names e.g. "metadata", "labels", "some_label". Returns an error if
// nesting element is not a section. There is no error if the element being deleted does not exist, because that would
// be a no-op anyway. By "section" I mean map[string]interface{} i.e. map capable of nesting elements. No support for
// addressing individual elements in an array (slice).
func DeleteValue(obj *unstructured.Unstructured, path ...string) error {
if len(path) == 0 {
return fmt.Errorf("empty path")
}
errpath := ""
errjson, _ := obj.MarshalJSON()
section := obj.UnstructuredContent()
for i, v := range path {
errpath += "[" + v + "]"
if i+1 == len(path) {
// Note: delete is a no-op if v is not in section
delete(section, v)
return nil
} else {
tmp, ok := section[v]
if !ok {
// The element we wanted to delete does not exist, so no-op
return nil
}
section, ok = tmp.(map[string]interface{})
if !ok {
return fmt.Errorf("found %s value, but section was expected in:\n%s", errpath, string(errjson))
}
}
}
panic("Unreachable code in DeleteValue")
}
// LoadUnstructured load an object from the textual data
//
// Using standard Reader interface (i.e. could be a file, hardcoded text, something else). Note: Unstructured object
// supports conversion to UnstructuredList for any object which contains "items" map directly under the object root.
// Both JSON and YAML formats are supported.
func LoadUnstructured(r io.Reader) (*unstructured.Unstructured, error) {
reader, _, isJson := yaml.GuessJSONStream(r, bytes.MinRead)
data, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
if !isJson {
tmp, err := yaml.ToJSON(data)
if err != nil {
return nil, err
}
data = tmp
}
result := unstructured.Unstructured{}
err = result.UnmarshalJSON(data)
return &result, err
}
// LoadInto load an object from the textual data into an existing K8s object
//
// This function supports both strong-typed object and Unstructured object. For example to load a deployment file
// the following can be used:
//
// dep := appsv1.Deployment{}
// if err := framework.LoadInto(bufio.NewReader(file), &dep); err != nil { . . .
//
// The benefits of using strong-typed objects (rather than unstructured.Unstructured) are:
// * K8s will validate the input file while loading it
// * Individual elements of the service definition will be easier to work with
// * Function framework.WaitFor() provides special handling waiting on pods for types which can own them
//
// Both JSON and YAML formats are supported.
func LoadInto(r io.Reader, into interface{}) error {
if err := yaml.NewYAMLOrJSONDecoder(r, bytes.MinRead).Decode(into); err != nil {
return err
}
return nil
}