kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/terraform/evaluate.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"github.com/agext/levenshtein"
    11  	"github.com/hashicorp/hcl/v2"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/convert"
    14  
    15  	"kubeform.dev/terraform-backend-sdk/addrs"
    16  	"kubeform.dev/terraform-backend-sdk/configs"
    17  	"kubeform.dev/terraform-backend-sdk/configs/configschema"
    18  	"kubeform.dev/terraform-backend-sdk/instances"
    19  	"kubeform.dev/terraform-backend-sdk/lang"
    20  	"kubeform.dev/terraform-backend-sdk/lang/marks"
    21  	"kubeform.dev/terraform-backend-sdk/plans"
    22  	"kubeform.dev/terraform-backend-sdk/states"
    23  	"kubeform.dev/terraform-backend-sdk/tfdiags"
    24  )
    25  
    26  // Evaluator provides the necessary contextual data for evaluating expressions
    27  // for a particular walk operation.
    28  type Evaluator struct {
    29  	// Operation defines what type of operation this evaluator is being used
    30  	// for.
    31  	Operation walkOperation
    32  
    33  	// Meta is contextual metadata about the current operation.
    34  	Meta *ContextMeta
    35  
    36  	// Config is the root node in the configuration tree.
    37  	Config *configs.Config
    38  
    39  	// VariableValues is a map from variable names to their associated values,
    40  	// within the module indicated by ModulePath. VariableValues is modified
    41  	// concurrently, and so it must be accessed only while holding
    42  	// VariableValuesLock.
    43  	//
    44  	// The first map level is string representations of addr.ModuleInstance
    45  	// values, while the second level is variable names.
    46  	VariableValues     map[string]map[string]cty.Value
    47  	VariableValuesLock *sync.Mutex
    48  
    49  	// Plugins is the library of available plugin components (providers and
    50  	// provisioners) that we have available to help us evaluate expressions
    51  	// that interact with plugin-provided objects.
    52  	//
    53  	// From this we only access the schemas of the plugins, and don't otherwise
    54  	// interact with plugin instances.
    55  	Plugins *contextPlugins
    56  
    57  	// State is the current state, embedded in a wrapper that ensures that
    58  	// it can be safely accessed and modified concurrently.
    59  	State *states.SyncState
    60  
    61  	// Changes is the set of proposed changes, embedded in a wrapper that
    62  	// ensures they can be safely accessed and modified concurrently.
    63  	Changes *plans.ChangesSync
    64  }
    65  
    66  // Scope creates an evaluation scope for the given module path and optional
    67  // resource.
    68  //
    69  // If the "self" argument is nil then the "self" object is not available
    70  // in evaluated expressions. Otherwise, it behaves as an alias for the given
    71  // address.
    72  func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope {
    73  	return &lang.Scope{
    74  		Data:     data,
    75  		SelfAddr: self,
    76  		PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval,
    77  		BaseDir:  ".", // Always current working directory for now.
    78  	}
    79  }
    80  
    81  // evaluationStateData is an implementation of lang.Data that resolves
    82  // references primarily (but not exclusively) using information from a State.
    83  type evaluationStateData struct {
    84  	Evaluator *Evaluator
    85  
    86  	// ModulePath is the path through the dynamic module tree to the module
    87  	// that references will be resolved relative to.
    88  	ModulePath addrs.ModuleInstance
    89  
    90  	// InstanceKeyData describes the values, if any, that are accessible due
    91  	// to repetition of a containing object using "count" or "for_each"
    92  	// arguments. (It is _not_ used for the for_each inside "dynamic" blocks,
    93  	// since the user specifies in that case which variable name to locally
    94  	// shadow.)
    95  	InstanceKeyData InstanceKeyEvalData
    96  
    97  	// Operation records the type of walk the evaluationStateData is being used
    98  	// for.
    99  	Operation walkOperation
   100  }
   101  
   102  // InstanceKeyEvalData is the old name for instances.RepetitionData, aliased
   103  // here for compatibility. In new code, use instances.RepetitionData instead.
   104  type InstanceKeyEvalData = instances.RepetitionData
   105  
   106  // EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for
   107  // evaluating in a context that has the given instance key.
   108  //
   109  // The forEachMap argument can be nil when preparing for evaluation
   110  // in a context where each.value is prohibited, such as a destroy-time
   111  // provisioner. In that case, the returned EachValue will always be
   112  // cty.NilVal.
   113  func EvalDataForInstanceKey(key addrs.InstanceKey, forEachMap map[string]cty.Value) InstanceKeyEvalData {
   114  	var evalData InstanceKeyEvalData
   115  	if key == nil {
   116  		return evalData
   117  	}
   118  
   119  	keyValue := key.Value()
   120  	switch keyValue.Type() {
   121  	case cty.String:
   122  		evalData.EachKey = keyValue
   123  		evalData.EachValue = forEachMap[keyValue.AsString()]
   124  	case cty.Number:
   125  		evalData.CountIndex = keyValue
   126  	}
   127  	return evalData
   128  }
   129  
   130  // EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance
   131  // key values at all, suitable for use in contexts where no keyed instance
   132  // is relevant.
   133  var EvalDataForNoInstanceKey = InstanceKeyEvalData{}
   134  
   135  // evaluationStateData must implement lang.Data
   136  var _ lang.Data = (*evaluationStateData)(nil)
   137  
   138  func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   139  	var diags tfdiags.Diagnostics
   140  	switch addr.Name {
   141  
   142  	case "index":
   143  		idxVal := d.InstanceKeyData.CountIndex
   144  		if idxVal == cty.NilVal {
   145  			diags = diags.Append(&hcl.Diagnostic{
   146  				Severity: hcl.DiagError,
   147  				Summary:  `Reference to "count" in non-counted context`,
   148  				Detail:   `The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.`,
   149  				Subject:  rng.ToHCL().Ptr(),
   150  			})
   151  			return cty.UnknownVal(cty.Number), diags
   152  		}
   153  		return idxVal, diags
   154  
   155  	default:
   156  		diags = diags.Append(&hcl.Diagnostic{
   157  			Severity: hcl.DiagError,
   158  			Summary:  `Invalid "count" attribute`,
   159  			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),
   160  			Subject:  rng.ToHCL().Ptr(),
   161  		})
   162  		return cty.DynamicVal, diags
   163  	}
   164  }
   165  
   166  func (d *evaluationStateData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   167  	var diags tfdiags.Diagnostics
   168  	var returnVal cty.Value
   169  	switch addr.Name {
   170  
   171  	case "key":
   172  		returnVal = d.InstanceKeyData.EachKey
   173  	case "value":
   174  		returnVal = d.InstanceKeyData.EachValue
   175  
   176  		if returnVal == cty.NilVal {
   177  			diags = diags.Append(&hcl.Diagnostic{
   178  				Severity: hcl.DiagError,
   179  				Summary:  `each.value cannot be used in this context`,
   180  				Detail:   `A reference to "each.value" has been used in a context in which it unavailable, such as when the configuration no longer contains the value in its "for_each" expression. Remove this reference to each.value in your configuration to work around this error.`,
   181  				Subject:  rng.ToHCL().Ptr(),
   182  			})
   183  			return cty.UnknownVal(cty.DynamicPseudoType), diags
   184  		}
   185  	default:
   186  		diags = diags.Append(&hcl.Diagnostic{
   187  			Severity: hcl.DiagError,
   188  			Summary:  `Invalid "each" attribute`,
   189  			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),
   190  			Subject:  rng.ToHCL().Ptr(),
   191  		})
   192  		return cty.DynamicVal, diags
   193  	}
   194  
   195  	if returnVal == cty.NilVal {
   196  		diags = diags.Append(&hcl.Diagnostic{
   197  			Severity: hcl.DiagError,
   198  			Summary:  `Reference to "each" in context without for_each`,
   199  			Detail:   `The "each" object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set.`,
   200  			Subject:  rng.ToHCL().Ptr(),
   201  		})
   202  		return cty.UnknownVal(cty.DynamicPseudoType), diags
   203  	}
   204  	return returnVal, diags
   205  }
   206  
   207  func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   208  	var diags tfdiags.Diagnostics
   209  
   210  	// First we'll make sure the requested value is declared in configuration,
   211  	// so we can produce a nice message if not.
   212  	moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   213  	if moduleConfig == nil {
   214  		// should never happen, since we can't be evaluating in a module
   215  		// that wasn't mentioned in configuration.
   216  		panic(fmt.Sprintf("input variable read from %s, which has no configuration", d.ModulePath))
   217  	}
   218  
   219  	config := moduleConfig.Module.Variables[addr.Name]
   220  	if config == nil {
   221  		var suggestions []string
   222  		for k := range moduleConfig.Module.Variables {
   223  			suggestions = append(suggestions, k)
   224  		}
   225  		suggestion := nameSuggestion(addr.Name, suggestions)
   226  		if suggestion != "" {
   227  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   228  		} else {
   229  			suggestion = fmt.Sprintf(" This variable can be declared with a variable %q {} block.", addr.Name)
   230  		}
   231  
   232  		diags = diags.Append(&hcl.Diagnostic{
   233  			Severity: hcl.DiagError,
   234  			Summary:  `Reference to undeclared input variable`,
   235  			Detail:   fmt.Sprintf(`An input variable with the name %q has not been declared.%s`, addr.Name, suggestion),
   236  			Subject:  rng.ToHCL().Ptr(),
   237  		})
   238  		return cty.DynamicVal, diags
   239  	}
   240  	d.Evaluator.VariableValuesLock.Lock()
   241  	defer d.Evaluator.VariableValuesLock.Unlock()
   242  
   243  	// During the validate walk, input variables are always unknown so
   244  	// that we are validating the configuration for all possible input values
   245  	// rather than for a specific set. Checking against a specific set of
   246  	// input values then happens during the plan walk.
   247  	//
   248  	// This is important because otherwise the validation walk will tend to be
   249  	// overly strict, requiring expressions throughout the configuration to
   250  	// be complicated to accommodate all possible inputs, whereas returning
   251  	// known here allows for simpler patterns like using input values as
   252  	// guards to broadly enable/disable resources, avoid processing things
   253  	// that are disabled, etc. Terraform's static validation leans towards
   254  	// being liberal in what it accepts because the subsequent plan walk has
   255  	// more information available and so can be more conservative.
   256  	if d.Operation == walkValidate {
   257  		// Ensure variable sensitivity is captured in the validate walk
   258  		if config.Sensitive {
   259  			return cty.UnknownVal(config.Type).Mark(marks.Sensitive), diags
   260  		}
   261  		return cty.UnknownVal(config.Type), diags
   262  	}
   263  
   264  	moduleAddrStr := d.ModulePath.String()
   265  	vals := d.Evaluator.VariableValues[moduleAddrStr]
   266  	if vals == nil {
   267  		return cty.UnknownVal(config.Type), diags
   268  	}
   269  
   270  	val, isSet := vals[addr.Name]
   271  	if !isSet {
   272  		if config.Default != cty.NilVal {
   273  			return config.Default, diags
   274  		}
   275  		return cty.UnknownVal(config.Type), diags
   276  	}
   277  
   278  	var err error
   279  	val, err = convert.Convert(val, config.ConstraintType)
   280  	if err != nil {
   281  		// We should never get here because this problem should've been caught
   282  		// during earlier validation, but we'll do something reasonable anyway.
   283  		diags = diags.Append(&hcl.Diagnostic{
   284  			Severity: hcl.DiagError,
   285  			Summary:  `Incorrect variable type`,
   286  			Detail:   fmt.Sprintf(`The resolved value of variable %q is not appropriate: %s.`, addr.Name, err),
   287  			Subject:  &config.DeclRange,
   288  		})
   289  		// Stub out our return value so that the semantic checker doesn't
   290  		// produce redundant downstream errors.
   291  		val = cty.UnknownVal(config.Type)
   292  	}
   293  
   294  	// Mark if sensitive
   295  	if config.Sensitive {
   296  		val = val.Mark(marks.Sensitive)
   297  	}
   298  
   299  	return val, diags
   300  }
   301  
   302  func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   303  	var diags tfdiags.Diagnostics
   304  
   305  	// First we'll make sure the requested value is declared in configuration,
   306  	// so we can produce a nice message if not.
   307  	moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   308  	if moduleConfig == nil {
   309  		// should never happen, since we can't be evaluating in a module
   310  		// that wasn't mentioned in configuration.
   311  		panic(fmt.Sprintf("local value read from %s, which has no configuration", d.ModulePath))
   312  	}
   313  
   314  	config := moduleConfig.Module.Locals[addr.Name]
   315  	if config == nil {
   316  		var suggestions []string
   317  		for k := range moduleConfig.Module.Locals {
   318  			suggestions = append(suggestions, k)
   319  		}
   320  		suggestion := nameSuggestion(addr.Name, suggestions)
   321  		if suggestion != "" {
   322  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   323  		}
   324  
   325  		diags = diags.Append(&hcl.Diagnostic{
   326  			Severity: hcl.DiagError,
   327  			Summary:  `Reference to undeclared local value`,
   328  			Detail:   fmt.Sprintf(`A local value with the name %q has not been declared.%s`, addr.Name, suggestion),
   329  			Subject:  rng.ToHCL().Ptr(),
   330  		})
   331  		return cty.DynamicVal, diags
   332  	}
   333  
   334  	val := d.Evaluator.State.LocalValue(addr.Absolute(d.ModulePath))
   335  	if val == cty.NilVal {
   336  		// Not evaluated yet?
   337  		val = cty.DynamicVal
   338  	}
   339  
   340  	return val, diags
   341  }
   342  
   343  func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   344  	var diags tfdiags.Diagnostics
   345  
   346  	// Output results live in the module that declares them, which is one of
   347  	// the child module instances of our current module path.
   348  	moduleAddr := d.ModulePath.Module().Child(addr.Name)
   349  
   350  	parentCfg := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   351  	callConfig, ok := parentCfg.Module.ModuleCalls[addr.Name]
   352  	if !ok {
   353  		diags = diags.Append(&hcl.Diagnostic{
   354  			Severity: hcl.DiagError,
   355  			Summary:  `Reference to undeclared module`,
   356  			Detail:   fmt.Sprintf(`The configuration contains no %s.`, moduleAddr),
   357  			Subject:  rng.ToHCL().Ptr(),
   358  		})
   359  		return cty.DynamicVal, diags
   360  	}
   361  
   362  	// We'll consult the configuration to see what output names we are
   363  	// expecting, so we can ensure the resulting object is of the expected
   364  	// type even if our data is incomplete for some reason.
   365  	moduleConfig := d.Evaluator.Config.Descendent(moduleAddr)
   366  	if moduleConfig == nil {
   367  		// should never happen, since we have a valid module call above, this
   368  		// should be caught during static validation.
   369  		panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr))
   370  	}
   371  	outputConfigs := moduleConfig.Module.Outputs
   372  
   373  	// Collect all the relevant outputs that current exist in the state.
   374  	// We know the instance path up to this point, and the child module name,
   375  	// so we only need to store these by instance key.
   376  	stateMap := map[addrs.InstanceKey]map[string]cty.Value{}
   377  	for _, output := range d.Evaluator.State.ModuleOutputs(d.ModulePath, addr) {
   378  		_, callInstance := output.Addr.Module.CallInstance()
   379  		instance, ok := stateMap[callInstance.Key]
   380  		if !ok {
   381  			instance = map[string]cty.Value{}
   382  			stateMap[callInstance.Key] = instance
   383  		}
   384  
   385  		instance[output.Addr.OutputValue.Name] = output.Value
   386  	}
   387  
   388  	// Get all changes that reside for this module call within our path.
   389  	// The change contains the full addr, so we can key these with strings.
   390  	changesMap := map[addrs.InstanceKey]map[string]*plans.OutputChangeSrc{}
   391  	for _, change := range d.Evaluator.Changes.GetOutputChanges(d.ModulePath, addr) {
   392  		_, callInstance := change.Addr.Module.CallInstance()
   393  		instance, ok := changesMap[callInstance.Key]
   394  		if !ok {
   395  			instance = map[string]*plans.OutputChangeSrc{}
   396  			changesMap[callInstance.Key] = instance
   397  		}
   398  
   399  		instance[change.Addr.OutputValue.Name] = change
   400  	}
   401  
   402  	// Build up all the module objects, creating a map of values for each
   403  	// module instance.
   404  	moduleInstances := map[addrs.InstanceKey]map[string]cty.Value{}
   405  
   406  	// create a dummy object type for validation below
   407  	unknownMap := map[string]cty.Type{}
   408  
   409  	// the structure is based on the configuration, so iterate through all the
   410  	// defined outputs, and add any instance state or changes we find.
   411  	for _, cfg := range outputConfigs {
   412  		// record the output names for validation
   413  		unknownMap[cfg.Name] = cty.DynamicPseudoType
   414  
   415  		// get all instance output for this path from the state
   416  		for key, states := range stateMap {
   417  			outputState, ok := states[cfg.Name]
   418  			if !ok {
   419  				continue
   420  			}
   421  
   422  			instance, ok := moduleInstances[key]
   423  			if !ok {
   424  				instance = map[string]cty.Value{}
   425  				moduleInstances[key] = instance
   426  			}
   427  
   428  			instance[cfg.Name] = outputState
   429  
   430  			if cfg.Sensitive {
   431  				instance[cfg.Name] = outputState.Mark(marks.Sensitive)
   432  			}
   433  		}
   434  
   435  		// any pending changes override the state state values
   436  		for key, changes := range changesMap {
   437  			changeSrc, ok := changes[cfg.Name]
   438  			if !ok {
   439  				continue
   440  			}
   441  
   442  			instance, ok := moduleInstances[key]
   443  			if !ok {
   444  				instance = map[string]cty.Value{}
   445  				moduleInstances[key] = instance
   446  			}
   447  
   448  			change, err := changeSrc.Decode()
   449  			if err != nil {
   450  				// This should happen only if someone has tampered with a plan
   451  				// file, so we won't bother with a pretty error for it.
   452  				diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", addr, err))
   453  				instance[cfg.Name] = cty.DynamicVal
   454  				continue
   455  			}
   456  
   457  			instance[cfg.Name] = change.After
   458  
   459  			if change.Sensitive {
   460  				instance[cfg.Name] = change.After.Mark(marks.Sensitive)
   461  			}
   462  		}
   463  	}
   464  
   465  	var ret cty.Value
   466  
   467  	// compile the outputs into the correct value type for the each mode
   468  	switch {
   469  	case callConfig.Count != nil:
   470  		// figure out what the last index we have is
   471  		length := -1
   472  		for key := range moduleInstances {
   473  			intKey, ok := key.(addrs.IntKey)
   474  			if !ok {
   475  				// old key from state which is being dropped
   476  				continue
   477  			}
   478  			if int(intKey) >= length {
   479  				length = int(intKey) + 1
   480  			}
   481  		}
   482  
   483  		if length > 0 {
   484  			vals := make([]cty.Value, length)
   485  			for key, instance := range moduleInstances {
   486  				intKey, ok := key.(addrs.IntKey)
   487  				if !ok {
   488  					// old key from state which is being dropped
   489  					continue
   490  				}
   491  
   492  				vals[int(intKey)] = cty.ObjectVal(instance)
   493  			}
   494  
   495  			// Insert unknown values where there are any missing instances
   496  			for i, v := range vals {
   497  				if v.IsNull() {
   498  					vals[i] = cty.DynamicVal
   499  					continue
   500  				}
   501  			}
   502  			ret = cty.TupleVal(vals)
   503  		} else {
   504  			ret = cty.EmptyTupleVal
   505  		}
   506  
   507  	case callConfig.ForEach != nil:
   508  		vals := make(map[string]cty.Value)
   509  		for key, instance := range moduleInstances {
   510  			strKey, ok := key.(addrs.StringKey)
   511  			if !ok {
   512  				continue
   513  			}
   514  
   515  			vals[string(strKey)] = cty.ObjectVal(instance)
   516  		}
   517  
   518  		if len(vals) > 0 {
   519  			ret = cty.ObjectVal(vals)
   520  		} else {
   521  			ret = cty.EmptyObjectVal
   522  		}
   523  
   524  	default:
   525  		val, ok := moduleInstances[addrs.NoKey]
   526  		if !ok {
   527  			// create the object if there wasn't one known
   528  			val = map[string]cty.Value{}
   529  			for k := range outputConfigs {
   530  				val[k] = cty.DynamicVal
   531  			}
   532  		}
   533  
   534  		ret = cty.ObjectVal(val)
   535  	}
   536  
   537  	// The module won't be expanded during validation, so we need to return an
   538  	// unknown value. This will ensure the types looks correct, since we built
   539  	// the objects based on the configuration.
   540  	if d.Operation == walkValidate {
   541  		// While we know the type here and it would be nice to validate whether
   542  		// indexes are valid or not, because tuples and objects have fixed
   543  		// numbers of elements we can't simply return an unknown value of the
   544  		// same type since we have not expanded any instances during
   545  		// validation.
   546  		//
   547  		// In order to validate the expression a little precisely, we'll create
   548  		// an unknown map or list here to get more type information.
   549  		ty := cty.Object(unknownMap)
   550  		switch {
   551  		case callConfig.Count != nil:
   552  			ret = cty.UnknownVal(cty.List(ty))
   553  		case callConfig.ForEach != nil:
   554  			ret = cty.UnknownVal(cty.Map(ty))
   555  		default:
   556  			ret = cty.UnknownVal(ty)
   557  		}
   558  	}
   559  
   560  	return ret, diags
   561  }
   562  
   563  func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   564  	var diags tfdiags.Diagnostics
   565  	switch addr.Name {
   566  
   567  	case "cwd":
   568  		var err error
   569  		var wd string
   570  		if d.Evaluator.Meta != nil {
   571  			// Meta is always non-nil in the normal case, but some test cases
   572  			// are not so realistic.
   573  			wd = d.Evaluator.Meta.OriginalWorkingDir
   574  		}
   575  		if wd == "" {
   576  			wd, err = os.Getwd()
   577  			if err != nil {
   578  				diags = diags.Append(&hcl.Diagnostic{
   579  					Severity: hcl.DiagError,
   580  					Summary:  `Failed to get working directory`,
   581  					Detail:   fmt.Sprintf(`The value for path.cwd cannot be determined due to a system error: %s`, err),
   582  					Subject:  rng.ToHCL().Ptr(),
   583  				})
   584  				return cty.DynamicVal, diags
   585  			}
   586  		}
   587  		// The current working directory should always be absolute, whether we
   588  		// just looked it up or whether we were relying on ContextMeta's
   589  		// (possibly non-normalized) path.
   590  		wd, err = filepath.Abs(wd)
   591  		if err != nil {
   592  			diags = diags.Append(&hcl.Diagnostic{
   593  				Severity: hcl.DiagError,
   594  				Summary:  `Failed to get working directory`,
   595  				Detail:   fmt.Sprintf(`The value for path.cwd cannot be determined due to a system error: %s`, err),
   596  				Subject:  rng.ToHCL().Ptr(),
   597  			})
   598  			return cty.DynamicVal, diags
   599  		}
   600  
   601  		return cty.StringVal(filepath.ToSlash(wd)), diags
   602  
   603  	case "module":
   604  		moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   605  		if moduleConfig == nil {
   606  			// should never happen, since we can't be evaluating in a module
   607  			// that wasn't mentioned in configuration.
   608  			panic(fmt.Sprintf("module.path read from module %s, which has no configuration", d.ModulePath))
   609  		}
   610  		sourceDir := moduleConfig.Module.SourceDir
   611  		return cty.StringVal(filepath.ToSlash(sourceDir)), diags
   612  
   613  	case "root":
   614  		sourceDir := d.Evaluator.Config.Module.SourceDir
   615  		return cty.StringVal(filepath.ToSlash(sourceDir)), diags
   616  
   617  	default:
   618  		suggestion := nameSuggestion(addr.Name, []string{"cwd", "module", "root"})
   619  		if suggestion != "" {
   620  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   621  		}
   622  		diags = diags.Append(&hcl.Diagnostic{
   623  			Severity: hcl.DiagError,
   624  			Summary:  `Invalid "path" attribute`,
   625  			Detail:   fmt.Sprintf(`The "path" object does not have an attribute named %q.%s`, addr.Name, suggestion),
   626  			Subject:  rng.ToHCL().Ptr(),
   627  		})
   628  		return cty.DynamicVal, diags
   629  	}
   630  }
   631  
   632  func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   633  	var diags tfdiags.Diagnostics
   634  	// First we'll consult the configuration to see if an resource of this
   635  	// name is declared at all.
   636  	moduleAddr := d.ModulePath
   637  	moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
   638  	if moduleConfig == nil {
   639  		// should never happen, since we can't be evaluating in a module
   640  		// that wasn't mentioned in configuration.
   641  		panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr))
   642  	}
   643  
   644  	config := moduleConfig.Module.ResourceByAddr(addr)
   645  	if config == nil {
   646  		diags = diags.Append(&hcl.Diagnostic{
   647  			Severity: hcl.DiagError,
   648  			Summary:  `Reference to undeclared resource`,
   649  			Detail:   fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Type, addr.Name, moduleDisplayAddr(moduleAddr)),
   650  			Subject:  rng.ToHCL().Ptr(),
   651  		})
   652  		return cty.DynamicVal, diags
   653  	}
   654  
   655  	rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath))
   656  
   657  	if rs == nil {
   658  		switch d.Operation {
   659  		case walkPlan, walkApply:
   660  			// During plan and apply as we evaluate each removed instance they
   661  			// are removed from the working state. Since we know there are no
   662  			// instances, return an empty container of the expected type.
   663  			switch {
   664  			case config.Count != nil:
   665  				return cty.EmptyTupleVal, diags
   666  			case config.ForEach != nil:
   667  				return cty.EmptyObjectVal, diags
   668  			default:
   669  				// While we can reference an expanded resource with 0
   670  				// instances, we cannot reference instances that do not exist.
   671  				// Due to the fact that we may have direct references to
   672  				// instances that may end up in a root output during destroy
   673  				// (since a planned destroy cannot yet remove root outputs), we
   674  				// need to return a dynamic value here to allow evaluation to
   675  				// continue.
   676  				log.Printf("[ERROR] unknown instance %q referenced during plan", addr.Absolute(d.ModulePath))
   677  				return cty.DynamicVal, diags
   678  			}
   679  
   680  		default:
   681  			// We should only end up here during the validate walk,
   682  			// since later walks should have at least partial states populated
   683  			// for all resources in the configuration.
   684  			return cty.DynamicVal, diags
   685  		}
   686  	}
   687  
   688  	providerAddr := rs.ProviderConfig
   689  
   690  	schema := d.getResourceSchema(addr, providerAddr)
   691  	if schema == nil {
   692  		// This shouldn't happen, since validation before we get here should've
   693  		// taken care of it, but we'll show a reasonable error message anyway.
   694  		diags = diags.Append(&hcl.Diagnostic{
   695  			Severity: hcl.DiagError,
   696  			Summary:  `Missing resource type schema`,
   697  			Detail:   fmt.Sprintf("No schema is available for %s in %s. This is a bug in Terraform and should be reported.", addr, providerAddr),
   698  			Subject:  rng.ToHCL().Ptr(),
   699  		})
   700  		return cty.DynamicVal, diags
   701  	}
   702  	ty := schema.ImpliedType()
   703  
   704  	// Decode all instances in the current state
   705  	instances := map[addrs.InstanceKey]cty.Value{}
   706  	pendingDestroy := d.Evaluator.Changes.IsFullDestroy()
   707  	for key, is := range rs.Instances {
   708  		if is == nil || is.Current == nil {
   709  			// Assume we're dealing with an instance that hasn't been created yet.
   710  			instances[key] = cty.UnknownVal(ty)
   711  			continue
   712  		}
   713  
   714  		instAddr := addr.Instance(key).Absolute(d.ModulePath)
   715  
   716  		change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen)
   717  		if change != nil {
   718  			// Don't take any resources that are yet to be deleted into account.
   719  			// If the referenced resource is CreateBeforeDestroy, then orphaned
   720  			// instances will be in the state, as they are not destroyed until
   721  			// after their dependants are updated.
   722  			if change.Action == plans.Delete {
   723  				if !pendingDestroy {
   724  					continue
   725  				}
   726  			}
   727  		}
   728  
   729  		// Planned resources are temporarily stored in state with empty values,
   730  		// and need to be replaced by the planned value here.
   731  		if is.Current.Status == states.ObjectPlanned {
   732  			if change == nil {
   733  				// If the object is in planned status then we should not get
   734  				// here, since we should have found a pending value in the plan
   735  				// above instead.
   736  				diags = diags.Append(&hcl.Diagnostic{
   737  					Severity: hcl.DiagError,
   738  					Summary:  "Missing pending object in plan",
   739  					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),
   740  					Subject:  &config.DeclRange,
   741  				})
   742  				continue
   743  			}
   744  			val, err := change.After.Decode(ty)
   745  			if err != nil {
   746  				diags = diags.Append(&hcl.Diagnostic{
   747  					Severity: hcl.DiagError,
   748  					Summary:  "Invalid resource instance data in plan",
   749  					Detail:   fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err),
   750  					Subject:  &config.DeclRange,
   751  				})
   752  				continue
   753  			}
   754  
   755  			// If our provider schema contains sensitive values, mark those as sensitive
   756  			afterMarks := change.AfterValMarks
   757  			if schema.ContainsSensitive() {
   758  				afterMarks = append(afterMarks, schema.ValueMarks(val, nil)...)
   759  			}
   760  
   761  			instances[key] = val.MarkWithPaths(afterMarks)
   762  			continue
   763  		}
   764  
   765  		ios, err := is.Current.Decode(ty)
   766  		if err != nil {
   767  			// This shouldn't happen, since by the time we get here we
   768  			// should have upgraded the state data already.
   769  			diags = diags.Append(&hcl.Diagnostic{
   770  				Severity: hcl.DiagError,
   771  				Summary:  "Invalid resource instance data in state",
   772  				Detail:   fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err),
   773  				Subject:  &config.DeclRange,
   774  			})
   775  			continue
   776  		}
   777  
   778  		val := ios.Value
   779  
   780  		// If our schema contains sensitive values, mark those as sensitive.
   781  		// Since decoding the instance object can also apply sensitivity marks,
   782  		// we must remove and combine those before remarking to avoid a double-
   783  		// mark error.
   784  		if schema.ContainsSensitive() {
   785  			var marks []cty.PathValueMarks
   786  			val, marks = val.UnmarkDeepWithPaths()
   787  			marks = append(marks, schema.ValueMarks(val, nil)...)
   788  			val = val.MarkWithPaths(marks)
   789  		}
   790  		instances[key] = val
   791  	}
   792  
   793  	var ret cty.Value
   794  
   795  	switch {
   796  	case config.Count != nil:
   797  		// figure out what the last index we have is
   798  		length := -1
   799  		for key := range instances {
   800  			intKey, ok := key.(addrs.IntKey)
   801  			if !ok {
   802  				continue
   803  			}
   804  			if int(intKey) >= length {
   805  				length = int(intKey) + 1
   806  			}
   807  		}
   808  
   809  		if length > 0 {
   810  			vals := make([]cty.Value, length)
   811  			for key, instance := range instances {
   812  				intKey, ok := key.(addrs.IntKey)
   813  				if !ok {
   814  					// old key from state, which isn't valid for evaluation
   815  					continue
   816  				}
   817  
   818  				vals[int(intKey)] = instance
   819  			}
   820  
   821  			// Insert unknown values where there are any missing instances
   822  			for i, v := range vals {
   823  				if v == cty.NilVal {
   824  					vals[i] = cty.UnknownVal(ty)
   825  				}
   826  			}
   827  			ret = cty.TupleVal(vals)
   828  		} else {
   829  			ret = cty.EmptyTupleVal
   830  		}
   831  
   832  	case config.ForEach != nil:
   833  		vals := make(map[string]cty.Value)
   834  		for key, instance := range instances {
   835  			strKey, ok := key.(addrs.StringKey)
   836  			if !ok {
   837  				// old key that is being dropped and not used for evaluation
   838  				continue
   839  			}
   840  			vals[string(strKey)] = instance
   841  		}
   842  
   843  		if len(vals) > 0 {
   844  			// We use an object rather than a map here because resource schemas
   845  			// may include dynamically-typed attributes, which will then cause
   846  			// each instance to potentially have a different runtime type even
   847  			// though they all conform to the static schema.
   848  			ret = cty.ObjectVal(vals)
   849  		} else {
   850  			ret = cty.EmptyObjectVal
   851  		}
   852  
   853  	default:
   854  		val, ok := instances[addrs.NoKey]
   855  		if !ok {
   856  			// if the instance is missing, insert an unknown value
   857  			val = cty.UnknownVal(ty)
   858  		}
   859  
   860  		ret = val
   861  	}
   862  
   863  	// since the plan was not yet created during validate, the values we
   864  	// collected here may not correspond with configuration, so they must be
   865  	// unknown.
   866  	if d.Operation == walkValidate {
   867  		// While we know the type here and it would be nice to validate whether
   868  		// indexes are valid or not, because tuples and objects have fixed
   869  		// numbers of elements we can't simply return an unknown value of the
   870  		// same type since we have not expanded any instances during
   871  		// validation.
   872  		//
   873  		// In order to validate the expression a little precisely, we'll create
   874  		// an unknown map or list here to get more type information.
   875  		switch {
   876  		case config.Count != nil:
   877  			ret = cty.UnknownVal(cty.List(ty))
   878  		case config.ForEach != nil:
   879  			ret = cty.UnknownVal(cty.Map(ty))
   880  		default:
   881  			ret = cty.UnknownVal(ty)
   882  		}
   883  	}
   884  
   885  	return ret, diags
   886  }
   887  
   888  func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.AbsProviderConfig) *configschema.Block {
   889  	schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr.Provider, addr.Mode, addr.Type)
   890  	if err != nil {
   891  		// We have plently other codepaths that will detect and report
   892  		// schema lookup errors before we'd reach this point, so we'll just
   893  		// treat a failure here the same as having no schema.
   894  		return nil
   895  	}
   896  	return schema
   897  }
   898  
   899  func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   900  	var diags tfdiags.Diagnostics
   901  	switch addr.Name {
   902  
   903  	case "workspace":
   904  		workspaceName := d.Evaluator.Meta.Env
   905  		return cty.StringVal(workspaceName), diags
   906  
   907  	case "env":
   908  		// Prior to Terraform 0.12 there was an attribute "env", which was
   909  		// an alias name for "workspace". This was deprecated and is now
   910  		// removed.
   911  		diags = diags.Append(&hcl.Diagnostic{
   912  			Severity: hcl.DiagError,
   913  			Summary:  `Invalid "terraform" attribute`,
   914  			Detail:   `The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The "state environment" concept was renamed to "workspace" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.`,
   915  			Subject:  rng.ToHCL().Ptr(),
   916  		})
   917  		return cty.DynamicVal, diags
   918  
   919  	default:
   920  		diags = diags.Append(&hcl.Diagnostic{
   921  			Severity: hcl.DiagError,
   922  			Summary:  `Invalid "terraform" attribute`,
   923  			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),
   924  			Subject:  rng.ToHCL().Ptr(),
   925  		})
   926  		return cty.DynamicVal, diags
   927  	}
   928  }
   929  
   930  // nameSuggestion tries to find a name from the given slice of suggested names
   931  // that is close to the given name and returns it if found. If no suggestion
   932  // is close enough, returns the empty string.
   933  //
   934  // The suggestions are tried in order, so earlier suggestions take precedence
   935  // if the given string is similar to two or more suggestions.
   936  //
   937  // This function is intended to be used with a relatively-small number of
   938  // suggestions. It's not optimized for hundreds or thousands of them.
   939  func nameSuggestion(given string, suggestions []string) string {
   940  	for _, suggestion := range suggestions {
   941  		dist := levenshtein.Distance(given, suggestion, nil)
   942  		if dist < 3 { // threshold determined experimentally
   943  			return suggestion
   944  		}
   945  	}
   946  	return ""
   947  }
   948  
   949  // moduleDisplayAddr returns a string describing the given module instance
   950  // address that is appropriate for returning to users in situations where the
   951  // root module is possible. Specifically, it returns "the root module" if the
   952  // root module instance is given, or a string representation of the module
   953  // address otherwise.
   954  func moduleDisplayAddr(addr addrs.ModuleInstance) string {
   955  	switch {
   956  	case addr.IsRoot():
   957  		return "the root module"
   958  	default:
   959  		return addr.String()
   960  	}
   961  }