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