]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
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 | |
107c1cdb | 370 | // if an overridden diff exists. |
15c0b25d AP |
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 | } |