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