]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/evaluate.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / evaluate.go
1 package terraform
2
3 import (
4 "fmt"
5 "log"
6 "os"
7 "path/filepath"
8 "strconv"
9 "sync"
10
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"
15
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"
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 // 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.
52 //
53 // This must not be mutated during evaluation.
54 Schemas *Schemas
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,
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 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.
106 //
107 // For correct operation, this should always be of type cty.Number if not
108 // nil.
109 CountIndex cty.Value
110
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
114 // or neither set.
115 //
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
119 }
120
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.)
129
130 var countIdx cty.Value
131 if intKey, ok := key.(addrs.IntKey); ok {
132 countIdx = cty.NumberIntVal(int64(intKey))
133 }
134
135 return InstanceKeyEvalData{
136 CountIndex: countIdx,
137 }
138 }
139
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
142 // is relevant.
143 var EvalDataForNoInstanceKey = InstanceKeyEvalData{}
144
145 // evaluationStateData must implement lang.Data
146 var _ lang.Data = (*evaluationStateData)(nil)
147
148 func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
149 var diags tfdiags.Diagnostics
150 switch addr.Name {
151
152 case "index":
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(),
160 })
161 return cty.UnknownVal(cty.Number), diags
162 }
163 return idxVal, diags
164
165 default:
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(),
171 })
172 return cty.DynamicVal, diags
173 }
174 }
175
176 func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
177 var diags tfdiags.Diagnostics
178
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))
186 }
187
188 config := moduleConfig.Module.Variables[addr.Name]
189 if config == nil {
190 var suggestions []string
191 for k := range moduleConfig.Module.Variables {
192 suggestions = append(suggestions, k)
193 }
194 suggestion := nameSuggestion(addr.Name, suggestions)
195 if suggestion != "" {
196 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
197 } else {
198 suggestion = fmt.Sprintf(" This variable can be declared with a variable %q {} block.", addr.Name)
199 }
200
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(),
206 })
207 return cty.DynamicVal, diags
208 }
209
210 wantType := cty.DynamicPseudoType
211 if config.Type != cty.NilType {
212 wantType = config.Type
213 }
214
215 d.Evaluator.VariableValuesLock.Lock()
216 defer d.Evaluator.VariableValuesLock.Unlock()
217
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.
222 //
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
233 }
234
235 moduleAddrStr := d.ModulePath.String()
236 vals := d.Evaluator.VariableValues[moduleAddrStr]
237 if vals == nil {
238 return cty.UnknownVal(wantType), diags
239 }
240
241 val, isSet := vals[addr.Name]
242 if !isSet {
243 if config.Default != cty.NilVal {
244 return config.Default, diags
245 }
246 return cty.UnknownVal(wantType), diags
247 }
248
249 var err error
250 val, err = convert.Convert(val, wantType)
251 if err != nil {
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,
259 })
260 // Stub out our return value so that the semantic checker doesn't
261 // produce redundant downstream errors.
262 val = cty.UnknownVal(wantType)
263 }
264
265 return val, diags
266 }
267
268 func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
269 var diags tfdiags.Diagnostics
270
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))
278 }
279
280 config := moduleConfig.Module.Locals[addr.Name]
281 if config == nil {
282 var suggestions []string
283 for k := range moduleConfig.Module.Locals {
284 suggestions = append(suggestions, k)
285 }
286 suggestion := nameSuggestion(addr.Name, suggestions)
287 if suggestion != "" {
288 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
289 }
290
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(),
296 })
297 return cty.DynamicVal, diags
298 }
299
300 val := d.Evaluator.State.LocalValue(addr.Absolute(d.ModulePath))
301 if val == cty.NilVal {
302 // Not evaluated yet?
303 val = cty.DynamicVal
304 }
305
306 return val, diags
307 }
308
309 func (d *evaluationStateData) GetModuleInstance(addr addrs.ModuleCallInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
310 var diags tfdiags.Diagnostics
311
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)
315
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))
324 }
325 outputConfigs := moduleConfig.Module.Outputs
326
327 vals := map[string]cty.Value{}
328 for n := range outputConfigs {
329 addr := addrs.OutputValue{Name: n}.Absolute(moduleAddr)
330
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()
336 if err != nil {
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
341 continue
342 }
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
346 } else {
347 os := d.Evaluator.State.OutputValue(addr)
348 if os == nil {
349 // Not evaluated yet?
350 vals[n] = cty.DynamicVal
351 continue
352 }
353 vals[n] = os.Value
354 }
355 }
356 return cty.ObjectVal(vals), diags
357 }
358
359 func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.ModuleCallOutput, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
360 var diags tfdiags.Diagnostics
361
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
366
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
374 // evaluated.
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(),
380 })
381 return cty.DynamicVal, diags
382 }
383
384 config := moduleConfig.Module.Outputs[addr.Name]
385 if config == nil {
386 var suggestions []string
387 for k := range moduleConfig.Module.Outputs {
388 suggestions = append(suggestions, k)
389 }
390 suggestion := nameSuggestion(addr.Name, suggestions)
391 if suggestion != "" {
392 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
393 }
394
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(),
400 })
401 return cty.DynamicVal, diags
402 }
403
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()
409 if err != nil {
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
414 }
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
418 }
419
420 os := d.Evaluator.State.OutputValue(absAddr)
421 if os == nil {
422 // Not evaluated yet?
423 return cty.DynamicVal, diags
424 }
425
426 return os.Value, diags
427 }
428
429 func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
430 var diags tfdiags.Diagnostics
431 switch addr.Name {
432
433 case "cwd":
434 wd, err := os.Getwd()
435 if err != nil {
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(),
441 })
442 return cty.DynamicVal, diags
443 }
444 return cty.StringVal(filepath.ToSlash(wd)), diags
445
446 case "module":
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))
452 }
453 sourceDir := moduleConfig.Module.SourceDir
454 return cty.StringVal(filepath.ToSlash(sourceDir)), diags
455
456 case "root":
457 sourceDir := d.Evaluator.Config.Module.SourceDir
458 return cty.StringVal(filepath.ToSlash(sourceDir)), diags
459
460 default:
461 suggestion := nameSuggestion(addr.Name, []string{"cwd", "module", "root"})
462 if suggestion != "" {
463 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
464 }
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(),
470 })
471 return cty.DynamicVal, diags
472 }
473 }
474
475 func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
476 var diags tfdiags.Diagnostics
477
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.
482
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))
491 }
492
493 config := moduleConfig.Module.ResourceByAddr(addr.ContainingResource())
494 if config == nil {
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(),
500 })
501 return cty.DynamicVal, diags
502 }
503
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))
508
509 if rs == nil {
510 schema := d.getResourceSchema(addr.ContainingResource(), config.ProviderConfigAddr().Absolute(d.ModulePath))
511
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
520 }
521
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
529 }
530
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
535 }
536
537 schema := d.getResourceSchema(addr.ContainingResource(), rs.ProviderConfig)
538
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)
545
546 multi := false
547
548 switch rs.EachMode {
549 case states.NoEach:
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(),
556 })
557 return cty.DynamicVal, diags
558 }
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(),
567 })
568 return cty.DynamicVal, diags
569 }
570 case states.EachMap:
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(),
578 })
579 return cty.DynamicVal, diags
580 }
581 }
582
583 if !multi {
584 log.Printf("[TRACE] GetResourceInstance: %s is a single instance", addr)
585 is := rs.Instance(key)
586 if is == nil {
587 return cty.UnknownVal(schema.ImpliedType()), diags
588 }
589 return d.getResourceInstanceSingle(addr, rng, is, config, rs.ProviderConfig)
590 }
591
592 log.Printf("[TRACE] GetResourceInstance: %s has multiple keyed instances", addr)
593 return d.getResourceInstancesAll(addr.ContainingResource(), rng, config, rs, rs.ProviderConfig)
594 }
595
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
598
599 schema := d.getResourceSchema(addr.ContainingResource(), providerAddr)
600 if schema == nil {
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(),
608 })
609 return cty.DynamicVal, diags
610 }
611
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
616 }
617
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)
624 if err != nil {
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,
630 })
631 return cty.UnknownVal(ty), diags
632 }
633 return val, diags
634 } else {
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,
643 })
644 return cty.UnknownVal(ty), diags
645 }
646 }
647
648 ios, err := is.Current.Decode(ty)
649 if err != nil {
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,
657 })
658 return cty.UnknownVal(ty), diags
659 }
660
661 return ios.Value, diags
662 }
663
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
666
667 schema := d.getResourceSchema(addr, providerAddr)
668 if schema == nil {
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(),
676 })
677 return cty.DynamicVal, diags
678 }
679
680 switch rs.EachMode {
681
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.
685 length := 0
686 for k := range rs.Instances {
687 if ik, ok := k.(addrs.IntKey); ok {
688 if int(ik) >= length {
689 length = int(ik) + 1
690 }
691 }
692 }
693
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]
699 if exists {
700 instAddr := addr.Instance(key).Absolute(d.ModulePath)
701
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)
707 if err != nil {
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,
713 })
714 continue
715 }
716 vals[i] = val
717 continue
718 } else {
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,
727 })
728 continue
729 }
730 }
731
732 ios, err := is.Current.Decode(ty)
733 if err != nil {
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,
741 })
742 continue
743 }
744 vals[i] = ios.Value
745 } else {
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())
751 }
752 }
753
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
759
760 case states.EachMap:
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)
766
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)
774 if err != nil {
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,
780 })
781 continue
782 }
783 vals[string(sk)] = val
784 continue
785 } else {
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,
794 })
795 continue
796 }
797 }
798
799 ios, err := is.Current.Decode(ty)
800 if err != nil {
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,
808 })
809 continue
810 }
811 vals[string(sk)] = ios.Value
812 }
813 }
814
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
820
821 default:
822 // Should never happen since caller should deal with other modes
823 panic(fmt.Sprintf("unsupported EachMode %s", rs.EachMode))
824 }
825 }
826
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)
831 return schema
832 }
833
834 // coerceInstanceKey attempts to convert the given key to the type expected
835 // for the given EachMode.
836 //
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
845 return key
846 }
847
848 switch mode {
849 case states.NoEach:
850 // No conversions possible at all
851 return key
852 case states.EachMap:
853 if intKey, isInt := key.(addrs.IntKey); isInt {
854 return addrs.StringKey(strconv.Itoa(int(intKey)))
855 }
856 return key
857 case states.EachList:
858 if strKey, isStr := key.(addrs.StringKey); isStr {
859 i, err := strconv.Atoi(string(strKey))
860 if err != nil {
861 return key
862 }
863 return addrs.IntKey(i)
864 }
865 return key
866 default:
867 return key
868 }
869 }
870
871 func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
872 var diags tfdiags.Diagnostics
873 switch addr.Name {
874
875 case "workspace":
876 workspaceName := d.Evaluator.Meta.Env
877 return cty.StringVal(workspaceName), diags
878
879 case "env":
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
882 // removed.
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(),
888 })
889 return cty.DynamicVal, diags
890
891 default:
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(),
897 })
898 return cty.DynamicVal, diags
899 }
900 }
901
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.
905 //
906 // The suggestions are tried in order, so earlier suggestions take precedence
907 // if the given string is similar to two or more suggestions.
908 //
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
915 return suggestion
916 }
917 }
918 return ""
919 }
920
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 {
927 switch {
928 case addr.IsRoot():
929 return "the root module"
930 default:
931 return addr.String()
932 }
933 }