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