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