diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/diff.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/terraform/diff.go | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/diff.go b/vendor/github.com/hashicorp/terraform/terraform/diff.go new file mode 100644 index 0000000..a9fae6c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/terraform/diff.go | |||
@@ -0,0 +1,866 @@ | |||
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 | |||
31 | // Diff trackes the changes that are necessary to apply a configuration | ||
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 | ||
373 | // mean to be used for additional data a resource may want to pass through. | ||
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 | ||
554 | // methods but everywhere else within in the terraform package. | ||
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 | } | ||