diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go new file mode 100644 index 0000000..7db3dec --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go | |||
@@ -0,0 +1,559 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "fmt" | ||
6 | "reflect" | ||
7 | "strings" | ||
8 | "sync" | ||
9 | |||
10 | "github.com/hashicorp/terraform/terraform" | ||
11 | ) | ||
12 | |||
13 | // newValueWriter is a minor re-implementation of MapFieldWriter to include | ||
14 | // keys that should be marked as computed, to represent the new part of a | ||
15 | // pseudo-diff. | ||
16 | type newValueWriter struct { | ||
17 | *MapFieldWriter | ||
18 | |||
19 | // A list of keys that should be marked as computed. | ||
20 | computedKeys map[string]bool | ||
21 | |||
22 | // A lock to prevent races on writes. The underlying writer will have one as | ||
23 | // well - this is for computed keys. | ||
24 | lock sync.Mutex | ||
25 | |||
26 | // To be used with init. | ||
27 | once sync.Once | ||
28 | } | ||
29 | |||
30 | // init performs any initialization tasks for the newValueWriter. | ||
31 | func (w *newValueWriter) init() { | ||
32 | if w.computedKeys == nil { | ||
33 | w.computedKeys = make(map[string]bool) | ||
34 | } | ||
35 | } | ||
36 | |||
37 | // WriteField overrides MapValueWriter's WriteField, adding the ability to flag | ||
38 | // the address as computed. | ||
39 | func (w *newValueWriter) WriteField(address []string, value interface{}, computed bool) error { | ||
40 | // Fail the write if we have a non-nil value and computed is true. | ||
41 | // NewComputed values should not have a value when written. | ||
42 | if value != nil && computed { | ||
43 | return errors.New("Non-nil value with computed set") | ||
44 | } | ||
45 | |||
46 | if err := w.MapFieldWriter.WriteField(address, value); err != nil { | ||
47 | return err | ||
48 | } | ||
49 | |||
50 | w.once.Do(w.init) | ||
51 | |||
52 | w.lock.Lock() | ||
53 | defer w.lock.Unlock() | ||
54 | if computed { | ||
55 | w.computedKeys[strings.Join(address, ".")] = true | ||
56 | } | ||
57 | return nil | ||
58 | } | ||
59 | |||
60 | // ComputedKeysMap returns the underlying computed keys map. | ||
61 | func (w *newValueWriter) ComputedKeysMap() map[string]bool { | ||
62 | w.once.Do(w.init) | ||
63 | return w.computedKeys | ||
64 | } | ||
65 | |||
66 | // newValueReader is a minor re-implementation of MapFieldReader and is the | ||
67 | // read counterpart to MapValueWriter, allowing the read of keys flagged as | ||
68 | // computed to accommodate the diff override logic in ResourceDiff. | ||
69 | type newValueReader struct { | ||
70 | *MapFieldReader | ||
71 | |||
72 | // The list of computed keys from a newValueWriter. | ||
73 | computedKeys map[string]bool | ||
74 | } | ||
75 | |||
76 | // ReadField reads the values from the underlying writer, returning the | ||
77 | // computed value if it is found as well. | ||
78 | func (r *newValueReader) ReadField(address []string) (FieldReadResult, error) { | ||
79 | addrKey := strings.Join(address, ".") | ||
80 | v, err := r.MapFieldReader.ReadField(address) | ||
81 | if err != nil { | ||
82 | return FieldReadResult{}, err | ||
83 | } | ||
84 | for computedKey := range r.computedKeys { | ||
85 | if childAddrOf(addrKey, computedKey) { | ||
86 | if strings.HasSuffix(addrKey, ".#") { | ||
87 | // This is a count value for a list or set that has been marked as | ||
88 | // computed, or a sub-list/sub-set of a complex resource that has | ||
89 | // been marked as computed. We need to pass through to other readers | ||
90 | // so that an accurate previous count can be fetched for the diff. | ||
91 | v.Exists = false | ||
92 | } | ||
93 | v.Computed = true | ||
94 | } | ||
95 | } | ||
96 | |||
97 | return v, nil | ||
98 | } | ||
99 | |||
100 | // ResourceDiff is used to query and make custom changes to an in-flight diff. | ||
101 | // It can be used to veto particular changes in the diff, customize the diff | ||
102 | // that has been created, or diff values not controlled by config. | ||
103 | // | ||
104 | // The object functions similar to ResourceData, however most notably lacks | ||
105 | // Set, SetPartial, and Partial, as it should be used to change diff values | ||
106 | // only. Most other first-class ResourceData functions exist, namely Get, | ||
107 | // GetOk, HasChange, and GetChange exist. | ||
108 | // | ||
109 | // All functions in ResourceDiff, save for ForceNew, can only be used on | ||
110 | // computed fields. | ||
111 | type ResourceDiff struct { | ||
112 | // The schema for the resource being worked on. | ||
113 | schema map[string]*Schema | ||
114 | |||
115 | // The current config for this resource. | ||
116 | config *terraform.ResourceConfig | ||
117 | |||
118 | // The state for this resource as it exists post-refresh, after the initial | ||
119 | // diff. | ||
120 | state *terraform.InstanceState | ||
121 | |||
122 | // The diff created by Terraform. This diff is used, along with state, | ||
123 | // config, and custom-set diff data, to provide a multi-level reader | ||
124 | // experience similar to ResourceData. | ||
125 | diff *terraform.InstanceDiff | ||
126 | |||
127 | // The internal reader structure that contains the state, config, the default | ||
128 | // diff, and the new diff. | ||
129 | multiReader *MultiLevelFieldReader | ||
130 | |||
131 | // A writer that writes overridden new fields. | ||
132 | newWriter *newValueWriter | ||
133 | |||
134 | // Tracks which keys have been updated by ResourceDiff to ensure that the | ||
135 | // diff does not get re-run on keys that were not touched, or diffs that were | ||
136 | // just removed (re-running on the latter would just roll back the removal). | ||
137 | updatedKeys map[string]bool | ||
138 | |||
139 | // Tracks which keys were flagged as forceNew. These keys are not saved in | ||
140 | // newWriter, but we need to track them so that they can be re-diffed later. | ||
141 | forcedNewKeys map[string]bool | ||
142 | } | ||
143 | |||
144 | // newResourceDiff creates a new ResourceDiff instance. | ||
145 | func newResourceDiff(schema map[string]*Schema, config *terraform.ResourceConfig, state *terraform.InstanceState, diff *terraform.InstanceDiff) *ResourceDiff { | ||
146 | d := &ResourceDiff{ | ||
147 | config: config, | ||
148 | state: state, | ||
149 | diff: diff, | ||
150 | schema: schema, | ||
151 | } | ||
152 | |||
153 | d.newWriter = &newValueWriter{ | ||
154 | MapFieldWriter: &MapFieldWriter{Schema: d.schema}, | ||
155 | } | ||
156 | readers := make(map[string]FieldReader) | ||
157 | var stateAttributes map[string]string | ||
158 | if d.state != nil { | ||
159 | stateAttributes = d.state.Attributes | ||
160 | readers["state"] = &MapFieldReader{ | ||
161 | Schema: d.schema, | ||
162 | Map: BasicMapReader(stateAttributes), | ||
163 | } | ||
164 | } | ||
165 | if d.config != nil { | ||
166 | readers["config"] = &ConfigFieldReader{ | ||
167 | Schema: d.schema, | ||
168 | Config: d.config, | ||
169 | } | ||
170 | } | ||
171 | if d.diff != nil { | ||
172 | readers["diff"] = &DiffFieldReader{ | ||
173 | Schema: d.schema, | ||
174 | Diff: d.diff, | ||
175 | Source: &MultiLevelFieldReader{ | ||
176 | Levels: []string{"state", "config"}, | ||
177 | Readers: readers, | ||
178 | }, | ||
179 | } | ||
180 | } | ||
181 | readers["newDiff"] = &newValueReader{ | ||
182 | MapFieldReader: &MapFieldReader{ | ||
183 | Schema: d.schema, | ||
184 | Map: BasicMapReader(d.newWriter.Map()), | ||
185 | }, | ||
186 | computedKeys: d.newWriter.ComputedKeysMap(), | ||
187 | } | ||
188 | d.multiReader = &MultiLevelFieldReader{ | ||
189 | Levels: []string{ | ||
190 | "state", | ||
191 | "config", | ||
192 | "diff", | ||
193 | "newDiff", | ||
194 | }, | ||
195 | |||
196 | Readers: readers, | ||
197 | } | ||
198 | |||
199 | d.updatedKeys = make(map[string]bool) | ||
200 | d.forcedNewKeys = make(map[string]bool) | ||
201 | |||
202 | return d | ||
203 | } | ||
204 | |||
205 | // UpdatedKeys returns the keys that were updated by this ResourceDiff run. | ||
206 | // These are the only keys that a diff should be re-calculated for. | ||
207 | // | ||
208 | // This is the combined result of both keys for which diff values were updated | ||
209 | // for or cleared, and also keys that were flagged to be re-diffed as a result | ||
210 | // of ForceNew. | ||
211 | func (d *ResourceDiff) UpdatedKeys() []string { | ||
212 | var s []string | ||
213 | for k := range d.updatedKeys { | ||
214 | s = append(s, k) | ||
215 | } | ||
216 | for k := range d.forcedNewKeys { | ||
217 | for _, l := range s { | ||
218 | if k == l { | ||
219 | break | ||
220 | } | ||
221 | } | ||
222 | s = append(s, k) | ||
223 | } | ||
224 | return s | ||
225 | } | ||
226 | |||
227 | // Clear wipes the diff for a particular key. It is called by ResourceDiff's | ||
228 | // functionality to remove any possibility of conflicts, but can be called on | ||
229 | // its own to just remove a specific key from the diff completely. | ||
230 | // | ||
231 | // Note that this does not wipe an override. This function is only allowed on | ||
232 | // computed keys. | ||
233 | func (d *ResourceDiff) Clear(key string) error { | ||
234 | if err := d.checkKey(key, "Clear", true); err != nil { | ||
235 | return err | ||
236 | } | ||
237 | |||
238 | return d.clear(key) | ||
239 | } | ||
240 | |||
241 | func (d *ResourceDiff) clear(key string) error { | ||
242 | // Check the schema to make sure that this key exists first. | ||
243 | schemaL := addrToSchema(strings.Split(key, "."), d.schema) | ||
244 | if len(schemaL) == 0 { | ||
245 | return fmt.Errorf("%s is not a valid key", key) | ||
246 | } | ||
247 | |||
248 | for k := range d.diff.Attributes { | ||
249 | if strings.HasPrefix(k, key) { | ||
250 | delete(d.diff.Attributes, k) | ||
251 | } | ||
252 | } | ||
253 | return nil | ||
254 | } | ||
255 | |||
256 | // GetChangedKeysPrefix helps to implement Resource.CustomizeDiff | ||
257 | // where we need to act on all nested fields | ||
258 | // without calling out each one separately | ||
259 | func (d *ResourceDiff) GetChangedKeysPrefix(prefix string) []string { | ||
260 | keys := make([]string, 0) | ||
261 | for k := range d.diff.Attributes { | ||
262 | if strings.HasPrefix(k, prefix) { | ||
263 | keys = append(keys, k) | ||
264 | } | ||
265 | } | ||
266 | return keys | ||
267 | } | ||
268 | |||
269 | // diffChange helps to implement resourceDiffer and derives its change values | ||
270 | // from ResourceDiff's own change data, in addition to existing diff, config, and state. | ||
271 | func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool, bool) { | ||
272 | old, new, customized := d.getChange(key) | ||
273 | |||
274 | if !old.Exists { | ||
275 | old.Value = nil | ||
276 | } | ||
277 | if !new.Exists || d.removed(key) { | ||
278 | new.Value = nil | ||
279 | } | ||
280 | |||
281 | return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed, customized | ||
282 | } | ||
283 | |||
284 | // SetNew is used to set a new diff value for the mentioned key. The value must | ||
285 | // be correct for the attribute's schema (mostly relevant for maps, lists, and | ||
286 | // sets). The original value from the state is used as the old value. | ||
287 | // | ||
288 | // This function is only allowed on computed attributes. | ||
289 | func (d *ResourceDiff) SetNew(key string, value interface{}) error { | ||
290 | if err := d.checkKey(key, "SetNew", false); err != nil { | ||
291 | return err | ||
292 | } | ||
293 | |||
294 | return d.setDiff(key, value, false) | ||
295 | } | ||
296 | |||
297 | // SetNewComputed functions like SetNew, except that it blanks out a new value | ||
298 | // and marks it as computed. | ||
299 | // | ||
300 | // This function is only allowed on computed attributes. | ||
301 | func (d *ResourceDiff) SetNewComputed(key string) error { | ||
302 | if err := d.checkKey(key, "SetNewComputed", false); err != nil { | ||
303 | return err | ||
304 | } | ||
305 | |||
306 | return d.setDiff(key, nil, true) | ||
307 | } | ||
308 | |||
309 | // setDiff performs common diff setting behaviour. | ||
310 | func (d *ResourceDiff) setDiff(key string, new interface{}, computed bool) error { | ||
311 | if err := d.clear(key); err != nil { | ||
312 | return err | ||
313 | } | ||
314 | |||
315 | if err := d.newWriter.WriteField(strings.Split(key, "."), new, computed); err != nil { | ||
316 | return fmt.Errorf("Cannot set new diff value for key %s: %s", key, err) | ||
317 | } | ||
318 | |||
319 | d.updatedKeys[key] = true | ||
320 | |||
321 | return nil | ||
322 | } | ||
323 | |||
324 | // ForceNew force-flags ForceNew in the schema for a specific key, and | ||
325 | // re-calculates its diff, effectively causing this attribute to force a new | ||
326 | // resource. | ||
327 | // | ||
328 | // Keep in mind that forcing a new resource will force a second run of the | ||
329 | // resource's CustomizeDiff function (with a new ResourceDiff) once the current | ||
330 | // one has completed. This second run is performed without state. This behavior | ||
331 | // will be the same as if a new resource is being created and is performed to | ||
332 | // ensure that the diff looks like the diff for a new resource as much as | ||
333 | // possible. CustomizeDiff should expect such a scenario and act correctly. | ||
334 | // | ||
335 | // This function is a no-op/error if there is no diff. | ||
336 | // | ||
337 | // Note that the change to schema is permanent for the lifecycle of this | ||
338 | // specific ResourceDiff instance. | ||
339 | func (d *ResourceDiff) ForceNew(key string) error { | ||
340 | if !d.HasChange(key) { | ||
341 | return fmt.Errorf("ForceNew: No changes for %s", key) | ||
342 | } | ||
343 | |||
344 | keyParts := strings.Split(key, ".") | ||
345 | var schema *Schema | ||
346 | schemaL := addrToSchema(keyParts, d.schema) | ||
347 | if len(schemaL) > 0 { | ||
348 | schema = schemaL[len(schemaL)-1] | ||
349 | } else { | ||
350 | return fmt.Errorf("ForceNew: %s is not a valid key", key) | ||
351 | } | ||
352 | |||
353 | schema.ForceNew = true | ||
354 | |||
355 | // Flag this for a re-diff. Don't save any values to guarantee that existing | ||
356 | // diffs aren't messed with, as this gets messy when dealing with complex | ||
357 | // structures, zero values, etc. | ||
358 | d.forcedNewKeys[keyParts[0]] = true | ||
359 | |||
360 | return nil | ||
361 | } | ||
362 | |||
363 | // Get hands off to ResourceData.Get. | ||
364 | func (d *ResourceDiff) Get(key string) interface{} { | ||
365 | r, _ := d.GetOk(key) | ||
366 | return r | ||
367 | } | ||
368 | |||
369 | // GetChange gets the change between the state and diff, checking first to see | ||
370 | // if a overridden diff exists. | ||
371 | // | ||
372 | // This implementation differs from ResourceData's in the way that we first get | ||
373 | // results from the exact levels for the new diff, then from state and diff as | ||
374 | // per normal. | ||
375 | func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) { | ||
376 | old, new, _ := d.getChange(key) | ||
377 | return old.Value, new.Value | ||
378 | } | ||
379 | |||
380 | // GetOk functions the same way as ResourceData.GetOk, but it also checks the | ||
381 | // new diff levels to provide data consistent with the current state of the | ||
382 | // customized diff. | ||
383 | func (d *ResourceDiff) GetOk(key string) (interface{}, bool) { | ||
384 | r := d.get(strings.Split(key, "."), "newDiff") | ||
385 | exists := r.Exists && !r.Computed | ||
386 | if exists { | ||
387 | // If it exists, we also want to verify it is not the zero-value. | ||
388 | value := r.Value | ||
389 | zero := r.Schema.Type.Zero() | ||
390 | |||
391 | if eq, ok := value.(Equal); ok { | ||
392 | exists = !eq.Equal(zero) | ||
393 | } else { | ||
394 | exists = !reflect.DeepEqual(value, zero) | ||
395 | } | ||
396 | } | ||
397 | |||
398 | return r.Value, exists | ||
399 | } | ||
400 | |||
401 | // GetOkExists functions the same way as GetOkExists within ResourceData, but | ||
402 | // it also checks the new diff levels to provide data consistent with the | ||
403 | // current state of the customized diff. | ||
404 | // | ||
405 | // This is nearly the same function as GetOk, yet it does not check | ||
406 | // for the zero value of the attribute's type. This allows for attributes | ||
407 | // without a default, to fully check for a literal assignment, regardless | ||
408 | // of the zero-value for that type. | ||
409 | func (d *ResourceDiff) GetOkExists(key string) (interface{}, bool) { | ||
410 | r := d.get(strings.Split(key, "."), "newDiff") | ||
411 | exists := r.Exists && !r.Computed | ||
412 | return r.Value, exists | ||
413 | } | ||
414 | |||
415 | // NewValueKnown returns true if the new value for the given key is available | ||
416 | // as its final value at diff time. If the return value is false, this means | ||
417 | // either the value is based of interpolation that was unavailable at diff | ||
418 | // time, or that the value was explicitly marked as computed by SetNewComputed. | ||
419 | func (d *ResourceDiff) NewValueKnown(key string) bool { | ||
420 | r := d.get(strings.Split(key, "."), "newDiff") | ||
421 | return !r.Computed | ||
422 | } | ||
423 | |||
424 | // HasChange checks to see if there is a change between state and the diff, or | ||
425 | // in the overridden diff. | ||
426 | func (d *ResourceDiff) HasChange(key string) bool { | ||
427 | old, new := d.GetChange(key) | ||
428 | |||
429 | // If the type implements the Equal interface, then call that | ||
430 | // instead of just doing a reflect.DeepEqual. An example where this is | ||
431 | // needed is *Set | ||
432 | if eq, ok := old.(Equal); ok { | ||
433 | return !eq.Equal(new) | ||
434 | } | ||
435 | |||
436 | return !reflect.DeepEqual(old, new) | ||
437 | } | ||
438 | |||
439 | // Id returns the ID of this resource. | ||
440 | // | ||
441 | // Note that technically, ID does not change during diffs (it either has | ||
442 | // already changed in the refresh, or will change on update), hence we do not | ||
443 | // support updating the ID or fetching it from anything else other than state. | ||
444 | func (d *ResourceDiff) Id() string { | ||
445 | var result string | ||
446 | |||
447 | if d.state != nil { | ||
448 | result = d.state.ID | ||
449 | } | ||
450 | return result | ||
451 | } | ||
452 | |||
453 | // getChange gets values from two different levels, designed for use in | ||
454 | // diffChange, HasChange, and GetChange. | ||
455 | // | ||
456 | // This implementation differs from ResourceData's in the way that we first get | ||
457 | // results from the exact levels for the new diff, then from state and diff as | ||
458 | // per normal. | ||
459 | func (d *ResourceDiff) getChange(key string) (getResult, getResult, bool) { | ||
460 | old := d.get(strings.Split(key, "."), "state") | ||
461 | var new getResult | ||
462 | for p := range d.updatedKeys { | ||
463 | if childAddrOf(key, p) { | ||
464 | new = d.getExact(strings.Split(key, "."), "newDiff") | ||
465 | return old, new, true | ||
466 | } | ||
467 | } | ||
468 | new = d.get(strings.Split(key, "."), "newDiff") | ||
469 | return old, new, false | ||
470 | } | ||
471 | |||
472 | // removed checks to see if the key is present in the existing, pre-customized | ||
473 | // diff and if it was marked as NewRemoved. | ||
474 | func (d *ResourceDiff) removed(k string) bool { | ||
475 | diff, ok := d.diff.Attributes[k] | ||
476 | if !ok { | ||
477 | return false | ||
478 | } | ||
479 | return diff.NewRemoved | ||
480 | } | ||
481 | |||
482 | // get performs the appropriate multi-level reader logic for ResourceDiff, | ||
483 | // starting at source. Refer to newResourceDiff for the level order. | ||
484 | func (d *ResourceDiff) get(addr []string, source string) getResult { | ||
485 | result, err := d.multiReader.ReadFieldMerge(addr, source) | ||
486 | if err != nil { | ||
487 | panic(err) | ||
488 | } | ||
489 | |||
490 | return d.finalizeResult(addr, result) | ||
491 | } | ||
492 | |||
493 | // getExact gets an attribute from the exact level referenced by source. | ||
494 | func (d *ResourceDiff) getExact(addr []string, source string) getResult { | ||
495 | result, err := d.multiReader.ReadFieldExact(addr, source) | ||
496 | if err != nil { | ||
497 | panic(err) | ||
498 | } | ||
499 | |||
500 | return d.finalizeResult(addr, result) | ||
501 | } | ||
502 | |||
503 | // finalizeResult does some post-processing of the result produced by get and getExact. | ||
504 | func (d *ResourceDiff) finalizeResult(addr []string, result FieldReadResult) getResult { | ||
505 | // If the result doesn't exist, then we set the value to the zero value | ||
506 | var schema *Schema | ||
507 | if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { | ||
508 | schema = schemaL[len(schemaL)-1] | ||
509 | } | ||
510 | |||
511 | if result.Value == nil && schema != nil { | ||
512 | result.Value = result.ValueOrZero(schema) | ||
513 | } | ||
514 | |||
515 | // Transform the FieldReadResult into a getResult. It might be worth | ||
516 | // merging these two structures one day. | ||
517 | return getResult{ | ||
518 | Value: result.Value, | ||
519 | ValueProcessed: result.ValueProcessed, | ||
520 | Computed: result.Computed, | ||
521 | Exists: result.Exists, | ||
522 | Schema: schema, | ||
523 | } | ||
524 | } | ||
525 | |||
526 | // childAddrOf does a comparison of two addresses to see if one is the child of | ||
527 | // the other. | ||
528 | func childAddrOf(child, parent string) bool { | ||
529 | cs := strings.Split(child, ".") | ||
530 | ps := strings.Split(parent, ".") | ||
531 | if len(ps) > len(cs) { | ||
532 | return false | ||
533 | } | ||
534 | return reflect.DeepEqual(ps, cs[:len(ps)]) | ||
535 | } | ||
536 | |||
537 | // checkKey checks the key to make sure it exists and is computed. | ||
538 | func (d *ResourceDiff) checkKey(key, caller string, nested bool) error { | ||
539 | var schema *Schema | ||
540 | if nested { | ||
541 | keyParts := strings.Split(key, ".") | ||
542 | schemaL := addrToSchema(keyParts, d.schema) | ||
543 | if len(schemaL) > 0 { | ||
544 | schema = schemaL[len(schemaL)-1] | ||
545 | } | ||
546 | } else { | ||
547 | s, ok := d.schema[key] | ||
548 | if ok { | ||
549 | schema = s | ||
550 | } | ||
551 | } | ||
552 | if schema == nil { | ||
553 | return fmt.Errorf("%s: invalid key: %s", caller, key) | ||
554 | } | ||
555 | if !schema.Computed { | ||
556 | return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key) | ||
557 | } | ||
558 | return nil | ||
559 | } | ||