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