github.com/ibm-cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/terraform/diff.go (about)

     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  }