github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/terraform/diff.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package terraform
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"log"
    11  	"reflect"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  
    18  	"github.com/terramate-io/tf/addrs"
    19  	"github.com/terramate-io/tf/configs/configschema"
    20  	"github.com/terramate-io/tf/configs/hcl2shim"
    21  	"github.com/zclconf/go-cty/cty"
    22  
    23  	"github.com/mitchellh/copystructure"
    24  )
    25  
    26  // DiffChangeType is an enum with the kind of changes a diff has planned.
    27  type DiffChangeType byte
    28  
    29  const (
    30  	DiffInvalid DiffChangeType = iota
    31  	DiffNone
    32  	DiffCreate
    33  	DiffUpdate
    34  	DiffDestroy
    35  	DiffDestroyCreate
    36  
    37  	// DiffRefresh is only used in the UI for displaying diffs.
    38  	// Managed resource reads never appear in plan, and when data source
    39  	// reads appear they are represented as DiffCreate in core before
    40  	// transforming to DiffRefresh in the UI layer.
    41  	DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion
    42  )
    43  
    44  // multiVal matches the index key to a flatmapped set, list or map
    45  var multiVal = regexp.MustCompile(`\.(#|%)$`)
    46  
    47  // Diff tracks the changes that are necessary to apply a configuration
    48  // to an existing infrastructure.
    49  type Diff struct {
    50  	// Modules contains all the modules that have a diff
    51  	Modules []*ModuleDiff
    52  }
    53  
    54  // Prune cleans out unused structures in the diff without affecting
    55  // the behavior of the diff at all.
    56  //
    57  // This is not safe to call concurrently. This is safe to call on a
    58  // nil Diff.
    59  func (d *Diff) Prune() {
    60  	if d == nil {
    61  		return
    62  	}
    63  
    64  	// Prune all empty modules
    65  	newModules := make([]*ModuleDiff, 0, len(d.Modules))
    66  	for _, m := range d.Modules {
    67  		// If the module isn't empty, we keep it
    68  		if !m.Empty() {
    69  			newModules = append(newModules, m)
    70  		}
    71  	}
    72  	if len(newModules) == 0 {
    73  		newModules = nil
    74  	}
    75  	d.Modules = newModules
    76  }
    77  
    78  // AddModule adds the module with the given path to the diff.
    79  //
    80  // This should be the preferred method to add module diffs since it
    81  // allows us to optimize lookups later as well as control sorting.
    82  func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff {
    83  	// Lower the new-style address into a legacy-style address.
    84  	// This requires that none of the steps have instance keys, which is
    85  	// true for all addresses at the time of implementing this because
    86  	// "count" and "for_each" are not yet implemented for modules.
    87  	legacyPath := make([]string, len(path))
    88  	for i, step := range path {
    89  		if step.InstanceKey != addrs.NoKey {
    90  			// FIXME: Once the rest of Terraform is ready to use count and
    91  			// for_each, remove all of this and just write the addrs.ModuleInstance
    92  			// value itself into the ModuleState.
    93  			panic("diff cannot represent modules with count or for_each keys")
    94  		}
    95  
    96  		legacyPath[i] = step.Name
    97  	}
    98  
    99  	m := &ModuleDiff{Path: legacyPath}
   100  	m.init()
   101  	d.Modules = append(d.Modules, m)
   102  	return m
   103  }
   104  
   105  // ModuleByPath is used to lookup the module diff for the given path.
   106  // This should be the preferred lookup mechanism as it allows for future
   107  // lookup optimizations.
   108  func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff {
   109  	if d == nil {
   110  		return nil
   111  	}
   112  	for _, mod := range d.Modules {
   113  		if mod.Path == nil {
   114  			panic("missing module path")
   115  		}
   116  		modPath := normalizeModulePath(mod.Path)
   117  		if modPath.String() == path.String() {
   118  			return mod
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  // RootModule returns the ModuleState for the root module
   125  func (d *Diff) RootModule() *ModuleDiff {
   126  	root := d.ModuleByPath(addrs.RootModuleInstance)
   127  	if root == nil {
   128  		panic("missing root module")
   129  	}
   130  	return root
   131  }
   132  
   133  // Empty returns true if the diff has no changes.
   134  func (d *Diff) Empty() bool {
   135  	if d == nil {
   136  		return true
   137  	}
   138  
   139  	for _, m := range d.Modules {
   140  		if !m.Empty() {
   141  			return false
   142  		}
   143  	}
   144  
   145  	return true
   146  }
   147  
   148  // Equal compares two diffs for exact equality.
   149  //
   150  // This is different from the Same comparison that is supported which
   151  // checks for operation equality taking into account computed values. Equal
   152  // instead checks for exact equality.
   153  func (d *Diff) Equal(d2 *Diff) bool {
   154  	// If one is nil, they must both be nil
   155  	if d == nil || d2 == nil {
   156  		return d == d2
   157  	}
   158  
   159  	// Sort the modules
   160  	sort.Sort(moduleDiffSort(d.Modules))
   161  	sort.Sort(moduleDiffSort(d2.Modules))
   162  
   163  	// Copy since we have to modify the module destroy flag to false so
   164  	// we don't compare that. TODO: delete this when we get rid of the
   165  	// destroy flag on modules.
   166  	dCopy := d.DeepCopy()
   167  	d2Copy := d2.DeepCopy()
   168  	for _, m := range dCopy.Modules {
   169  		m.Destroy = false
   170  	}
   171  	for _, m := range d2Copy.Modules {
   172  		m.Destroy = false
   173  	}
   174  
   175  	// Use DeepEqual
   176  	return reflect.DeepEqual(dCopy, d2Copy)
   177  }
   178  
   179  // DeepCopy performs a deep copy of all parts of the Diff, making the
   180  // resulting Diff safe to use without modifying this one.
   181  func (d *Diff) DeepCopy() *Diff {
   182  	copy, err := copystructure.Config{Lock: true}.Copy(d)
   183  	if err != nil {
   184  		panic(err)
   185  	}
   186  
   187  	return copy.(*Diff)
   188  }
   189  
   190  func (d *Diff) String() string {
   191  	var buf bytes.Buffer
   192  
   193  	keys := make([]string, 0, len(d.Modules))
   194  	lookup := make(map[string]*ModuleDiff)
   195  	for _, m := range d.Modules {
   196  		addr := normalizeModulePath(m.Path)
   197  		key := addr.String()
   198  		keys = append(keys, key)
   199  		lookup[key] = m
   200  	}
   201  	sort.Strings(keys)
   202  
   203  	for _, key := range keys {
   204  		m := lookup[key]
   205  		mStr := m.String()
   206  
   207  		// If we're the root module, we just write the output directly.
   208  		if reflect.DeepEqual(m.Path, rootModulePath) {
   209  			buf.WriteString(mStr + "\n")
   210  			continue
   211  		}
   212  
   213  		buf.WriteString(fmt.Sprintf("%s:\n", key))
   214  
   215  		s := bufio.NewScanner(strings.NewReader(mStr))
   216  		for s.Scan() {
   217  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
   218  		}
   219  	}
   220  
   221  	return strings.TrimSpace(buf.String())
   222  }
   223  
   224  func (d *Diff) init() {
   225  	if d.Modules == nil {
   226  		rootDiff := &ModuleDiff{Path: rootModulePath}
   227  		d.Modules = []*ModuleDiff{rootDiff}
   228  	}
   229  	for _, m := range d.Modules {
   230  		m.init()
   231  	}
   232  }
   233  
   234  // ModuleDiff tracks the differences between resources to apply within
   235  // a single module.
   236  type ModuleDiff struct {
   237  	Path      []string
   238  	Resources map[string]*InstanceDiff
   239  	Destroy   bool // Set only by the destroy plan
   240  }
   241  
   242  func (d *ModuleDiff) init() {
   243  	if d.Resources == nil {
   244  		d.Resources = make(map[string]*InstanceDiff)
   245  	}
   246  	for _, r := range d.Resources {
   247  		r.init()
   248  	}
   249  }
   250  
   251  // ChangeType returns the type of changes that the diff for this
   252  // module includes.
   253  //
   254  // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or
   255  // DiffCreate. If an instance within the module has a DiffDestroyCreate
   256  // then this will register as a DiffCreate for a module.
   257  func (d *ModuleDiff) ChangeType() DiffChangeType {
   258  	result := DiffNone
   259  	for _, r := range d.Resources {
   260  		change := r.ChangeType()
   261  		switch change {
   262  		case DiffCreate, DiffDestroy:
   263  			if result == DiffNone {
   264  				result = change
   265  			}
   266  		case DiffDestroyCreate, DiffUpdate:
   267  			result = DiffUpdate
   268  		}
   269  	}
   270  
   271  	return result
   272  }
   273  
   274  // Empty returns true if the diff has no changes within this module.
   275  func (d *ModuleDiff) Empty() bool {
   276  	if d.Destroy {
   277  		return false
   278  	}
   279  
   280  	if len(d.Resources) == 0 {
   281  		return true
   282  	}
   283  
   284  	for _, rd := range d.Resources {
   285  		if !rd.Empty() {
   286  			return false
   287  		}
   288  	}
   289  
   290  	return true
   291  }
   292  
   293  // Instances returns the instance diffs for the id given. This can return
   294  // multiple instance diffs if there are counts within the resource.
   295  func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
   296  	var result []*InstanceDiff
   297  	for k, diff := range d.Resources {
   298  		if k == id || strings.HasPrefix(k, id+".") {
   299  			if !diff.Empty() {
   300  				result = append(result, diff)
   301  			}
   302  		}
   303  	}
   304  
   305  	return result
   306  }
   307  
   308  // IsRoot says whether or not this module diff is for the root module.
   309  func (d *ModuleDiff) IsRoot() bool {
   310  	return reflect.DeepEqual(d.Path, rootModulePath)
   311  }
   312  
   313  // String outputs the diff in a long but command-line friendly output
   314  // format that users can read to quickly inspect a diff.
   315  func (d *ModuleDiff) String() string {
   316  	var buf bytes.Buffer
   317  
   318  	names := make([]string, 0, len(d.Resources))
   319  	for name, _ := range d.Resources {
   320  		names = append(names, name)
   321  	}
   322  	sort.Strings(names)
   323  
   324  	for _, name := range names {
   325  		rdiff := d.Resources[name]
   326  
   327  		crud := "UPDATE"
   328  		switch {
   329  		case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
   330  			crud = "DESTROY/CREATE"
   331  		case rdiff.GetDestroy() || rdiff.GetDestroyDeposed():
   332  			crud = "DESTROY"
   333  		case rdiff.RequiresNew():
   334  			crud = "CREATE"
   335  		}
   336  
   337  		extra := ""
   338  		if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() {
   339  			extra = " (deposed only)"
   340  		}
   341  
   342  		buf.WriteString(fmt.Sprintf(
   343  			"%s: %s%s\n",
   344  			crud,
   345  			name,
   346  			extra))
   347  
   348  		keyLen := 0
   349  		rdiffAttrs := rdiff.CopyAttributes()
   350  		keys := make([]string, 0, len(rdiffAttrs))
   351  		for key, _ := range rdiffAttrs {
   352  			if key == "id" {
   353  				continue
   354  			}
   355  
   356  			keys = append(keys, key)
   357  			if len(key) > keyLen {
   358  				keyLen = len(key)
   359  			}
   360  		}
   361  		sort.Strings(keys)
   362  
   363  		for _, attrK := range keys {
   364  			attrDiff, _ := rdiff.GetAttribute(attrK)
   365  
   366  			v := attrDiff.New
   367  			u := attrDiff.Old
   368  			if attrDiff.NewComputed {
   369  				v = "<computed>"
   370  			}
   371  
   372  			if attrDiff.Sensitive {
   373  				u = "<sensitive>"
   374  				v = "<sensitive>"
   375  			}
   376  
   377  			updateMsg := ""
   378  			if attrDiff.RequiresNew {
   379  				updateMsg = " (forces new resource)"
   380  			} else if attrDiff.Sensitive {
   381  				updateMsg = " (attribute changed)"
   382  			}
   383  
   384  			buf.WriteString(fmt.Sprintf(
   385  				"  %s:%s %#v => %#v%s\n",
   386  				attrK,
   387  				strings.Repeat(" ", keyLen-len(attrK)),
   388  				u,
   389  				v,
   390  				updateMsg))
   391  		}
   392  	}
   393  
   394  	return buf.String()
   395  }
   396  
   397  // InstanceDiff is the diff of a resource from some state to another.
   398  type InstanceDiff struct {
   399  	mu             sync.Mutex
   400  	Attributes     map[string]*ResourceAttrDiff
   401  	Destroy        bool
   402  	DestroyDeposed bool
   403  	DestroyTainted bool
   404  
   405  	// Meta is a simple K/V map that is stored in a diff and persisted to
   406  	// plans but otherwise is completely ignored by Terraform core. It is
   407  	// meant to be used for additional data a resource may want to pass through.
   408  	// The value here must only contain Go primitives and collections.
   409  	Meta map[string]interface{}
   410  }
   411  
   412  func (d *InstanceDiff) Lock()   { d.mu.Lock() }
   413  func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
   414  
   415  // ApplyToValue merges the receiver into the given base value, returning a
   416  // new value that incorporates the planned changes. The given value must
   417  // conform to the given schema, or this method will panic.
   418  //
   419  // This method is intended for shimming old subsystems that still use this
   420  // legacy diff type to work with the new-style types.
   421  func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) {
   422  	// Create an InstanceState attributes from our existing state.
   423  	// We can use this to more easily apply the diff changes.
   424  	attrs := hcl2shim.FlatmapValueFromHCL2(base)
   425  	applied, err := d.Apply(attrs, schema)
   426  	if err != nil {
   427  		return base, err
   428  	}
   429  
   430  	val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType())
   431  	if err != nil {
   432  		return base, err
   433  	}
   434  
   435  	return schema.CoerceValue(val)
   436  }
   437  
   438  // Apply applies the diff to the provided flatmapped attributes,
   439  // returning the new instance attributes.
   440  //
   441  // This method is intended for shimming old subsystems that still use this
   442  // legacy diff type to work with the new-style types.
   443  func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
   444  	// We always build a new value here, even if the given diff is "empty",
   445  	// because we might be planning to create a new instance that happens
   446  	// to have no attributes set, and so we want to produce an empty object
   447  	// rather than just echoing back the null old value.
   448  	if attrs == nil {
   449  		attrs = map[string]string{}
   450  	}
   451  
   452  	// Rather applying the diff to mutate the attrs, we'll copy new values into
   453  	// here to avoid the possibility of leaving stale values.
   454  	result := map[string]string{}
   455  
   456  	if d.Destroy || d.DestroyDeposed || d.DestroyTainted {
   457  		return result, nil
   458  	}
   459  
   460  	return d.applyBlockDiff(nil, attrs, schema)
   461  }
   462  
   463  func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
   464  	result := map[string]string{}
   465  	name := ""
   466  	if len(path) > 0 {
   467  		name = path[len(path)-1]
   468  	}
   469  
   470  	// localPrefix is used to build the local result map
   471  	localPrefix := ""
   472  	if name != "" {
   473  		localPrefix = name + "."
   474  	}
   475  
   476  	// iterate over the schema rather than the attributes, so we can handle
   477  	// different block types separately from plain attributes
   478  	for n, attrSchema := range schema.Attributes {
   479  		var err error
   480  		newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema)
   481  
   482  		if err != nil {
   483  			return result, err
   484  		}
   485  
   486  		for k, v := range newAttrs {
   487  			result[localPrefix+k] = v
   488  		}
   489  	}
   490  
   491  	blockPrefix := strings.Join(path, ".")
   492  	if blockPrefix != "" {
   493  		blockPrefix += "."
   494  	}
   495  	for n, block := range schema.BlockTypes {
   496  		// we need to find the set of all keys that traverse this block
   497  		candidateKeys := map[string]bool{}
   498  		blockKey := blockPrefix + n + "."
   499  		localBlockPrefix := localPrefix + n + "."
   500  
   501  		// we can only trust the diff for sets, since the path changes, so don't
   502  		// count existing values as candidate keys. If it turns out we're
   503  		// keeping the attributes, we will catch it down below with "keepBlock"
   504  		// after we check the set count.
   505  		if block.Nesting != configschema.NestingSet {
   506  			for k := range attrs {
   507  				if strings.HasPrefix(k, blockKey) {
   508  					nextDot := strings.Index(k[len(blockKey):], ".")
   509  					if nextDot < 0 {
   510  						continue
   511  					}
   512  					nextDot += len(blockKey)
   513  					candidateKeys[k[len(blockKey):nextDot]] = true
   514  				}
   515  			}
   516  		}
   517  
   518  		for k, diff := range d.Attributes {
   519  			// helper/schema should not insert nil diff values, but don't panic
   520  			// if it does.
   521  			if diff == nil {
   522  				continue
   523  			}
   524  
   525  			if strings.HasPrefix(k, blockKey) {
   526  				nextDot := strings.Index(k[len(blockKey):], ".")
   527  				if nextDot < 0 {
   528  					continue
   529  				}
   530  
   531  				if diff.NewRemoved {
   532  					continue
   533  				}
   534  
   535  				nextDot += len(blockKey)
   536  				candidateKeys[k[len(blockKey):nextDot]] = true
   537  			}
   538  		}
   539  
   540  		// check each set candidate to see if it was removed.
   541  		// we need to do this, because when entire sets are removed, they may
   542  		// have the wrong key, and ony show diffs going to ""
   543  		if block.Nesting == configschema.NestingSet {
   544  			for k := range candidateKeys {
   545  				indexPrefix := strings.Join(append(path, n, k), ".") + "."
   546  				keep := false
   547  				// now check each set element to see if it's a new diff, or one
   548  				// that we're dropping. Since we're only applying the "New"
   549  				// portion of the set, we can ignore diffs that only contain "Old"
   550  				for attr, diff := range d.Attributes {
   551  					// helper/schema should not insert nil diff values, but don't panic
   552  					// if it does.
   553  					if diff == nil {
   554  						continue
   555  					}
   556  
   557  					if !strings.HasPrefix(attr, indexPrefix) {
   558  						continue
   559  					}
   560  
   561  					// check for empty "count" keys
   562  					if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" {
   563  						continue
   564  					}
   565  
   566  					// removed items don't count either
   567  					if diff.NewRemoved {
   568  						continue
   569  					}
   570  
   571  					// this must be a diff to keep
   572  					keep = true
   573  					break
   574  				}
   575  				if !keep {
   576  					delete(candidateKeys, k)
   577  				}
   578  			}
   579  		}
   580  
   581  		for k := range candidateKeys {
   582  			newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block)
   583  			if err != nil {
   584  				return result, err
   585  			}
   586  
   587  			for attr, v := range newAttrs {
   588  				result[localBlockPrefix+attr] = v
   589  			}
   590  		}
   591  
   592  		keepBlock := true
   593  		// check this block's count diff directly first, since we may not
   594  		// have candidates because it was removed and only set to "0"
   595  		if diff, ok := d.Attributes[blockKey+"#"]; ok {
   596  			if diff.New == "0" || diff.NewRemoved {
   597  				keepBlock = false
   598  			}
   599  		}
   600  
   601  		// if there was no diff at all, then we need to keep the block attributes
   602  		if len(candidateKeys) == 0 && keepBlock {
   603  			for k, v := range attrs {
   604  				if strings.HasPrefix(k, blockKey) {
   605  					// we need the key relative to this block, so remove the
   606  					// entire prefix, then re-insert the block name.
   607  					localKey := localBlockPrefix + k[len(blockKey):]
   608  					result[localKey] = v
   609  				}
   610  			}
   611  		}
   612  
   613  		countAddr := strings.Join(append(path, n, "#"), ".")
   614  		if countDiff, ok := d.Attributes[countAddr]; ok {
   615  			if countDiff.NewComputed {
   616  				result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue
   617  			} else {
   618  				result[localBlockPrefix+"#"] = countDiff.New
   619  
   620  				// While sets are complete, list are not, and we may not have all the
   621  				// information to track removals. If the list was truncated, we need to
   622  				// remove the extra items from the result.
   623  				if block.Nesting == configschema.NestingList &&
   624  					countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue {
   625  					length, _ := strconv.Atoi(countDiff.New)
   626  					for k := range result {
   627  						if !strings.HasPrefix(k, localBlockPrefix) {
   628  							continue
   629  						}
   630  
   631  						index := k[len(localBlockPrefix):]
   632  						nextDot := strings.Index(index, ".")
   633  						if nextDot < 1 {
   634  							continue
   635  						}
   636  						index = index[:nextDot]
   637  						i, err := strconv.Atoi(index)
   638  						if err != nil {
   639  							// this shouldn't happen since we added these
   640  							// ourself, but make note of it just in case.
   641  							log.Printf("[ERROR] bad list index in %q: %s", k, err)
   642  							continue
   643  						}
   644  						if i >= length {
   645  							delete(result, k)
   646  						}
   647  					}
   648  				}
   649  			}
   650  		} else if origCount, ok := attrs[countAddr]; ok && keepBlock {
   651  			result[localBlockPrefix+"#"] = origCount
   652  		} else {
   653  			result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result)
   654  		}
   655  	}
   656  
   657  	return result, nil
   658  }
   659  
   660  func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   661  	ty := attrSchema.Type
   662  	switch {
   663  	case ty.IsListType(), ty.IsTupleType(), ty.IsMapType():
   664  		return d.applyCollectionDiff(path, attrs, attrSchema)
   665  	case ty.IsSetType():
   666  		return d.applySetDiff(path, attrs, attrSchema)
   667  	default:
   668  		return d.applySingleAttrDiff(path, attrs, attrSchema)
   669  	}
   670  }
   671  
   672  func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   673  	currentKey := strings.Join(path, ".")
   674  
   675  	attr := path[len(path)-1]
   676  
   677  	result := map[string]string{}
   678  	diff := d.Attributes[currentKey]
   679  	old, exists := attrs[currentKey]
   680  
   681  	if diff != nil && diff.NewComputed {
   682  		result[attr] = hcl2shim.UnknownVariableValue
   683  		return result, nil
   684  	}
   685  
   686  	// "id" must exist and not be an empty string, or it must be unknown.
   687  	// This only applied to top-level "id" fields.
   688  	if attr == "id" && len(path) == 1 {
   689  		if old == "" {
   690  			result[attr] = hcl2shim.UnknownVariableValue
   691  		} else {
   692  			result[attr] = old
   693  		}
   694  		return result, nil
   695  	}
   696  
   697  	// attribute diffs are sometimes missed, so assume no diff means keep the
   698  	// old value
   699  	if diff == nil {
   700  		if exists {
   701  			result[attr] = old
   702  		} else {
   703  			// We need required values, so set those with an empty value. It
   704  			// must be set in the config, since if it were missing it would have
   705  			// failed validation.
   706  			if attrSchema.Required {
   707  				// we only set a missing string here, since bool or number types
   708  				// would have distinct zero value which shouldn't have been
   709  				// lost.
   710  				if attrSchema.Type == cty.String {
   711  					result[attr] = ""
   712  				}
   713  			}
   714  		}
   715  		return result, nil
   716  	}
   717  
   718  	// check for missmatched diff values
   719  	if exists &&
   720  		old != diff.Old &&
   721  		old != hcl2shim.UnknownVariableValue &&
   722  		diff.Old != hcl2shim.UnknownVariableValue {
   723  		return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old)
   724  	}
   725  
   726  	if diff.NewRemoved {
   727  		// don't set anything in the new value
   728  		return map[string]string{}, nil
   729  	}
   730  
   731  	if diff.Old == diff.New && diff.New == "" {
   732  		// this can only be a valid empty string
   733  		if attrSchema.Type == cty.String {
   734  			result[attr] = ""
   735  		}
   736  		return result, nil
   737  	}
   738  
   739  	if attrSchema.Computed && diff.NewComputed {
   740  		result[attr] = hcl2shim.UnknownVariableValue
   741  		return result, nil
   742  	}
   743  
   744  	result[attr] = diff.New
   745  
   746  	return result, nil
   747  }
   748  
   749  func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   750  	result := map[string]string{}
   751  
   752  	prefix := ""
   753  	if len(path) > 1 {
   754  		prefix = strings.Join(path[:len(path)-1], ".") + "."
   755  	}
   756  
   757  	name := ""
   758  	if len(path) > 0 {
   759  		name = path[len(path)-1]
   760  	}
   761  
   762  	currentKey := prefix + name
   763  
   764  	// check the index first for special handling
   765  	for k, diff := range d.Attributes {
   766  		// check the index value, which can be set, and 0
   767  		if k == currentKey+".#" || k == currentKey+".%" || k == currentKey {
   768  			if diff.NewRemoved {
   769  				return result, nil
   770  			}
   771  
   772  			if diff.NewComputed {
   773  				result[k[len(prefix):]] = hcl2shim.UnknownVariableValue
   774  				return result, nil
   775  			}
   776  
   777  			// do what the diff tells us to here, so that it's consistent with applies
   778  			if diff.New == "0" {
   779  				result[k[len(prefix):]] = "0"
   780  				return result, nil
   781  			}
   782  		}
   783  	}
   784  
   785  	// collect all the keys from the diff and the old state
   786  	noDiff := true
   787  	keys := map[string]bool{}
   788  	for k := range d.Attributes {
   789  		if !strings.HasPrefix(k, currentKey+".") {
   790  			continue
   791  		}
   792  		noDiff = false
   793  		keys[k] = true
   794  	}
   795  
   796  	noAttrs := true
   797  	for k := range attrs {
   798  		if !strings.HasPrefix(k, currentKey+".") {
   799  			continue
   800  		}
   801  		noAttrs = false
   802  		keys[k] = true
   803  	}
   804  
   805  	// If there's no diff and no attrs, then there's no value at all.
   806  	// This prevents an unexpected zero-count attribute in the attributes.
   807  	if noDiff && noAttrs {
   808  		return result, nil
   809  	}
   810  
   811  	idx := "#"
   812  	if attrSchema.Type.IsMapType() {
   813  		idx = "%"
   814  	}
   815  
   816  	for k := range keys {
   817  		// generate an schema placeholder for the values
   818  		elSchema := &configschema.Attribute{
   819  			Type: attrSchema.Type.ElementType(),
   820  		}
   821  
   822  		res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema)
   823  		if err != nil {
   824  			return result, err
   825  		}
   826  
   827  		for k, v := range res {
   828  			result[name+"."+k] = v
   829  		}
   830  	}
   831  
   832  	// Just like in nested list blocks, for simple lists we may need to fill in
   833  	// missing empty strings.
   834  	countKey := name + "." + idx
   835  	count := result[countKey]
   836  	length, _ := strconv.Atoi(count)
   837  
   838  	if count != "" && count != hcl2shim.UnknownVariableValue &&
   839  		attrSchema.Type.Equals(cty.List(cty.String)) {
   840  		// insert empty strings into missing indexes
   841  		for i := 0; i < length; i++ {
   842  			key := fmt.Sprintf("%s.%d", name, i)
   843  			if _, ok := result[key]; !ok {
   844  				result[key] = ""
   845  			}
   846  		}
   847  	}
   848  
   849  	// now check for truncation in any type of list
   850  	if attrSchema.Type.IsListType() {
   851  		for key := range result {
   852  			if key == countKey {
   853  				continue
   854  			}
   855  
   856  			if len(key) <= len(name)+1 {
   857  				// not sure what this is, but don't panic
   858  				continue
   859  			}
   860  
   861  			index := key[len(name)+1:]
   862  
   863  			// It is possible to have nested sets or maps, so look for another dot
   864  			dot := strings.Index(index, ".")
   865  			if dot > 0 {
   866  				index = index[:dot]
   867  			}
   868  
   869  			// This shouldn't have any more dots, since the element type is only string.
   870  			num, err := strconv.Atoi(index)
   871  			if err != nil {
   872  				log.Printf("[ERROR] bad list index in %q: %s", currentKey, err)
   873  				continue
   874  			}
   875  
   876  			if num >= length {
   877  				delete(result, key)
   878  			}
   879  		}
   880  	}
   881  
   882  	// Fill in the count value if it wasn't present in the diff for some reason,
   883  	// or if there is no count at all.
   884  	_, countDiff := d.Attributes[countKey]
   885  	if result[countKey] == "" || (!countDiff && len(keys) != len(result)) {
   886  		result[countKey] = countFlatmapContainerValues(countKey, result)
   887  	}
   888  
   889  	return result, nil
   890  }
   891  
   892  func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   893  	// We only need this special behavior for sets of object.
   894  	if !attrSchema.Type.ElementType().IsObjectType() {
   895  		// The normal collection apply behavior will work okay for this one, then.
   896  		return d.applyCollectionDiff(path, attrs, attrSchema)
   897  	}
   898  
   899  	// When we're dealing with a set of an object type we actually want to
   900  	// use our normal _block type_ apply behaviors, so we'll construct ourselves
   901  	// a synthetic schema that treats the object type as a block type and
   902  	// then delegate to our block apply method.
   903  	synthSchema := &configschema.Block{
   904  		Attributes: make(map[string]*configschema.Attribute),
   905  	}
   906  
   907  	for name, ty := range attrSchema.Type.ElementType().AttributeTypes() {
   908  		// We can safely make everything into an attribute here because in the
   909  		// event that there are nested set attributes we'll end up back in
   910  		// here again recursively and can then deal with the next level of
   911  		// expansion.
   912  		synthSchema.Attributes[name] = &configschema.Attribute{
   913  			Type:     ty,
   914  			Optional: true,
   915  		}
   916  	}
   917  
   918  	parentPath := path[:len(path)-1]
   919  	childName := path[len(path)-1]
   920  	containerSchema := &configschema.Block{
   921  		BlockTypes: map[string]*configschema.NestedBlock{
   922  			childName: {
   923  				Nesting: configschema.NestingSet,
   924  				Block:   *synthSchema,
   925  			},
   926  		},
   927  	}
   928  
   929  	return d.applyBlockDiff(parentPath, attrs, containerSchema)
   930  }
   931  
   932  // countFlatmapContainerValues returns the number of values in the flatmapped container
   933  // (set, map, list) indexed by key. The key argument is expected to include the
   934  // trailing ".#", or ".%".
   935  func countFlatmapContainerValues(key string, attrs map[string]string) string {
   936  	if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) {
   937  		panic(fmt.Sprintf("invalid index value %q", key))
   938  	}
   939  
   940  	prefix := key[:len(key)-1]
   941  	items := map[string]int{}
   942  
   943  	for k := range attrs {
   944  		if k == key {
   945  			continue
   946  		}
   947  		if !strings.HasPrefix(k, prefix) {
   948  			continue
   949  		}
   950  
   951  		suffix := k[len(prefix):]
   952  		dot := strings.Index(suffix, ".")
   953  		if dot > 0 {
   954  			suffix = suffix[:dot]
   955  		}
   956  
   957  		items[suffix]++
   958  	}
   959  	return strconv.Itoa(len(items))
   960  }
   961  
   962  // ResourceAttrDiff is the diff of a single attribute of a resource.
   963  type ResourceAttrDiff struct {
   964  	Old         string      // Old Value
   965  	New         string      // New Value
   966  	NewComputed bool        // True if new value is computed (unknown currently)
   967  	NewRemoved  bool        // True if this attribute is being removed
   968  	NewExtra    interface{} // Extra information for the provider
   969  	RequiresNew bool        // True if change requires new resource
   970  	Sensitive   bool        // True if the data should not be displayed in UI output
   971  	Type        DiffAttrType
   972  }
   973  
   974  // Empty returns true if the diff for this attr is neutral
   975  func (d *ResourceAttrDiff) Empty() bool {
   976  	return d.Old == d.New && !d.NewComputed && !d.NewRemoved
   977  }
   978  
   979  func (d *ResourceAttrDiff) GoString() string {
   980  	return fmt.Sprintf("*%#v", *d)
   981  }
   982  
   983  // DiffAttrType is an enum type that says whether a resource attribute
   984  // diff is an input attribute (comes from the configuration) or an
   985  // output attribute (comes as a result of applying the configuration). An
   986  // example input would be "ami" for AWS and an example output would be
   987  // "private_ip".
   988  type DiffAttrType byte
   989  
   990  const (
   991  	DiffAttrUnknown DiffAttrType = iota
   992  	DiffAttrInput
   993  	DiffAttrOutput
   994  )
   995  
   996  func (d *InstanceDiff) init() {
   997  	if d.Attributes == nil {
   998  		d.Attributes = make(map[string]*ResourceAttrDiff)
   999  	}
  1000  }
  1001  
  1002  func NewInstanceDiff() *InstanceDiff {
  1003  	return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
  1004  }
  1005  
  1006  func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
  1007  	if d == nil {
  1008  		return nil, nil
  1009  	}
  1010  
  1011  	dCopy, err := copystructure.Config{Lock: true}.Copy(d)
  1012  	if err != nil {
  1013  		return nil, err
  1014  	}
  1015  
  1016  	return dCopy.(*InstanceDiff), nil
  1017  }
  1018  
  1019  // ChangeType returns the DiffChangeType represented by the diff
  1020  // for this single instance.
  1021  func (d *InstanceDiff) ChangeType() DiffChangeType {
  1022  	if d.Empty() {
  1023  		return DiffNone
  1024  	}
  1025  
  1026  	if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
  1027  		return DiffDestroyCreate
  1028  	}
  1029  
  1030  	if d.GetDestroy() || d.GetDestroyDeposed() {
  1031  		return DiffDestroy
  1032  	}
  1033  
  1034  	if d.RequiresNew() {
  1035  		return DiffCreate
  1036  	}
  1037  
  1038  	return DiffUpdate
  1039  }
  1040  
  1041  // Empty returns true if this diff encapsulates no changes.
  1042  func (d *InstanceDiff) Empty() bool {
  1043  	if d == nil {
  1044  		return true
  1045  	}
  1046  
  1047  	d.mu.Lock()
  1048  	defer d.mu.Unlock()
  1049  	return !d.Destroy &&
  1050  		!d.DestroyTainted &&
  1051  		!d.DestroyDeposed &&
  1052  		len(d.Attributes) == 0
  1053  }
  1054  
  1055  // Equal compares two diffs for exact equality.
  1056  //
  1057  // This is different from the Same comparison that is supported which
  1058  // checks for operation equality taking into account computed values. Equal
  1059  // instead checks for exact equality.
  1060  func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
  1061  	// If one is nil, they must both be nil
  1062  	if d == nil || d2 == nil {
  1063  		return d == d2
  1064  	}
  1065  
  1066  	// Use DeepEqual
  1067  	return reflect.DeepEqual(d, d2)
  1068  }
  1069  
  1070  // DeepCopy performs a deep copy of all parts of the InstanceDiff
  1071  func (d *InstanceDiff) DeepCopy() *InstanceDiff {
  1072  	copy, err := copystructure.Config{Lock: true}.Copy(d)
  1073  	if err != nil {
  1074  		panic(err)
  1075  	}
  1076  
  1077  	return copy.(*InstanceDiff)
  1078  }
  1079  
  1080  func (d *InstanceDiff) GoString() string {
  1081  	return fmt.Sprintf("*%#v", InstanceDiff{
  1082  		Attributes:     d.Attributes,
  1083  		Destroy:        d.Destroy,
  1084  		DestroyTainted: d.DestroyTainted,
  1085  		DestroyDeposed: d.DestroyDeposed,
  1086  	})
  1087  }
  1088  
  1089  // RequiresNew returns true if the diff requires the creation of a new
  1090  // resource (implying the destruction of the old).
  1091  func (d *InstanceDiff) RequiresNew() bool {
  1092  	if d == nil {
  1093  		return false
  1094  	}
  1095  
  1096  	d.mu.Lock()
  1097  	defer d.mu.Unlock()
  1098  
  1099  	return d.requiresNew()
  1100  }
  1101  
  1102  func (d *InstanceDiff) requiresNew() bool {
  1103  	if d == nil {
  1104  		return false
  1105  	}
  1106  
  1107  	if d.DestroyTainted {
  1108  		return true
  1109  	}
  1110  
  1111  	for _, rd := range d.Attributes {
  1112  		if rd != nil && rd.RequiresNew {
  1113  			return true
  1114  		}
  1115  	}
  1116  
  1117  	return false
  1118  }
  1119  
  1120  func (d *InstanceDiff) GetDestroyDeposed() bool {
  1121  	d.mu.Lock()
  1122  	defer d.mu.Unlock()
  1123  
  1124  	return d.DestroyDeposed
  1125  }
  1126  
  1127  func (d *InstanceDiff) SetDestroyDeposed(b bool) {
  1128  	d.mu.Lock()
  1129  	defer d.mu.Unlock()
  1130  
  1131  	d.DestroyDeposed = b
  1132  }
  1133  
  1134  // These methods are properly locked, for use outside other InstanceDiff
  1135  // methods but everywhere else within the terraform package.
  1136  // TODO refactor the locking scheme
  1137  func (d *InstanceDiff) SetTainted(b bool) {
  1138  	d.mu.Lock()
  1139  	defer d.mu.Unlock()
  1140  
  1141  	d.DestroyTainted = b
  1142  }
  1143  
  1144  func (d *InstanceDiff) GetDestroyTainted() bool {
  1145  	d.mu.Lock()
  1146  	defer d.mu.Unlock()
  1147  
  1148  	return d.DestroyTainted
  1149  }
  1150  
  1151  func (d *InstanceDiff) SetDestroy(b bool) {
  1152  	d.mu.Lock()
  1153  	defer d.mu.Unlock()
  1154  
  1155  	d.Destroy = b
  1156  }
  1157  
  1158  func (d *InstanceDiff) GetDestroy() bool {
  1159  	d.mu.Lock()
  1160  	defer d.mu.Unlock()
  1161  
  1162  	return d.Destroy
  1163  }
  1164  
  1165  func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
  1166  	d.mu.Lock()
  1167  	defer d.mu.Unlock()
  1168  
  1169  	d.Attributes[key] = attr
  1170  }
  1171  
  1172  func (d *InstanceDiff) DelAttribute(key string) {
  1173  	d.mu.Lock()
  1174  	defer d.mu.Unlock()
  1175  
  1176  	delete(d.Attributes, key)
  1177  }
  1178  
  1179  func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
  1180  	d.mu.Lock()
  1181  	defer d.mu.Unlock()
  1182  
  1183  	attr, ok := d.Attributes[key]
  1184  	return attr, ok
  1185  }
  1186  func (d *InstanceDiff) GetAttributesLen() int {
  1187  	d.mu.Lock()
  1188  	defer d.mu.Unlock()
  1189  
  1190  	return len(d.Attributes)
  1191  }
  1192  
  1193  // Safely copies the Attributes map
  1194  func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
  1195  	d.mu.Lock()
  1196  	defer d.mu.Unlock()
  1197  
  1198  	attrs := make(map[string]*ResourceAttrDiff)
  1199  	for k, v := range d.Attributes {
  1200  		attrs[k] = v
  1201  	}
  1202  
  1203  	return attrs
  1204  }
  1205  
  1206  // Same checks whether or not two InstanceDiff's are the "same". When
  1207  // we say "same", it is not necessarily exactly equal. Instead, it is
  1208  // just checking that the same attributes are changing, a destroy
  1209  // isn't suddenly happening, etc.
  1210  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
  1211  	// we can safely compare the pointers without a lock
  1212  	switch {
  1213  	case d == nil && d2 == nil:
  1214  		return true, ""
  1215  	case d == nil || d2 == nil:
  1216  		return false, "one nil"
  1217  	case d == d2:
  1218  		return true, ""
  1219  	}
  1220  
  1221  	d.mu.Lock()
  1222  	defer d.mu.Unlock()
  1223  
  1224  	// If we're going from requiring new to NOT requiring new, then we have
  1225  	// to see if all required news were computed. If so, it is allowed since
  1226  	// computed may also mean "same value and therefore not new".
  1227  	oldNew := d.requiresNew()
  1228  	newNew := d2.RequiresNew()
  1229  	if oldNew && !newNew {
  1230  		oldNew = false
  1231  
  1232  		// This section builds a list of ignorable attributes for requiresNew
  1233  		// by removing off any elements of collections going to zero elements.
  1234  		// For collections going to zero, they may not exist at all in the
  1235  		// new diff (and hence RequiresNew == false).
  1236  		ignoreAttrs := make(map[string]struct{})
  1237  		for k, diffOld := range d.Attributes {
  1238  			if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") {
  1239  				continue
  1240  			}
  1241  
  1242  			// This case is in here as a protection measure. The bug that this
  1243  			// code originally fixed (GH-11349) didn't have to deal with computed
  1244  			// so I'm not 100% sure what the correct behavior is. Best to leave
  1245  			// the old behavior.
  1246  			if diffOld.NewComputed {
  1247  				continue
  1248  			}
  1249  
  1250  			// We're looking for the case a map goes to exactly 0.
  1251  			if diffOld.New != "0" {
  1252  				continue
  1253  			}
  1254  
  1255  			// Found it! Ignore all of these. The prefix here is stripping
  1256  			// off the "%" so it is just "k."
  1257  			prefix := k[:len(k)-1]
  1258  			for k2, _ := range d.Attributes {
  1259  				if strings.HasPrefix(k2, prefix) {
  1260  					ignoreAttrs[k2] = struct{}{}
  1261  				}
  1262  			}
  1263  		}
  1264  
  1265  		for k, rd := range d.Attributes {
  1266  			if _, ok := ignoreAttrs[k]; ok {
  1267  				continue
  1268  			}
  1269  
  1270  			// If the field is requires new and NOT computed, then what
  1271  			// we have is a diff mismatch for sure. We set that the old
  1272  			// diff does REQUIRE a ForceNew.
  1273  			if rd != nil && rd.RequiresNew && !rd.NewComputed {
  1274  				oldNew = true
  1275  				break
  1276  			}
  1277  		}
  1278  	}
  1279  
  1280  	if oldNew != newNew {
  1281  		return false, fmt.Sprintf(
  1282  			"diff RequiresNew; old: %t, new: %t", oldNew, newNew)
  1283  	}
  1284  
  1285  	// Verify that destroy matches. The second boolean here allows us to
  1286  	// have mismatching Destroy if we're moving from RequiresNew true
  1287  	// to false above. Therefore, the second boolean will only pass if
  1288  	// we're moving from Destroy: true to false as well.
  1289  	if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew {
  1290  		return false, fmt.Sprintf(
  1291  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
  1292  	}
  1293  
  1294  	// Go through the old diff and make sure the new diff has all the
  1295  	// same attributes. To start, build up the check map to be all the keys.
  1296  	checkOld := make(map[string]struct{})
  1297  	checkNew := make(map[string]struct{})
  1298  	for k, _ := range d.Attributes {
  1299  		checkOld[k] = struct{}{}
  1300  	}
  1301  	for k, _ := range d2.CopyAttributes() {
  1302  		checkNew[k] = struct{}{}
  1303  	}
  1304  
  1305  	// Make an ordered list so we are sure the approximated hashes are left
  1306  	// to process at the end of the loop
  1307  	keys := make([]string, 0, len(d.Attributes))
  1308  	for k, _ := range d.Attributes {
  1309  		keys = append(keys, k)
  1310  	}
  1311  	sort.StringSlice(keys).Sort()
  1312  
  1313  	for _, k := range keys {
  1314  		diffOld := d.Attributes[k]
  1315  
  1316  		if _, ok := checkOld[k]; !ok {
  1317  			// We're not checking this key for whatever reason (see where
  1318  			// check is modified).
  1319  			continue
  1320  		}
  1321  
  1322  		// Remove this key since we'll never hit it again
  1323  		delete(checkOld, k)
  1324  		delete(checkNew, k)
  1325  
  1326  		_, ok := d2.GetAttribute(k)
  1327  		if !ok {
  1328  			// If there's no new attribute, and the old diff expected the attribute
  1329  			// to be removed, that's just fine.
  1330  			if diffOld.NewRemoved {
  1331  				continue
  1332  			}
  1333  
  1334  			// If the last diff was a computed value then the absense of
  1335  			// that value is allowed since it may mean the value ended up
  1336  			// being the same.
  1337  			if diffOld.NewComputed {
  1338  				ok = true
  1339  			}
  1340  
  1341  			// No exact match, but maybe this is a set containing computed
  1342  			// values. So check if there is an approximate hash in the key
  1343  			// and if so, try to match the key.
  1344  			if strings.Contains(k, "~") {
  1345  				parts := strings.Split(k, ".")
  1346  				parts2 := append([]string(nil), parts...)
  1347  
  1348  				re := regexp.MustCompile(`^~\d+$`)
  1349  				for i, part := range parts {
  1350  					if re.MatchString(part) {
  1351  						// we're going to consider this the base of a
  1352  						// computed hash, and remove all longer matching fields
  1353  						ok = true
  1354  
  1355  						parts2[i] = `\d+`
  1356  						parts2 = parts2[:i+1]
  1357  						break
  1358  					}
  1359  				}
  1360  
  1361  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
  1362  				if err != nil {
  1363  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
  1364  				}
  1365  
  1366  				for k2, _ := range checkNew {
  1367  					if re.MatchString(k2) {
  1368  						delete(checkNew, k2)
  1369  					}
  1370  				}
  1371  			}
  1372  
  1373  			// This is a little tricky, but when a diff contains a computed
  1374  			// list, set, or map that can only be interpolated after the apply
  1375  			// command has created the dependent resources, it could turn out
  1376  			// that the result is actually the same as the existing state which
  1377  			// would remove the key from the diff.
  1378  			if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
  1379  				ok = true
  1380  			}
  1381  
  1382  			// Similarly, in a RequiresNew scenario, a list that shows up in the plan
  1383  			// diff can disappear from the apply diff, which is calculated from an
  1384  			// empty state.
  1385  			if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
  1386  				ok = true
  1387  			}
  1388  
  1389  			if !ok {
  1390  				return false, fmt.Sprintf("attribute mismatch: %s", k)
  1391  			}
  1392  		}
  1393  
  1394  		// search for the suffix of the base of a [computed] map, list or set.
  1395  		match := multiVal.FindStringSubmatch(k)
  1396  
  1397  		if diffOld.NewComputed && len(match) == 2 {
  1398  			matchLen := len(match[1])
  1399  
  1400  			// This is a computed list, set, or map, so remove any keys with
  1401  			// this prefix from the check list.
  1402  			kprefix := k[:len(k)-matchLen]
  1403  			for k2, _ := range checkOld {
  1404  				if strings.HasPrefix(k2, kprefix) {
  1405  					delete(checkOld, k2)
  1406  				}
  1407  			}
  1408  			for k2, _ := range checkNew {
  1409  				if strings.HasPrefix(k2, kprefix) {
  1410  					delete(checkNew, k2)
  1411  				}
  1412  			}
  1413  		}
  1414  
  1415  		// We don't compare the values because we can't currently actually
  1416  		// guarantee to generate the same value two two diffs created from
  1417  		// the same state+config: we have some pesky interpolation functions
  1418  		// that do not behave as pure functions (uuid, timestamp) and so they
  1419  		// can be different each time a diff is produced.
  1420  		// FIXME: Re-organize our config handling so that we don't re-evaluate
  1421  		// expressions when we produce a second comparison diff during
  1422  		// apply (for EvalCompareDiff).
  1423  	}
  1424  
  1425  	// Check for leftover attributes
  1426  	if len(checkNew) > 0 {
  1427  		extras := make([]string, 0, len(checkNew))
  1428  		for attr, _ := range checkNew {
  1429  			extras = append(extras, attr)
  1430  		}
  1431  		return false,
  1432  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
  1433  	}
  1434  
  1435  	return true, ""
  1436  }
  1437  
  1438  // moduleDiffSort implements sort.Interface to sort module diffs by path.
  1439  type moduleDiffSort []*ModuleDiff
  1440  
  1441  func (s moduleDiffSort) Len() int      { return len(s) }
  1442  func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  1443  func (s moduleDiffSort) Less(i, j int) bool {
  1444  	a := s[i]
  1445  	b := s[j]
  1446  
  1447  	// If the lengths are different, then the shorter one always wins
  1448  	if len(a.Path) != len(b.Path) {
  1449  		return len(a.Path) < len(b.Path)
  1450  	}
  1451  
  1452  	// Otherwise, compare lexically
  1453  	return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")
  1454  }