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