diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go new file mode 100644 index 0000000..1660a67 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go | |||
@@ -0,0 +1,334 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strconv" | ||
6 | ) | ||
7 | |||
8 | // FieldReaders are responsible for decoding fields out of data into | ||
9 | // the proper typed representation. ResourceData uses this to query data | ||
10 | // out of multiple sources: config, state, diffs, etc. | ||
11 | type FieldReader interface { | ||
12 | ReadField([]string) (FieldReadResult, error) | ||
13 | } | ||
14 | |||
15 | // FieldReadResult encapsulates all the resulting data from reading | ||
16 | // a field. | ||
17 | type FieldReadResult struct { | ||
18 | // Value is the actual read value. NegValue is the _negative_ value | ||
19 | // or the items that should be removed (if they existed). NegValue | ||
20 | // doesn't make sense for primitives but is important for any | ||
21 | // container types such as maps, sets, lists. | ||
22 | Value interface{} | ||
23 | ValueProcessed interface{} | ||
24 | |||
25 | // Exists is true if the field was found in the data. False means | ||
26 | // it wasn't found if there was no error. | ||
27 | Exists bool | ||
28 | |||
29 | // Computed is true if the field was found but the value | ||
30 | // is computed. | ||
31 | Computed bool | ||
32 | } | ||
33 | |||
34 | // ValueOrZero returns the value of this result or the zero value of the | ||
35 | // schema type, ensuring a consistent non-nil return value. | ||
36 | func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} { | ||
37 | if r.Value != nil { | ||
38 | return r.Value | ||
39 | } | ||
40 | |||
41 | return s.ZeroValue() | ||
42 | } | ||
43 | |||
44 | // addrToSchema finds the final element schema for the given address | ||
45 | // and the given schema. It returns all the schemas that led to the final | ||
46 | // schema. These are in order of the address (out to in). | ||
47 | func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema { | ||
48 | current := &Schema{ | ||
49 | Type: typeObject, | ||
50 | Elem: schemaMap, | ||
51 | } | ||
52 | |||
53 | // If we aren't given an address, then the user is requesting the | ||
54 | // full object, so we return the special value which is the full object. | ||
55 | if len(addr) == 0 { | ||
56 | return []*Schema{current} | ||
57 | } | ||
58 | |||
59 | result := make([]*Schema, 0, len(addr)) | ||
60 | for len(addr) > 0 { | ||
61 | k := addr[0] | ||
62 | addr = addr[1:] | ||
63 | |||
64 | REPEAT: | ||
65 | // We want to trim off the first "typeObject" since its not a | ||
66 | // real lookup that people do. i.e. []string{"foo"} in a structure | ||
67 | // isn't {typeObject, typeString}, its just a {typeString}. | ||
68 | if len(result) > 0 || current.Type != typeObject { | ||
69 | result = append(result, current) | ||
70 | } | ||
71 | |||
72 | switch t := current.Type; t { | ||
73 | case TypeBool, TypeInt, TypeFloat, TypeString: | ||
74 | if len(addr) > 0 { | ||
75 | return nil | ||
76 | } | ||
77 | case TypeList, TypeSet: | ||
78 | isIndex := len(addr) > 0 && addr[0] == "#" | ||
79 | |||
80 | switch v := current.Elem.(type) { | ||
81 | case *Resource: | ||
82 | current = &Schema{ | ||
83 | Type: typeObject, | ||
84 | Elem: v.Schema, | ||
85 | } | ||
86 | case *Schema: | ||
87 | current = v | ||
88 | case ValueType: | ||
89 | current = &Schema{Type: v} | ||
90 | default: | ||
91 | // we may not know the Elem type and are just looking for the | ||
92 | // index | ||
93 | if isIndex { | ||
94 | break | ||
95 | } | ||
96 | |||
97 | if len(addr) == 0 { | ||
98 | // we've processed the address, so return what we've | ||
99 | // collected | ||
100 | return result | ||
101 | } | ||
102 | |||
103 | if len(addr) == 1 { | ||
104 | if _, err := strconv.Atoi(addr[0]); err == nil { | ||
105 | // we're indexing a value without a schema. This can | ||
106 | // happen if the list is nested in another schema type. | ||
107 | // Default to a TypeString like we do with a map | ||
108 | current = &Schema{Type: TypeString} | ||
109 | break | ||
110 | } | ||
111 | } | ||
112 | |||
113 | return nil | ||
114 | } | ||
115 | |||
116 | // If we only have one more thing and the next thing | ||
117 | // is a #, then we're accessing the index which is always | ||
118 | // an int. | ||
119 | if isIndex { | ||
120 | current = &Schema{Type: TypeInt} | ||
121 | break | ||
122 | } | ||
123 | |||
124 | case TypeMap: | ||
125 | if len(addr) > 0 { | ||
126 | switch v := current.Elem.(type) { | ||
127 | case ValueType: | ||
128 | current = &Schema{Type: v} | ||
129 | default: | ||
130 | // maps default to string values. This is all we can have | ||
131 | // if this is nested in another list or map. | ||
132 | current = &Schema{Type: TypeString} | ||
133 | } | ||
134 | } | ||
135 | case typeObject: | ||
136 | // If we're already in the object, then we want to handle Sets | ||
137 | // and Lists specially. Basically, their next key is the lookup | ||
138 | // key (the set value or the list element). For these scenarios, | ||
139 | // we just want to skip it and move to the next element if there | ||
140 | // is one. | ||
141 | if len(result) > 0 { | ||
142 | lastType := result[len(result)-2].Type | ||
143 | if lastType == TypeSet || lastType == TypeList { | ||
144 | if len(addr) == 0 { | ||
145 | break | ||
146 | } | ||
147 | |||
148 | k = addr[0] | ||
149 | addr = addr[1:] | ||
150 | } | ||
151 | } | ||
152 | |||
153 | m := current.Elem.(map[string]*Schema) | ||
154 | val, ok := m[k] | ||
155 | if !ok { | ||
156 | return nil | ||
157 | } | ||
158 | |||
159 | current = val | ||
160 | goto REPEAT | ||
161 | } | ||
162 | } | ||
163 | |||
164 | return result | ||
165 | } | ||
166 | |||
167 | // readListField is a generic method for reading a list field out of a | ||
168 | // a FieldReader. It does this based on the assumption that there is a key | ||
169 | // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. | ||
170 | // after that point. | ||
171 | func readListField( | ||
172 | r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { | ||
173 | addrPadded := make([]string, len(addr)+1) | ||
174 | copy(addrPadded, addr) | ||
175 | addrPadded[len(addrPadded)-1] = "#" | ||
176 | |||
177 | // Get the number of elements in the list | ||
178 | countResult, err := r.ReadField(addrPadded) | ||
179 | if err != nil { | ||
180 | return FieldReadResult{}, err | ||
181 | } | ||
182 | if !countResult.Exists { | ||
183 | // No count, means we have no list | ||
184 | countResult.Value = 0 | ||
185 | } | ||
186 | |||
187 | // If we have an empty list, then return an empty list | ||
188 | if countResult.Computed || countResult.Value.(int) == 0 { | ||
189 | return FieldReadResult{ | ||
190 | Value: []interface{}{}, | ||
191 | Exists: countResult.Exists, | ||
192 | Computed: countResult.Computed, | ||
193 | }, nil | ||
194 | } | ||
195 | |||
196 | // Go through each count, and get the item value out of it | ||
197 | result := make([]interface{}, countResult.Value.(int)) | ||
198 | for i, _ := range result { | ||
199 | is := strconv.FormatInt(int64(i), 10) | ||
200 | addrPadded[len(addrPadded)-1] = is | ||
201 | rawResult, err := r.ReadField(addrPadded) | ||
202 | if err != nil { | ||
203 | return FieldReadResult{}, err | ||
204 | } | ||
205 | if !rawResult.Exists { | ||
206 | // This should never happen, because by the time the data | ||
207 | // gets to the FieldReaders, all the defaults should be set by | ||
208 | // Schema. | ||
209 | rawResult.Value = nil | ||
210 | } | ||
211 | |||
212 | result[i] = rawResult.Value | ||
213 | } | ||
214 | |||
215 | return FieldReadResult{ | ||
216 | Value: result, | ||
217 | Exists: true, | ||
218 | }, nil | ||
219 | } | ||
220 | |||
221 | // readObjectField is a generic method for reading objects out of FieldReaders | ||
222 | // based on the assumption that building an address of []string{k, FIELD} | ||
223 | // will result in the proper field data. | ||
224 | func readObjectField( | ||
225 | r FieldReader, | ||
226 | addr []string, | ||
227 | schema map[string]*Schema) (FieldReadResult, error) { | ||
228 | result := make(map[string]interface{}) | ||
229 | exists := false | ||
230 | for field, s := range schema { | ||
231 | addrRead := make([]string, len(addr), len(addr)+1) | ||
232 | copy(addrRead, addr) | ||
233 | addrRead = append(addrRead, field) | ||
234 | rawResult, err := r.ReadField(addrRead) | ||
235 | if err != nil { | ||
236 | return FieldReadResult{}, err | ||
237 | } | ||
238 | if rawResult.Exists { | ||
239 | exists = true | ||
240 | } | ||
241 | |||
242 | result[field] = rawResult.ValueOrZero(s) | ||
243 | } | ||
244 | |||
245 | return FieldReadResult{ | ||
246 | Value: result, | ||
247 | Exists: exists, | ||
248 | }, nil | ||
249 | } | ||
250 | |||
251 | // convert map values to the proper primitive type based on schema.Elem | ||
252 | func mapValuesToPrimitive(m map[string]interface{}, schema *Schema) error { | ||
253 | |||
254 | elemType := TypeString | ||
255 | if et, ok := schema.Elem.(ValueType); ok { | ||
256 | elemType = et | ||
257 | } | ||
258 | |||
259 | switch elemType { | ||
260 | case TypeInt, TypeFloat, TypeBool: | ||
261 | for k, v := range m { | ||
262 | vs, ok := v.(string) | ||
263 | if !ok { | ||
264 | continue | ||
265 | } | ||
266 | |||
267 | v, err := stringToPrimitive(vs, false, &Schema{Type: elemType}) | ||
268 | if err != nil { | ||
269 | return err | ||
270 | } | ||
271 | |||
272 | m[k] = v | ||
273 | } | ||
274 | } | ||
275 | return nil | ||
276 | } | ||
277 | |||
278 | func stringToPrimitive( | ||
279 | value string, computed bool, schema *Schema) (interface{}, error) { | ||
280 | var returnVal interface{} | ||
281 | switch schema.Type { | ||
282 | case TypeBool: | ||
283 | if value == "" { | ||
284 | returnVal = false | ||
285 | break | ||
286 | } | ||
287 | if computed { | ||
288 | break | ||
289 | } | ||
290 | |||
291 | v, err := strconv.ParseBool(value) | ||
292 | if err != nil { | ||
293 | return nil, err | ||
294 | } | ||
295 | |||
296 | returnVal = v | ||
297 | case TypeFloat: | ||
298 | if value == "" { | ||
299 | returnVal = 0.0 | ||
300 | break | ||
301 | } | ||
302 | if computed { | ||
303 | break | ||
304 | } | ||
305 | |||
306 | v, err := strconv.ParseFloat(value, 64) | ||
307 | if err != nil { | ||
308 | return nil, err | ||
309 | } | ||
310 | |||
311 | returnVal = v | ||
312 | case TypeInt: | ||
313 | if value == "" { | ||
314 | returnVal = 0 | ||
315 | break | ||
316 | } | ||
317 | if computed { | ||
318 | break | ||
319 | } | ||
320 | |||
321 | v, err := strconv.ParseInt(value, 0, 0) | ||
322 | if err != nil { | ||
323 | return nil, err | ||
324 | } | ||
325 | |||
326 | returnVal = int(v) | ||
327 | case TypeString: | ||
328 | returnVal = value | ||
329 | default: | ||
330 | panic(fmt.Sprintf("Unknown type: %s", schema.Type)) | ||
331 | } | ||
332 | |||
333 | return returnVal, nil | ||
334 | } | ||