github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  	// Meta is a simple K/V map that is stored in a diff and persisted to
   369  	// plans but otherwise is completely ignored by Terraform core. It is
   370  	// mean to be used for additional data a resource may want to pass through.
   371  	// The value here must only contain Go primitives and collections.
   372  	Meta map[string]interface{}
   373  }
   374  
   375  func (d *InstanceDiff) Lock()   { d.mu.Lock() }
   376  func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
   377  
   378  // ResourceAttrDiff is the diff of a single attribute of a resource.
   379  type ResourceAttrDiff struct {
   380  	Old         string      // Old Value
   381  	New         string      // New Value
   382  	NewComputed bool        // True if new value is computed (unknown currently)
   383  	NewRemoved  bool        // True if this attribute is being removed
   384  	NewExtra    interface{} // Extra information for the provider
   385  	RequiresNew bool        // True if change requires new resource
   386  	Sensitive   bool        // True if the data should not be displayed in UI output
   387  	Type        DiffAttrType
   388  }
   389  
   390  // Empty returns true if the diff for this attr is neutral
   391  func (d *ResourceAttrDiff) Empty() bool {
   392  	return d.Old == d.New && !d.NewComputed && !d.NewRemoved
   393  }
   394  
   395  func (d *ResourceAttrDiff) GoString() string {
   396  	return fmt.Sprintf("*%#v", *d)
   397  }
   398  
   399  // DiffAttrType is an enum type that says whether a resource attribute
   400  // diff is an input attribute (comes from the configuration) or an
   401  // output attribute (comes as a result of applying the configuration). An
   402  // example input would be "ami" for AWS and an example output would be
   403  // "private_ip".
   404  type DiffAttrType byte
   405  
   406  const (
   407  	DiffAttrUnknown DiffAttrType = iota
   408  	DiffAttrInput
   409  	DiffAttrOutput
   410  )
   411  
   412  func (d *InstanceDiff) init() {
   413  	if d.Attributes == nil {
   414  		d.Attributes = make(map[string]*ResourceAttrDiff)
   415  	}
   416  }
   417  
   418  func NewInstanceDiff() *InstanceDiff {
   419  	return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
   420  }
   421  
   422  func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
   423  	if d == nil {
   424  		return nil, nil
   425  	}
   426  
   427  	dCopy, err := copystructure.Config{Lock: true}.Copy(d)
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	return dCopy.(*InstanceDiff), nil
   433  }
   434  
   435  // ChangeType returns the DiffChangeType represented by the diff
   436  // for this single instance.
   437  func (d *InstanceDiff) ChangeType() DiffChangeType {
   438  	if d.Empty() {
   439  		return DiffNone
   440  	}
   441  
   442  	if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
   443  		return DiffDestroyCreate
   444  	}
   445  
   446  	if d.GetDestroy() || d.GetDestroyDeposed() {
   447  		return DiffDestroy
   448  	}
   449  
   450  	if d.RequiresNew() {
   451  		return DiffCreate
   452  	}
   453  
   454  	return DiffUpdate
   455  }
   456  
   457  // Empty returns true if this diff encapsulates no changes.
   458  func (d *InstanceDiff) Empty() bool {
   459  	if d == nil {
   460  		return true
   461  	}
   462  
   463  	d.mu.Lock()
   464  	defer d.mu.Unlock()
   465  	return !d.Destroy &&
   466  		!d.DestroyTainted &&
   467  		!d.DestroyDeposed &&
   468  		len(d.Attributes) == 0
   469  }
   470  
   471  // Equal compares two diffs for exact equality.
   472  //
   473  // This is different from the Same comparison that is supported which
   474  // checks for operation equality taking into account computed values. Equal
   475  // instead checks for exact equality.
   476  func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
   477  	// If one is nil, they must both be nil
   478  	if d == nil || d2 == nil {
   479  		return d == d2
   480  	}
   481  
   482  	// Use DeepEqual
   483  	return reflect.DeepEqual(d, d2)
   484  }
   485  
   486  // DeepCopy performs a deep copy of all parts of the InstanceDiff
   487  func (d *InstanceDiff) DeepCopy() *InstanceDiff {
   488  	copy, err := copystructure.Config{Lock: true}.Copy(d)
   489  	if err != nil {
   490  		panic(err)
   491  	}
   492  
   493  	return copy.(*InstanceDiff)
   494  }
   495  
   496  func (d *InstanceDiff) GoString() string {
   497  	return fmt.Sprintf("*%#v", InstanceDiff{
   498  		Attributes:     d.Attributes,
   499  		Destroy:        d.Destroy,
   500  		DestroyTainted: d.DestroyTainted,
   501  		DestroyDeposed: d.DestroyDeposed,
   502  	})
   503  }
   504  
   505  // RequiresNew returns true if the diff requires the creation of a new
   506  // resource (implying the destruction of the old).
   507  func (d *InstanceDiff) RequiresNew() bool {
   508  	if d == nil {
   509  		return false
   510  	}
   511  
   512  	d.mu.Lock()
   513  	defer d.mu.Unlock()
   514  
   515  	return d.requiresNew()
   516  }
   517  
   518  func (d *InstanceDiff) requiresNew() bool {
   519  	if d == nil {
   520  		return false
   521  	}
   522  
   523  	if d.DestroyTainted {
   524  		return true
   525  	}
   526  
   527  	for _, rd := range d.Attributes {
   528  		if rd != nil && rd.RequiresNew {
   529  			return true
   530  		}
   531  	}
   532  
   533  	return false
   534  }
   535  
   536  func (d *InstanceDiff) GetDestroyDeposed() bool {
   537  	d.mu.Lock()
   538  	defer d.mu.Unlock()
   539  
   540  	return d.DestroyDeposed
   541  }
   542  
   543  func (d *InstanceDiff) SetDestroyDeposed(b bool) {
   544  	d.mu.Lock()
   545  	defer d.mu.Unlock()
   546  
   547  	d.DestroyDeposed = b
   548  }
   549  
   550  // These methods are properly locked, for use outside other InstanceDiff
   551  // methods but everywhere else within in the terraform package.
   552  // TODO refactor the locking scheme
   553  func (d *InstanceDiff) SetTainted(b bool) {
   554  	d.mu.Lock()
   555  	defer d.mu.Unlock()
   556  
   557  	d.DestroyTainted = b
   558  }
   559  
   560  func (d *InstanceDiff) GetDestroyTainted() bool {
   561  	d.mu.Lock()
   562  	defer d.mu.Unlock()
   563  
   564  	return d.DestroyTainted
   565  }
   566  
   567  func (d *InstanceDiff) SetDestroy(b bool) {
   568  	d.mu.Lock()
   569  	defer d.mu.Unlock()
   570  
   571  	d.Destroy = b
   572  }
   573  
   574  func (d *InstanceDiff) GetDestroy() bool {
   575  	d.mu.Lock()
   576  	defer d.mu.Unlock()
   577  
   578  	return d.Destroy
   579  }
   580  
   581  func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
   582  	d.mu.Lock()
   583  	defer d.mu.Unlock()
   584  
   585  	d.Attributes[key] = attr
   586  }
   587  
   588  func (d *InstanceDiff) DelAttribute(key string) {
   589  	d.mu.Lock()
   590  	defer d.mu.Unlock()
   591  
   592  	delete(d.Attributes, key)
   593  }
   594  
   595  func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
   596  	d.mu.Lock()
   597  	defer d.mu.Unlock()
   598  
   599  	attr, ok := d.Attributes[key]
   600  	return attr, ok
   601  }
   602  func (d *InstanceDiff) GetAttributesLen() int {
   603  	d.mu.Lock()
   604  	defer d.mu.Unlock()
   605  
   606  	return len(d.Attributes)
   607  }
   608  
   609  // Safely copies the Attributes map
   610  func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
   611  	d.mu.Lock()
   612  	defer d.mu.Unlock()
   613  
   614  	attrs := make(map[string]*ResourceAttrDiff)
   615  	for k, v := range d.Attributes {
   616  		attrs[k] = v
   617  	}
   618  
   619  	return attrs
   620  }
   621  
   622  // Same checks whether or not two InstanceDiff's are the "same". When
   623  // we say "same", it is not necessarily exactly equal. Instead, it is
   624  // just checking that the same attributes are changing, a destroy
   625  // isn't suddenly happening, etc.
   626  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
   627  	// we can safely compare the pointers without a lock
   628  	switch {
   629  	case d == nil && d2 == nil:
   630  		return true, ""
   631  	case d == nil || d2 == nil:
   632  		return false, "one nil"
   633  	case d == d2:
   634  		return true, ""
   635  	}
   636  
   637  	d.mu.Lock()
   638  	defer d.mu.Unlock()
   639  
   640  	// If we're going from requiring new to NOT requiring new, then we have
   641  	// to see if all required news were computed. If so, it is allowed since
   642  	// computed may also mean "same value and therefore not new".
   643  	oldNew := d.requiresNew()
   644  	newNew := d2.RequiresNew()
   645  	if oldNew && !newNew {
   646  		oldNew = false
   647  
   648  		// This section builds a list of ignorable attributes for requiresNew
   649  		// by removing off any elements of collections going to zero elements.
   650  		// For collections going to zero, they may not exist at all in the
   651  		// new diff (and hence RequiresNew == false).
   652  		ignoreAttrs := make(map[string]struct{})
   653  		for k, diffOld := range d.Attributes {
   654  			if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") {
   655  				continue
   656  			}
   657  
   658  			// This case is in here as a protection measure. The bug that this
   659  			// code originally fixed (GH-11349) didn't have to deal with computed
   660  			// so I'm not 100% sure what the correct behavior is. Best to leave
   661  			// the old behavior.
   662  			if diffOld.NewComputed {
   663  				continue
   664  			}
   665  
   666  			// We're looking for the case a map goes to exactly 0.
   667  			if diffOld.New != "0" {
   668  				continue
   669  			}
   670  
   671  			// Found it! Ignore all of these. The prefix here is stripping
   672  			// off the "%" so it is just "k."
   673  			prefix := k[:len(k)-1]
   674  			for k2, _ := range d.Attributes {
   675  				if strings.HasPrefix(k2, prefix) {
   676  					ignoreAttrs[k2] = struct{}{}
   677  				}
   678  			}
   679  		}
   680  
   681  		for k, rd := range d.Attributes {
   682  			if _, ok := ignoreAttrs[k]; ok {
   683  				continue
   684  			}
   685  
   686  			// If the field is requires new and NOT computed, then what
   687  			// we have is a diff mismatch for sure. We set that the old
   688  			// diff does REQUIRE a ForceNew.
   689  			if rd != nil && rd.RequiresNew && !rd.NewComputed {
   690  				oldNew = true
   691  				break
   692  			}
   693  		}
   694  	}
   695  
   696  	if oldNew != newNew {
   697  		return false, fmt.Sprintf(
   698  			"diff RequiresNew; old: %t, new: %t", oldNew, newNew)
   699  	}
   700  
   701  	// Verify that destroy matches. The second boolean here allows us to
   702  	// have mismatching Destroy if we're moving from RequiresNew true
   703  	// to false above. Therefore, the second boolean will only pass if
   704  	// we're moving from Destroy: true to false as well.
   705  	if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew {
   706  		return false, fmt.Sprintf(
   707  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
   708  	}
   709  
   710  	// Go through the old diff and make sure the new diff has all the
   711  	// same attributes. To start, build up the check map to be all the keys.
   712  	checkOld := make(map[string]struct{})
   713  	checkNew := make(map[string]struct{})
   714  	for k, _ := range d.Attributes {
   715  		checkOld[k] = struct{}{}
   716  	}
   717  	for k, _ := range d2.CopyAttributes() {
   718  		checkNew[k] = struct{}{}
   719  	}
   720  
   721  	// Make an ordered list so we are sure the approximated hashes are left
   722  	// to process at the end of the loop
   723  	keys := make([]string, 0, len(d.Attributes))
   724  	for k, _ := range d.Attributes {
   725  		keys = append(keys, k)
   726  	}
   727  	sort.StringSlice(keys).Sort()
   728  
   729  	for _, k := range keys {
   730  		diffOld := d.Attributes[k]
   731  
   732  		if _, ok := checkOld[k]; !ok {
   733  			// We're not checking this key for whatever reason (see where
   734  			// check is modified).
   735  			continue
   736  		}
   737  
   738  		// Remove this key since we'll never hit it again
   739  		delete(checkOld, k)
   740  		delete(checkNew, k)
   741  
   742  		_, ok := d2.GetAttribute(k)
   743  		if !ok {
   744  			// If there's no new attribute, and the old diff expected the attribute
   745  			// to be removed, that's just fine.
   746  			if diffOld.NewRemoved {
   747  				continue
   748  			}
   749  
   750  			// If the last diff was a computed value then the absense of
   751  			// that value is allowed since it may mean the value ended up
   752  			// being the same.
   753  			if diffOld.NewComputed {
   754  				ok = true
   755  			}
   756  
   757  			// No exact match, but maybe this is a set containing computed
   758  			// values. So check if there is an approximate hash in the key
   759  			// and if so, try to match the key.
   760  			if strings.Contains(k, "~") {
   761  				parts := strings.Split(k, ".")
   762  				parts2 := append([]string(nil), parts...)
   763  
   764  				re := regexp.MustCompile(`^~\d+$`)
   765  				for i, part := range parts {
   766  					if re.MatchString(part) {
   767  						// we're going to consider this the base of a
   768  						// computed hash, and remove all longer matching fields
   769  						ok = true
   770  
   771  						parts2[i] = `\d+`
   772  						parts2 = parts2[:i+1]
   773  						break
   774  					}
   775  				}
   776  
   777  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
   778  				if err != nil {
   779  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
   780  				}
   781  
   782  				for k2, _ := range checkNew {
   783  					if re.MatchString(k2) {
   784  						delete(checkNew, k2)
   785  					}
   786  				}
   787  			}
   788  
   789  			// This is a little tricky, but when a diff contains a computed
   790  			// list, set, or map that can only be interpolated after the apply
   791  			// command has created the dependent resources, it could turn out
   792  			// that the result is actually the same as the existing state which
   793  			// would remove the key from the diff.
   794  			if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
   795  				ok = true
   796  			}
   797  
   798  			// Similarly, in a RequiresNew scenario, a list that shows up in the plan
   799  			// diff can disappear from the apply diff, which is calculated from an
   800  			// empty state.
   801  			if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
   802  				ok = true
   803  			}
   804  
   805  			if !ok {
   806  				return false, fmt.Sprintf("attribute mismatch: %s", k)
   807  			}
   808  		}
   809  
   810  		// search for the suffix of the base of a [computed] map, list or set.
   811  		multiVal := regexp.MustCompile(`\.(#|~#|%)$`)
   812  		match := multiVal.FindStringSubmatch(k)
   813  
   814  		if diffOld.NewComputed && len(match) == 2 {
   815  			matchLen := len(match[1])
   816  
   817  			// This is a computed list, set, or map, so remove any keys with
   818  			// this prefix from the check list.
   819  			kprefix := k[:len(k)-matchLen]
   820  			for k2, _ := range checkOld {
   821  				if strings.HasPrefix(k2, kprefix) {
   822  					delete(checkOld, k2)
   823  				}
   824  			}
   825  			for k2, _ := range checkNew {
   826  				if strings.HasPrefix(k2, kprefix) {
   827  					delete(checkNew, k2)
   828  				}
   829  			}
   830  		}
   831  
   832  		// TODO: check for the same value if not computed
   833  	}
   834  
   835  	// Check for leftover attributes
   836  	if len(checkNew) > 0 {
   837  		extras := make([]string, 0, len(checkNew))
   838  		for attr, _ := range checkNew {
   839  			extras = append(extras, attr)
   840  		}
   841  		return false,
   842  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
   843  	}
   844  
   845  	return true, ""
   846  }
   847  
   848  // moduleDiffSort implements sort.Interface to sort module diffs by path.
   849  type moduleDiffSort []*ModuleDiff
   850  
   851  func (s moduleDiffSort) Len() int      { return len(s) }
   852  func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   853  func (s moduleDiffSort) Less(i, j int) bool {
   854  	a := s[i]
   855  	b := s[j]
   856  
   857  	// If the lengths are different, then the shorter one always wins
   858  	if len(a.Path) != len(b.Path) {
   859  		return len(a.Path) < len(b.Path)
   860  	}
   861  
   862  	// Otherwise, compare lexically
   863  	return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")
   864  }