]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "bufio" | |
5 | "bytes" | |
6 | "fmt" | |
7 | "reflect" | |
8 | "regexp" | |
9 | "sort" | |
10 | "strings" | |
11 | "sync" | |
12 | ||
13 | "github.com/mitchellh/copystructure" | |
14 | ) | |
15 | ||
16 | // DiffChangeType is an enum with the kind of changes a diff has planned. | |
17 | type DiffChangeType byte | |
18 | ||
19 | const ( | |
20 | DiffInvalid DiffChangeType = iota | |
21 | DiffNone | |
22 | DiffCreate | |
23 | DiffUpdate | |
24 | DiffDestroy | |
25 | DiffDestroyCreate | |
26 | ) | |
27 | ||
28 | // multiVal matches the index key to a flatmapped set, list or map | |
29 | var multiVal = regexp.MustCompile(`\.(#|%)$`) | |
30 | ||
c680a8e1 | 31 | // Diff tracks the changes that are necessary to apply a configuration |
bae9f6d2 JC |
32 | // to an existing infrastructure. |
33 | type Diff struct { | |
34 | // Modules contains all the modules that have a diff | |
35 | Modules []*ModuleDiff | |
36 | } | |
37 | ||
38 | // Prune cleans out unused structures in the diff without affecting | |
39 | // the behavior of the diff at all. | |
40 | // | |
41 | // This is not safe to call concurrently. This is safe to call on a | |
42 | // nil Diff. | |
43 | func (d *Diff) Prune() { | |
44 | if d == nil { | |
45 | return | |
46 | } | |
47 | ||
48 | // Prune all empty modules | |
49 | newModules := make([]*ModuleDiff, 0, len(d.Modules)) | |
50 | for _, m := range d.Modules { | |
51 | // If the module isn't empty, we keep it | |
52 | if !m.Empty() { | |
53 | newModules = append(newModules, m) | |
54 | } | |
55 | } | |
56 | if len(newModules) == 0 { | |
57 | newModules = nil | |
58 | } | |
59 | d.Modules = newModules | |
60 | } | |
61 | ||
62 | // AddModule adds the module with the given path to the diff. | |
63 | // | |
64 | // This should be the preferred method to add module diffs since it | |
65 | // allows us to optimize lookups later as well as control sorting. | |
66 | func (d *Diff) AddModule(path []string) *ModuleDiff { | |
67 | m := &ModuleDiff{Path: path} | |
68 | m.init() | |
69 | d.Modules = append(d.Modules, m) | |
70 | return m | |
71 | } | |
72 | ||
73 | // ModuleByPath is used to lookup the module diff for the given path. | |
74 | // This should be the preferred lookup mechanism as it allows for future | |
75 | // lookup optimizations. | |
76 | func (d *Diff) ModuleByPath(path []string) *ModuleDiff { | |
77 | if d == nil { | |
78 | return nil | |
79 | } | |
80 | for _, mod := range d.Modules { | |
81 | if mod.Path == nil { | |
82 | panic("missing module path") | |
83 | } | |
84 | if reflect.DeepEqual(mod.Path, path) { | |
85 | return mod | |
86 | } | |
87 | } | |
88 | return nil | |
89 | } | |
90 | ||
91 | // RootModule returns the ModuleState for the root module | |
92 | func (d *Diff) RootModule() *ModuleDiff { | |
93 | root := d.ModuleByPath(rootModulePath) | |
94 | if root == nil { | |
95 | panic("missing root module") | |
96 | } | |
97 | return root | |
98 | } | |
99 | ||
100 | // Empty returns true if the diff has no changes. | |
101 | func (d *Diff) Empty() bool { | |
102 | if d == nil { | |
103 | return true | |
104 | } | |
105 | ||
106 | for _, m := range d.Modules { | |
107 | if !m.Empty() { | |
108 | return false | |
109 | } | |
110 | } | |
111 | ||
112 | return true | |
113 | } | |
114 | ||
115 | // Equal compares two diffs for exact equality. | |
116 | // | |
117 | // This is different from the Same comparison that is supported which | |
118 | // checks for operation equality taking into account computed values. Equal | |
119 | // instead checks for exact equality. | |
120 | func (d *Diff) Equal(d2 *Diff) bool { | |
121 | // If one is nil, they must both be nil | |
122 | if d == nil || d2 == nil { | |
123 | return d == d2 | |
124 | } | |
125 | ||
126 | // Sort the modules | |
127 | sort.Sort(moduleDiffSort(d.Modules)) | |
128 | sort.Sort(moduleDiffSort(d2.Modules)) | |
129 | ||
130 | // Copy since we have to modify the module destroy flag to false so | |
131 | // we don't compare that. TODO: delete this when we get rid of the | |
132 | // destroy flag on modules. | |
133 | dCopy := d.DeepCopy() | |
134 | d2Copy := d2.DeepCopy() | |
135 | for _, m := range dCopy.Modules { | |
136 | m.Destroy = false | |
137 | } | |
138 | for _, m := range d2Copy.Modules { | |
139 | m.Destroy = false | |
140 | } | |
141 | ||
142 | // Use DeepEqual | |
143 | return reflect.DeepEqual(dCopy, d2Copy) | |
144 | } | |
145 | ||
146 | // DeepCopy performs a deep copy of all parts of the Diff, making the | |
147 | // resulting Diff safe to use without modifying this one. | |
148 | func (d *Diff) DeepCopy() *Diff { | |
149 | copy, err := copystructure.Config{Lock: true}.Copy(d) | |
150 | if err != nil { | |
151 | panic(err) | |
152 | } | |
153 | ||
154 | return copy.(*Diff) | |
155 | } | |
156 | ||
157 | func (d *Diff) String() string { | |
158 | var buf bytes.Buffer | |
159 | ||
160 | keys := make([]string, 0, len(d.Modules)) | |
161 | lookup := make(map[string]*ModuleDiff) | |
162 | for _, m := range d.Modules { | |
163 | key := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], ".")) | |
164 | keys = append(keys, key) | |
165 | lookup[key] = m | |
166 | } | |
167 | sort.Strings(keys) | |
168 | ||
169 | for _, key := range keys { | |
170 | m := lookup[key] | |
171 | mStr := m.String() | |
172 | ||
173 | // If we're the root module, we just write the output directly. | |
174 | if reflect.DeepEqual(m.Path, rootModulePath) { | |
175 | buf.WriteString(mStr + "\n") | |
176 | continue | |
177 | } | |
178 | ||
179 | buf.WriteString(fmt.Sprintf("%s:\n", key)) | |
180 | ||
181 | s := bufio.NewScanner(strings.NewReader(mStr)) | |
182 | for s.Scan() { | |
183 | buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) | |
184 | } | |
185 | } | |
186 | ||
187 | return strings.TrimSpace(buf.String()) | |
188 | } | |
189 | ||
190 | func (d *Diff) init() { | |
191 | if d.Modules == nil { | |
192 | rootDiff := &ModuleDiff{Path: rootModulePath} | |
193 | d.Modules = []*ModuleDiff{rootDiff} | |
194 | } | |
195 | for _, m := range d.Modules { | |
196 | m.init() | |
197 | } | |
198 | } | |
199 | ||
200 | // ModuleDiff tracks the differences between resources to apply within | |
201 | // a single module. | |
202 | type ModuleDiff struct { | |
203 | Path []string | |
204 | Resources map[string]*InstanceDiff | |
205 | Destroy bool // Set only by the destroy plan | |
206 | } | |
207 | ||
208 | func (d *ModuleDiff) init() { | |
209 | if d.Resources == nil { | |
210 | d.Resources = make(map[string]*InstanceDiff) | |
211 | } | |
212 | for _, r := range d.Resources { | |
213 | r.init() | |
214 | } | |
215 | } | |
216 | ||
217 | // ChangeType returns the type of changes that the diff for this | |
218 | // module includes. | |
219 | // | |
220 | // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or | |
221 | // DiffCreate. If an instance within the module has a DiffDestroyCreate | |
222 | // then this will register as a DiffCreate for a module. | |
223 | func (d *ModuleDiff) ChangeType() DiffChangeType { | |
224 | result := DiffNone | |
225 | for _, r := range d.Resources { | |
226 | change := r.ChangeType() | |
227 | switch change { | |
228 | case DiffCreate, DiffDestroy: | |
229 | if result == DiffNone { | |
230 | result = change | |
231 | } | |
232 | case DiffDestroyCreate, DiffUpdate: | |
233 | result = DiffUpdate | |
234 | } | |
235 | } | |
236 | ||
237 | return result | |
238 | } | |
239 | ||
240 | // Empty returns true if the diff has no changes within this module. | |
241 | func (d *ModuleDiff) Empty() bool { | |
242 | if d.Destroy { | |
243 | return false | |
244 | } | |
245 | ||
246 | if len(d.Resources) == 0 { | |
247 | return true | |
248 | } | |
249 | ||
250 | for _, rd := range d.Resources { | |
251 | if !rd.Empty() { | |
252 | return false | |
253 | } | |
254 | } | |
255 | ||
256 | return true | |
257 | } | |
258 | ||
259 | // Instances returns the instance diffs for the id given. This can return | |
260 | // multiple instance diffs if there are counts within the resource. | |
261 | func (d *ModuleDiff) Instances(id string) []*InstanceDiff { | |
262 | var result []*InstanceDiff | |
263 | for k, diff := range d.Resources { | |
264 | if k == id || strings.HasPrefix(k, id+".") { | |
265 | if !diff.Empty() { | |
266 | result = append(result, diff) | |
267 | } | |
268 | } | |
269 | } | |
270 | ||
271 | return result | |
272 | } | |
273 | ||
274 | // IsRoot says whether or not this module diff is for the root module. | |
275 | func (d *ModuleDiff) IsRoot() bool { | |
276 | return reflect.DeepEqual(d.Path, rootModulePath) | |
277 | } | |
278 | ||
279 | // String outputs the diff in a long but command-line friendly output | |
280 | // format that users can read to quickly inspect a diff. | |
281 | func (d *ModuleDiff) String() string { | |
282 | var buf bytes.Buffer | |
283 | ||
284 | names := make([]string, 0, len(d.Resources)) | |
285 | for name, _ := range d.Resources { | |
286 | names = append(names, name) | |
287 | } | |
288 | sort.Strings(names) | |
289 | ||
290 | for _, name := range names { | |
291 | rdiff := d.Resources[name] | |
292 | ||
293 | crud := "UPDATE" | |
294 | switch { | |
295 | case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()): | |
296 | crud = "DESTROY/CREATE" | |
297 | case rdiff.GetDestroy() || rdiff.GetDestroyDeposed(): | |
298 | crud = "DESTROY" | |
299 | case rdiff.RequiresNew(): | |
300 | crud = "CREATE" | |
301 | } | |
302 | ||
303 | extra := "" | |
304 | if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() { | |
305 | extra = " (deposed only)" | |
306 | } | |
307 | ||
308 | buf.WriteString(fmt.Sprintf( | |
309 | "%s: %s%s\n", | |
310 | crud, | |
311 | name, | |
312 | extra)) | |
313 | ||
314 | keyLen := 0 | |
315 | rdiffAttrs := rdiff.CopyAttributes() | |
316 | keys := make([]string, 0, len(rdiffAttrs)) | |
317 | for key, _ := range rdiffAttrs { | |
318 | if key == "id" { | |
319 | continue | |
320 | } | |
321 | ||
322 | keys = append(keys, key) | |
323 | if len(key) > keyLen { | |
324 | keyLen = len(key) | |
325 | } | |
326 | } | |
327 | sort.Strings(keys) | |
328 | ||
329 | for _, attrK := range keys { | |
330 | attrDiff, _ := rdiff.GetAttribute(attrK) | |
331 | ||
332 | v := attrDiff.New | |
333 | u := attrDiff.Old | |
334 | if attrDiff.NewComputed { | |
335 | v = "<computed>" | |
336 | } | |
337 | ||
338 | if attrDiff.Sensitive { | |
339 | u = "<sensitive>" | |
340 | v = "<sensitive>" | |
341 | } | |
342 | ||
343 | updateMsg := "" | |
344 | if attrDiff.RequiresNew { | |
345 | updateMsg = " (forces new resource)" | |
346 | } else if attrDiff.Sensitive { | |
347 | updateMsg = " (attribute changed)" | |
348 | } | |
349 | ||
350 | buf.WriteString(fmt.Sprintf( | |
351 | " %s:%s %#v => %#v%s\n", | |
352 | attrK, | |
353 | strings.Repeat(" ", keyLen-len(attrK)), | |
354 | u, | |
355 | v, | |
356 | updateMsg)) | |
357 | } | |
358 | } | |
359 | ||
360 | return buf.String() | |
361 | } | |
362 | ||
363 | // InstanceDiff is the diff of a resource from some state to another. | |
364 | type InstanceDiff struct { | |
365 | mu sync.Mutex | |
366 | Attributes map[string]*ResourceAttrDiff | |
367 | Destroy bool | |
368 | DestroyDeposed bool | |
369 | DestroyTainted bool | |
370 | ||
371 | // Meta is a simple K/V map that is stored in a diff and persisted to | |
372 | // plans but otherwise is completely ignored by Terraform core. It is | |
c680a8e1 | 373 | // meant to be used for additional data a resource may want to pass through. |
bae9f6d2 JC |
374 | // The value here must only contain Go primitives and collections. |
375 | Meta map[string]interface{} | |
376 | } | |
377 | ||
378 | func (d *InstanceDiff) Lock() { d.mu.Lock() } | |
379 | func (d *InstanceDiff) Unlock() { d.mu.Unlock() } | |
380 | ||
381 | // ResourceAttrDiff is the diff of a single attribute of a resource. | |
382 | type ResourceAttrDiff struct { | |
383 | Old string // Old Value | |
384 | New string // New Value | |
385 | NewComputed bool // True if new value is computed (unknown currently) | |
386 | NewRemoved bool // True if this attribute is being removed | |
387 | NewExtra interface{} // Extra information for the provider | |
388 | RequiresNew bool // True if change requires new resource | |
389 | Sensitive bool // True if the data should not be displayed in UI output | |
390 | Type DiffAttrType | |
391 | } | |
392 | ||
393 | // Empty returns true if the diff for this attr is neutral | |
394 | func (d *ResourceAttrDiff) Empty() bool { | |
395 | return d.Old == d.New && !d.NewComputed && !d.NewRemoved | |
396 | } | |
397 | ||
398 | func (d *ResourceAttrDiff) GoString() string { | |
399 | return fmt.Sprintf("*%#v", *d) | |
400 | } | |
401 | ||
402 | // DiffAttrType is an enum type that says whether a resource attribute | |
403 | // diff is an input attribute (comes from the configuration) or an | |
404 | // output attribute (comes as a result of applying the configuration). An | |
405 | // example input would be "ami" for AWS and an example output would be | |
406 | // "private_ip". | |
407 | type DiffAttrType byte | |
408 | ||
409 | const ( | |
410 | DiffAttrUnknown DiffAttrType = iota | |
411 | DiffAttrInput | |
412 | DiffAttrOutput | |
413 | ) | |
414 | ||
415 | func (d *InstanceDiff) init() { | |
416 | if d.Attributes == nil { | |
417 | d.Attributes = make(map[string]*ResourceAttrDiff) | |
418 | } | |
419 | } | |
420 | ||
421 | func NewInstanceDiff() *InstanceDiff { | |
422 | return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} | |
423 | } | |
424 | ||
425 | func (d *InstanceDiff) Copy() (*InstanceDiff, error) { | |
426 | if d == nil { | |
427 | return nil, nil | |
428 | } | |
429 | ||
430 | dCopy, err := copystructure.Config{Lock: true}.Copy(d) | |
431 | if err != nil { | |
432 | return nil, err | |
433 | } | |
434 | ||
435 | return dCopy.(*InstanceDiff), nil | |
436 | } | |
437 | ||
438 | // ChangeType returns the DiffChangeType represented by the diff | |
439 | // for this single instance. | |
440 | func (d *InstanceDiff) ChangeType() DiffChangeType { | |
441 | if d.Empty() { | |
442 | return DiffNone | |
443 | } | |
444 | ||
445 | if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { | |
446 | return DiffDestroyCreate | |
447 | } | |
448 | ||
449 | if d.GetDestroy() || d.GetDestroyDeposed() { | |
450 | return DiffDestroy | |
451 | } | |
452 | ||
453 | if d.RequiresNew() { | |
454 | return DiffCreate | |
455 | } | |
456 | ||
457 | return DiffUpdate | |
458 | } | |
459 | ||
460 | // Empty returns true if this diff encapsulates no changes. | |
461 | func (d *InstanceDiff) Empty() bool { | |
462 | if d == nil { | |
463 | return true | |
464 | } | |
465 | ||
466 | d.mu.Lock() | |
467 | defer d.mu.Unlock() | |
468 | return !d.Destroy && | |
469 | !d.DestroyTainted && | |
470 | !d.DestroyDeposed && | |
471 | len(d.Attributes) == 0 | |
472 | } | |
473 | ||
474 | // Equal compares two diffs for exact equality. | |
475 | // | |
476 | // This is different from the Same comparison that is supported which | |
477 | // checks for operation equality taking into account computed values. Equal | |
478 | // instead checks for exact equality. | |
479 | func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { | |
480 | // If one is nil, they must both be nil | |
481 | if d == nil || d2 == nil { | |
482 | return d == d2 | |
483 | } | |
484 | ||
485 | // Use DeepEqual | |
486 | return reflect.DeepEqual(d, d2) | |
487 | } | |
488 | ||
489 | // DeepCopy performs a deep copy of all parts of the InstanceDiff | |
490 | func (d *InstanceDiff) DeepCopy() *InstanceDiff { | |
491 | copy, err := copystructure.Config{Lock: true}.Copy(d) | |
492 | if err != nil { | |
493 | panic(err) | |
494 | } | |
495 | ||
496 | return copy.(*InstanceDiff) | |
497 | } | |
498 | ||
499 | func (d *InstanceDiff) GoString() string { | |
500 | return fmt.Sprintf("*%#v", InstanceDiff{ | |
501 | Attributes: d.Attributes, | |
502 | Destroy: d.Destroy, | |
503 | DestroyTainted: d.DestroyTainted, | |
504 | DestroyDeposed: d.DestroyDeposed, | |
505 | }) | |
506 | } | |
507 | ||
508 | // RequiresNew returns true if the diff requires the creation of a new | |
509 | // resource (implying the destruction of the old). | |
510 | func (d *InstanceDiff) RequiresNew() bool { | |
511 | if d == nil { | |
512 | return false | |
513 | } | |
514 | ||
515 | d.mu.Lock() | |
516 | defer d.mu.Unlock() | |
517 | ||
518 | return d.requiresNew() | |
519 | } | |
520 | ||
521 | func (d *InstanceDiff) requiresNew() bool { | |
522 | if d == nil { | |
523 | return false | |
524 | } | |
525 | ||
526 | if d.DestroyTainted { | |
527 | return true | |
528 | } | |
529 | ||
530 | for _, rd := range d.Attributes { | |
531 | if rd != nil && rd.RequiresNew { | |
532 | return true | |
533 | } | |
534 | } | |
535 | ||
536 | return false | |
537 | } | |
538 | ||
539 | func (d *InstanceDiff) GetDestroyDeposed() bool { | |
540 | d.mu.Lock() | |
541 | defer d.mu.Unlock() | |
542 | ||
543 | return d.DestroyDeposed | |
544 | } | |
545 | ||
546 | func (d *InstanceDiff) SetDestroyDeposed(b bool) { | |
547 | d.mu.Lock() | |
548 | defer d.mu.Unlock() | |
549 | ||
550 | d.DestroyDeposed = b | |
551 | } | |
552 | ||
553 | // These methods are properly locked, for use outside other InstanceDiff | |
c680a8e1 | 554 | // methods but everywhere else within the terraform package. |
bae9f6d2 JC |
555 | // TODO refactor the locking scheme |
556 | func (d *InstanceDiff) SetTainted(b bool) { | |
557 | d.mu.Lock() | |
558 | defer d.mu.Unlock() | |
559 | ||
560 | d.DestroyTainted = b | |
561 | } | |
562 | ||
563 | func (d *InstanceDiff) GetDestroyTainted() bool { | |
564 | d.mu.Lock() | |
565 | defer d.mu.Unlock() | |
566 | ||
567 | return d.DestroyTainted | |
568 | } | |
569 | ||
570 | func (d *InstanceDiff) SetDestroy(b bool) { | |
571 | d.mu.Lock() | |
572 | defer d.mu.Unlock() | |
573 | ||
574 | d.Destroy = b | |
575 | } | |
576 | ||
577 | func (d *InstanceDiff) GetDestroy() bool { | |
578 | d.mu.Lock() | |
579 | defer d.mu.Unlock() | |
580 | ||
581 | return d.Destroy | |
582 | } | |
583 | ||
584 | func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) { | |
585 | d.mu.Lock() | |
586 | defer d.mu.Unlock() | |
587 | ||
588 | d.Attributes[key] = attr | |
589 | } | |
590 | ||
591 | func (d *InstanceDiff) DelAttribute(key string) { | |
592 | d.mu.Lock() | |
593 | defer d.mu.Unlock() | |
594 | ||
595 | delete(d.Attributes, key) | |
596 | } | |
597 | ||
598 | func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { | |
599 | d.mu.Lock() | |
600 | defer d.mu.Unlock() | |
601 | ||
602 | attr, ok := d.Attributes[key] | |
603 | return attr, ok | |
604 | } | |
605 | func (d *InstanceDiff) GetAttributesLen() int { | |
606 | d.mu.Lock() | |
607 | defer d.mu.Unlock() | |
608 | ||
609 | return len(d.Attributes) | |
610 | } | |
611 | ||
612 | // Safely copies the Attributes map | |
613 | func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { | |
614 | d.mu.Lock() | |
615 | defer d.mu.Unlock() | |
616 | ||
617 | attrs := make(map[string]*ResourceAttrDiff) | |
618 | for k, v := range d.Attributes { | |
619 | attrs[k] = v | |
620 | } | |
621 | ||
622 | return attrs | |
623 | } | |
624 | ||
625 | // Same checks whether or not two InstanceDiff's are the "same". When | |
626 | // we say "same", it is not necessarily exactly equal. Instead, it is | |
627 | // just checking that the same attributes are changing, a destroy | |
628 | // isn't suddenly happening, etc. | |
629 | func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { | |
630 | // we can safely compare the pointers without a lock | |
631 | switch { | |
632 | case d == nil && d2 == nil: | |
633 | return true, "" | |
634 | case d == nil || d2 == nil: | |
635 | return false, "one nil" | |
636 | case d == d2: | |
637 | return true, "" | |
638 | } | |
639 | ||
640 | d.mu.Lock() | |
641 | defer d.mu.Unlock() | |
642 | ||
643 | // If we're going from requiring new to NOT requiring new, then we have | |
644 | // to see if all required news were computed. If so, it is allowed since | |
645 | // computed may also mean "same value and therefore not new". | |
646 | oldNew := d.requiresNew() | |
647 | newNew := d2.RequiresNew() | |
648 | if oldNew && !newNew { | |
649 | oldNew = false | |
650 | ||
651 | // This section builds a list of ignorable attributes for requiresNew | |
652 | // by removing off any elements of collections going to zero elements. | |
653 | // For collections going to zero, they may not exist at all in the | |
654 | // new diff (and hence RequiresNew == false). | |
655 | ignoreAttrs := make(map[string]struct{}) | |
656 | for k, diffOld := range d.Attributes { | |
657 | if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") { | |
658 | continue | |
659 | } | |
660 | ||
661 | // This case is in here as a protection measure. The bug that this | |
662 | // code originally fixed (GH-11349) didn't have to deal with computed | |
663 | // so I'm not 100% sure what the correct behavior is. Best to leave | |
664 | // the old behavior. | |
665 | if diffOld.NewComputed { | |
666 | continue | |
667 | } | |
668 | ||
669 | // We're looking for the case a map goes to exactly 0. | |
670 | if diffOld.New != "0" { | |
671 | continue | |
672 | } | |
673 | ||
674 | // Found it! Ignore all of these. The prefix here is stripping | |
675 | // off the "%" so it is just "k." | |
676 | prefix := k[:len(k)-1] | |
677 | for k2, _ := range d.Attributes { | |
678 | if strings.HasPrefix(k2, prefix) { | |
679 | ignoreAttrs[k2] = struct{}{} | |
680 | } | |
681 | } | |
682 | } | |
683 | ||
684 | for k, rd := range d.Attributes { | |
685 | if _, ok := ignoreAttrs[k]; ok { | |
686 | continue | |
687 | } | |
688 | ||
689 | // If the field is requires new and NOT computed, then what | |
690 | // we have is a diff mismatch for sure. We set that the old | |
691 | // diff does REQUIRE a ForceNew. | |
692 | if rd != nil && rd.RequiresNew && !rd.NewComputed { | |
693 | oldNew = true | |
694 | break | |
695 | } | |
696 | } | |
697 | } | |
698 | ||
699 | if oldNew != newNew { | |
700 | return false, fmt.Sprintf( | |
701 | "diff RequiresNew; old: %t, new: %t", oldNew, newNew) | |
702 | } | |
703 | ||
704 | // Verify that destroy matches. The second boolean here allows us to | |
705 | // have mismatching Destroy if we're moving from RequiresNew true | |
706 | // to false above. Therefore, the second boolean will only pass if | |
707 | // we're moving from Destroy: true to false as well. | |
708 | if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { | |
709 | return false, fmt.Sprintf( | |
710 | "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) | |
711 | } | |
712 | ||
713 | // Go through the old diff and make sure the new diff has all the | |
714 | // same attributes. To start, build up the check map to be all the keys. | |
715 | checkOld := make(map[string]struct{}) | |
716 | checkNew := make(map[string]struct{}) | |
717 | for k, _ := range d.Attributes { | |
718 | checkOld[k] = struct{}{} | |
719 | } | |
720 | for k, _ := range d2.CopyAttributes() { | |
721 | checkNew[k] = struct{}{} | |
722 | } | |
723 | ||
724 | // Make an ordered list so we are sure the approximated hashes are left | |
725 | // to process at the end of the loop | |
726 | keys := make([]string, 0, len(d.Attributes)) | |
727 | for k, _ := range d.Attributes { | |
728 | keys = append(keys, k) | |
729 | } | |
730 | sort.StringSlice(keys).Sort() | |
731 | ||
732 | for _, k := range keys { | |
733 | diffOld := d.Attributes[k] | |
734 | ||
735 | if _, ok := checkOld[k]; !ok { | |
736 | // We're not checking this key for whatever reason (see where | |
737 | // check is modified). | |
738 | continue | |
739 | } | |
740 | ||
741 | // Remove this key since we'll never hit it again | |
742 | delete(checkOld, k) | |
743 | delete(checkNew, k) | |
744 | ||
745 | _, ok := d2.GetAttribute(k) | |
746 | if !ok { | |
747 | // If there's no new attribute, and the old diff expected the attribute | |
748 | // to be removed, that's just fine. | |
749 | if diffOld.NewRemoved { | |
750 | continue | |
751 | } | |
752 | ||
753 | // If the last diff was a computed value then the absense of | |
754 | // that value is allowed since it may mean the value ended up | |
755 | // being the same. | |
756 | if diffOld.NewComputed { | |
757 | ok = true | |
758 | } | |
759 | ||
760 | // No exact match, but maybe this is a set containing computed | |
761 | // values. So check if there is an approximate hash in the key | |
762 | // and if so, try to match the key. | |
763 | if strings.Contains(k, "~") { | |
764 | parts := strings.Split(k, ".") | |
765 | parts2 := append([]string(nil), parts...) | |
766 | ||
767 | re := regexp.MustCompile(`^~\d+$`) | |
768 | for i, part := range parts { | |
769 | if re.MatchString(part) { | |
770 | // we're going to consider this the base of a | |
771 | // computed hash, and remove all longer matching fields | |
772 | ok = true | |
773 | ||
774 | parts2[i] = `\d+` | |
775 | parts2 = parts2[:i+1] | |
776 | break | |
777 | } | |
778 | } | |
779 | ||
780 | re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) | |
781 | if err != nil { | |
782 | return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) | |
783 | } | |
784 | ||
785 | for k2, _ := range checkNew { | |
786 | if re.MatchString(k2) { | |
787 | delete(checkNew, k2) | |
788 | } | |
789 | } | |
790 | } | |
791 | ||
792 | // This is a little tricky, but when a diff contains a computed | |
793 | // list, set, or map that can only be interpolated after the apply | |
794 | // command has created the dependent resources, it could turn out | |
795 | // that the result is actually the same as the existing state which | |
796 | // would remove the key from the diff. | |
797 | if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { | |
798 | ok = true | |
799 | } | |
800 | ||
801 | // Similarly, in a RequiresNew scenario, a list that shows up in the plan | |
802 | // diff can disappear from the apply diff, which is calculated from an | |
803 | // empty state. | |
804 | if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { | |
805 | ok = true | |
806 | } | |
807 | ||
808 | if !ok { | |
809 | return false, fmt.Sprintf("attribute mismatch: %s", k) | |
810 | } | |
811 | } | |
812 | ||
813 | // search for the suffix of the base of a [computed] map, list or set. | |
814 | match := multiVal.FindStringSubmatch(k) | |
815 | ||
816 | if diffOld.NewComputed && len(match) == 2 { | |
817 | matchLen := len(match[1]) | |
818 | ||
819 | // This is a computed list, set, or map, so remove any keys with | |
820 | // this prefix from the check list. | |
821 | kprefix := k[:len(k)-matchLen] | |
822 | for k2, _ := range checkOld { | |
823 | if strings.HasPrefix(k2, kprefix) { | |
824 | delete(checkOld, k2) | |
825 | } | |
826 | } | |
827 | for k2, _ := range checkNew { | |
828 | if strings.HasPrefix(k2, kprefix) { | |
829 | delete(checkNew, k2) | |
830 | } | |
831 | } | |
832 | } | |
833 | ||
834 | // TODO: check for the same value if not computed | |
835 | } | |
836 | ||
837 | // Check for leftover attributes | |
838 | if len(checkNew) > 0 { | |
839 | extras := make([]string, 0, len(checkNew)) | |
840 | for attr, _ := range checkNew { | |
841 | extras = append(extras, attr) | |
842 | } | |
843 | return false, | |
844 | fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) | |
845 | } | |
846 | ||
847 | return true, "" | |
848 | } | |
849 | ||
850 | // moduleDiffSort implements sort.Interface to sort module diffs by path. | |
851 | type moduleDiffSort []*ModuleDiff | |
852 | ||
853 | func (s moduleDiffSort) Len() int { return len(s) } | |
854 | func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
855 | func (s moduleDiffSort) Less(i, j int) bool { | |
856 | a := s[i] | |
857 | b := s[j] | |
858 | ||
859 | // If the lengths are different, then the shorter one always wins | |
860 | if len(a.Path) != len(b.Path) { | |
861 | return len(a.Path) < len(b.Path) | |
862 | } | |
863 | ||
864 | // Otherwise, compare lexically | |
865 | return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") | |
866 | } |