]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "bufio" | |
5 | "bytes" | |
6 | "fmt" | |
107c1cdb | 7 | "log" |
bae9f6d2 JC |
8 | "reflect" |
9 | "regexp" | |
10 | "sort" | |
107c1cdb | 11 | "strconv" |
bae9f6d2 JC |
12 | "strings" |
13 | "sync" | |
14 | ||
107c1cdb ND |
15 | "github.com/hashicorp/terraform/addrs" |
16 | "github.com/hashicorp/terraform/config" | |
17 | "github.com/hashicorp/terraform/config/hcl2shim" | |
18 | "github.com/hashicorp/terraform/configs/configschema" | |
19 | "github.com/zclconf/go-cty/cty" | |
20 | ||
bae9f6d2 JC |
21 | "github.com/mitchellh/copystructure" |
22 | ) | |
23 | ||
24 | // DiffChangeType is an enum with the kind of changes a diff has planned. | |
25 | type DiffChangeType byte | |
26 | ||
27 | const ( | |
28 | DiffInvalid DiffChangeType = iota | |
29 | DiffNone | |
30 | DiffCreate | |
31 | DiffUpdate | |
32 | DiffDestroy | |
33 | DiffDestroyCreate | |
15c0b25d AP |
34 | |
35 | // DiffRefresh is only used in the UI for displaying diffs. | |
36 | // Managed resource reads never appear in plan, and when data source | |
37 | // reads appear they are represented as DiffCreate in core before | |
38 | // transforming to DiffRefresh in the UI layer. | |
39 | DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion | |
bae9f6d2 JC |
40 | ) |
41 | ||
42 | // multiVal matches the index key to a flatmapped set, list or map | |
43 | var multiVal = regexp.MustCompile(`\.(#|%)$`) | |
44 | ||
c680a8e1 | 45 | // Diff tracks the changes that are necessary to apply a configuration |
bae9f6d2 JC |
46 | // to an existing infrastructure. |
47 | type Diff struct { | |
48 | // Modules contains all the modules that have a diff | |
49 | Modules []*ModuleDiff | |
50 | } | |
51 | ||
52 | // Prune cleans out unused structures in the diff without affecting | |
53 | // the behavior of the diff at all. | |
54 | // | |
55 | // This is not safe to call concurrently. This is safe to call on a | |
56 | // nil Diff. | |
57 | func (d *Diff) Prune() { | |
58 | if d == nil { | |
59 | return | |
60 | } | |
61 | ||
62 | // Prune all empty modules | |
63 | newModules := make([]*ModuleDiff, 0, len(d.Modules)) | |
64 | for _, m := range d.Modules { | |
65 | // If the module isn't empty, we keep it | |
66 | if !m.Empty() { | |
67 | newModules = append(newModules, m) | |
68 | } | |
69 | } | |
70 | if len(newModules) == 0 { | |
71 | newModules = nil | |
72 | } | |
73 | d.Modules = newModules | |
74 | } | |
75 | ||
76 | // AddModule adds the module with the given path to the diff. | |
77 | // | |
78 | // This should be the preferred method to add module diffs since it | |
79 | // allows us to optimize lookups later as well as control sorting. | |
107c1cdb ND |
80 | func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff { |
81 | // Lower the new-style address into a legacy-style address. | |
82 | // This requires that none of the steps have instance keys, which is | |
83 | // true for all addresses at the time of implementing this because | |
84 | // "count" and "for_each" are not yet implemented for modules. | |
85 | legacyPath := make([]string, len(path)) | |
86 | for i, step := range path { | |
87 | if step.InstanceKey != addrs.NoKey { | |
88 | // FIXME: Once the rest of Terraform is ready to use count and | |
89 | // for_each, remove all of this and just write the addrs.ModuleInstance | |
90 | // value itself into the ModuleState. | |
91 | panic("diff cannot represent modules with count or for_each keys") | |
92 | } | |
93 | ||
94 | legacyPath[i] = step.Name | |
95 | } | |
96 | ||
97 | m := &ModuleDiff{Path: legacyPath} | |
bae9f6d2 JC |
98 | m.init() |
99 | d.Modules = append(d.Modules, m) | |
100 | return m | |
101 | } | |
102 | ||
103 | // ModuleByPath is used to lookup the module diff for the given path. | |
104 | // This should be the preferred lookup mechanism as it allows for future | |
105 | // lookup optimizations. | |
107c1cdb | 106 | func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff { |
bae9f6d2 JC |
107 | if d == nil { |
108 | return nil | |
109 | } | |
110 | for _, mod := range d.Modules { | |
111 | if mod.Path == nil { | |
112 | panic("missing module path") | |
113 | } | |
107c1cdb ND |
114 | modPath := normalizeModulePath(mod.Path) |
115 | if modPath.String() == path.String() { | |
bae9f6d2 JC |
116 | return mod |
117 | } | |
118 | } | |
119 | return nil | |
120 | } | |
121 | ||
122 | // RootModule returns the ModuleState for the root module | |
123 | func (d *Diff) RootModule() *ModuleDiff { | |
107c1cdb | 124 | root := d.ModuleByPath(addrs.RootModuleInstance) |
bae9f6d2 JC |
125 | if root == nil { |
126 | panic("missing root module") | |
127 | } | |
128 | return root | |
129 | } | |
130 | ||
131 | // Empty returns true if the diff has no changes. | |
132 | func (d *Diff) Empty() bool { | |
133 | if d == nil { | |
134 | return true | |
135 | } | |
136 | ||
137 | for _, m := range d.Modules { | |
138 | if !m.Empty() { | |
139 | return false | |
140 | } | |
141 | } | |
142 | ||
143 | return true | |
144 | } | |
145 | ||
146 | // Equal compares two diffs for exact equality. | |
147 | // | |
148 | // This is different from the Same comparison that is supported which | |
149 | // checks for operation equality taking into account computed values. Equal | |
150 | // instead checks for exact equality. | |
151 | func (d *Diff) Equal(d2 *Diff) bool { | |
152 | // If one is nil, they must both be nil | |
153 | if d == nil || d2 == nil { | |
154 | return d == d2 | |
155 | } | |
156 | ||
157 | // Sort the modules | |
158 | sort.Sort(moduleDiffSort(d.Modules)) | |
159 | sort.Sort(moduleDiffSort(d2.Modules)) | |
160 | ||
161 | // Copy since we have to modify the module destroy flag to false so | |
162 | // we don't compare that. TODO: delete this when we get rid of the | |
163 | // destroy flag on modules. | |
164 | dCopy := d.DeepCopy() | |
165 | d2Copy := d2.DeepCopy() | |
166 | for _, m := range dCopy.Modules { | |
167 | m.Destroy = false | |
168 | } | |
169 | for _, m := range d2Copy.Modules { | |
170 | m.Destroy = false | |
171 | } | |
172 | ||
173 | // Use DeepEqual | |
174 | return reflect.DeepEqual(dCopy, d2Copy) | |
175 | } | |
176 | ||
177 | // DeepCopy performs a deep copy of all parts of the Diff, making the | |
178 | // resulting Diff safe to use without modifying this one. | |
179 | func (d *Diff) DeepCopy() *Diff { | |
180 | copy, err := copystructure.Config{Lock: true}.Copy(d) | |
181 | if err != nil { | |
182 | panic(err) | |
183 | } | |
184 | ||
185 | return copy.(*Diff) | |
186 | } | |
187 | ||
188 | func (d *Diff) String() string { | |
189 | var buf bytes.Buffer | |
190 | ||
191 | keys := make([]string, 0, len(d.Modules)) | |
192 | lookup := make(map[string]*ModuleDiff) | |
193 | for _, m := range d.Modules { | |
107c1cdb ND |
194 | addr := normalizeModulePath(m.Path) |
195 | key := addr.String() | |
bae9f6d2 JC |
196 | keys = append(keys, key) |
197 | lookup[key] = m | |
198 | } | |
199 | sort.Strings(keys) | |
200 | ||
201 | for _, key := range keys { | |
202 | m := lookup[key] | |
203 | mStr := m.String() | |
204 | ||
205 | // If we're the root module, we just write the output directly. | |
206 | if reflect.DeepEqual(m.Path, rootModulePath) { | |
207 | buf.WriteString(mStr + "\n") | |
208 | continue | |
209 | } | |
210 | ||
211 | buf.WriteString(fmt.Sprintf("%s:\n", key)) | |
212 | ||
213 | s := bufio.NewScanner(strings.NewReader(mStr)) | |
214 | for s.Scan() { | |
215 | buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) | |
216 | } | |
217 | } | |
218 | ||
219 | return strings.TrimSpace(buf.String()) | |
220 | } | |
221 | ||
222 | func (d *Diff) init() { | |
223 | if d.Modules == nil { | |
224 | rootDiff := &ModuleDiff{Path: rootModulePath} | |
225 | d.Modules = []*ModuleDiff{rootDiff} | |
226 | } | |
227 | for _, m := range d.Modules { | |
228 | m.init() | |
229 | } | |
230 | } | |
231 | ||
232 | // ModuleDiff tracks the differences between resources to apply within | |
233 | // a single module. | |
234 | type ModuleDiff struct { | |
235 | Path []string | |
236 | Resources map[string]*InstanceDiff | |
237 | Destroy bool // Set only by the destroy plan | |
238 | } | |
239 | ||
240 | func (d *ModuleDiff) init() { | |
241 | if d.Resources == nil { | |
242 | d.Resources = make(map[string]*InstanceDiff) | |
243 | } | |
244 | for _, r := range d.Resources { | |
245 | r.init() | |
246 | } | |
247 | } | |
248 | ||
249 | // ChangeType returns the type of changes that the diff for this | |
250 | // module includes. | |
251 | // | |
252 | // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or | |
253 | // DiffCreate. If an instance within the module has a DiffDestroyCreate | |
254 | // then this will register as a DiffCreate for a module. | |
255 | func (d *ModuleDiff) ChangeType() DiffChangeType { | |
256 | result := DiffNone | |
257 | for _, r := range d.Resources { | |
258 | change := r.ChangeType() | |
259 | switch change { | |
260 | case DiffCreate, DiffDestroy: | |
261 | if result == DiffNone { | |
262 | result = change | |
263 | } | |
264 | case DiffDestroyCreate, DiffUpdate: | |
265 | result = DiffUpdate | |
266 | } | |
267 | } | |
268 | ||
269 | return result | |
270 | } | |
271 | ||
272 | // Empty returns true if the diff has no changes within this module. | |
273 | func (d *ModuleDiff) Empty() bool { | |
274 | if d.Destroy { | |
275 | return false | |
276 | } | |
277 | ||
278 | if len(d.Resources) == 0 { | |
279 | return true | |
280 | } | |
281 | ||
282 | for _, rd := range d.Resources { | |
283 | if !rd.Empty() { | |
284 | return false | |
285 | } | |
286 | } | |
287 | ||
288 | return true | |
289 | } | |
290 | ||
291 | // Instances returns the instance diffs for the id given. This can return | |
292 | // multiple instance diffs if there are counts within the resource. | |
293 | func (d *ModuleDiff) Instances(id string) []*InstanceDiff { | |
294 | var result []*InstanceDiff | |
295 | for k, diff := range d.Resources { | |
296 | if k == id || strings.HasPrefix(k, id+".") { | |
297 | if !diff.Empty() { | |
298 | result = append(result, diff) | |
299 | } | |
300 | } | |
301 | } | |
302 | ||
303 | return result | |
304 | } | |
305 | ||
306 | // IsRoot says whether or not this module diff is for the root module. | |
307 | func (d *ModuleDiff) IsRoot() bool { | |
308 | return reflect.DeepEqual(d.Path, rootModulePath) | |
309 | } | |
310 | ||
311 | // String outputs the diff in a long but command-line friendly output | |
312 | // format that users can read to quickly inspect a diff. | |
313 | func (d *ModuleDiff) String() string { | |
314 | var buf bytes.Buffer | |
315 | ||
316 | names := make([]string, 0, len(d.Resources)) | |
317 | for name, _ := range d.Resources { | |
318 | names = append(names, name) | |
319 | } | |
320 | sort.Strings(names) | |
321 | ||
322 | for _, name := range names { | |
323 | rdiff := d.Resources[name] | |
324 | ||
325 | crud := "UPDATE" | |
326 | switch { | |
327 | case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()): | |
328 | crud = "DESTROY/CREATE" | |
329 | case rdiff.GetDestroy() || rdiff.GetDestroyDeposed(): | |
330 | crud = "DESTROY" | |
331 | case rdiff.RequiresNew(): | |
332 | crud = "CREATE" | |
333 | } | |
334 | ||
335 | extra := "" | |
336 | if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() { | |
337 | extra = " (deposed only)" | |
338 | } | |
339 | ||
340 | buf.WriteString(fmt.Sprintf( | |
341 | "%s: %s%s\n", | |
342 | crud, | |
343 | name, | |
344 | extra)) | |
345 | ||
346 | keyLen := 0 | |
347 | rdiffAttrs := rdiff.CopyAttributes() | |
348 | keys := make([]string, 0, len(rdiffAttrs)) | |
349 | for key, _ := range rdiffAttrs { | |
350 | if key == "id" { | |
351 | continue | |
352 | } | |
353 | ||
354 | keys = append(keys, key) | |
355 | if len(key) > keyLen { | |
356 | keyLen = len(key) | |
357 | } | |
358 | } | |
359 | sort.Strings(keys) | |
360 | ||
361 | for _, attrK := range keys { | |
362 | attrDiff, _ := rdiff.GetAttribute(attrK) | |
363 | ||
364 | v := attrDiff.New | |
365 | u := attrDiff.Old | |
366 | if attrDiff.NewComputed { | |
367 | v = "<computed>" | |
368 | } | |
369 | ||
370 | if attrDiff.Sensitive { | |
371 | u = "<sensitive>" | |
372 | v = "<sensitive>" | |
373 | } | |
374 | ||
375 | updateMsg := "" | |
376 | if attrDiff.RequiresNew { | |
377 | updateMsg = " (forces new resource)" | |
378 | } else if attrDiff.Sensitive { | |
379 | updateMsg = " (attribute changed)" | |
380 | } | |
381 | ||
382 | buf.WriteString(fmt.Sprintf( | |
383 | " %s:%s %#v => %#v%s\n", | |
384 | attrK, | |
385 | strings.Repeat(" ", keyLen-len(attrK)), | |
386 | u, | |
387 | v, | |
388 | updateMsg)) | |
389 | } | |
390 | } | |
391 | ||
392 | return buf.String() | |
393 | } | |
394 | ||
395 | // InstanceDiff is the diff of a resource from some state to another. | |
396 | type InstanceDiff struct { | |
397 | mu sync.Mutex | |
398 | Attributes map[string]*ResourceAttrDiff | |
399 | Destroy bool | |
400 | DestroyDeposed bool | |
401 | DestroyTainted bool | |
402 | ||
403 | // Meta is a simple K/V map that is stored in a diff and persisted to | |
404 | // plans but otherwise is completely ignored by Terraform core. It is | |
c680a8e1 | 405 | // meant to be used for additional data a resource may want to pass through. |
bae9f6d2 JC |
406 | // The value here must only contain Go primitives and collections. |
407 | Meta map[string]interface{} | |
408 | } | |
409 | ||
410 | func (d *InstanceDiff) Lock() { d.mu.Lock() } | |
411 | func (d *InstanceDiff) Unlock() { d.mu.Unlock() } | |
412 | ||
107c1cdb ND |
413 | // ApplyToValue merges the receiver into the given base value, returning a |
414 | // new value that incorporates the planned changes. The given value must | |
415 | // conform to the given schema, or this method will panic. | |
416 | // | |
417 | // This method is intended for shimming old subsystems that still use this | |
418 | // legacy diff type to work with the new-style types. | |
419 | func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) { | |
420 | // Create an InstanceState attributes from our existing state. | |
421 | // We can use this to more easily apply the diff changes. | |
422 | attrs := hcl2shim.FlatmapValueFromHCL2(base) | |
423 | applied, err := d.Apply(attrs, schema) | |
424 | if err != nil { | |
425 | return base, err | |
426 | } | |
427 | ||
428 | val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType()) | |
429 | if err != nil { | |
430 | return base, err | |
431 | } | |
432 | ||
433 | return schema.CoerceValue(val) | |
434 | } | |
435 | ||
436 | // Apply applies the diff to the provided flatmapped attributes, | |
437 | // returning the new instance attributes. | |
438 | // | |
439 | // This method is intended for shimming old subsystems that still use this | |
440 | // legacy diff type to work with the new-style types. | |
441 | func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) { | |
442 | // We always build a new value here, even if the given diff is "empty", | |
443 | // because we might be planning to create a new instance that happens | |
444 | // to have no attributes set, and so we want to produce an empty object | |
445 | // rather than just echoing back the null old value. | |
446 | if attrs == nil { | |
447 | attrs = map[string]string{} | |
448 | } | |
449 | ||
450 | // Rather applying the diff to mutate the attrs, we'll copy new values into | |
451 | // here to avoid the possibility of leaving stale values. | |
452 | result := map[string]string{} | |
453 | ||
454 | if d.Destroy || d.DestroyDeposed || d.DestroyTainted { | |
455 | return result, nil | |
456 | } | |
457 | ||
458 | return d.applyBlockDiff(nil, attrs, schema) | |
459 | } | |
460 | ||
461 | func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) { | |
462 | result := map[string]string{} | |
463 | name := "" | |
464 | if len(path) > 0 { | |
465 | name = path[len(path)-1] | |
466 | } | |
467 | ||
468 | // localPrefix is used to build the local result map | |
469 | localPrefix := "" | |
470 | if name != "" { | |
471 | localPrefix = name + "." | |
472 | } | |
473 | ||
474 | // iterate over the schema rather than the attributes, so we can handle | |
475 | // different block types separately from plain attributes | |
476 | for n, attrSchema := range schema.Attributes { | |
477 | var err error | |
478 | newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema) | |
479 | ||
480 | if err != nil { | |
481 | return result, err | |
482 | } | |
483 | ||
484 | for k, v := range newAttrs { | |
485 | result[localPrefix+k] = v | |
486 | } | |
487 | } | |
488 | ||
489 | blockPrefix := strings.Join(path, ".") | |
490 | if blockPrefix != "" { | |
491 | blockPrefix += "." | |
492 | } | |
493 | for n, block := range schema.BlockTypes { | |
494 | // we need to find the set of all keys that traverse this block | |
495 | candidateKeys := map[string]bool{} | |
496 | blockKey := blockPrefix + n + "." | |
497 | localBlockPrefix := localPrefix + n + "." | |
498 | ||
499 | // we can only trust the diff for sets, since the path changes, so don't | |
500 | // count existing values as candidate keys. If it turns out we're | |
501 | // keeping the attributes, we will catch it down below with "keepBlock" | |
502 | // after we check the set count. | |
503 | if block.Nesting != configschema.NestingSet { | |
504 | for k := range attrs { | |
505 | if strings.HasPrefix(k, blockKey) { | |
506 | nextDot := strings.Index(k[len(blockKey):], ".") | |
507 | if nextDot < 0 { | |
508 | continue | |
509 | } | |
510 | nextDot += len(blockKey) | |
511 | candidateKeys[k[len(blockKey):nextDot]] = true | |
512 | } | |
513 | } | |
514 | } | |
515 | ||
516 | for k, diff := range d.Attributes { | |
517 | if strings.HasPrefix(k, blockKey) { | |
518 | nextDot := strings.Index(k[len(blockKey):], ".") | |
519 | if nextDot < 0 { | |
520 | continue | |
521 | } | |
522 | ||
523 | if diff.NewRemoved { | |
524 | continue | |
525 | } | |
526 | ||
527 | nextDot += len(blockKey) | |
528 | candidateKeys[k[len(blockKey):nextDot]] = true | |
529 | } | |
530 | } | |
531 | ||
532 | // check each set candidate to see if it was removed. | |
533 | // we need to do this, because when entire sets are removed, they may | |
534 | // have the wrong key, and ony show diffs going to "" | |
535 | if block.Nesting == configschema.NestingSet { | |
536 | for k := range candidateKeys { | |
537 | indexPrefix := strings.Join(append(path, n, k), ".") + "." | |
538 | keep := false | |
539 | // now check each set element to see if it's a new diff, or one | |
540 | // that we're dropping. Since we're only applying the "New" | |
541 | // portion of the set, we can ignore diffs that only contain "Old" | |
542 | for attr, diff := range d.Attributes { | |
543 | if !strings.HasPrefix(attr, indexPrefix) { | |
544 | continue | |
545 | } | |
546 | ||
547 | // check for empty "count" keys | |
548 | if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" { | |
549 | continue | |
550 | } | |
551 | ||
552 | // removed items don't count either | |
553 | if diff.NewRemoved { | |
554 | continue | |
555 | } | |
556 | ||
557 | // this must be a diff to keep | |
558 | keep = true | |
559 | break | |
560 | } | |
561 | if !keep { | |
562 | delete(candidateKeys, k) | |
563 | } | |
564 | } | |
565 | } | |
566 | ||
567 | for k := range candidateKeys { | |
568 | newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block) | |
569 | if err != nil { | |
570 | return result, err | |
571 | } | |
572 | ||
573 | for attr, v := range newAttrs { | |
574 | result[localBlockPrefix+attr] = v | |
575 | } | |
576 | } | |
577 | ||
578 | keepBlock := true | |
579 | // check this block's count diff directly first, since we may not | |
580 | // have candidates because it was removed and only set to "0" | |
581 | if diff, ok := d.Attributes[blockKey+"#"]; ok { | |
582 | if diff.New == "0" || diff.NewRemoved { | |
583 | keepBlock = false | |
584 | } | |
585 | } | |
586 | ||
587 | // if there was no diff at all, then we need to keep the block attributes | |
588 | if len(candidateKeys) == 0 && keepBlock { | |
589 | for k, v := range attrs { | |
590 | if strings.HasPrefix(k, blockKey) { | |
591 | // we need the key relative to this block, so remove the | |
592 | // entire prefix, then re-insert the block name. | |
593 | localKey := localBlockPrefix + k[len(blockKey):] | |
594 | result[localKey] = v | |
595 | } | |
596 | } | |
597 | } | |
598 | ||
599 | countAddr := strings.Join(append(path, n, "#"), ".") | |
600 | if countDiff, ok := d.Attributes[countAddr]; ok { | |
601 | if countDiff.NewComputed { | |
602 | result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue | |
603 | } else { | |
604 | result[localBlockPrefix+"#"] = countDiff.New | |
605 | ||
606 | // While sets are complete, list are not, and we may not have all the | |
607 | // information to track removals. If the list was truncated, we need to | |
608 | // remove the extra items from the result. | |
609 | if block.Nesting == configschema.NestingList && | |
610 | countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue { | |
611 | length, _ := strconv.Atoi(countDiff.New) | |
612 | for k := range result { | |
613 | if !strings.HasPrefix(k, localBlockPrefix) { | |
614 | continue | |
615 | } | |
616 | ||
617 | index := k[len(localBlockPrefix):] | |
618 | nextDot := strings.Index(index, ".") | |
619 | if nextDot < 1 { | |
620 | continue | |
621 | } | |
622 | index = index[:nextDot] | |
623 | i, err := strconv.Atoi(index) | |
624 | if err != nil { | |
625 | // this shouldn't happen since we added these | |
626 | // ourself, but make note of it just in case. | |
627 | log.Printf("[ERROR] bad list index in %q: %s", k, err) | |
628 | continue | |
629 | } | |
630 | if i >= length { | |
631 | delete(result, k) | |
632 | } | |
633 | } | |
634 | } | |
635 | } | |
636 | } else if origCount, ok := attrs[countAddr]; ok && keepBlock { | |
637 | result[localBlockPrefix+"#"] = origCount | |
638 | } else { | |
639 | result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result) | |
640 | } | |
641 | } | |
642 | ||
643 | return result, nil | |
644 | } | |
645 | ||
646 | func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { | |
647 | ty := attrSchema.Type | |
648 | switch { | |
649 | case ty.IsListType(), ty.IsTupleType(), ty.IsMapType(): | |
650 | return d.applyCollectionDiff(path, attrs, attrSchema) | |
651 | case ty.IsSetType(): | |
652 | return d.applySetDiff(path, attrs, attrSchema) | |
653 | default: | |
654 | return d.applySingleAttrDiff(path, attrs, attrSchema) | |
655 | } | |
656 | } | |
657 | ||
658 | func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { | |
659 | currentKey := strings.Join(path, ".") | |
660 | ||
661 | attr := path[len(path)-1] | |
662 | ||
663 | result := map[string]string{} | |
664 | diff := d.Attributes[currentKey] | |
665 | old, exists := attrs[currentKey] | |
666 | ||
667 | if diff != nil && diff.NewComputed { | |
668 | result[attr] = config.UnknownVariableValue | |
669 | return result, nil | |
670 | } | |
671 | ||
672 | // "id" must exist and not be an empty string, or it must be unknown. | |
673 | // This only applied to top-level "id" fields. | |
674 | if attr == "id" && len(path) == 1 { | |
675 | if old == "" { | |
676 | result[attr] = config.UnknownVariableValue | |
677 | } else { | |
678 | result[attr] = old | |
679 | } | |
680 | return result, nil | |
681 | } | |
682 | ||
683 | // attribute diffs are sometimes missed, so assume no diff means keep the | |
684 | // old value | |
685 | if diff == nil { | |
686 | if exists { | |
687 | result[attr] = old | |
688 | } else { | |
689 | // We need required values, so set those with an empty value. It | |
690 | // must be set in the config, since if it were missing it would have | |
691 | // failed validation. | |
692 | if attrSchema.Required { | |
693 | // we only set a missing string here, since bool or number types | |
694 | // would have distinct zero value which shouldn't have been | |
695 | // lost. | |
696 | if attrSchema.Type == cty.String { | |
697 | result[attr] = "" | |
698 | } | |
699 | } | |
700 | } | |
701 | return result, nil | |
702 | } | |
703 | ||
704 | // check for missmatched diff values | |
705 | if exists && | |
706 | old != diff.Old && | |
707 | old != config.UnknownVariableValue && | |
708 | diff.Old != config.UnknownVariableValue { | |
709 | return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old) | |
710 | } | |
711 | ||
712 | if diff.NewRemoved { | |
713 | // don't set anything in the new value | |
714 | return map[string]string{}, nil | |
715 | } | |
716 | ||
717 | if diff.Old == diff.New && diff.New == "" { | |
718 | // this can only be a valid empty string | |
719 | if attrSchema.Type == cty.String { | |
720 | result[attr] = "" | |
721 | } | |
722 | return result, nil | |
723 | } | |
724 | ||
725 | if attrSchema.Computed && diff.NewComputed { | |
726 | result[attr] = config.UnknownVariableValue | |
727 | return result, nil | |
728 | } | |
729 | ||
730 | result[attr] = diff.New | |
731 | ||
732 | return result, nil | |
733 | } | |
734 | ||
735 | func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { | |
736 | result := map[string]string{} | |
737 | ||
738 | prefix := "" | |
739 | if len(path) > 1 { | |
740 | prefix = strings.Join(path[:len(path)-1], ".") + "." | |
741 | } | |
742 | ||
743 | name := "" | |
744 | if len(path) > 0 { | |
745 | name = path[len(path)-1] | |
746 | } | |
747 | ||
748 | currentKey := prefix + name | |
749 | ||
750 | // check the index first for special handling | |
751 | for k, diff := range d.Attributes { | |
752 | // check the index value, which can be set, and 0 | |
753 | if k == currentKey+".#" || k == currentKey+".%" || k == currentKey { | |
754 | if diff.NewRemoved { | |
755 | return result, nil | |
756 | } | |
757 | ||
758 | if diff.NewComputed { | |
759 | result[k[len(prefix):]] = config.UnknownVariableValue | |
760 | return result, nil | |
761 | } | |
762 | ||
763 | // do what the diff tells us to here, so that it's consistent with applies | |
764 | if diff.New == "0" { | |
765 | result[k[len(prefix):]] = "0" | |
766 | return result, nil | |
767 | } | |
768 | } | |
769 | } | |
770 | ||
771 | // collect all the keys from the diff and the old state | |
772 | noDiff := true | |
773 | keys := map[string]bool{} | |
774 | for k := range d.Attributes { | |
775 | if !strings.HasPrefix(k, currentKey+".") { | |
776 | continue | |
777 | } | |
778 | noDiff = false | |
779 | keys[k] = true | |
780 | } | |
781 | ||
782 | noAttrs := true | |
783 | for k := range attrs { | |
784 | if !strings.HasPrefix(k, currentKey+".") { | |
785 | continue | |
786 | } | |
787 | noAttrs = false | |
788 | keys[k] = true | |
789 | } | |
790 | ||
791 | // If there's no diff and no attrs, then there's no value at all. | |
792 | // This prevents an unexpected zero-count attribute in the attributes. | |
793 | if noDiff && noAttrs { | |
794 | return result, nil | |
795 | } | |
796 | ||
797 | idx := "#" | |
798 | if attrSchema.Type.IsMapType() { | |
799 | idx = "%" | |
800 | } | |
801 | ||
802 | for k := range keys { | |
803 | // generate an schema placeholder for the values | |
804 | elSchema := &configschema.Attribute{ | |
805 | Type: attrSchema.Type.ElementType(), | |
806 | } | |
807 | ||
808 | res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema) | |
809 | if err != nil { | |
810 | return result, err | |
811 | } | |
812 | ||
813 | for k, v := range res { | |
814 | result[name+"."+k] = v | |
815 | } | |
816 | } | |
817 | ||
818 | // Just like in nested list blocks, for simple lists we may need to fill in | |
819 | // missing empty strings. | |
820 | countKey := name + "." + idx | |
821 | count := result[countKey] | |
822 | length, _ := strconv.Atoi(count) | |
823 | ||
824 | if count != "" && count != hcl2shim.UnknownVariableValue && | |
825 | attrSchema.Type.Equals(cty.List(cty.String)) { | |
826 | // insert empty strings into missing indexes | |
827 | for i := 0; i < length; i++ { | |
828 | key := fmt.Sprintf("%s.%d", name, i) | |
829 | if _, ok := result[key]; !ok { | |
830 | result[key] = "" | |
831 | } | |
832 | } | |
833 | } | |
834 | ||
835 | // now check for truncation in any type of list | |
836 | if attrSchema.Type.IsListType() { | |
837 | for key := range result { | |
838 | if key == countKey { | |
839 | continue | |
840 | } | |
841 | ||
842 | if len(key) <= len(name)+1 { | |
843 | // not sure what this is, but don't panic | |
844 | continue | |
845 | } | |
846 | ||
847 | index := key[len(name)+1:] | |
848 | ||
849 | // It is possible to have nested sets or maps, so look for another dot | |
850 | dot := strings.Index(index, ".") | |
851 | if dot > 0 { | |
852 | index = index[:dot] | |
853 | } | |
854 | ||
855 | // This shouldn't have any more dots, since the element type is only string. | |
856 | num, err := strconv.Atoi(index) | |
857 | if err != nil { | |
858 | log.Printf("[ERROR] bad list index in %q: %s", currentKey, err) | |
859 | continue | |
860 | } | |
861 | ||
862 | if num >= length { | |
863 | delete(result, key) | |
864 | } | |
865 | } | |
866 | } | |
867 | ||
868 | // Fill in the count value if it wasn't present in the diff for some reason, | |
869 | // or if there is no count at all. | |
870 | _, countDiff := d.Attributes[countKey] | |
871 | if result[countKey] == "" || (!countDiff && len(keys) != len(result)) { | |
872 | result[countKey] = countFlatmapContainerValues(countKey, result) | |
873 | } | |
874 | ||
875 | return result, nil | |
876 | } | |
877 | ||
878 | func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { | |
879 | // We only need this special behavior for sets of object. | |
880 | if !attrSchema.Type.ElementType().IsObjectType() { | |
881 | // The normal collection apply behavior will work okay for this one, then. | |
882 | return d.applyCollectionDiff(path, attrs, attrSchema) | |
883 | } | |
884 | ||
885 | // When we're dealing with a set of an object type we actually want to | |
886 | // use our normal _block type_ apply behaviors, so we'll construct ourselves | |
887 | // a synthetic schema that treats the object type as a block type and | |
888 | // then delegate to our block apply method. | |
889 | synthSchema := &configschema.Block{ | |
890 | Attributes: make(map[string]*configschema.Attribute), | |
891 | } | |
892 | ||
893 | for name, ty := range attrSchema.Type.ElementType().AttributeTypes() { | |
894 | // We can safely make everything into an attribute here because in the | |
895 | // event that there are nested set attributes we'll end up back in | |
896 | // here again recursively and can then deal with the next level of | |
897 | // expansion. | |
898 | synthSchema.Attributes[name] = &configschema.Attribute{ | |
899 | Type: ty, | |
900 | Optional: true, | |
901 | } | |
902 | } | |
903 | ||
904 | parentPath := path[:len(path)-1] | |
905 | childName := path[len(path)-1] | |
906 | containerSchema := &configschema.Block{ | |
907 | BlockTypes: map[string]*configschema.NestedBlock{ | |
908 | childName: { | |
909 | Nesting: configschema.NestingSet, | |
910 | Block: *synthSchema, | |
911 | }, | |
912 | }, | |
913 | } | |
914 | ||
915 | return d.applyBlockDiff(parentPath, attrs, containerSchema) | |
916 | } | |
917 | ||
918 | // countFlatmapContainerValues returns the number of values in the flatmapped container | |
919 | // (set, map, list) indexed by key. The key argument is expected to include the | |
920 | // trailing ".#", or ".%". | |
921 | func countFlatmapContainerValues(key string, attrs map[string]string) string { | |
922 | if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { | |
923 | panic(fmt.Sprintf("invalid index value %q", key)) | |
924 | } | |
925 | ||
926 | prefix := key[:len(key)-1] | |
927 | items := map[string]int{} | |
928 | ||
929 | for k := range attrs { | |
930 | if k == key { | |
931 | continue | |
932 | } | |
933 | if !strings.HasPrefix(k, prefix) { | |
934 | continue | |
935 | } | |
936 | ||
937 | suffix := k[len(prefix):] | |
938 | dot := strings.Index(suffix, ".") | |
939 | if dot > 0 { | |
940 | suffix = suffix[:dot] | |
941 | } | |
942 | ||
943 | items[suffix]++ | |
944 | } | |
945 | return strconv.Itoa(len(items)) | |
946 | } | |
947 | ||
bae9f6d2 JC |
948 | // ResourceAttrDiff is the diff of a single attribute of a resource. |
949 | type ResourceAttrDiff struct { | |
950 | Old string // Old Value | |
951 | New string // New Value | |
952 | NewComputed bool // True if new value is computed (unknown currently) | |
953 | NewRemoved bool // True if this attribute is being removed | |
954 | NewExtra interface{} // Extra information for the provider | |
955 | RequiresNew bool // True if change requires new resource | |
956 | Sensitive bool // True if the data should not be displayed in UI output | |
957 | Type DiffAttrType | |
958 | } | |
959 | ||
960 | // Empty returns true if the diff for this attr is neutral | |
961 | func (d *ResourceAttrDiff) Empty() bool { | |
962 | return d.Old == d.New && !d.NewComputed && !d.NewRemoved | |
963 | } | |
964 | ||
965 | func (d *ResourceAttrDiff) GoString() string { | |
966 | return fmt.Sprintf("*%#v", *d) | |
967 | } | |
968 | ||
969 | // DiffAttrType is an enum type that says whether a resource attribute | |
970 | // diff is an input attribute (comes from the configuration) or an | |
971 | // output attribute (comes as a result of applying the configuration). An | |
972 | // example input would be "ami" for AWS and an example output would be | |
973 | // "private_ip". | |
974 | type DiffAttrType byte | |
975 | ||
976 | const ( | |
977 | DiffAttrUnknown DiffAttrType = iota | |
978 | DiffAttrInput | |
979 | DiffAttrOutput | |
980 | ) | |
981 | ||
982 | func (d *InstanceDiff) init() { | |
983 | if d.Attributes == nil { | |
984 | d.Attributes = make(map[string]*ResourceAttrDiff) | |
985 | } | |
986 | } | |
987 | ||
988 | func NewInstanceDiff() *InstanceDiff { | |
989 | return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} | |
990 | } | |
991 | ||
992 | func (d *InstanceDiff) Copy() (*InstanceDiff, error) { | |
993 | if d == nil { | |
994 | return nil, nil | |
995 | } | |
996 | ||
997 | dCopy, err := copystructure.Config{Lock: true}.Copy(d) | |
998 | if err != nil { | |
999 | return nil, err | |
1000 | } | |
1001 | ||
1002 | return dCopy.(*InstanceDiff), nil | |
1003 | } | |
1004 | ||
1005 | // ChangeType returns the DiffChangeType represented by the diff | |
1006 | // for this single instance. | |
1007 | func (d *InstanceDiff) ChangeType() DiffChangeType { | |
1008 | if d.Empty() { | |
1009 | return DiffNone | |
1010 | } | |
1011 | ||
1012 | if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { | |
1013 | return DiffDestroyCreate | |
1014 | } | |
1015 | ||
1016 | if d.GetDestroy() || d.GetDestroyDeposed() { | |
1017 | return DiffDestroy | |
1018 | } | |
1019 | ||
1020 | if d.RequiresNew() { | |
1021 | return DiffCreate | |
1022 | } | |
1023 | ||
1024 | return DiffUpdate | |
1025 | } | |
1026 | ||
1027 | // Empty returns true if this diff encapsulates no changes. | |
1028 | func (d *InstanceDiff) Empty() bool { | |
1029 | if d == nil { | |
1030 | return true | |
1031 | } | |
1032 | ||
1033 | d.mu.Lock() | |
1034 | defer d.mu.Unlock() | |
1035 | return !d.Destroy && | |
1036 | !d.DestroyTainted && | |
1037 | !d.DestroyDeposed && | |
1038 | len(d.Attributes) == 0 | |
1039 | } | |
1040 | ||
1041 | // Equal compares two diffs for exact equality. | |
1042 | // | |
1043 | // This is different from the Same comparison that is supported which | |
1044 | // checks for operation equality taking into account computed values. Equal | |
1045 | // instead checks for exact equality. | |
1046 | func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { | |
1047 | // If one is nil, they must both be nil | |
1048 | if d == nil || d2 == nil { | |
1049 | return d == d2 | |
1050 | } | |
1051 | ||
1052 | // Use DeepEqual | |
1053 | return reflect.DeepEqual(d, d2) | |
1054 | } | |
1055 | ||
1056 | // DeepCopy performs a deep copy of all parts of the InstanceDiff | |
1057 | func (d *InstanceDiff) DeepCopy() *InstanceDiff { | |
1058 | copy, err := copystructure.Config{Lock: true}.Copy(d) | |
1059 | if err != nil { | |
1060 | panic(err) | |
1061 | } | |
1062 | ||
1063 | return copy.(*InstanceDiff) | |
1064 | } | |
1065 | ||
1066 | func (d *InstanceDiff) GoString() string { | |
1067 | return fmt.Sprintf("*%#v", InstanceDiff{ | |
1068 | Attributes: d.Attributes, | |
1069 | Destroy: d.Destroy, | |
1070 | DestroyTainted: d.DestroyTainted, | |
1071 | DestroyDeposed: d.DestroyDeposed, | |
1072 | }) | |
1073 | } | |
1074 | ||
1075 | // RequiresNew returns true if the diff requires the creation of a new | |
1076 | // resource (implying the destruction of the old). | |
1077 | func (d *InstanceDiff) RequiresNew() bool { | |
1078 | if d == nil { | |
1079 | return false | |
1080 | } | |
1081 | ||
1082 | d.mu.Lock() | |
1083 | defer d.mu.Unlock() | |
1084 | ||
1085 | return d.requiresNew() | |
1086 | } | |
1087 | ||
1088 | func (d *InstanceDiff) requiresNew() bool { | |
1089 | if d == nil { | |
1090 | return false | |
1091 | } | |
1092 | ||
1093 | if d.DestroyTainted { | |
1094 | return true | |
1095 | } | |
1096 | ||
1097 | for _, rd := range d.Attributes { | |
1098 | if rd != nil && rd.RequiresNew { | |
1099 | return true | |
1100 | } | |
1101 | } | |
1102 | ||
1103 | return false | |
1104 | } | |
1105 | ||
1106 | func (d *InstanceDiff) GetDestroyDeposed() bool { | |
1107 | d.mu.Lock() | |
1108 | defer d.mu.Unlock() | |
1109 | ||
1110 | return d.DestroyDeposed | |
1111 | } | |
1112 | ||
1113 | func (d *InstanceDiff) SetDestroyDeposed(b bool) { | |
1114 | d.mu.Lock() | |
1115 | defer d.mu.Unlock() | |
1116 | ||
1117 | d.DestroyDeposed = b | |
1118 | } | |
1119 | ||
1120 | // These methods are properly locked, for use outside other InstanceDiff | |
c680a8e1 | 1121 | // methods but everywhere else within the terraform package. |
bae9f6d2 JC |
1122 | // TODO refactor the locking scheme |
1123 | func (d *InstanceDiff) SetTainted(b bool) { | |
1124 | d.mu.Lock() | |
1125 | defer d.mu.Unlock() | |
1126 | ||
1127 | d.DestroyTainted = b | |
1128 | } | |
1129 | ||
1130 | func (d *InstanceDiff) GetDestroyTainted() bool { | |
1131 | d.mu.Lock() | |
1132 | defer d.mu.Unlock() | |
1133 | ||
1134 | return d.DestroyTainted | |
1135 | } | |
1136 | ||
1137 | func (d *InstanceDiff) SetDestroy(b bool) { | |
1138 | d.mu.Lock() | |
1139 | defer d.mu.Unlock() | |
1140 | ||
1141 | d.Destroy = b | |
1142 | } | |
1143 | ||
1144 | func (d *InstanceDiff) GetDestroy() bool { | |
1145 | d.mu.Lock() | |
1146 | defer d.mu.Unlock() | |
1147 | ||
1148 | return d.Destroy | |
1149 | } | |
1150 | ||
1151 | func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) { | |
1152 | d.mu.Lock() | |
1153 | defer d.mu.Unlock() | |
1154 | ||
1155 | d.Attributes[key] = attr | |
1156 | } | |
1157 | ||
1158 | func (d *InstanceDiff) DelAttribute(key string) { | |
1159 | d.mu.Lock() | |
1160 | defer d.mu.Unlock() | |
1161 | ||
1162 | delete(d.Attributes, key) | |
1163 | } | |
1164 | ||
1165 | func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { | |
1166 | d.mu.Lock() | |
1167 | defer d.mu.Unlock() | |
1168 | ||
1169 | attr, ok := d.Attributes[key] | |
1170 | return attr, ok | |
1171 | } | |
1172 | func (d *InstanceDiff) GetAttributesLen() int { | |
1173 | d.mu.Lock() | |
1174 | defer d.mu.Unlock() | |
1175 | ||
1176 | return len(d.Attributes) | |
1177 | } | |
1178 | ||
1179 | // Safely copies the Attributes map | |
1180 | func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { | |
1181 | d.mu.Lock() | |
1182 | defer d.mu.Unlock() | |
1183 | ||
1184 | attrs := make(map[string]*ResourceAttrDiff) | |
1185 | for k, v := range d.Attributes { | |
1186 | attrs[k] = v | |
1187 | } | |
1188 | ||
1189 | return attrs | |
1190 | } | |
1191 | ||
1192 | // Same checks whether or not two InstanceDiff's are the "same". When | |
1193 | // we say "same", it is not necessarily exactly equal. Instead, it is | |
1194 | // just checking that the same attributes are changing, a destroy | |
1195 | // isn't suddenly happening, etc. | |
1196 | func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { | |
1197 | // we can safely compare the pointers without a lock | |
1198 | switch { | |
1199 | case d == nil && d2 == nil: | |
1200 | return true, "" | |
1201 | case d == nil || d2 == nil: | |
1202 | return false, "one nil" | |
1203 | case d == d2: | |
1204 | return true, "" | |
1205 | } | |
1206 | ||
1207 | d.mu.Lock() | |
1208 | defer d.mu.Unlock() | |
1209 | ||
1210 | // If we're going from requiring new to NOT requiring new, then we have | |
1211 | // to see if all required news were computed. If so, it is allowed since | |
1212 | // computed may also mean "same value and therefore not new". | |
1213 | oldNew := d.requiresNew() | |
1214 | newNew := d2.RequiresNew() | |
1215 | if oldNew && !newNew { | |
1216 | oldNew = false | |
1217 | ||
1218 | // This section builds a list of ignorable attributes for requiresNew | |
1219 | // by removing off any elements of collections going to zero elements. | |
1220 | // For collections going to zero, they may not exist at all in the | |
1221 | // new diff (and hence RequiresNew == false). | |
1222 | ignoreAttrs := make(map[string]struct{}) | |
1223 | for k, diffOld := range d.Attributes { | |
1224 | if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") { | |
1225 | continue | |
1226 | } | |
1227 | ||
1228 | // This case is in here as a protection measure. The bug that this | |
1229 | // code originally fixed (GH-11349) didn't have to deal with computed | |
1230 | // so I'm not 100% sure what the correct behavior is. Best to leave | |
1231 | // the old behavior. | |
1232 | if diffOld.NewComputed { | |
1233 | continue | |
1234 | } | |
1235 | ||
1236 | // We're looking for the case a map goes to exactly 0. | |
1237 | if diffOld.New != "0" { | |
1238 | continue | |
1239 | } | |
1240 | ||
1241 | // Found it! Ignore all of these. The prefix here is stripping | |
1242 | // off the "%" so it is just "k." | |
1243 | prefix := k[:len(k)-1] | |
1244 | for k2, _ := range d.Attributes { | |
1245 | if strings.HasPrefix(k2, prefix) { | |
1246 | ignoreAttrs[k2] = struct{}{} | |
1247 | } | |
1248 | } | |
1249 | } | |
1250 | ||
1251 | for k, rd := range d.Attributes { | |
1252 | if _, ok := ignoreAttrs[k]; ok { | |
1253 | continue | |
1254 | } | |
1255 | ||
1256 | // If the field is requires new and NOT computed, then what | |
1257 | // we have is a diff mismatch for sure. We set that the old | |
1258 | // diff does REQUIRE a ForceNew. | |
1259 | if rd != nil && rd.RequiresNew && !rd.NewComputed { | |
1260 | oldNew = true | |
1261 | break | |
1262 | } | |
1263 | } | |
1264 | } | |
1265 | ||
1266 | if oldNew != newNew { | |
1267 | return false, fmt.Sprintf( | |
1268 | "diff RequiresNew; old: %t, new: %t", oldNew, newNew) | |
1269 | } | |
1270 | ||
1271 | // Verify that destroy matches. The second boolean here allows us to | |
1272 | // have mismatching Destroy if we're moving from RequiresNew true | |
1273 | // to false above. Therefore, the second boolean will only pass if | |
1274 | // we're moving from Destroy: true to false as well. | |
1275 | if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { | |
1276 | return false, fmt.Sprintf( | |
1277 | "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) | |
1278 | } | |
1279 | ||
1280 | // Go through the old diff and make sure the new diff has all the | |
1281 | // same attributes. To start, build up the check map to be all the keys. | |
1282 | checkOld := make(map[string]struct{}) | |
1283 | checkNew := make(map[string]struct{}) | |
1284 | for k, _ := range d.Attributes { | |
1285 | checkOld[k] = struct{}{} | |
1286 | } | |
1287 | for k, _ := range d2.CopyAttributes() { | |
1288 | checkNew[k] = struct{}{} | |
1289 | } | |
1290 | ||
1291 | // Make an ordered list so we are sure the approximated hashes are left | |
1292 | // to process at the end of the loop | |
1293 | keys := make([]string, 0, len(d.Attributes)) | |
1294 | for k, _ := range d.Attributes { | |
1295 | keys = append(keys, k) | |
1296 | } | |
1297 | sort.StringSlice(keys).Sort() | |
1298 | ||
1299 | for _, k := range keys { | |
1300 | diffOld := d.Attributes[k] | |
1301 | ||
1302 | if _, ok := checkOld[k]; !ok { | |
1303 | // We're not checking this key for whatever reason (see where | |
1304 | // check is modified). | |
1305 | continue | |
1306 | } | |
1307 | ||
1308 | // Remove this key since we'll never hit it again | |
1309 | delete(checkOld, k) | |
1310 | delete(checkNew, k) | |
1311 | ||
1312 | _, ok := d2.GetAttribute(k) | |
1313 | if !ok { | |
1314 | // If there's no new attribute, and the old diff expected the attribute | |
1315 | // to be removed, that's just fine. | |
1316 | if diffOld.NewRemoved { | |
1317 | continue | |
1318 | } | |
1319 | ||
1320 | // If the last diff was a computed value then the absense of | |
1321 | // that value is allowed since it may mean the value ended up | |
1322 | // being the same. | |
1323 | if diffOld.NewComputed { | |
1324 | ok = true | |
1325 | } | |
1326 | ||
1327 | // No exact match, but maybe this is a set containing computed | |
1328 | // values. So check if there is an approximate hash in the key | |
1329 | // and if so, try to match the key. | |
1330 | if strings.Contains(k, "~") { | |
1331 | parts := strings.Split(k, ".") | |
1332 | parts2 := append([]string(nil), parts...) | |
1333 | ||
1334 | re := regexp.MustCompile(`^~\d+$`) | |
1335 | for i, part := range parts { | |
1336 | if re.MatchString(part) { | |
1337 | // we're going to consider this the base of a | |
1338 | // computed hash, and remove all longer matching fields | |
1339 | ok = true | |
1340 | ||
1341 | parts2[i] = `\d+` | |
1342 | parts2 = parts2[:i+1] | |
1343 | break | |
1344 | } | |
1345 | } | |
1346 | ||
1347 | re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) | |
1348 | if err != nil { | |
1349 | return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) | |
1350 | } | |
1351 | ||
1352 | for k2, _ := range checkNew { | |
1353 | if re.MatchString(k2) { | |
1354 | delete(checkNew, k2) | |
1355 | } | |
1356 | } | |
1357 | } | |
1358 | ||
1359 | // This is a little tricky, but when a diff contains a computed | |
1360 | // list, set, or map that can only be interpolated after the apply | |
1361 | // command has created the dependent resources, it could turn out | |
1362 | // that the result is actually the same as the existing state which | |
1363 | // would remove the key from the diff. | |
1364 | if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { | |
1365 | ok = true | |
1366 | } | |
1367 | ||
1368 | // Similarly, in a RequiresNew scenario, a list that shows up in the plan | |
1369 | // diff can disappear from the apply diff, which is calculated from an | |
1370 | // empty state. | |
1371 | if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { | |
1372 | ok = true | |
1373 | } | |
1374 | ||
1375 | if !ok { | |
1376 | return false, fmt.Sprintf("attribute mismatch: %s", k) | |
1377 | } | |
1378 | } | |
1379 | ||
1380 | // search for the suffix of the base of a [computed] map, list or set. | |
1381 | match := multiVal.FindStringSubmatch(k) | |
1382 | ||
1383 | if diffOld.NewComputed && len(match) == 2 { | |
1384 | matchLen := len(match[1]) | |
1385 | ||
1386 | // This is a computed list, set, or map, so remove any keys with | |
1387 | // this prefix from the check list. | |
1388 | kprefix := k[:len(k)-matchLen] | |
1389 | for k2, _ := range checkOld { | |
1390 | if strings.HasPrefix(k2, kprefix) { | |
1391 | delete(checkOld, k2) | |
1392 | } | |
1393 | } | |
1394 | for k2, _ := range checkNew { | |
1395 | if strings.HasPrefix(k2, kprefix) { | |
1396 | delete(checkNew, k2) | |
1397 | } | |
1398 | } | |
1399 | } | |
1400 | ||
15c0b25d AP |
1401 | // We don't compare the values because we can't currently actually |
1402 | // guarantee to generate the same value two two diffs created from | |
1403 | // the same state+config: we have some pesky interpolation functions | |
1404 | // that do not behave as pure functions (uuid, timestamp) and so they | |
1405 | // can be different each time a diff is produced. | |
1406 | // FIXME: Re-organize our config handling so that we don't re-evaluate | |
1407 | // expressions when we produce a second comparison diff during | |
1408 | // apply (for EvalCompareDiff). | |
bae9f6d2 JC |
1409 | } |
1410 | ||
1411 | // Check for leftover attributes | |
1412 | if len(checkNew) > 0 { | |
1413 | extras := make([]string, 0, len(checkNew)) | |
1414 | for attr, _ := range checkNew { | |
1415 | extras = append(extras, attr) | |
1416 | } | |
1417 | return false, | |
1418 | fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) | |
1419 | } | |
1420 | ||
1421 | return true, "" | |
1422 | } | |
1423 | ||
1424 | // moduleDiffSort implements sort.Interface to sort module diffs by path. | |
1425 | type moduleDiffSort []*ModuleDiff | |
1426 | ||
1427 | func (s moduleDiffSort) Len() int { return len(s) } | |
1428 | func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
1429 | func (s moduleDiffSort) Less(i, j int) bool { | |
1430 | a := s[i] | |
1431 | b := s[j] | |
1432 | ||
1433 | // If the lengths are different, then the shorter one always wins | |
1434 | if len(a.Path) != len(b.Path) { | |
1435 | return len(a.Path) < len(b.Path) | |
1436 | } | |
1437 | ||
1438 | // Otherwise, compare lexically | |
1439 | return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") | |
1440 | } |