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