github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/evaluate.go (about)

     1  package durgaform
     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  
    14  	"github.com/eliastor/durgaform/internal/addrs"
    15  	"github.com/eliastor/durgaform/internal/configs"
    16  	"github.com/eliastor/durgaform/internal/configs/configschema"
    17  	"github.com/eliastor/durgaform/internal/instances"
    18  	"github.com/eliastor/durgaform/internal/lang"
    19  	"github.com/eliastor/durgaform/internal/lang/marks"
    20  	"github.com/eliastor/durgaform/internal/plans"
    21  	"github.com/eliastor/durgaform/internal/states"
    22  	"github.com/eliastor/durgaform/internal/tfdiags"
    23  )
    24  
    25  // Evaluator provides the necessary contextual data for evaluating expressions
    26  // for a particular walk operation.
    27  type Evaluator struct {
    28  	// Operation defines what type of operation this evaluator is being used
    29  	// for.
    30  	Operation walkOperation
    31  
    32  	// Meta is contextual metadata about the current operation.
    33  	Meta *ContextMeta
    34  
    35  	// Config is the root node in the configuration tree.
    36  	Config *configs.Config
    37  
    38  	// VariableValues is a map from variable names to their associated values,
    39  	// within the module indicated by ModulePath. VariableValues is modified
    40  	// concurrently, and so it must be accessed only while holding
    41  	// VariableValuesLock.
    42  	//
    43  	// The first map level is string representations of addr.ModuleInstance
    44  	// values, while the second level is variable names.
    45  	VariableValues     map[string]map[string]cty.Value
    46  	VariableValuesLock *sync.Mutex
    47  
    48  	// Plugins is the library of available plugin components (providers and
    49  	// provisioners) that we have available to help us evaluate expressions
    50  	// that interact with plugin-provided objects.
    51  	//
    52  	// From this we only access the schemas of the plugins, and don't otherwise
    53  	// interact with plugin instances.
    54  	Plugins *contextPlugins
    55  
    56  	// State is the current state, embedded in a wrapper that ensures that
    57  	// it can be safely accessed and modified concurrently.
    58  	State *states.SyncState
    59  
    60  	// Changes is the set of proposed changes, embedded in a wrapper that
    61  	// ensures they can be safely accessed and modified concurrently.
    62  	Changes *plans.ChangesSync
    63  }
    64  
    65  // Scope creates an evaluation scope for the given module path and optional
    66  // resource.
    67  //
    68  // If the "self" argument is nil then the "self" object is not available
    69  // in evaluated expressions. Otherwise, it behaves as an alias for the given
    70  // address.
    71  func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope {
    72  	return &lang.Scope{
    73  		Data:     data,
    74  		SelfAddr: self,
    75  		PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval,
    76  		BaseDir:  ".", // Always current working directory for now.
    77  	}
    78  }
    79  
    80  // evaluationStateData is an implementation of lang.Data that resolves
    81  // references primarily (but not exclusively) using information from a State.
    82  type evaluationStateData struct {
    83  	Evaluator *Evaluator
    84  
    85  	// ModulePath is the path through the dynamic module tree to the module
    86  	// that references will be resolved relative to.
    87  	ModulePath addrs.ModuleInstance
    88  
    89  	// InstanceKeyData describes the values, if any, that are accessible due
    90  	// to repetition of a containing object using "count" or "for_each"
    91  	// arguments. (It is _not_ used for the for_each inside "dynamic" blocks,
    92  	// since the user specifies in that case which variable name to locally
    93  	// shadow.)
    94  	InstanceKeyData InstanceKeyEvalData
    95  
    96  	// Operation records the type of walk the evaluationStateData is being used
    97  	// for.
    98  	Operation walkOperation
    99  }
   100  
   101  // InstanceKeyEvalData is the old name for instances.RepetitionData, aliased
   102  // here for compatibility. In new code, use instances.RepetitionData instead.
   103  type InstanceKeyEvalData = instances.RepetitionData
   104  
   105  // EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for
   106  // evaluating in a context that has the given instance key.
   107  //
   108  // The forEachMap argument can be nil when preparing for evaluation
   109  // in a context where each.value is prohibited, such as a destroy-time
   110  // provisioner. In that case, the returned EachValue will always be
   111  // cty.NilVal.
   112  func EvalDataForInstanceKey(key addrs.InstanceKey, forEachMap map[string]cty.Value) InstanceKeyEvalData {
   113  	var evalData InstanceKeyEvalData
   114  	if key == nil {
   115  		return evalData
   116  	}
   117  
   118  	keyValue := key.Value()
   119  	switch keyValue.Type() {
   120  	case cty.String:
   121  		evalData.EachKey = keyValue
   122  		evalData.EachValue = forEachMap[keyValue.AsString()]
   123  	case cty.Number:
   124  		evalData.CountIndex = keyValue
   125  	}
   126  	return evalData
   127  }
   128  
   129  // EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance
   130  // key values at all, suitable for use in contexts where no keyed instance
   131  // is relevant.
   132  var EvalDataForNoInstanceKey = InstanceKeyEvalData{}
   133  
   134  // evaluationStateData must implement lang.Data
   135  var _ lang.Data = (*evaluationStateData)(nil)
   136  
   137  func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   138  	var diags tfdiags.Diagnostics
   139  	switch addr.Name {
   140  
   141  	case "index":
   142  		idxVal := d.InstanceKeyData.CountIndex
   143  		if idxVal == cty.NilVal {
   144  			diags = diags.Append(&hcl.Diagnostic{
   145  				Severity: hcl.DiagError,
   146  				Summary:  `Reference to "count" in non-counted context`,
   147  				Detail:   `The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.`,
   148  				Subject:  rng.ToHCL().Ptr(),
   149  			})
   150  			return cty.UnknownVal(cty.Number), diags
   151  		}
   152  		return idxVal, diags
   153  
   154  	default:
   155  		diags = diags.Append(&hcl.Diagnostic{
   156  			Severity: hcl.DiagError,
   157  			Summary:  `Invalid "count" attribute`,
   158  			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),
   159  			Subject:  rng.ToHCL().Ptr(),
   160  		})
   161  		return cty.DynamicVal, diags
   162  	}
   163  }
   164  
   165  func (d *evaluationStateData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   166  	var diags tfdiags.Diagnostics
   167  	var returnVal cty.Value
   168  	switch addr.Name {
   169  
   170  	case "key":
   171  		returnVal = d.InstanceKeyData.EachKey
   172  	case "value":
   173  		returnVal = d.InstanceKeyData.EachValue
   174  
   175  		if returnVal == cty.NilVal {
   176  			diags = diags.Append(&hcl.Diagnostic{
   177  				Severity: hcl.DiagError,
   178  				Summary:  `each.value cannot be used in this context`,
   179  				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.`,
   180  				Subject:  rng.ToHCL().Ptr(),
   181  			})
   182  			return cty.UnknownVal(cty.DynamicPseudoType), diags
   183  		}
   184  	default:
   185  		diags = diags.Append(&hcl.Diagnostic{
   186  			Severity: hcl.DiagError,
   187  			Summary:  `Invalid "each" attribute`,
   188  			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),
   189  			Subject:  rng.ToHCL().Ptr(),
   190  		})
   191  		return cty.DynamicVal, diags
   192  	}
   193  
   194  	if returnVal == cty.NilVal {
   195  		diags = diags.Append(&hcl.Diagnostic{
   196  			Severity: hcl.DiagError,
   197  			Summary:  `Reference to "each" in context without for_each`,
   198  			Detail:   `The "each" object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set.`,
   199  			Subject:  rng.ToHCL().Ptr(),
   200  		})
   201  		return cty.UnknownVal(cty.DynamicPseudoType), diags
   202  	}
   203  	return returnVal, diags
   204  }
   205  
   206  func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   207  	var diags tfdiags.Diagnostics
   208  
   209  	// First we'll make sure the requested value is declared in configuration,
   210  	// so we can produce a nice message if not.
   211  	moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
   212  	if moduleConfig == nil {
   213  		// should never happen, since we can't be evaluating in a module
   214  		// that wasn't mentioned in configuration.
   215  		panic(fmt.Sprintf("input variable read from %s, which has no configuration", d.ModulePath))
   216  	}
   217  
   218  	config := moduleConfig.Module.Variables[addr.Name]
   219  	if config == nil {
   220  		var suggestions []string
   221  		for k := range moduleConfig.Module.Variables {
   222  			suggestions = append(suggestions, k)
   223  		}
   224  		suggestion := nameSuggestion(addr.Name, suggestions)
   225  		if suggestion != "" {
   226  			suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   227  		} else {
   228  			suggestion = fmt.Sprintf(" This variable can be declared with a variable %q {} block.", addr.Name)
   229  		}
   230  
   231  		diags = diags.Append(&hcl.Diagnostic{
   232  			Severity: hcl.DiagError,
   233  			Summary:  `Reference to undeclared input variable`,
   234  			Detail:   fmt.Sprintf(`An input variable with the name %q has not been declared.%s`, addr.Name, suggestion),
   235  			Subject:  rng.ToHCL().Ptr(),
   236  		})
   237  		return cty.DynamicVal, diags
   238  	}
   239  	d.Evaluator.VariableValuesLock.Lock()
   240  	defer d.Evaluator.VariableValuesLock.Unlock()
   241  
   242  	// During the validate walk, input variables are always unknown so
   243  	// that we are validating the configuration for all possible input values
   244  	// rather than for a specific set. Checking against a specific set of
   245  	// input values then happens during the plan walk.
   246  	//
   247  	// This is important because otherwise the validation walk will tend to be
   248  	// overly strict, requiring expressions throughout the configuration to
   249  	// be complicated to accommodate all possible inputs, whereas returning
   250  	// unknown here allows for simpler patterns like using input values as
   251  	// guards to broadly enable/disable resources, avoid processing things
   252  	// that are disabled, etc. Durgaform's static validation leans towards
   253  	// being liberal in what it accepts because the subsequent plan walk has
   254  	// more information available and so can be more conservative.
   255  	if d.Operation == walkValidate {
   256  		// Ensure variable sensitivity is captured in the validate walk
   257  		if config.Sensitive {
   258  			return cty.UnknownVal(config.Type).Mark(marks.Sensitive), diags
   259  		}
   260  		return cty.UnknownVal(config.Type), diags
   261  	}
   262  
   263  	moduleAddrStr := d.ModulePath.String()
   264  	vals := d.Evaluator.VariableValues[moduleAddrStr]
   265  	if vals == nil {
   266  		return cty.UnknownVal(config.Type), diags
   267  	}
   268  
   269  	// d.Evaluator.VariableValues should always contain valid "final values"
   270  	// for variables, which is to say that they have already had type
   271  	// conversions, validations, and default value handling applied to them.
   272  	// Those are the responsibility of the graph notes representing the
   273  	// variable declarations. Therefore here we just trust that we already
   274  	// have a correct value.
   275  
   276  	val, isSet := vals[addr.Name]
   277  	if !isSet {
   278  		// We should not be able to get here without having a valid value
   279  		// for every variable, so this always indicates a bug in either
   280  		// the graph builder (not including all the needed nodes) or in
   281  		// the graph nodes representing variables.
   282  		diags = diags.Append(&hcl.Diagnostic{
   283  			Severity: hcl.DiagError,
   284  			Summary:  `Reference to unresolved input variable`,
   285  			Detail: fmt.Sprintf(
   286  				`The final value for %s is missing in Durgaform's evaluation context. This is a bug in Terraform; please report it!`,
   287  				addr.Absolute(d.ModulePath),
   288  			),
   289  			Subject: rng.ToHCL().Ptr(),
   290  		})
   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  	// Build the provider address from configuration, since we may not have
   656  	// state available in all cases.
   657  	// We need to build an abs provider address, but we can use a default
   658  	// instance since we're only interested in the schema.
   659  	schema := d.getResourceSchema(addr, config.Provider)
   660  	if schema == nil {
   661  		// This shouldn't happen, since validation before we get here should've
   662  		// taken care of it, but we'll show a reasonable error message anyway.
   663  		diags = diags.Append(&hcl.Diagnostic{
   664  			Severity: hcl.DiagError,
   665  			Summary:  `Missing resource type schema`,
   666  			Detail:   fmt.Sprintf("No schema is available for %s in %s. This is a bug in Durgaform and should be reported.", addr, config.Provider),
   667  			Subject:  rng.ToHCL().Ptr(),
   668  		})
   669  		return cty.DynamicVal, diags
   670  	}
   671  	ty := schema.ImpliedType()
   672  
   673  	rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath))
   674  
   675  	if rs == nil {
   676  		switch d.Operation {
   677  		case walkPlan, walkApply:
   678  			// During plan and apply as we evaluate each removed instance they
   679  			// are removed from the working state. Since we know there are no
   680  			// instances, return an empty container of the expected type.
   681  			switch {
   682  			case config.Count != nil:
   683  				return cty.EmptyTupleVal, diags
   684  			case config.ForEach != nil:
   685  				return cty.EmptyObjectVal, diags
   686  			default:
   687  				// While we can reference an expanded resource with 0
   688  				// instances, we cannot reference instances that do not exist.
   689  				// Due to the fact that we may have direct references to
   690  				// instances that may end up in a root output during destroy
   691  				// (since a planned destroy cannot yet remove root outputs), we
   692  				// need to return a dynamic value here to allow evaluation to
   693  				// continue.
   694  				log.Printf("[ERROR] unknown instance %q referenced during %s", addr.Absolute(d.ModulePath), d.Operation)
   695  				return cty.DynamicVal, diags
   696  			}
   697  
   698  		default:
   699  			// We should only end up here during the validate walk,
   700  			// since later walks should have at least partial states populated
   701  			// for all resources in the configuration.
   702  			return cty.DynamicVal, diags
   703  		}
   704  	}
   705  
   706  	// Decode all instances in the current state
   707  	instances := map[addrs.InstanceKey]cty.Value{}
   708  	pendingDestroy := d.Evaluator.Changes.IsFullDestroy()
   709  	for key, is := range rs.Instances {
   710  		if is == nil || is.Current == nil {
   711  			// Assume we're dealing with an instance that hasn't been created yet.
   712  			instances[key] = cty.UnknownVal(ty)
   713  			continue
   714  		}
   715  
   716  		instAddr := addr.Instance(key).Absolute(d.ModulePath)
   717  
   718  		change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen)
   719  		if change != nil {
   720  			// Don't take any resources that are yet to be deleted into account.
   721  			// If the referenced resource is CreateBeforeDestroy, then orphaned
   722  			// instances will be in the state, as they are not destroyed until
   723  			// after their dependants are updated.
   724  			if change.Action == plans.Delete {
   725  				if !pendingDestroy {
   726  					continue
   727  				}
   728  			}
   729  		}
   730  
   731  		// Planned resources are temporarily stored in state with empty values,
   732  		// and need to be replaced by the planned value here.
   733  		if is.Current.Status == states.ObjectPlanned {
   734  			if change == nil {
   735  				// If the object is in planned status then we should not get
   736  				// here, since we should have found a pending value in the plan
   737  				// above instead.
   738  				diags = diags.Append(&hcl.Diagnostic{
   739  					Severity: hcl.DiagError,
   740  					Summary:  "Missing pending object in plan",
   741  					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 Durgaform; please report it.", instAddr),
   742  					Subject:  &config.DeclRange,
   743  				})
   744  				continue
   745  			}
   746  			val, err := change.After.Decode(ty)
   747  			if err != nil {
   748  				diags = diags.Append(&hcl.Diagnostic{
   749  					Severity: hcl.DiagError,
   750  					Summary:  "Invalid resource instance data in plan",
   751  					Detail:   fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err),
   752  					Subject:  &config.DeclRange,
   753  				})
   754  				continue
   755  			}
   756  
   757  			// If our provider schema contains sensitive values, mark those as sensitive
   758  			afterMarks := change.AfterValMarks
   759  			if schema.ContainsSensitive() {
   760  				afterMarks = append(afterMarks, schema.ValueMarks(val, nil)...)
   761  			}
   762  
   763  			instances[key] = val.MarkWithPaths(afterMarks)
   764  			continue
   765  		}
   766  
   767  		ios, err := is.Current.Decode(ty)
   768  		if err != nil {
   769  			// This shouldn't happen, since by the time we get here we
   770  			// should have upgraded the state data already.
   771  			diags = diags.Append(&hcl.Diagnostic{
   772  				Severity: hcl.DiagError,
   773  				Summary:  "Invalid resource instance data in state",
   774  				Detail:   fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err),
   775  				Subject:  &config.DeclRange,
   776  			})
   777  			continue
   778  		}
   779  
   780  		val := ios.Value
   781  
   782  		// If our schema contains sensitive values, mark those as sensitive.
   783  		// Since decoding the instance object can also apply sensitivity marks,
   784  		// we must remove and combine those before remarking to avoid a double-
   785  		// mark error.
   786  		if schema.ContainsSensitive() {
   787  			var marks []cty.PathValueMarks
   788  			val, marks = val.UnmarkDeepWithPaths()
   789  			marks = append(marks, schema.ValueMarks(val, nil)...)
   790  			val = val.MarkWithPaths(marks)
   791  		}
   792  		instances[key] = val
   793  	}
   794  
   795  	// ret should be populated with a valid value in all cases below
   796  	var ret cty.Value
   797  
   798  	switch {
   799  	case config.Count != nil:
   800  		// figure out what the last index we have is
   801  		length := -1
   802  		for key := range instances {
   803  			intKey, ok := key.(addrs.IntKey)
   804  			if !ok {
   805  				continue
   806  			}
   807  			if int(intKey) >= length {
   808  				length = int(intKey) + 1
   809  			}
   810  		}
   811  
   812  		if length > 0 {
   813  			vals := make([]cty.Value, length)
   814  			for key, instance := range instances {
   815  				intKey, ok := key.(addrs.IntKey)
   816  				if !ok {
   817  					// old key from state, which isn't valid for evaluation
   818  					continue
   819  				}
   820  
   821  				vals[int(intKey)] = instance
   822  			}
   823  
   824  			// Insert unknown values where there are any missing instances
   825  			for i, v := range vals {
   826  				if v == cty.NilVal {
   827  					vals[i] = cty.UnknownVal(ty)
   828  				}
   829  			}
   830  			ret = cty.TupleVal(vals)
   831  		} else {
   832  			ret = cty.EmptyTupleVal
   833  		}
   834  
   835  	case config.ForEach != nil:
   836  		vals := make(map[string]cty.Value)
   837  		for key, instance := range instances {
   838  			strKey, ok := key.(addrs.StringKey)
   839  			if !ok {
   840  				// old key that is being dropped and not used for evaluation
   841  				continue
   842  			}
   843  			vals[string(strKey)] = instance
   844  		}
   845  
   846  		if len(vals) > 0 {
   847  			// We use an object rather than a map here because resource schemas
   848  			// may include dynamically-typed attributes, which will then cause
   849  			// each instance to potentially have a different runtime type even
   850  			// though they all conform to the static schema.
   851  			ret = cty.ObjectVal(vals)
   852  		} else {
   853  			ret = cty.EmptyObjectVal
   854  		}
   855  
   856  	default:
   857  		val, ok := instances[addrs.NoKey]
   858  		if !ok {
   859  			// if the instance is missing, insert an unknown value
   860  			val = cty.UnknownVal(ty)
   861  		}
   862  
   863  		ret = val
   864  	}
   865  
   866  	return ret, diags
   867  }
   868  
   869  func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.Provider) *configschema.Block {
   870  	schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr, addr.Mode, addr.Type)
   871  	if err != nil {
   872  		// We have plently other codepaths that will detect and report
   873  		// schema lookup errors before we'd reach this point, so we'll just
   874  		// treat a failure here the same as having no schema.
   875  		return nil
   876  	}
   877  	return schema
   878  }
   879  
   880  func (d *evaluationStateData) GetDurgaformAttr(addr addrs.DurgaformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
   881  	var diags tfdiags.Diagnostics
   882  	switch addr.Name {
   883  
   884  	case "workspace":
   885  		workspaceName := d.Evaluator.Meta.Env
   886  		return cty.StringVal(workspaceName), diags
   887  
   888  	case "env":
   889  		// Prior to Durgaform 0.12 there was an attribute "env", which was
   890  		// an alias name for "workspace". This was deprecated and is now
   891  		// removed.
   892  		diags = diags.Append(&hcl.Diagnostic{
   893  			Severity: hcl.DiagError,
   894  			Summary:  `Invalid "durgaform" attribute`,
   895  			Detail:   `The durgaform.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.`,
   896  			Subject:  rng.ToHCL().Ptr(),
   897  		})
   898  		return cty.DynamicVal, diags
   899  
   900  	default:
   901  		diags = diags.Append(&hcl.Diagnostic{
   902  			Severity: hcl.DiagError,
   903  			Summary:  `Invalid "durgaform" attribute`,
   904  			Detail:   fmt.Sprintf(`The "durgaform" object does not have an attribute named %q. The only supported attribute is terraform.workspace, the name of the currently-selected workspace.`, addr.Name),
   905  			Subject:  rng.ToHCL().Ptr(),
   906  		})
   907  		return cty.DynamicVal, diags
   908  	}
   909  }
   910  
   911  // nameSuggestion tries to find a name from the given slice of suggested names
   912  // that is close to the given name and returns it if found. If no suggestion
   913  // is close enough, returns the empty string.
   914  //
   915  // The suggestions are tried in order, so earlier suggestions take precedence
   916  // if the given string is similar to two or more suggestions.
   917  //
   918  // This function is intended to be used with a relatively-small number of
   919  // suggestions. It's not optimized for hundreds or thousands of them.
   920  func nameSuggestion(given string, suggestions []string) string {
   921  	for _, suggestion := range suggestions {
   922  		dist := levenshtein.Distance(given, suggestion, nil)
   923  		if dist < 3 { // threshold determined experimentally
   924  			return suggestion
   925  		}
   926  	}
   927  	return ""
   928  }
   929  
   930  // moduleDisplayAddr returns a string describing the given module instance
   931  // address that is appropriate for returning to users in situations where the
   932  // root module is possible. Specifically, it returns "the root module" if the
   933  // root module instance is given, or a string representation of the module
   934  // address otherwise.
   935  func moduleDisplayAddr(addr addrs.ModuleInstance) string {
   936  	switch {
   937  	case addr.IsRoot():
   938  		return "the root module"
   939  	default:
   940  		return addr.String()
   941  	}
   942  }