13 "github.com/mitchellh/copystructure"
16 // DiffChangeType is an enum with the kind of changes a diff has planned.
17 type DiffChangeType byte
20 DiffInvalid DiffChangeType = iota
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
34 // multiVal matches the index key to a flatmapped set, list or map
35 var multiVal = regexp.MustCompile(`\.(#|%)$`)
37 // Diff tracks the changes that are necessary to apply a configuration
38 // to an existing infrastructure.
40 // Modules contains all the modules that have a diff
44 // Prune cleans out unused structures in the diff without affecting
45 // the behavior of the diff at all.
47 // This is not safe to call concurrently. This is safe to call on a
49 func (d *Diff) Prune() {
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
59 newModules = append(newModules, m)
62 if len(newModules) == 0 {
65 d.Modules = newModules
68 // AddModule adds the module with the given path to the diff.
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}
75 d.Modules = append(d.Modules, m)
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 {
86 for _, mod := range d.Modules {
88 panic("missing module path")
90 if reflect.DeepEqual(mod.Path, path) {
97 // RootModule returns the ModuleState for the root module
98 func (d *Diff) RootModule() *ModuleDiff {
99 root := d.ModuleByPath(rootModulePath)
101 panic("missing root module")
106 // Empty returns true if the diff has no changes.
107 func (d *Diff) Empty() bool {
112 for _, m := range d.Modules {
121 // Equal compares two diffs for exact equality.
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 {
133 sort.Sort(moduleDiffSort(d.Modules))
134 sort.Sort(moduleDiffSort(d2.Modules))
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 {
144 for _, m := range d2Copy.Modules {
149 return reflect.DeepEqual(dCopy, d2Copy)
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)
163 func (d *Diff) String() string {
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)
175 for _, key := range keys {
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")
185 buf.WriteString(fmt.Sprintf("%s:\n", key))
187 s := bufio.NewScanner(strings.NewReader(mStr))
189 buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
193 return strings.TrimSpace(buf.String())
196 func (d *Diff) init() {
197 if d.Modules == nil {
198 rootDiff := &ModuleDiff{Path: rootModulePath}
199 d.Modules = []*ModuleDiff{rootDiff}
201 for _, m := range d.Modules {
206 // ModuleDiff tracks the differences between resources to apply within
208 type ModuleDiff struct {
210 Resources map[string]*InstanceDiff
211 Destroy bool // Set only by the destroy plan
214 func (d *ModuleDiff) init() {
215 if d.Resources == nil {
216 d.Resources = make(map[string]*InstanceDiff)
218 for _, r := range d.Resources {
223 // ChangeType returns the type of changes that the diff for this
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 {
231 for _, r := range d.Resources {
232 change := r.ChangeType()
234 case DiffCreate, DiffDestroy:
235 if result == DiffNone {
238 case DiffDestroyCreate, DiffUpdate:
246 // Empty returns true if the diff has no changes within this module.
247 func (d *ModuleDiff) Empty() bool {
252 if len(d.Resources) == 0 {
256 for _, rd := range d.Resources {
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+".") {
272 result = append(result, diff)
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)
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 {
290 names := make([]string, 0, len(d.Resources))
291 for name, _ := range d.Resources {
292 names = append(names, name)
296 for _, name := range names {
297 rdiff := d.Resources[name]
301 case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
302 crud = "DESTROY/CREATE"
303 case rdiff.GetDestroy() || rdiff.GetDestroyDeposed():
305 case rdiff.RequiresNew():
310 if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() {
311 extra = " (deposed only)"
314 buf.WriteString(fmt.Sprintf(
321 rdiffAttrs := rdiff.CopyAttributes()
322 keys := make([]string, 0, len(rdiffAttrs))
323 for key, _ := range rdiffAttrs {
328 keys = append(keys, key)
329 if len(key) > keyLen {
335 for _, attrK := range keys {
336 attrDiff, _ := rdiff.GetAttribute(attrK)
340 if attrDiff.NewComputed {
344 if attrDiff.Sensitive {
350 if attrDiff.RequiresNew {
351 updateMsg = " (forces new resource)"
352 } else if attrDiff.Sensitive {
353 updateMsg = " (attribute changed)"
356 buf.WriteString(fmt.Sprintf(
357 " %s:%s %#v => %#v%s\n",
359 strings.Repeat(" ", keyLen-len(attrK)),
369 // InstanceDiff is the diff of a resource from some state to another.
370 type InstanceDiff struct {
372 Attributes map[string]*ResourceAttrDiff
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{}
384 func (d *InstanceDiff) Lock() { d.mu.Lock() }
385 func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
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
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
404 func (d *ResourceAttrDiff) GoString() string {
405 return fmt.Sprintf("*%#v", *d)
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
413 type DiffAttrType byte
416 DiffAttrUnknown DiffAttrType = iota
421 func (d *InstanceDiff) init() {
422 if d.Attributes == nil {
423 d.Attributes = make(map[string]*ResourceAttrDiff)
427 func NewInstanceDiff() *InstanceDiff {
428 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
431 func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
436 dCopy, err := copystructure.Config{Lock: true}.Copy(d)
441 return dCopy.(*InstanceDiff), nil
444 // ChangeType returns the DiffChangeType represented by the diff
445 // for this single instance.
446 func (d *InstanceDiff) ChangeType() DiffChangeType {
451 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
452 return DiffDestroyCreate
455 if d.GetDestroy() || d.GetDestroyDeposed() {
466 // Empty returns true if this diff encapsulates no changes.
467 func (d *InstanceDiff) Empty() bool {
477 len(d.Attributes) == 0
480 // Equal compares two diffs for exact equality.
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 {
492 return reflect.DeepEqual(d, d2)
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)
502 return copy.(*InstanceDiff)
505 func (d *InstanceDiff) GoString() string {
506 return fmt.Sprintf("*%#v", InstanceDiff{
507 Attributes: d.Attributes,
509 DestroyTainted: d.DestroyTainted,
510 DestroyDeposed: d.DestroyDeposed,
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 {
524 return d.requiresNew()
527 func (d *InstanceDiff) requiresNew() bool {
532 if d.DestroyTainted {
536 for _, rd := range d.Attributes {
537 if rd != nil && rd.RequiresNew {
545 func (d *InstanceDiff) GetDestroyDeposed() bool {
549 return d.DestroyDeposed
552 func (d *InstanceDiff) SetDestroyDeposed(b bool) {
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) {
569 func (d *InstanceDiff) GetDestroyTainted() bool {
573 return d.DestroyTainted
576 func (d *InstanceDiff) SetDestroy(b bool) {
583 func (d *InstanceDiff) GetDestroy() bool {
590 func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
594 d.Attributes[key] = attr
597 func (d *InstanceDiff) DelAttribute(key string) {
601 delete(d.Attributes, key)
604 func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
608 attr, ok := d.Attributes[key]
611 func (d *InstanceDiff) GetAttributesLen() int {
615 return len(d.Attributes)
618 // Safely copies the Attributes map
619 func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
623 attrs := make(map[string]*ResourceAttrDiff)
624 for k, v := range d.Attributes {
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
638 case d == nil && d2 == nil:
640 case d == nil || d2 == nil:
641 return false, "one nil"
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 {
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, ".#") {
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
671 if diffOld.NewComputed {
675 // We're looking for the case a map goes to exactly 0.
676 if diffOld.New != "0" {
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{}{}
690 for k, rd := range d.Attributes {
691 if _, ok := ignoreAttrs[k]; ok {
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 {
705 if oldNew != newNew {
706 return false, fmt.Sprintf(
707 "diff RequiresNew; old: %t, new: %t", oldNew, newNew)
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())
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{}{}
726 for k, _ := range d2.CopyAttributes() {
727 checkNew[k] = struct{}{}
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)
736 sort.StringSlice(keys).Sort()
738 for _, k := range keys {
739 diffOld := d.Attributes[k]
741 if _, ok := checkOld[k]; !ok {
742 // We're not checking this key for whatever reason (see where
743 // check is modified).
747 // Remove this key since we'll never hit it again
751 _, ok := d2.GetAttribute(k)
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 {
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
762 if diffOld.NewComputed {
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...)
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
781 parts2 = parts2[:i+1]
786 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
788 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
791 for k2, _ := range checkNew {
792 if re.MatchString(k2) {
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, ".%")) {
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
810 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
815 return false, fmt.Sprintf("attribute mismatch: %s", k)
819 // search for the suffix of the base of a [computed] map, list or set.
820 match := multiVal.FindStringSubmatch(k)
822 if diffOld.NewComputed && len(match) == 2 {
823 matchLen := len(match[1])
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) {
833 for k2, _ := range checkNew {
834 if strings.HasPrefix(k2, kprefix) {
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).
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)
857 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
863 // moduleDiffSort implements sort.Interface to sort module diffs by path.
864 type moduleDiffSort []*ModuleDiff
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 {
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)
877 // Otherwise, compare lexically
878 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")