github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/evaluate.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sync"
     8  
     9  	"github.com/agext/levenshtein"
    10  	"github.com/hashicorp/hcl/v2"
    11  	"github.com/zclconf/go-cty/cty"
    12  	"github.com/zclconf/go-cty/cty/convert"
    13  
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    15  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
    16  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    17  	"github.com/hashicorp/terraform-plugin-sdk/internal/lang"
    18  	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
    19  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    20  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    21  )
    22  
    23  // Evaluator provides the necessary contextual data for evaluating expressions
    24  // for a particular walk operation.
    25  type Evaluator struct {
    26  	// Operation defines what type of operation this evaluator is being used
    27  	// for.
    28  	Operation walkOperation
    29  
    30  	// Meta is contextual metadata about the current operation.
    31  	Meta *ContextMeta
    32  
    33  	// Config is the root node in the configuration tree.
    34  	Config *configs.Config
    35  
    36  	// VariableValues is a map from variable names to their associated values,
    37  	// within the module indicated by ModulePath. VariableValues is modified
    38  	// concurrently, and so it must be accessed only while holding
    39  	// VariableValuesLock.
    40  	//
    41  	// The first map level is string representations of addr.ModuleInstance
    42  	// values, while the second level is variable names.
    43  	VariableValues     map[string]map[string]cty.Value
    44  	VariableValuesLock *sync.Mutex
    45  
    46  	// Schemas is a repository of all of the schemas we should need to
    47  	// evaluate expressions. This must be constructed by the caller to
    48  	// include schemas for all of the providers, resource types, data sources
    49  	// and provisioners used by the given configuration and state.
    50  	//
    51  	// This must not be mutated during evaluation.
    52  	Schemas *Schemas
    53  
    54  	// State is the current state, embedded in a wrapper that ensures that
    55  	// it can be safely accessed and modified concurrently.
    56  	State *states.SyncState
    57  
    58  	// Changes is the set of proposed changes, embedded in a wrapper that
    59  	// ensures they can be safely accessed and modified concurrently.
    60  	Changes *plans.ChangesSync
    61  }
    62  
    63  // Scope creates an evaluation scope for the given module path and optional
    64  // resource.
    65  //
    66  // If the "self" argument is nil then the "self" object is not available
    67  // in evaluated expressions. Otherwise, it behaves as an alias for the given
    68  // address.
    69  func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope {
    70  	return &lang.Scope{
    71  		Data:     data,
    72  		SelfAddr: self,
    73  		PureOnly: e.Operation != walkApply && e.Operation != walkDestroy,
    74  		BaseDir:  ".", // Always current working directory for now.
    75  	}
    76  }
    77  
    78  // evaluationStateData is an implementation of lang.Data that resolves
    79  // references primarily (but not exclusively) using information from a State.
    80  type evaluationStateData struct {
    81  	Evaluator *Evaluator
    82  
    83  	// ModulePath is the path through the dynamic module tree to the module
    84  	// that references will be resolved relative to.
    85  	ModulePath addrs.ModuleInstance
    86  
    87  	// InstanceKeyData describes the values, if any, that are accessible due
    88  	// to repetition of a containing object using "count" or "for_each"
    89  	// arguments. (It is _not_ used for the for_each inside "dynamic" blocks,
    90  	// since the user specifies in that case which variable name to locally
    91  	// shadow.)
    92  	InstanceKeyData InstanceKeyEvalData
    93  
    94  	// Operation records the type of walk the evaluationStateData is being used
    95  	// for.
    96  	Operation walkOperation
    97  }
    98  
    99  // InstanceKeyEvalData is used during evaluation to specify which values,
   100  // if any, should be produced for count.index, each.key, and each.value.
   101  type InstanceKeyEvalData struct {
   102  	// CountIndex is the value for count.index, or cty.NilVal if evaluating
   103  	// in a context where the "count" argument is not active.
   104  	//
   105  	// For correct operation, this should always be of type cty.Number if not
   106  	// nil.
   107  	CountIndex cty.Value
   108  
   109  	// EachKey and EachValue are the values for each.key and each.value
   110  	// respectively, or cty.NilVal if evaluating in a context where the
   111  	// "for_each" argument is not active. These must either both be set
   112  	// or neither set.
   113  	//
   114  	// For correct operation, EachKey must always be either of type cty.String
   115  	// or cty.Number if not nil.
   116  	EachKey, EachValue cty.Value
   117  }
   118  
   119  // EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for
   120  // evaluating in a context that has the given instance key.
   121  func EvalDataForInstanceKey(key addrs.InstanceKey, forEachMap map[string]cty.Value) InstanceKeyEvalData {
   122  	var countIdx cty.Value
   123  	var eachKey cty.Value
   124  	var eachVal cty.Value
   125  
   126  	if intKey, ok := key.(addrs.IntKey); ok {
   127  		countIdx = cty.NumberIntVal(int64(intKey))
   128  	}
   129  
   130  	if stringKey, ok := key.(addrs.StringKey); ok {
   131  		eachKey = cty.StringVal(string(stringKey))
   132  		eachVal = forEachMap[string(stringKey)]
   133  	}
   134  
   135  	return InstanceKeyEvalData{
   136  		CountIndex: countIdx,
   137  		EachKey:    eachKey,
   138  		EachValue:  eachVal,
   139  	}
   140  }
   141  
   142  // EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance
   143  // key values at all, suitable for use in contexts where no keyed instance
   144  // is relevant.
   145  var EvalDataForNoInstanceKey = InstanceKeyEvalData{}
   146  
   147  // evaluationStateData must implement lang.Data
   148  var _ lang.Data = (*evaluationStateData)(nil)
   149  
   150  func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   151  	var diags tfdiags.Diagnostics
   152  	switch addr.Name {
   153  
   154  	case "index":
   155  		idxVal := d.InstanceKeyData.CountIndex
   156  		if idxVal == cty.NilVal {
   157  			diags = diags.Append(&hcl.Diagnostic{
   158  				Severity: hcl.DiagError,
   159  				Summary:  `Reference to "count" in non-counted context`,
   160  				Detail:   fmt.Sprintf(`The "count" object can be used only in "resource" and "data" blocks, and only when the "count" argument is set.`),
   161  				Subject:  rng.ToHCL().Ptr(),
   162  			})
   163  			return cty.UnknownVal(cty.Number), diags
   164  		}
   165  		return idxVal, diags
   166  
   167  	default:
   168  		diags = diags.Append(&hcl.Diagnostic{
   169  			Severity: hcl.DiagError,
   170  			Summary:  `Invalid "count" attribute`,
   171  			Detail:   fmt.Sprintf(`The "count" object does not have an attribute named %q. The only supported attribute is count.index, which is the index of each instance of a resource block that has the "count" argument set.`, addr.Name),
   172  			Subject:  rng.ToHCL().Ptr(),
   173  		})
   174  		return cty.DynamicVal, diags
   175  	}
   176  }
   177  
   178  func (d *evaluationStateData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   179  	var diags tfdiags.Diagnostics
   180  	var returnVal cty.Value
   181  	switch addr.Name {
   182  
   183  	case "key":
   184  		returnVal = d.InstanceKeyData.EachKey
   185  	case "value":
   186  		returnVal = d.InstanceKeyData.EachValue
   187  	default:
   188  		diags = diags.Append(&hcl.Diagnostic{
   189  			Severity: hcl.DiagError,
   190  			Summary:  `Invalid "each" attribute`,
   191  			Detail:   fmt.Sprintf(`The "each" object does not have an attribute named %q. The supported attributes are each.key and each.value, the current key and value pair of the "for_each" attribute set.`, addr.Name),
   192  			Subject:  rng.ToHCL().Ptr(),
   193  		})
   194  		return cty.DynamicVal, diags
   195  	}
   196  
   197  	if returnVal == cty.NilVal {
   198  		diags = diags.Append(&hcl.Diagnostic{
   199  			Severity: hcl.DiagError,
   200  			Summary:  `Reference to "each" in context without for_each`,
   201  			Detail:   fmt.Sprintf(`The "each" object can be used only in "resource" blocks, and only when the "for_each" argument is set.`),
   202  			Subject:  rng.ToHCL().Ptr(),
   203  		})
   204  		return cty.UnknownVal(cty.DynamicPseudoType), diags
   205  	}
   206  	return returnVal, diags
   207  }
   208  
   209  func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   210  	var diags tfdiags.Diagnostics
   211  
   212  	// First we'll make sure the requested value is declared in configuration,
   213  	// so we can produce a nice message if not.
   214  	moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   215  	if moduleConfig == nil {
   216  		// should never happen, since we can't be evaluating in a module
   217  		// that wasn't mentioned in configuration.
   218  		panic(fmt.Sprintf("input variable read from %s, which has no configuration", d.ModulePath))
   219  	}
   220  
   221  	config := moduleConfig.Module.Variables[addr.Name]
   222  	if config == nil {
   223  		var suggestions []string
   224  		for k := range moduleConfig.Module.Variables {
   225  			suggestions = append(suggestions, k)
   226  		}
   227  		suggestion := nameSuggestion(addr.Name, suggestions)
   228  		if suggestion != "" {
   229  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   230  		} else {
   231  			suggestion = fmt.Sprintf(" This variable can be declared with a variable %q {} block.", addr.Name)
   232  		}
   233  
   234  		diags = diags.Append(&hcl.Diagnostic{
   235  			Severity: hcl.DiagError,
   236  			Summary:  `Reference to undeclared input variable`,
   237  			Detail:   fmt.Sprintf(`An input variable with the name %q has not been declared.%s`, addr.Name, suggestion),
   238  			Subject:  rng.ToHCL().Ptr(),
   239  		})
   240  		return cty.DynamicVal, diags
   241  	}
   242  
   243  	wantType := cty.DynamicPseudoType
   244  	if config.Type != cty.NilType {
   245  		wantType = config.Type
   246  	}
   247  
   248  	d.Evaluator.VariableValuesLock.Lock()
   249  	defer d.Evaluator.VariableValuesLock.Unlock()
   250  
   251  	// During the validate walk, input variables are always unknown so
   252  	// that we are validating the configuration for all possible input values
   253  	// rather than for a specific set. Checking against a specific set of
   254  	// input values then happens during the plan walk.
   255  	//
   256  	// This is important because otherwise the validation walk will tend to be
   257  	// overly strict, requiring expressions throughout the configuration to
   258  	// be complicated to accommodate all possible inputs, whereas returning
   259  	// known here allows for simpler patterns like using input values as
   260  	// guards to broadly enable/disable resources, avoid processing things
   261  	// that are disabled, etc. Terraform's static validation leans towards
   262  	// being liberal in what it accepts because the subsequent plan walk has
   263  	// more information available and so can be more conservative.
   264  	if d.Operation == walkValidate {
   265  		return cty.UnknownVal(wantType), diags
   266  	}
   267  
   268  	moduleAddrStr := d.ModulePath.String()
   269  	vals := d.Evaluator.VariableValues[moduleAddrStr]
   270  	if vals == nil {
   271  		return cty.UnknownVal(wantType), diags
   272  	}
   273  
   274  	val, isSet := vals[addr.Name]
   275  	if !isSet {
   276  		if config.Default != cty.NilVal {
   277  			return config.Default, diags
   278  		}
   279  		return cty.UnknownVal(wantType), diags
   280  	}
   281  
   282  	var err error
   283  	val, err = convert.Convert(val, wantType)
   284  	if err != nil {
   285  		// We should never get here because this problem should've been caught
   286  		// during earlier validation, but we'll do something reasonable anyway.
   287  		diags = diags.Append(&hcl.Diagnostic{
   288  			Severity: hcl.DiagError,
   289  			Summary:  `Incorrect variable type`,
   290  			Detail:   fmt.Sprintf(`The resolved value of variable %q is not appropriate: %s.`, addr.Name, err),
   291  			Subject:  &config.DeclRange,
   292  		})
   293  		// Stub out our return value so that the semantic checker doesn't
   294  		// produce redundant downstream errors.
   295  		val = cty.UnknownVal(wantType)
   296  	}
   297  
   298  	return val, diags
   299  }
   300  
   301  func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   302  	var diags tfdiags.Diagnostics
   303  
   304  	// First we'll make sure the requested value is declared in configuration,
   305  	// so we can produce a nice message if not.
   306  	moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   307  	if moduleConfig == nil {
   308  		// should never happen, since we can't be evaluating in a module
   309  		// that wasn't mentioned in configuration.
   310  		panic(fmt.Sprintf("local value read from %s, which has no configuration", d.ModulePath))
   311  	}
   312  
   313  	config := moduleConfig.Module.Locals[addr.Name]
   314  	if config == nil {
   315  		var suggestions []string
   316  		for k := range moduleConfig.Module.Locals {
   317  			suggestions = append(suggestions, k)
   318  		}
   319  		suggestion := nameSuggestion(addr.Name, suggestions)
   320  		if suggestion != "" {
   321  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   322  		}
   323  
   324  		diags = diags.Append(&hcl.Diagnostic{
   325  			Severity: hcl.DiagError,
   326  			Summary:  `Reference to undeclared local value`,
   327  			Detail:   fmt.Sprintf(`A local value with the name %q has not been declared.%s`, addr.Name, suggestion),
   328  			Subject:  rng.ToHCL().Ptr(),
   329  		})
   330  		return cty.DynamicVal, diags
   331  	}
   332  
   333  	val := d.Evaluator.State.LocalValue(addr.Absolute(d.ModulePath))
   334  	if val == cty.NilVal {
   335  		// Not evaluated yet?
   336  		val = cty.DynamicVal
   337  	}
   338  
   339  	return val, diags
   340  }
   341  
   342  func (d *evaluationStateData) GetModuleInstance(addr addrs.ModuleCallInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   343  	var diags tfdiags.Diagnostics
   344  
   345  	// Output results live in the module that declares them, which is one of
   346  	// the child module instances of our current module path.
   347  	moduleAddr := addr.ModuleInstance(d.ModulePath)
   348  
   349  	// We'll consult the configuration to see what output names we are
   350  	// expecting, so we can ensure the resulting object is of the expected
   351  	// type even if our data is incomplete for some reason.
   352  	moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
   353  	if moduleConfig == nil {
   354  		// should never happen, since this should've been caught during
   355  		// static validation.
   356  		panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr))
   357  	}
   358  	outputConfigs := moduleConfig.Module.Outputs
   359  
   360  	vals := map[string]cty.Value{}
   361  	for n := range outputConfigs {
   362  		addr := addrs.OutputValue{Name: n}.Absolute(moduleAddr)
   363  
   364  		// If a pending change is present in our current changeset then its value
   365  		// takes priority over what's in state. (It will usually be the same but
   366  		// will differ if the new value is unknown during planning.)
   367  		if changeSrc := d.Evaluator.Changes.GetOutputChange(addr); changeSrc != nil {
   368  			change, err := changeSrc.Decode()
   369  			if err != nil {
   370  				// This should happen only if someone has tampered with a plan
   371  				// file, so we won't bother with a pretty error for it.
   372  				diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", addr, err))
   373  				vals[n] = cty.DynamicVal
   374  				continue
   375  			}
   376  			// We care only about the "after" value, which is the value this output
   377  			// will take on after the plan is applied.
   378  			vals[n] = change.After
   379  		} else {
   380  			os := d.Evaluator.State.OutputValue(addr)
   381  			if os == nil {
   382  				// Not evaluated yet?
   383  				vals[n] = cty.DynamicVal
   384  				continue
   385  			}
   386  			vals[n] = os.Value
   387  		}
   388  	}
   389  	return cty.ObjectVal(vals), diags
   390  }
   391  
   392  func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.ModuleCallOutput, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   393  	var diags tfdiags.Diagnostics
   394  
   395  	// Output results live in the module that declares them, which is one of
   396  	// the child module instances of our current module path.
   397  	absAddr := addr.AbsOutputValue(d.ModulePath)
   398  	moduleAddr := absAddr.Module
   399  
   400  	// First we'll consult the configuration to see if an output of this
   401  	// name is declared at all.
   402  	moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
   403  	if moduleConfig == nil {
   404  		// this doesn't happen in normal circumstances due to our validation
   405  		// pass, but it can turn up in some unusual situations, like in the
   406  		// "terraform console" repl where arbitrary expressions can be
   407  		// evaluated.
   408  		diags = diags.Append(&hcl.Diagnostic{
   409  			Severity: hcl.DiagError,
   410  			Summary:  `Reference to undeclared module`,
   411  			Detail:   fmt.Sprintf(`The configuration contains no %s.`, moduleAddr),
   412  			Subject:  rng.ToHCL().Ptr(),
   413  		})
   414  		return cty.DynamicVal, diags
   415  	}
   416  
   417  	config := moduleConfig.Module.Outputs[addr.Name]
   418  	if config == nil {
   419  		var suggestions []string
   420  		for k := range moduleConfig.Module.Outputs {
   421  			suggestions = append(suggestions, k)
   422  		}
   423  		suggestion := nameSuggestion(addr.Name, suggestions)
   424  		if suggestion != "" {
   425  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   426  		}
   427  
   428  		diags = diags.Append(&hcl.Diagnostic{
   429  			Severity: hcl.DiagError,
   430  			Summary:  `Reference to undeclared output value`,
   431  			Detail:   fmt.Sprintf(`An output value with the name %q has not been declared in %s.%s`, addr.Name, moduleDisplayAddr(moduleAddr), suggestion),
   432  			Subject:  rng.ToHCL().Ptr(),
   433  		})
   434  		return cty.DynamicVal, diags
   435  	}
   436  
   437  	// If a pending change is present in our current changeset then its value
   438  	// takes priority over what's in state. (It will usually be the same but
   439  	// will differ if the new value is unknown during planning.)
   440  	if changeSrc := d.Evaluator.Changes.GetOutputChange(absAddr); changeSrc != nil {
   441  		change, err := changeSrc.Decode()
   442  		if err != nil {
   443  			// This should happen only if someone has tampered with a plan
   444  			// file, so we won't bother with a pretty error for it.
   445  			diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", absAddr, err))
   446  			return cty.DynamicVal, diags
   447  		}
   448  		// We care only about the "after" value, which is the value this output
   449  		// will take on after the plan is applied.
   450  		return change.After, diags
   451  	}
   452  
   453  	os := d.Evaluator.State.OutputValue(absAddr)
   454  	if os == nil {
   455  		// Not evaluated yet?
   456  		return cty.DynamicVal, diags
   457  	}
   458  
   459  	return os.Value, diags
   460  }
   461  
   462  func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   463  	var diags tfdiags.Diagnostics
   464  	switch addr.Name {
   465  
   466  	case "cwd":
   467  		wd, err := os.Getwd()
   468  		if err != nil {
   469  			diags = diags.Append(&hcl.Diagnostic{
   470  				Severity: hcl.DiagError,
   471  				Summary:  `Failed to get working directory`,
   472  				Detail:   fmt.Sprintf(`The value for path.cwd cannot be determined due to a system error: %s`, err),
   473  				Subject:  rng.ToHCL().Ptr(),
   474  			})
   475  			return cty.DynamicVal, diags
   476  		}
   477  		return cty.StringVal(filepath.ToSlash(wd)), diags
   478  
   479  	case "module":
   480  		moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   481  		if moduleConfig == nil {
   482  			// should never happen, since we can't be evaluating in a module
   483  			// that wasn't mentioned in configuration.
   484  			panic(fmt.Sprintf("module.path read from module %s, which has no configuration", d.ModulePath))
   485  		}
   486  		sourceDir := moduleConfig.Module.SourceDir
   487  		return cty.StringVal(filepath.ToSlash(sourceDir)), diags
   488  
   489  	case "root":
   490  		sourceDir := d.Evaluator.Config.Module.SourceDir
   491  		return cty.StringVal(filepath.ToSlash(sourceDir)), diags
   492  
   493  	default:
   494  		suggestion := nameSuggestion(addr.Name, []string{"cwd", "module", "root"})
   495  		if suggestion != "" {
   496  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   497  		}
   498  		diags = diags.Append(&hcl.Diagnostic{
   499  			Severity: hcl.DiagError,
   500  			Summary:  `Invalid "path" attribute`,
   501  			Detail:   fmt.Sprintf(`The "path" object does not have an attribute named %q.%s`, addr.Name, suggestion),
   502  			Subject:  rng.ToHCL().Ptr(),
   503  		})
   504  		return cty.DynamicVal, diags
   505  	}
   506  }
   507  
   508  func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   509  	var diags tfdiags.Diagnostics
   510  	// First we'll consult the configuration to see if an resource of this
   511  	// name is declared at all.
   512  	moduleAddr := d.ModulePath
   513  	moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
   514  	if moduleConfig == nil {
   515  		// should never happen, since we can't be evaluating in a module
   516  		// that wasn't mentioned in configuration.
   517  		panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr))
   518  	}
   519  
   520  	config := moduleConfig.Module.ResourceByAddr(addr)
   521  	if config == nil {
   522  		diags = diags.Append(&hcl.Diagnostic{
   523  			Severity: hcl.DiagError,
   524  			Summary:  `Reference to undeclared resource`,
   525  			Detail:   fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Type, addr.Name, moduleDisplayAddr(moduleAddr)),
   526  			Subject:  rng.ToHCL().Ptr(),
   527  		})
   528  		return cty.DynamicVal, diags
   529  	}
   530  
   531  	rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath))
   532  
   533  	if rs == nil {
   534  		// we must return DynamicVal so that both interpretations
   535  		// can proceed without generating errors, and we'll deal with this
   536  		// in a later step where more information is gathered.
   537  		// (In practice we should only end up here during the validate walk,
   538  		// since later walks should have at least partial states populated
   539  		// for all resources in the configuration.)
   540  		return cty.DynamicVal, diags
   541  	}
   542  
   543  	// Break out early during validation, because resource may not be expanded
   544  	// yet and indexed references may show up as invalid.
   545  	if d.Operation == walkValidate {
   546  		return cty.DynamicVal, diags
   547  	}
   548  
   549  	return d.getResourceInstancesAll(addr, rng, config, rs, rs.ProviderConfig)
   550  }
   551  
   552  func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng tfdiags.SourceRange, config *configs.Resource, rs *states.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
   553  	var diags tfdiags.Diagnostics
   554  
   555  	instAddr := addrs.ResourceInstance{Resource: addr, Key: addrs.NoKey}
   556  
   557  	schema := d.getResourceSchema(addr, providerAddr)
   558  	if schema == nil {
   559  		// This shouldn't happen, since validation before we get here should've
   560  		// taken care of it, but we'll show a reasonable error message anyway.
   561  		diags = diags.Append(&hcl.Diagnostic{
   562  			Severity: hcl.DiagError,
   563  			Summary:  `Missing resource type schema`,
   564  			Detail:   fmt.Sprintf("No schema is available for %s in %s. This is a bug in Terraform and should be reported.", addr, providerAddr),
   565  			Subject:  rng.ToHCL().Ptr(),
   566  		})
   567  		return cty.DynamicVal, diags
   568  	}
   569  
   570  	switch rs.EachMode {
   571  	case states.NoEach:
   572  		ty := schema.ImpliedType()
   573  		is := rs.Instances[addrs.NoKey]
   574  		if is == nil || is.Current == nil {
   575  			// Assume we're dealing with an instance that hasn't been created yet.
   576  			return cty.UnknownVal(ty), diags
   577  		}
   578  
   579  		if is.Current.Status == states.ObjectPlanned {
   580  			// If there's a pending change for this instance in our plan, we'll prefer
   581  			// that. This is important because the state can't represent unknown values
   582  			// and so its data is inaccurate when changes are pending.
   583  			if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr.Absolute(d.ModulePath), states.CurrentGen); change != nil {
   584  				val, err := change.After.Decode(ty)
   585  				if err != nil {
   586  					diags = diags.Append(&hcl.Diagnostic{
   587  						Severity: hcl.DiagError,
   588  						Summary:  "Invalid resource instance data in plan",
   589  						Detail:   fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", addr.Absolute(d.ModulePath), err),
   590  						Subject:  &config.DeclRange,
   591  					})
   592  					return cty.UnknownVal(ty), diags
   593  				}
   594  				return val, diags
   595  			} else {
   596  				// If the object is in planned status then we should not
   597  				// get here, since we should've found a pending value
   598  				// in the plan above instead.
   599  				diags = diags.Append(&hcl.Diagnostic{
   600  					Severity: hcl.DiagError,
   601  					Summary:  "Missing pending object in plan",
   602  					Detail:   fmt.Sprintf("Instance %s is marked as having a change pending but that change is not recorded in the plan. This is a bug in Terraform; please report it.", addr),
   603  					Subject:  &config.DeclRange,
   604  				})
   605  				return cty.UnknownVal(ty), diags
   606  			}
   607  		}
   608  
   609  		ios, err := is.Current.Decode(ty)
   610  		if err != nil {
   611  			// This shouldn't happen, since by the time we get here
   612  			// we should've upgraded the state data already.
   613  			diags = diags.Append(&hcl.Diagnostic{
   614  				Severity: hcl.DiagError,
   615  				Summary:  "Invalid resource instance data in state",
   616  				Detail:   fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", addr.Absolute(d.ModulePath), err),
   617  				Subject:  &config.DeclRange,
   618  			})
   619  			return cty.UnknownVal(ty), diags
   620  		}
   621  
   622  		return ios.Value, diags
   623  
   624  	case states.EachList:
   625  		// We need to infer the length of our resulting tuple by searching
   626  		// for the max IntKey in our instances map.
   627  		length := 0
   628  		for k := range rs.Instances {
   629  			if ik, ok := k.(addrs.IntKey); ok {
   630  				if int(ik) >= length {
   631  					length = int(ik) + 1
   632  				}
   633  			}
   634  		}
   635  
   636  		vals := make([]cty.Value, length)
   637  		for i := 0; i < length; i++ {
   638  			ty := schema.ImpliedType()
   639  			key := addrs.IntKey(i)
   640  			is, exists := rs.Instances[key]
   641  			if exists && is.Current != nil {
   642  				instAddr := addr.Instance(key).Absolute(d.ModulePath)
   643  
   644  				// Prefer pending value in plan if present. See getResourceInstanceSingle
   645  				// comment for the rationale.
   646  				if is.Current.Status == states.ObjectPlanned {
   647  					if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen); change != nil {
   648  						val, err := change.After.Decode(ty)
   649  						if err != nil {
   650  							diags = diags.Append(&hcl.Diagnostic{
   651  								Severity: hcl.DiagError,
   652  								Summary:  "Invalid resource instance data in plan",
   653  								Detail:   fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err),
   654  								Subject:  &config.DeclRange,
   655  							})
   656  							continue
   657  						}
   658  						vals[i] = val
   659  						continue
   660  					} else {
   661  						// If the object is in planned status then we should not
   662  						// get here, since we should've found a pending value
   663  						// in the plan above instead.
   664  						diags = diags.Append(&hcl.Diagnostic{
   665  							Severity: hcl.DiagError,
   666  							Summary:  "Missing pending object in plan",
   667  							Detail:   fmt.Sprintf("Instance %s is marked as having a change pending but that change is not recorded in the plan. This is a bug in Terraform; please report it.", instAddr),
   668  							Subject:  &config.DeclRange,
   669  						})
   670  						continue
   671  					}
   672  				}
   673  
   674  				ios, err := is.Current.Decode(ty)
   675  				if err != nil {
   676  					// This shouldn't happen, since by the time we get here
   677  					// we should've upgraded the state data already.
   678  					diags = diags.Append(&hcl.Diagnostic{
   679  						Severity: hcl.DiagError,
   680  						Summary:  "Invalid resource instance data in state",
   681  						Detail:   fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err),
   682  						Subject:  &config.DeclRange,
   683  					})
   684  					continue
   685  				}
   686  				vals[i] = ios.Value
   687  			} else {
   688  				// There shouldn't normally be "gaps" in our list but we'll
   689  				// allow it under the assumption that we're in a weird situation
   690  				// where e.g. someone has run "terraform state mv" to reorder
   691  				// a list and left a hole behind.
   692  				vals[i] = cty.UnknownVal(schema.ImpliedType())
   693  			}
   694  		}
   695  
   696  		// We use a tuple rather than a list here because resource schemas may
   697  		// include dynamically-typed attributes, which will then cause each
   698  		// instance to potentially have a different runtime type even though
   699  		// they all conform to the static schema.
   700  		return cty.TupleVal(vals), diags
   701  
   702  	case states.EachMap:
   703  		ty := schema.ImpliedType()
   704  		vals := make(map[string]cty.Value, len(rs.Instances))
   705  		for k, is := range rs.Instances {
   706  			if sk, ok := k.(addrs.StringKey); ok {
   707  				instAddr := addr.Instance(k).Absolute(d.ModulePath)
   708  
   709  				// Prefer pending value in plan if present. See getResourceInstanceSingle
   710  				// comment for the rationale.
   711  				// Prefer pending value in plan if present. See getResourceInstanceSingle
   712  				// comment for the rationale.
   713  				if is.Current.Status == states.ObjectPlanned {
   714  					if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen); change != nil {
   715  						val, err := change.After.Decode(ty)
   716  						if err != nil {
   717  							diags = diags.Append(&hcl.Diagnostic{
   718  								Severity: hcl.DiagError,
   719  								Summary:  "Invalid resource instance data in plan",
   720  								Detail:   fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err),
   721  								Subject:  &config.DeclRange,
   722  							})
   723  							continue
   724  						}
   725  						vals[string(sk)] = val
   726  						continue
   727  					} else {
   728  						// If the object is in planned status then we should not
   729  						// get here, since we should've found a pending value
   730  						// in the plan above instead.
   731  						diags = diags.Append(&hcl.Diagnostic{
   732  							Severity: hcl.DiagError,
   733  							Summary:  "Missing pending object in plan",
   734  							Detail:   fmt.Sprintf("Instance %s is marked as having a change pending but that change is not recorded in the plan. This is a bug in Terraform; please report it.", instAddr),
   735  							Subject:  &config.DeclRange,
   736  						})
   737  						continue
   738  					}
   739  				}
   740  
   741  				ios, err := is.Current.Decode(ty)
   742  				if err != nil {
   743  					// This shouldn't happen, since by the time we get here
   744  					// we should've upgraded the state data already.
   745  					diags = diags.Append(&hcl.Diagnostic{
   746  						Severity: hcl.DiagError,
   747  						Summary:  "Invalid resource instance data in state",
   748  						Detail:   fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err),
   749  						Subject:  &config.DeclRange,
   750  					})
   751  					continue
   752  				}
   753  				vals[string(sk)] = ios.Value
   754  			}
   755  		}
   756  
   757  		// We use an object rather than a map here because resource schemas may
   758  		// include dynamically-typed attributes, which will then cause each
   759  		// instance to potentially have a different runtime type even though
   760  		// they all conform to the static schema.
   761  		return cty.ObjectVal(vals), diags
   762  
   763  	default:
   764  		// Should never happen since caller should deal with other modes
   765  		panic(fmt.Sprintf("unsupported EachMode %s", rs.EachMode))
   766  	}
   767  }
   768  
   769  func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.AbsProviderConfig) *configschema.Block {
   770  	providerType := providerAddr.ProviderConfig.Type
   771  	schemas := d.Evaluator.Schemas
   772  	schema, _ := schemas.ResourceTypeConfig(providerType, addr.Mode, addr.Type)
   773  	return schema
   774  }
   775  
   776  func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   777  	var diags tfdiags.Diagnostics
   778  	switch addr.Name {
   779  
   780  	case "workspace":
   781  		workspaceName := d.Evaluator.Meta.Env
   782  		return cty.StringVal(workspaceName), diags
   783  
   784  	case "env":
   785  		// Prior to Terraform 0.12 there was an attribute "env", which was
   786  		// an alias name for "workspace". This was deprecated and is now
   787  		// removed.
   788  		diags = diags.Append(&hcl.Diagnostic{
   789  			Severity: hcl.DiagError,
   790  			Summary:  `Invalid "terraform" attribute`,
   791  			Detail:   `The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The "state environment" concept was rename to "workspace" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.`,
   792  			Subject:  rng.ToHCL().Ptr(),
   793  		})
   794  		return cty.DynamicVal, diags
   795  
   796  	default:
   797  		diags = diags.Append(&hcl.Diagnostic{
   798  			Severity: hcl.DiagError,
   799  			Summary:  `Invalid "terraform" attribute`,
   800  			Detail:   fmt.Sprintf(`The "terraform" object does not have an attribute named %q. The only supported attribute is terraform.workspace, the name of the currently-selected workspace.`, addr.Name),
   801  			Subject:  rng.ToHCL().Ptr(),
   802  		})
   803  		return cty.DynamicVal, diags
   804  	}
   805  }
   806  
   807  // nameSuggestion tries to find a name from the given slice of suggested names
   808  // that is close to the given name and returns it if found. If no suggestion
   809  // is close enough, returns the empty string.
   810  //
   811  // The suggestions are tried in order, so earlier suggestions take precedence
   812  // if the given string is similar to two or more suggestions.
   813  //
   814  // This function is intended to be used with a relatively-small number of
   815  // suggestions. It's not optimized for hundreds or thousands of them.
   816  func nameSuggestion(given string, suggestions []string) string {
   817  	for _, suggestion := range suggestions {
   818  		dist := levenshtein.Distance(given, suggestion, nil)
   819  		if dist < 3 { // threshold determined experimentally
   820  			return suggestion
   821  		}
   822  	}
   823  	return ""
   824  }
   825  
   826  // moduleDisplayAddr returns a string describing the given module instance
   827  // address that is appropriate for returning to users in situations where the
   828  // root module is possible. Specifically, it returns "the root module" if the
   829  // root module instance is given, or a string representation of the module
   830  // address otherwise.
   831  func moduleDisplayAddr(addr addrs.ModuleInstance) string {
   832  	switch {
   833  	case addr.IsRoot():
   834  		return "the root module"
   835  	default:
   836  		return addr.String()
   837  	}
   838  }