11 "github.com/agext/levenshtein"
12 "github.com/hashicorp/hcl2/hcl"
13 "github.com/zclconf/go-cty/cty"
14 "github.com/zclconf/go-cty/cty/convert"
16 "github.com/hashicorp/terraform/addrs"
17 "github.com/hashicorp/terraform/configs"
18 "github.com/hashicorp/terraform/configs/configschema"
19 "github.com/hashicorp/terraform/lang"
20 "github.com/hashicorp/terraform/plans"
21 "github.com/hashicorp/terraform/states"
22 "github.com/hashicorp/terraform/tfdiags"
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
30 Operation walkOperation
32 // Meta is contextual metadata about the current operation.
35 // Config is the root node in the configuration tree.
36 Config *configs.Config
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.
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
48 // Schemas is a repository of all of the schemas we should need to
49 // evaluate expressions. This must be constructed by the caller to
50 // include schemas for all of the providers, resource types, data sources
51 // and provisioners used by the given configuration and state.
53 // This must not be mutated during evaluation.
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
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
65 // Scope creates an evaluation scope for the given module path and optional
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
71 func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope {
75 PureOnly: e.Operation != walkApply && e.Operation != walkDestroy,
76 BaseDir: ".", // Always current working directory for now.
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 {
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
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
94 InstanceKeyData InstanceKeyEvalData
96 // Operation records the type of walk the evaluationStateData is being used
98 Operation walkOperation
101 // InstanceKeyEvalData is used during evaluation to specify which values,
102 // if any, should be produced for count.index, each.key, and each.value.
103 type InstanceKeyEvalData struct {
104 // CountIndex is the value for count.index, or cty.NilVal if evaluating
105 // in a context where the "count" argument is not active.
107 // For correct operation, this should always be of type cty.Number if not
111 // EachKey and EachValue are the values for each.key and each.value
112 // respectively, or cty.NilVal if evaluating in a context where the
113 // "for_each" argument is not active. These must either both be set
116 // For correct operation, EachKey must always be either of type cty.String
117 // or cty.Number if not nil.
118 EachKey, EachValue cty.Value
121 // EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for
122 // evaluating in a context that has the given instance key.
123 func EvalDataForInstanceKey(key addrs.InstanceKey) InstanceKeyEvalData {
124 // At the moment we don't actually implement for_each, so we only
125 // ever populate CountIndex.
126 // (When we implement for_each later we may need to reorganize this some,
127 // so that we can resolve the ambiguity that an int key may either be
128 // a count.index or an each.key where for_each is over a list.)
130 var countIdx cty.Value
131 if intKey, ok := key.(addrs.IntKey); ok {
132 countIdx = cty.NumberIntVal(int64(intKey))
135 return InstanceKeyEvalData{
136 CountIndex: countIdx,
140 // EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance
141 // key values at all, suitable for use in contexts where no keyed instance
143 var EvalDataForNoInstanceKey = InstanceKeyEvalData{}
145 // evaluationStateData must implement lang.Data
146 var _ lang.Data = (*evaluationStateData)(nil)
148 func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
149 var diags tfdiags.Diagnostics
153 idxVal := d.InstanceKeyData.CountIndex
154 if idxVal == cty.NilVal {
155 diags = diags.Append(&hcl.Diagnostic{
156 Severity: hcl.DiagError,
157 Summary: `Reference to "count" in non-counted context`,
158 Detail: fmt.Sprintf(`The "count" object can be used only in "resource" and "data" blocks, and only when the "count" argument is set.`),
159 Subject: rng.ToHCL().Ptr(),
161 return cty.UnknownVal(cty.Number), diags
166 diags = diags.Append(&hcl.Diagnostic{
167 Severity: hcl.DiagError,
168 Summary: `Invalid "count" attribute`,
169 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),
170 Subject: rng.ToHCL().Ptr(),
172 return cty.DynamicVal, diags
176 func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
177 var diags tfdiags.Diagnostics
179 // First we'll make sure the requested value is declared in configuration,
180 // so we can produce a nice message if not.
181 moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
182 if moduleConfig == nil {
183 // should never happen, since we can't be evaluating in a module
184 // that wasn't mentioned in configuration.
185 panic(fmt.Sprintf("input variable read from %s, which has no configuration", d.ModulePath))
188 config := moduleConfig.Module.Variables[addr.Name]
190 var suggestions []string
191 for k := range moduleConfig.Module.Variables {
192 suggestions = append(suggestions, k)
194 suggestion := nameSuggestion(addr.Name, suggestions)
195 if suggestion != "" {
196 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
198 suggestion = fmt.Sprintf(" This variable can be declared with a variable %q {} block.", addr.Name)
201 diags = diags.Append(&hcl.Diagnostic{
202 Severity: hcl.DiagError,
203 Summary: `Reference to undeclared input variable`,
204 Detail: fmt.Sprintf(`An input variable with the name %q has not been declared.%s`, addr.Name, suggestion),
205 Subject: rng.ToHCL().Ptr(),
207 return cty.DynamicVal, diags
210 wantType := cty.DynamicPseudoType
211 if config.Type != cty.NilType {
212 wantType = config.Type
215 d.Evaluator.VariableValuesLock.Lock()
216 defer d.Evaluator.VariableValuesLock.Unlock()
218 // During the validate walk, input variables are always unknown so
219 // that we are validating the configuration for all possible input values
220 // rather than for a specific set. Checking against a specific set of
221 // input values then happens during the plan walk.
223 // This is important because otherwise the validation walk will tend to be
224 // overly strict, requiring expressions throughout the configuration to
225 // be complicated to accommodate all possible inputs, whereas returning
226 // known here allows for simpler patterns like using input values as
227 // guards to broadly enable/disable resources, avoid processing things
228 // that are disabled, etc. Terraform's static validation leans towards
229 // being liberal in what it accepts because the subsequent plan walk has
230 // more information available and so can be more conservative.
231 if d.Operation == walkValidate {
232 return cty.UnknownVal(wantType), diags
235 moduleAddrStr := d.ModulePath.String()
236 vals := d.Evaluator.VariableValues[moduleAddrStr]
238 return cty.UnknownVal(wantType), diags
241 val, isSet := vals[addr.Name]
243 if config.Default != cty.NilVal {
244 return config.Default, diags
246 return cty.UnknownVal(wantType), diags
250 val, err = convert.Convert(val, wantType)
252 // We should never get here because this problem should've been caught
253 // during earlier validation, but we'll do something reasonable anyway.
254 diags = diags.Append(&hcl.Diagnostic{
255 Severity: hcl.DiagError,
256 Summary: `Incorrect variable type`,
257 Detail: fmt.Sprintf(`The resolved value of variable %q is not appropriate: %s.`, addr.Name, err),
258 Subject: &config.DeclRange,
260 // Stub out our return value so that the semantic checker doesn't
261 // produce redundant downstream errors.
262 val = cty.UnknownVal(wantType)
268 func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
269 var diags tfdiags.Diagnostics
271 // First we'll make sure the requested value is declared in configuration,
272 // so we can produce a nice message if not.
273 moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
274 if moduleConfig == nil {
275 // should never happen, since we can't be evaluating in a module
276 // that wasn't mentioned in configuration.
277 panic(fmt.Sprintf("local value read from %s, which has no configuration", d.ModulePath))
280 config := moduleConfig.Module.Locals[addr.Name]
282 var suggestions []string
283 for k := range moduleConfig.Module.Locals {
284 suggestions = append(suggestions, k)
286 suggestion := nameSuggestion(addr.Name, suggestions)
287 if suggestion != "" {
288 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
291 diags = diags.Append(&hcl.Diagnostic{
292 Severity: hcl.DiagError,
293 Summary: `Reference to undeclared local value`,
294 Detail: fmt.Sprintf(`A local value with the name %q has not been declared.%s`, addr.Name, suggestion),
295 Subject: rng.ToHCL().Ptr(),
297 return cty.DynamicVal, diags
300 val := d.Evaluator.State.LocalValue(addr.Absolute(d.ModulePath))
301 if val == cty.NilVal {
302 // Not evaluated yet?
309 func (d *evaluationStateData) GetModuleInstance(addr addrs.ModuleCallInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
310 var diags tfdiags.Diagnostics
312 // Output results live in the module that declares them, which is one of
313 // the child module instances of our current module path.
314 moduleAddr := addr.ModuleInstance(d.ModulePath)
316 // We'll consult the configuration to see what output names we are
317 // expecting, so we can ensure the resulting object is of the expected
318 // type even if our data is incomplete for some reason.
319 moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
320 if moduleConfig == nil {
321 // should never happen, since this should've been caught during
322 // static validation.
323 panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr))
325 outputConfigs := moduleConfig.Module.Outputs
327 vals := map[string]cty.Value{}
328 for n := range outputConfigs {
329 addr := addrs.OutputValue{Name: n}.Absolute(moduleAddr)
331 // If a pending change is present in our current changeset then its value
332 // takes priority over what's in state. (It will usually be the same but
333 // will differ if the new value is unknown during planning.)
334 if changeSrc := d.Evaluator.Changes.GetOutputChange(addr); changeSrc != nil {
335 change, err := changeSrc.Decode()
337 // This should happen only if someone has tampered with a plan
338 // file, so we won't bother with a pretty error for it.
339 diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", addr, err))
340 vals[n] = cty.DynamicVal
343 // We care only about the "after" value, which is the value this output
344 // will take on after the plan is applied.
345 vals[n] = change.After
347 os := d.Evaluator.State.OutputValue(addr)
349 // Not evaluated yet?
350 vals[n] = cty.DynamicVal
356 return cty.ObjectVal(vals), diags
359 func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.ModuleCallOutput, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
360 var diags tfdiags.Diagnostics
362 // Output results live in the module that declares them, which is one of
363 // the child module instances of our current module path.
364 absAddr := addr.AbsOutputValue(d.ModulePath)
365 moduleAddr := absAddr.Module
367 // First we'll consult the configuration to see if an output of this
368 // name is declared at all.
369 moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
370 if moduleConfig == nil {
371 // this doesn't happen in normal circumstances due to our validation
372 // pass, but it can turn up in some unusual situations, like in the
373 // "terraform console" repl where arbitrary expressions can be
375 diags = diags.Append(&hcl.Diagnostic{
376 Severity: hcl.DiagError,
377 Summary: `Reference to undeclared module`,
378 Detail: fmt.Sprintf(`The configuration contains no %s.`, moduleAddr),
379 Subject: rng.ToHCL().Ptr(),
381 return cty.DynamicVal, diags
384 config := moduleConfig.Module.Outputs[addr.Name]
386 var suggestions []string
387 for k := range moduleConfig.Module.Outputs {
388 suggestions = append(suggestions, k)
390 suggestion := nameSuggestion(addr.Name, suggestions)
391 if suggestion != "" {
392 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
395 diags = diags.Append(&hcl.Diagnostic{
396 Severity: hcl.DiagError,
397 Summary: `Reference to undeclared output value`,
398 Detail: fmt.Sprintf(`An output value with the name %q has not been declared in %s.%s`, addr.Name, moduleDisplayAddr(moduleAddr), suggestion),
399 Subject: rng.ToHCL().Ptr(),
401 return cty.DynamicVal, diags
404 // If a pending change is present in our current changeset then its value
405 // takes priority over what's in state. (It will usually be the same but
406 // will differ if the new value is unknown during planning.)
407 if changeSrc := d.Evaluator.Changes.GetOutputChange(absAddr); changeSrc != nil {
408 change, err := changeSrc.Decode()
410 // This should happen only if someone has tampered with a plan
411 // file, so we won't bother with a pretty error for it.
412 diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", absAddr, err))
413 return cty.DynamicVal, diags
415 // We care only about the "after" value, which is the value this output
416 // will take on after the plan is applied.
417 return change.After, diags
420 os := d.Evaluator.State.OutputValue(absAddr)
422 // Not evaluated yet?
423 return cty.DynamicVal, diags
426 return os.Value, diags
429 func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
430 var diags tfdiags.Diagnostics
434 wd, err := os.Getwd()
436 diags = diags.Append(&hcl.Diagnostic{
437 Severity: hcl.DiagError,
438 Summary: `Failed to get working directory`,
439 Detail: fmt.Sprintf(`The value for path.cwd cannot be determined due to a system error: %s`, err),
440 Subject: rng.ToHCL().Ptr(),
442 return cty.DynamicVal, diags
444 return cty.StringVal(filepath.ToSlash(wd)), diags
447 moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
448 if moduleConfig == nil {
449 // should never happen, since we can't be evaluating in a module
450 // that wasn't mentioned in configuration.
451 panic(fmt.Sprintf("module.path read from module %s, which has no configuration", d.ModulePath))
453 sourceDir := moduleConfig.Module.SourceDir
454 return cty.StringVal(filepath.ToSlash(sourceDir)), diags
457 sourceDir := d.Evaluator.Config.Module.SourceDir
458 return cty.StringVal(filepath.ToSlash(sourceDir)), diags
461 suggestion := nameSuggestion(addr.Name, []string{"cwd", "module", "root"})
462 if suggestion != "" {
463 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
465 diags = diags.Append(&hcl.Diagnostic{
466 Severity: hcl.DiagError,
467 Summary: `Invalid "path" attribute`,
468 Detail: fmt.Sprintf(`The "path" object does not have an attribute named %q.%s`, addr.Name, suggestion),
469 Subject: rng.ToHCL().Ptr(),
471 return cty.DynamicVal, diags
475 func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
476 var diags tfdiags.Diagnostics
478 // Although we are giving a ResourceInstance address here, if it has
479 // a key of addrs.NoKey then it might actually be a request for all of
480 // the instances of a particular resource. The reference resolver can't
481 // resolve the ambiguity itself, so we must do it in here.
483 // First we'll consult the configuration to see if an resource of this
484 // name is declared at all.
485 moduleAddr := d.ModulePath
486 moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
487 if moduleConfig == nil {
488 // should never happen, since we can't be evaluating in a module
489 // that wasn't mentioned in configuration.
490 panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr))
493 config := moduleConfig.Module.ResourceByAddr(addr.ContainingResource())
495 diags = diags.Append(&hcl.Diagnostic{
496 Severity: hcl.DiagError,
497 Summary: `Reference to undeclared resource`,
498 Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Resource.Type, addr.Resource.Name, moduleDisplayAddr(moduleAddr)),
499 Subject: rng.ToHCL().Ptr(),
501 return cty.DynamicVal, diags
504 // First we'll find the state for the resource as a whole, and decide
505 // from there whether we're going to interpret the given address as a
506 // resource or a resource instance address.
507 rs := d.Evaluator.State.Resource(addr.ContainingResource().Absolute(d.ModulePath))
510 schema := d.getResourceSchema(addr.ContainingResource(), config.ProviderConfigAddr().Absolute(d.ModulePath))
512 // If it doesn't exist at all then we can't reliably determine whether
513 // single-instance or whole-resource interpretation was intended, but
514 // we can decide this partially...
515 if addr.Key != addrs.NoKey {
516 // If there's an instance key then the user must be intending
517 // single-instance interpretation, and so we can return a
518 // properly-typed unknown value to help with type checking.
519 return cty.UnknownVal(schema.ImpliedType()), diags
522 // otherwise we must return DynamicVal so that both interpretations
523 // can proceed without generating errors, and we'll deal with this
524 // in a later step where more information is gathered.
525 // (In practice we should only end up here during the validate walk,
526 // since later walks should have at least partial states populated
527 // for all resources in the configuration.)
528 return cty.DynamicVal, diags
531 // Break out early during validation, because resource may not be expanded
532 // yet and indexed references may show up as invalid.
533 if d.Operation == walkValidate {
534 return cty.DynamicVal, diags
537 schema := d.getResourceSchema(addr.ContainingResource(), rs.ProviderConfig)
539 // If we are able to automatically convert to the "right" type of instance
540 // key for this each mode then we'll do so, to match with how we generally
541 // treat values elsewhere in the language. This allows code below to
542 // assume that any possible conversions have already been dealt with and
543 // just worry about validation.
544 key := d.coerceInstanceKey(addr.Key, rs.EachMode)
550 if key != addrs.NoKey {
551 diags = diags.Append(&hcl.Diagnostic{
552 Severity: hcl.DiagError,
553 Summary: "Invalid resource index",
554 Detail: fmt.Sprintf("Resource %s does not have either \"count\" or \"for_each\" set, so it cannot be indexed.", addr.ContainingResource()),
555 Subject: rng.ToHCL().Ptr(),
557 return cty.DynamicVal, diags
559 case states.EachList:
560 multi = key == addrs.NoKey
561 if _, ok := addr.Key.(addrs.IntKey); !multi && !ok {
562 diags = diags.Append(&hcl.Diagnostic{
563 Severity: hcl.DiagError,
564 Summary: "Invalid resource index",
565 Detail: fmt.Sprintf("Resource %s must be indexed with a number value.", addr.ContainingResource()),
566 Subject: rng.ToHCL().Ptr(),
568 return cty.DynamicVal, diags
571 multi = key == addrs.NoKey
572 if _, ok := addr.Key.(addrs.IntKey); !multi && !ok {
573 diags = diags.Append(&hcl.Diagnostic{
574 Severity: hcl.DiagError,
575 Summary: "Invalid resource index",
576 Detail: fmt.Sprintf("Resource %s must be indexed with a string value.", addr.ContainingResource()),
577 Subject: rng.ToHCL().Ptr(),
579 return cty.DynamicVal, diags
584 log.Printf("[TRACE] GetResourceInstance: %s is a single instance", addr)
585 is := rs.Instance(key)
587 return cty.UnknownVal(schema.ImpliedType()), diags
589 return d.getResourceInstanceSingle(addr, rng, is, config, rs.ProviderConfig)
592 log.Printf("[TRACE] GetResourceInstance: %s has multiple keyed instances", addr)
593 return d.getResourceInstancesAll(addr.ContainingResource(), rng, config, rs, rs.ProviderConfig)
596 func (d *evaluationStateData) getResourceInstanceSingle(addr addrs.ResourceInstance, rng tfdiags.SourceRange, is *states.ResourceInstance, config *configs.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
597 var diags tfdiags.Diagnostics
599 schema := d.getResourceSchema(addr.ContainingResource(), providerAddr)
601 // This shouldn't happen, since validation before we get here should've
602 // taken care of it, but we'll show a reasonable error message anyway.
603 diags = diags.Append(&hcl.Diagnostic{
604 Severity: hcl.DiagError,
605 Summary: `Missing resource type schema`,
606 Detail: fmt.Sprintf("No schema is available for %s in %s. This is a bug in Terraform and should be reported.", addr, providerAddr),
607 Subject: rng.ToHCL().Ptr(),
609 return cty.DynamicVal, diags
612 ty := schema.ImpliedType()
613 if is == nil || is.Current == nil {
614 // Assume we're dealing with an instance that hasn't been created yet.
615 return cty.UnknownVal(ty), diags
618 if is.Current.Status == states.ObjectPlanned {
619 // If there's a pending change for this instance in our plan, we'll prefer
620 // that. This is important because the state can't represent unknown values
621 // and so its data is inaccurate when changes are pending.
622 if change := d.Evaluator.Changes.GetResourceInstanceChange(addr.Absolute(d.ModulePath), states.CurrentGen); change != nil {
623 val, err := change.After.Decode(ty)
625 diags = diags.Append(&hcl.Diagnostic{
626 Severity: hcl.DiagError,
627 Summary: "Invalid resource instance data in plan",
628 Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", addr.Absolute(d.ModulePath), err),
629 Subject: &config.DeclRange,
631 return cty.UnknownVal(ty), diags
635 // If the object is in planned status then we should not
636 // get here, since we should've found a pending value
637 // in the plan above instead.
638 diags = diags.Append(&hcl.Diagnostic{
639 Severity: hcl.DiagError,
640 Summary: "Missing pending object in plan",
641 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),
642 Subject: &config.DeclRange,
644 return cty.UnknownVal(ty), diags
648 ios, err := is.Current.Decode(ty)
650 // This shouldn't happen, since by the time we get here
651 // we should've upgraded the state data already.
652 diags = diags.Append(&hcl.Diagnostic{
653 Severity: hcl.DiagError,
654 Summary: "Invalid resource instance data in state",
655 Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", addr.Absolute(d.ModulePath), err),
656 Subject: &config.DeclRange,
658 return cty.UnknownVal(ty), diags
661 return ios.Value, diags
664 func (d *evaluationStateData) getResourceInstancesAll(addr addrs.Resource, rng tfdiags.SourceRange, config *configs.Resource, rs *states.Resource, providerAddr addrs.AbsProviderConfig) (cty.Value, tfdiags.Diagnostics) {
665 var diags tfdiags.Diagnostics
667 schema := d.getResourceSchema(addr, providerAddr)
669 // This shouldn't happen, since validation before we get here should've
670 // taken care of it, but we'll show a reasonable error message anyway.
671 diags = diags.Append(&hcl.Diagnostic{
672 Severity: hcl.DiagError,
673 Summary: `Missing resource type schema`,
674 Detail: fmt.Sprintf("No schema is available for %s in %s. This is a bug in Terraform and should be reported.", addr, providerAddr),
675 Subject: rng.ToHCL().Ptr(),
677 return cty.DynamicVal, diags
682 case states.EachList:
683 // We need to infer the length of our resulting tuple by searching
684 // for the max IntKey in our instances map.
686 for k := range rs.Instances {
687 if ik, ok := k.(addrs.IntKey); ok {
688 if int(ik) >= length {
694 vals := make([]cty.Value, length)
695 for i := 0; i < length; i++ {
696 ty := schema.ImpliedType()
697 key := addrs.IntKey(i)
698 is, exists := rs.Instances[key]
700 instAddr := addr.Instance(key).Absolute(d.ModulePath)
702 // Prefer pending value in plan if present. See getResourceInstanceSingle
703 // comment for the rationale.
704 if is.Current.Status == states.ObjectPlanned {
705 if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen); change != nil {
706 val, err := change.After.Decode(ty)
708 diags = diags.Append(&hcl.Diagnostic{
709 Severity: hcl.DiagError,
710 Summary: "Invalid resource instance data in plan",
711 Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err),
712 Subject: &config.DeclRange,
719 // If the object is in planned status then we should not
720 // get here, since we should've found a pending value
721 // in the plan above instead.
722 diags = diags.Append(&hcl.Diagnostic{
723 Severity: hcl.DiagError,
724 Summary: "Missing pending object in plan",
725 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),
726 Subject: &config.DeclRange,
732 ios, err := is.Current.Decode(ty)
734 // This shouldn't happen, since by the time we get here
735 // we should've upgraded the state data already.
736 diags = diags.Append(&hcl.Diagnostic{
737 Severity: hcl.DiagError,
738 Summary: "Invalid resource instance data in state",
739 Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err),
740 Subject: &config.DeclRange,
746 // There shouldn't normally be "gaps" in our list but we'll
747 // allow it under the assumption that we're in a weird situation
748 // where e.g. someone has run "terraform state mv" to reorder
749 // a list and left a hole behind.
750 vals[i] = cty.UnknownVal(schema.ImpliedType())
754 // We use a tuple rather than a list here because resource schemas may
755 // include dynamically-typed attributes, which will then cause each
756 // instance to potentially have a different runtime type even though
757 // they all conform to the static schema.
758 return cty.TupleVal(vals), diags
761 ty := schema.ImpliedType()
762 vals := make(map[string]cty.Value, len(rs.Instances))
763 for k, is := range rs.Instances {
764 if sk, ok := k.(addrs.StringKey); ok {
765 instAddr := addr.Instance(k).Absolute(d.ModulePath)
767 // Prefer pending value in plan if present. See getResourceInstanceSingle
768 // comment for the rationale.
769 // Prefer pending value in plan if present. See getResourceInstanceSingle
770 // comment for the rationale.
771 if is.Current.Status == states.ObjectPlanned {
772 if change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen); change != nil {
773 val, err := change.After.Decode(ty)
775 diags = diags.Append(&hcl.Diagnostic{
776 Severity: hcl.DiagError,
777 Summary: "Invalid resource instance data in plan",
778 Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err),
779 Subject: &config.DeclRange,
783 vals[string(sk)] = val
786 // If the object is in planned status then we should not
787 // get here, since we should've found a pending value
788 // in the plan above instead.
789 diags = diags.Append(&hcl.Diagnostic{
790 Severity: hcl.DiagError,
791 Summary: "Missing pending object in plan",
792 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),
793 Subject: &config.DeclRange,
799 ios, err := is.Current.Decode(ty)
801 // This shouldn't happen, since by the time we get here
802 // we should've upgraded the state data already.
803 diags = diags.Append(&hcl.Diagnostic{
804 Severity: hcl.DiagError,
805 Summary: "Invalid resource instance data in state",
806 Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err),
807 Subject: &config.DeclRange,
811 vals[string(sk)] = ios.Value
815 // We use an object rather than a map here because resource schemas may
816 // include dynamically-typed attributes, which will then cause each
817 // instance to potentially have a different runtime type even though
818 // they all conform to the static schema.
819 return cty.ObjectVal(vals), diags
822 // Should never happen since caller should deal with other modes
823 panic(fmt.Sprintf("unsupported EachMode %s", rs.EachMode))
827 func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.AbsProviderConfig) *configschema.Block {
828 providerType := providerAddr.ProviderConfig.Type
829 schemas := d.Evaluator.Schemas
830 schema, _ := schemas.ResourceTypeConfig(providerType, addr.Mode, addr.Type)
834 // coerceInstanceKey attempts to convert the given key to the type expected
835 // for the given EachMode.
837 // If the key is already of the correct type or if it cannot be converted then
838 // it is returned verbatim. If conversion is required and possible, the
839 // converted value is returned. Callers should not try to determine if
840 // conversion was possible, should instead just check if the result is of
841 // the expected type.
842 func (d *evaluationStateData) coerceInstanceKey(key addrs.InstanceKey, mode states.EachMode) addrs.InstanceKey {
843 if key == addrs.NoKey {
844 // An absent key can't be converted
850 // No conversions possible at all
853 if intKey, isInt := key.(addrs.IntKey); isInt {
854 return addrs.StringKey(strconv.Itoa(int(intKey)))
857 case states.EachList:
858 if strKey, isStr := key.(addrs.StringKey); isStr {
859 i, err := strconv.Atoi(string(strKey))
863 return addrs.IntKey(i)
871 func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
872 var diags tfdiags.Diagnostics
876 workspaceName := d.Evaluator.Meta.Env
877 return cty.StringVal(workspaceName), diags
880 // Prior to Terraform 0.12 there was an attribute "env", which was
881 // an alias name for "workspace". This was deprecated and is now
883 diags = diags.Append(&hcl.Diagnostic{
884 Severity: hcl.DiagError,
885 Summary: `Invalid "terraform" attribute`,
886 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.`,
887 Subject: rng.ToHCL().Ptr(),
889 return cty.DynamicVal, diags
892 diags = diags.Append(&hcl.Diagnostic{
893 Severity: hcl.DiagError,
894 Summary: `Invalid "terraform" attribute`,
895 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),
896 Subject: rng.ToHCL().Ptr(),
898 return cty.DynamicVal, diags
902 // nameSuggestion tries to find a name from the given slice of suggested names
903 // that is close to the given name and returns it if found. If no suggestion
904 // is close enough, returns the empty string.
906 // The suggestions are tried in order, so earlier suggestions take precedence
907 // if the given string is similar to two or more suggestions.
909 // This function is intended to be used with a relatively-small number of
910 // suggestions. It's not optimized for hundreds or thousands of them.
911 func nameSuggestion(given string, suggestions []string) string {
912 for _, suggestion := range suggestions {
913 dist := levenshtein.Distance(given, suggestion, nil)
914 if dist < 3 { // threshold determined experimentally
921 // moduleDisplayAddr returns a string describing the given module instance
922 // address that is appropriate for returning to users in situations where the
923 // root module is possible. Specifically, it returns "the root module" if the
924 // root module instance is given, or a string representation of the module
925 // address otherwise.
926 func moduleDisplayAddr(addr addrs.ModuleInstance) string {
929 return "the root module"