diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go new file mode 100644 index 0000000..b2bc8f6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go | |||
@@ -0,0 +1,502 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "reflect" | ||
6 | "strings" | ||
7 | "sync" | ||
8 | "time" | ||
9 | |||
10 | "github.com/hashicorp/terraform/terraform" | ||
11 | ) | ||
12 | |||
13 | // ResourceData is used to query and set the attributes of a resource. | ||
14 | // | ||
15 | // ResourceData is the primary argument received for CRUD operations on | ||
16 | // a resource as well as configuration of a provider. It is a powerful | ||
17 | // structure that can be used to not only query data, but check for changes, | ||
18 | // define partial state updates, etc. | ||
19 | // | ||
20 | // The most relevant methods to take a look at are Get, Set, and Partial. | ||
21 | type ResourceData struct { | ||
22 | // Settable (internally) | ||
23 | schema map[string]*Schema | ||
24 | config *terraform.ResourceConfig | ||
25 | state *terraform.InstanceState | ||
26 | diff *terraform.InstanceDiff | ||
27 | meta map[string]interface{} | ||
28 | timeouts *ResourceTimeout | ||
29 | |||
30 | // Don't set | ||
31 | multiReader *MultiLevelFieldReader | ||
32 | setWriter *MapFieldWriter | ||
33 | newState *terraform.InstanceState | ||
34 | partial bool | ||
35 | partialMap map[string]struct{} | ||
36 | once sync.Once | ||
37 | isNew bool | ||
38 | } | ||
39 | |||
40 | // getResult is the internal structure that is generated when a Get | ||
41 | // is called that contains some extra data that might be used. | ||
42 | type getResult struct { | ||
43 | Value interface{} | ||
44 | ValueProcessed interface{} | ||
45 | Computed bool | ||
46 | Exists bool | ||
47 | Schema *Schema | ||
48 | } | ||
49 | |||
50 | // UnsafeSetFieldRaw allows setting arbitrary values in state to arbitrary | ||
51 | // values, bypassing schema. This MUST NOT be used in normal circumstances - | ||
52 | // it exists only to support the remote_state data source. | ||
53 | func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) { | ||
54 | d.once.Do(d.init) | ||
55 | |||
56 | d.setWriter.unsafeWriteField(key, value) | ||
57 | } | ||
58 | |||
59 | // Get returns the data for the given key, or nil if the key doesn't exist | ||
60 | // in the schema. | ||
61 | // | ||
62 | // If the key does exist in the schema but doesn't exist in the configuration, | ||
63 | // then the default value for that type will be returned. For strings, this is | ||
64 | // "", for numbers it is 0, etc. | ||
65 | // | ||
66 | // If you want to test if something is set at all in the configuration, | ||
67 | // use GetOk. | ||
68 | func (d *ResourceData) Get(key string) interface{} { | ||
69 | v, _ := d.GetOk(key) | ||
70 | return v | ||
71 | } | ||
72 | |||
73 | // GetChange returns the old and new value for a given key. | ||
74 | // | ||
75 | // HasChange should be used to check if a change exists. It is possible | ||
76 | // that both the old and new value are the same if the old value was not | ||
77 | // set and the new value is. This is common, for example, for boolean | ||
78 | // fields which have a zero value of false. | ||
79 | func (d *ResourceData) GetChange(key string) (interface{}, interface{}) { | ||
80 | o, n := d.getChange(key, getSourceState, getSourceDiff) | ||
81 | return o.Value, n.Value | ||
82 | } | ||
83 | |||
84 | // GetOk returns the data for the given key and whether or not the key | ||
85 | // has been set to a non-zero value at some point. | ||
86 | // | ||
87 | // The first result will not necessarilly be nil if the value doesn't exist. | ||
88 | // The second result should be checked to determine this information. | ||
89 | func (d *ResourceData) GetOk(key string) (interface{}, bool) { | ||
90 | r := d.getRaw(key, getSourceSet) | ||
91 | exists := r.Exists && !r.Computed | ||
92 | if exists { | ||
93 | // If it exists, we also want to verify it is not the zero-value. | ||
94 | value := r.Value | ||
95 | zero := r.Schema.Type.Zero() | ||
96 | |||
97 | if eq, ok := value.(Equal); ok { | ||
98 | exists = !eq.Equal(zero) | ||
99 | } else { | ||
100 | exists = !reflect.DeepEqual(value, zero) | ||
101 | } | ||
102 | } | ||
103 | |||
104 | return r.Value, exists | ||
105 | } | ||
106 | |||
107 | func (d *ResourceData) getRaw(key string, level getSource) getResult { | ||
108 | var parts []string | ||
109 | if key != "" { | ||
110 | parts = strings.Split(key, ".") | ||
111 | } | ||
112 | |||
113 | return d.get(parts, level) | ||
114 | } | ||
115 | |||
116 | // HasChange returns whether or not the given key has been changed. | ||
117 | func (d *ResourceData) HasChange(key string) bool { | ||
118 | o, n := d.GetChange(key) | ||
119 | |||
120 | // If the type implements the Equal interface, then call that | ||
121 | // instead of just doing a reflect.DeepEqual. An example where this is | ||
122 | // needed is *Set | ||
123 | if eq, ok := o.(Equal); ok { | ||
124 | return !eq.Equal(n) | ||
125 | } | ||
126 | |||
127 | return !reflect.DeepEqual(o, n) | ||
128 | } | ||
129 | |||
130 | // Partial turns partial state mode on/off. | ||
131 | // | ||
132 | // When partial state mode is enabled, then only key prefixes specified | ||
133 | // by SetPartial will be in the final state. This allows providers to return | ||
134 | // partial states for partially applied resources (when errors occur). | ||
135 | func (d *ResourceData) Partial(on bool) { | ||
136 | d.partial = on | ||
137 | if on { | ||
138 | if d.partialMap == nil { | ||
139 | d.partialMap = make(map[string]struct{}) | ||
140 | } | ||
141 | } else { | ||
142 | d.partialMap = nil | ||
143 | } | ||
144 | } | ||
145 | |||
146 | // Set sets the value for the given key. | ||
147 | // | ||
148 | // If the key is invalid or the value is not a correct type, an error | ||
149 | // will be returned. | ||
150 | func (d *ResourceData) Set(key string, value interface{}) error { | ||
151 | d.once.Do(d.init) | ||
152 | |||
153 | // If the value is a pointer to a non-struct, get its value and | ||
154 | // use that. This allows Set to take a pointer to primitives to | ||
155 | // simplify the interface. | ||
156 | reflectVal := reflect.ValueOf(value) | ||
157 | if reflectVal.Kind() == reflect.Ptr { | ||
158 | if reflectVal.IsNil() { | ||
159 | // If the pointer is nil, then the value is just nil | ||
160 | value = nil | ||
161 | } else { | ||
162 | // Otherwise, we dereference the pointer as long as its not | ||
163 | // a pointer to a struct, since struct pointers are allowed. | ||
164 | reflectVal = reflect.Indirect(reflectVal) | ||
165 | if reflectVal.Kind() != reflect.Struct { | ||
166 | value = reflectVal.Interface() | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | |||
171 | return d.setWriter.WriteField(strings.Split(key, "."), value) | ||
172 | } | ||
173 | |||
174 | // SetPartial adds the key to the final state output while | ||
175 | // in partial state mode. The key must be a root key in the schema (i.e. | ||
176 | // it cannot be "list.0"). | ||
177 | // | ||
178 | // If partial state mode is disabled, then this has no effect. Additionally, | ||
179 | // whenever partial state mode is toggled, the partial data is cleared. | ||
180 | func (d *ResourceData) SetPartial(k string) { | ||
181 | if d.partial { | ||
182 | d.partialMap[k] = struct{}{} | ||
183 | } | ||
184 | } | ||
185 | |||
186 | func (d *ResourceData) MarkNewResource() { | ||
187 | d.isNew = true | ||
188 | } | ||
189 | |||
190 | func (d *ResourceData) IsNewResource() bool { | ||
191 | return d.isNew | ||
192 | } | ||
193 | |||
194 | // Id returns the ID of the resource. | ||
195 | func (d *ResourceData) Id() string { | ||
196 | var result string | ||
197 | |||
198 | if d.state != nil { | ||
199 | result = d.state.ID | ||
200 | } | ||
201 | |||
202 | if d.newState != nil { | ||
203 | result = d.newState.ID | ||
204 | } | ||
205 | |||
206 | return result | ||
207 | } | ||
208 | |||
209 | // ConnInfo returns the connection info for this resource. | ||
210 | func (d *ResourceData) ConnInfo() map[string]string { | ||
211 | if d.newState != nil { | ||
212 | return d.newState.Ephemeral.ConnInfo | ||
213 | } | ||
214 | |||
215 | if d.state != nil { | ||
216 | return d.state.Ephemeral.ConnInfo | ||
217 | } | ||
218 | |||
219 | return nil | ||
220 | } | ||
221 | |||
222 | // SetId sets the ID of the resource. If the value is blank, then the | ||
223 | // resource is destroyed. | ||
224 | func (d *ResourceData) SetId(v string) { | ||
225 | d.once.Do(d.init) | ||
226 | d.newState.ID = v | ||
227 | } | ||
228 | |||
229 | // SetConnInfo sets the connection info for a resource. | ||
230 | func (d *ResourceData) SetConnInfo(v map[string]string) { | ||
231 | d.once.Do(d.init) | ||
232 | d.newState.Ephemeral.ConnInfo = v | ||
233 | } | ||
234 | |||
235 | // SetType sets the ephemeral type for the data. This is only required | ||
236 | // for importing. | ||
237 | func (d *ResourceData) SetType(t string) { | ||
238 | d.once.Do(d.init) | ||
239 | d.newState.Ephemeral.Type = t | ||
240 | } | ||
241 | |||
242 | // State returns the new InstanceState after the diff and any Set | ||
243 | // calls. | ||
244 | func (d *ResourceData) State() *terraform.InstanceState { | ||
245 | var result terraform.InstanceState | ||
246 | result.ID = d.Id() | ||
247 | result.Meta = d.meta | ||
248 | |||
249 | // If we have no ID, then this resource doesn't exist and we just | ||
250 | // return nil. | ||
251 | if result.ID == "" { | ||
252 | return nil | ||
253 | } | ||
254 | |||
255 | if d.timeouts != nil { | ||
256 | if err := d.timeouts.StateEncode(&result); err != nil { | ||
257 | log.Printf("[ERR] Error encoding Timeout meta to Instance State: %s", err) | ||
258 | } | ||
259 | } | ||
260 | |||
261 | // Look for a magic key in the schema that determines we skip the | ||
262 | // integrity check of fields existing in the schema, allowing dynamic | ||
263 | // keys to be created. | ||
264 | hasDynamicAttributes := false | ||
265 | for k, _ := range d.schema { | ||
266 | if k == "__has_dynamic_attributes" { | ||
267 | hasDynamicAttributes = true | ||
268 | log.Printf("[INFO] Resource %s has dynamic attributes", result.ID) | ||
269 | } | ||
270 | } | ||
271 | |||
272 | // In order to build the final state attributes, we read the full | ||
273 | // attribute set as a map[string]interface{}, write it to a MapFieldWriter, | ||
274 | // and then use that map. | ||
275 | rawMap := make(map[string]interface{}) | ||
276 | for k := range d.schema { | ||
277 | source := getSourceSet | ||
278 | if d.partial { | ||
279 | source = getSourceState | ||
280 | if _, ok := d.partialMap[k]; ok { | ||
281 | source = getSourceSet | ||
282 | } | ||
283 | } | ||
284 | |||
285 | raw := d.get([]string{k}, source) | ||
286 | if raw.Exists && !raw.Computed { | ||
287 | rawMap[k] = raw.Value | ||
288 | if raw.ValueProcessed != nil { | ||
289 | rawMap[k] = raw.ValueProcessed | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | |||
294 | mapW := &MapFieldWriter{Schema: d.schema} | ||
295 | if err := mapW.WriteField(nil, rawMap); err != nil { | ||
296 | return nil | ||
297 | } | ||
298 | |||
299 | result.Attributes = mapW.Map() | ||
300 | |||
301 | if hasDynamicAttributes { | ||
302 | // If we have dynamic attributes, just copy the attributes map | ||
303 | // one for one into the result attributes. | ||
304 | for k, v := range d.setWriter.Map() { | ||
305 | // Don't clobber schema values. This limits usage of dynamic | ||
306 | // attributes to names which _do not_ conflict with schema | ||
307 | // keys! | ||
308 | if _, ok := result.Attributes[k]; !ok { | ||
309 | result.Attributes[k] = v | ||
310 | } | ||
311 | } | ||
312 | } | ||
313 | |||
314 | if d.newState != nil { | ||
315 | result.Ephemeral = d.newState.Ephemeral | ||
316 | } | ||
317 | |||
318 | // TODO: This is hacky and we can remove this when we have a proper | ||
319 | // state writer. We should instead have a proper StateFieldWriter | ||
320 | // and use that. | ||
321 | for k, schema := range d.schema { | ||
322 | if schema.Type != TypeMap { | ||
323 | continue | ||
324 | } | ||
325 | |||
326 | if result.Attributes[k] == "" { | ||
327 | delete(result.Attributes, k) | ||
328 | } | ||
329 | } | ||
330 | |||
331 | if v := d.Id(); v != "" { | ||
332 | result.Attributes["id"] = d.Id() | ||
333 | } | ||
334 | |||
335 | if d.state != nil { | ||
336 | result.Tainted = d.state.Tainted | ||
337 | } | ||
338 | |||
339 | return &result | ||
340 | } | ||
341 | |||
342 | // Timeout returns the data for the given timeout key | ||
343 | // Returns a duration of 20 minutes for any key not found, or not found and no default. | ||
344 | func (d *ResourceData) Timeout(key string) time.Duration { | ||
345 | key = strings.ToLower(key) | ||
346 | |||
347 | var timeout *time.Duration | ||
348 | switch key { | ||
349 | case TimeoutCreate: | ||
350 | timeout = d.timeouts.Create | ||
351 | case TimeoutRead: | ||
352 | timeout = d.timeouts.Read | ||
353 | case TimeoutUpdate: | ||
354 | timeout = d.timeouts.Update | ||
355 | case TimeoutDelete: | ||
356 | timeout = d.timeouts.Delete | ||
357 | } | ||
358 | |||
359 | if timeout != nil { | ||
360 | return *timeout | ||
361 | } | ||
362 | |||
363 | if d.timeouts.Default != nil { | ||
364 | return *d.timeouts.Default | ||
365 | } | ||
366 | |||
367 | // Return system default of 20 minutes | ||
368 | return 20 * time.Minute | ||
369 | } | ||
370 | |||
371 | func (d *ResourceData) init() { | ||
372 | // Initialize the field that will store our new state | ||
373 | var copyState terraform.InstanceState | ||
374 | if d.state != nil { | ||
375 | copyState = *d.state.DeepCopy() | ||
376 | } | ||
377 | d.newState = ©State | ||
378 | |||
379 | // Initialize the map for storing set data | ||
380 | d.setWriter = &MapFieldWriter{Schema: d.schema} | ||
381 | |||
382 | // Initialize the reader for getting data from the | ||
383 | // underlying sources (config, diff, etc.) | ||
384 | readers := make(map[string]FieldReader) | ||
385 | var stateAttributes map[string]string | ||
386 | if d.state != nil { | ||
387 | stateAttributes = d.state.Attributes | ||
388 | readers["state"] = &MapFieldReader{ | ||
389 | Schema: d.schema, | ||
390 | Map: BasicMapReader(stateAttributes), | ||
391 | } | ||
392 | } | ||
393 | if d.config != nil { | ||
394 | readers["config"] = &ConfigFieldReader{ | ||
395 | Schema: d.schema, | ||
396 | Config: d.config, | ||
397 | } | ||
398 | } | ||
399 | if d.diff != nil { | ||
400 | readers["diff"] = &DiffFieldReader{ | ||
401 | Schema: d.schema, | ||
402 | Diff: d.diff, | ||
403 | Source: &MultiLevelFieldReader{ | ||
404 | Levels: []string{"state", "config"}, | ||
405 | Readers: readers, | ||
406 | }, | ||
407 | } | ||
408 | } | ||
409 | readers["set"] = &MapFieldReader{ | ||
410 | Schema: d.schema, | ||
411 | Map: BasicMapReader(d.setWriter.Map()), | ||
412 | } | ||
413 | d.multiReader = &MultiLevelFieldReader{ | ||
414 | Levels: []string{ | ||
415 | "state", | ||
416 | "config", | ||
417 | "diff", | ||
418 | "set", | ||
419 | }, | ||
420 | |||
421 | Readers: readers, | ||
422 | } | ||
423 | } | ||
424 | |||
425 | func (d *ResourceData) diffChange( | ||
426 | k string) (interface{}, interface{}, bool, bool) { | ||
427 | // Get the change between the state and the config. | ||
428 | o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact) | ||
429 | if !o.Exists { | ||
430 | o.Value = nil | ||
431 | } | ||
432 | if !n.Exists { | ||
433 | n.Value = nil | ||
434 | } | ||
435 | |||
436 | // Return the old, new, and whether there is a change | ||
437 | return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed | ||
438 | } | ||
439 | |||
440 | func (d *ResourceData) getChange( | ||
441 | k string, | ||
442 | oldLevel getSource, | ||
443 | newLevel getSource) (getResult, getResult) { | ||
444 | var parts, parts2 []string | ||
445 | if k != "" { | ||
446 | parts = strings.Split(k, ".") | ||
447 | parts2 = strings.Split(k, ".") | ||
448 | } | ||
449 | |||
450 | o := d.get(parts, oldLevel) | ||
451 | n := d.get(parts2, newLevel) | ||
452 | return o, n | ||
453 | } | ||
454 | |||
455 | func (d *ResourceData) get(addr []string, source getSource) getResult { | ||
456 | d.once.Do(d.init) | ||
457 | |||
458 | level := "set" | ||
459 | flags := source & ^getSourceLevelMask | ||
460 | exact := flags&getSourceExact != 0 | ||
461 | source = source & getSourceLevelMask | ||
462 | if source >= getSourceSet { | ||
463 | level = "set" | ||
464 | } else if source >= getSourceDiff { | ||
465 | level = "diff" | ||
466 | } else if source >= getSourceConfig { | ||
467 | level = "config" | ||
468 | } else { | ||
469 | level = "state" | ||
470 | } | ||
471 | |||
472 | var result FieldReadResult | ||
473 | var err error | ||
474 | if exact { | ||
475 | result, err = d.multiReader.ReadFieldExact(addr, level) | ||
476 | } else { | ||
477 | result, err = d.multiReader.ReadFieldMerge(addr, level) | ||
478 | } | ||
479 | if err != nil { | ||
480 | panic(err) | ||
481 | } | ||
482 | |||
483 | // If the result doesn't exist, then we set the value to the zero value | ||
484 | var schema *Schema | ||
485 | if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { | ||
486 | schema = schemaL[len(schemaL)-1] | ||
487 | } | ||
488 | |||
489 | if result.Value == nil && schema != nil { | ||
490 | result.Value = result.ValueOrZero(schema) | ||
491 | } | ||
492 | |||
493 | // Transform the FieldReadResult into a getResult. It might be worth | ||
494 | // merging these two structures one day. | ||
495 | return getResult{ | ||
496 | Value: result.Value, | ||
497 | ValueProcessed: result.ValueProcessed, | ||
498 | Computed: result.Computed, | ||
499 | Exists: result.Exists, | ||
500 | Schema: schema, | ||
501 | } | ||
502 | } | ||