kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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 "kubeform.dev/terraform-backend-sdk/addrs" 16 "kubeform.dev/terraform-backend-sdk/configs" 17 "kubeform.dev/terraform-backend-sdk/configs/configschema" 18 "kubeform.dev/terraform-backend-sdk/instances" 19 "kubeform.dev/terraform-backend-sdk/lang" 20 "kubeform.dev/terraform-backend-sdk/lang/marks" 21 "kubeform.dev/terraform-backend-sdk/plans" 22 "kubeform.dev/terraform-backend-sdk/states" 23 "kubeform.dev/terraform-backend-sdk/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 d.Evaluator.VariableValuesLock.Lock() 241 defer d.Evaluator.VariableValuesLock.Unlock() 242 243 // During the validate walk, input variables are always unknown so 244 // that we are validating the configuration for all possible input values 245 // rather than for a specific set. Checking against a specific set of 246 // input values then happens during the plan walk. 247 // 248 // This is important because otherwise the validation walk will tend to be 249 // overly strict, requiring expressions throughout the configuration to 250 // be complicated to accommodate all possible inputs, whereas returning 251 // known here allows for simpler patterns like using input values as 252 // guards to broadly enable/disable resources, avoid processing things 253 // that are disabled, etc. Terraform's static validation leans towards 254 // being liberal in what it accepts because the subsequent plan walk has 255 // more information available and so can be more conservative. 256 if d.Operation == walkValidate { 257 // Ensure variable sensitivity is captured in the validate walk 258 if config.Sensitive { 259 return cty.UnknownVal(config.Type).Mark(marks.Sensitive), diags 260 } 261 return cty.UnknownVal(config.Type), diags 262 } 263 264 moduleAddrStr := d.ModulePath.String() 265 vals := d.Evaluator.VariableValues[moduleAddrStr] 266 if vals == nil { 267 return cty.UnknownVal(config.Type), diags 268 } 269 270 val, isSet := vals[addr.Name] 271 if !isSet { 272 if config.Default != cty.NilVal { 273 return config.Default, diags 274 } 275 return cty.UnknownVal(config.Type), diags 276 } 277 278 var err error 279 val, err = convert.Convert(val, config.ConstraintType) 280 if err != nil { 281 // We should never get here because this problem should've been caught 282 // during earlier validation, but we'll do something reasonable anyway. 283 diags = diags.Append(&hcl.Diagnostic{ 284 Severity: hcl.DiagError, 285 Summary: `Incorrect variable type`, 286 Detail: fmt.Sprintf(`The resolved value of variable %q is not appropriate: %s.`, addr.Name, err), 287 Subject: &config.DeclRange, 288 }) 289 // Stub out our return value so that the semantic checker doesn't 290 // produce redundant downstream errors. 291 val = cty.UnknownVal(config.Type) 292 } 293 294 // Mark if sensitive 295 if config.Sensitive { 296 val = val.Mark(marks.Sensitive) 297 } 298 299 return val, diags 300 } 301 302 func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 303 var diags tfdiags.Diagnostics 304 305 // First we'll make sure the requested value is declared in configuration, 306 // so we can produce a nice message if not. 307 moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath) 308 if moduleConfig == nil { 309 // should never happen, since we can't be evaluating in a module 310 // that wasn't mentioned in configuration. 311 panic(fmt.Sprintf("local value read from %s, which has no configuration", d.ModulePath)) 312 } 313 314 config := moduleConfig.Module.Locals[addr.Name] 315 if config == nil { 316 var suggestions []string 317 for k := range moduleConfig.Module.Locals { 318 suggestions = append(suggestions, k) 319 } 320 suggestion := nameSuggestion(addr.Name, suggestions) 321 if suggestion != "" { 322 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 323 } 324 325 diags = diags.Append(&hcl.Diagnostic{ 326 Severity: hcl.DiagError, 327 Summary: `Reference to undeclared local value`, 328 Detail: fmt.Sprintf(`A local value with the name %q has not been declared.%s`, addr.Name, suggestion), 329 Subject: rng.ToHCL().Ptr(), 330 }) 331 return cty.DynamicVal, diags 332 } 333 334 val := d.Evaluator.State.LocalValue(addr.Absolute(d.ModulePath)) 335 if val == cty.NilVal { 336 // Not evaluated yet? 337 val = cty.DynamicVal 338 } 339 340 return val, diags 341 } 342 343 func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 344 var diags tfdiags.Diagnostics 345 346 // Output results live in the module that declares them, which is one of 347 // the child module instances of our current module path. 348 moduleAddr := d.ModulePath.Module().Child(addr.Name) 349 350 parentCfg := d.Evaluator.Config.DescendentForInstance(d.ModulePath) 351 callConfig, ok := parentCfg.Module.ModuleCalls[addr.Name] 352 if !ok { 353 diags = diags.Append(&hcl.Diagnostic{ 354 Severity: hcl.DiagError, 355 Summary: `Reference to undeclared module`, 356 Detail: fmt.Sprintf(`The configuration contains no %s.`, moduleAddr), 357 Subject: rng.ToHCL().Ptr(), 358 }) 359 return cty.DynamicVal, diags 360 } 361 362 // We'll consult the configuration to see what output names we are 363 // expecting, so we can ensure the resulting object is of the expected 364 // type even if our data is incomplete for some reason. 365 moduleConfig := d.Evaluator.Config.Descendent(moduleAddr) 366 if moduleConfig == nil { 367 // should never happen, since we have a valid module call above, this 368 // should be caught during static validation. 369 panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr)) 370 } 371 outputConfigs := moduleConfig.Module.Outputs 372 373 // Collect all the relevant outputs that current exist in the state. 374 // We know the instance path up to this point, and the child module name, 375 // so we only need to store these by instance key. 376 stateMap := map[addrs.InstanceKey]map[string]cty.Value{} 377 for _, output := range d.Evaluator.State.ModuleOutputs(d.ModulePath, addr) { 378 _, callInstance := output.Addr.Module.CallInstance() 379 instance, ok := stateMap[callInstance.Key] 380 if !ok { 381 instance = map[string]cty.Value{} 382 stateMap[callInstance.Key] = instance 383 } 384 385 instance[output.Addr.OutputValue.Name] = output.Value 386 } 387 388 // Get all changes that reside for this module call within our path. 389 // The change contains the full addr, so we can key these with strings. 390 changesMap := map[addrs.InstanceKey]map[string]*plans.OutputChangeSrc{} 391 for _, change := range d.Evaluator.Changes.GetOutputChanges(d.ModulePath, addr) { 392 _, callInstance := change.Addr.Module.CallInstance() 393 instance, ok := changesMap[callInstance.Key] 394 if !ok { 395 instance = map[string]*plans.OutputChangeSrc{} 396 changesMap[callInstance.Key] = instance 397 } 398 399 instance[change.Addr.OutputValue.Name] = change 400 } 401 402 // Build up all the module objects, creating a map of values for each 403 // module instance. 404 moduleInstances := map[addrs.InstanceKey]map[string]cty.Value{} 405 406 // create a dummy object type for validation below 407 unknownMap := map[string]cty.Type{} 408 409 // the structure is based on the configuration, so iterate through all the 410 // defined outputs, and add any instance state or changes we find. 411 for _, cfg := range outputConfigs { 412 // record the output names for validation 413 unknownMap[cfg.Name] = cty.DynamicPseudoType 414 415 // get all instance output for this path from the state 416 for key, states := range stateMap { 417 outputState, ok := states[cfg.Name] 418 if !ok { 419 continue 420 } 421 422 instance, ok := moduleInstances[key] 423 if !ok { 424 instance = map[string]cty.Value{} 425 moduleInstances[key] = instance 426 } 427 428 instance[cfg.Name] = outputState 429 430 if cfg.Sensitive { 431 instance[cfg.Name] = outputState.Mark(marks.Sensitive) 432 } 433 } 434 435 // any pending changes override the state state values 436 for key, changes := range changesMap { 437 changeSrc, ok := changes[cfg.Name] 438 if !ok { 439 continue 440 } 441 442 instance, ok := moduleInstances[key] 443 if !ok { 444 instance = map[string]cty.Value{} 445 moduleInstances[key] = instance 446 } 447 448 change, err := changeSrc.Decode() 449 if err != nil { 450 // This should happen only if someone has tampered with a plan 451 // file, so we won't bother with a pretty error for it. 452 diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", addr, err)) 453 instance[cfg.Name] = cty.DynamicVal 454 continue 455 } 456 457 instance[cfg.Name] = change.After 458 459 if change.Sensitive { 460 instance[cfg.Name] = change.After.Mark(marks.Sensitive) 461 } 462 } 463 } 464 465 var ret cty.Value 466 467 // compile the outputs into the correct value type for the each mode 468 switch { 469 case callConfig.Count != nil: 470 // figure out what the last index we have is 471 length := -1 472 for key := range moduleInstances { 473 intKey, ok := key.(addrs.IntKey) 474 if !ok { 475 // old key from state which is being dropped 476 continue 477 } 478 if int(intKey) >= length { 479 length = int(intKey) + 1 480 } 481 } 482 483 if length > 0 { 484 vals := make([]cty.Value, length) 485 for key, instance := range moduleInstances { 486 intKey, ok := key.(addrs.IntKey) 487 if !ok { 488 // old key from state which is being dropped 489 continue 490 } 491 492 vals[int(intKey)] = cty.ObjectVal(instance) 493 } 494 495 // Insert unknown values where there are any missing instances 496 for i, v := range vals { 497 if v.IsNull() { 498 vals[i] = cty.DynamicVal 499 continue 500 } 501 } 502 ret = cty.TupleVal(vals) 503 } else { 504 ret = cty.EmptyTupleVal 505 } 506 507 case callConfig.ForEach != nil: 508 vals := make(map[string]cty.Value) 509 for key, instance := range moduleInstances { 510 strKey, ok := key.(addrs.StringKey) 511 if !ok { 512 continue 513 } 514 515 vals[string(strKey)] = cty.ObjectVal(instance) 516 } 517 518 if len(vals) > 0 { 519 ret = cty.ObjectVal(vals) 520 } else { 521 ret = cty.EmptyObjectVal 522 } 523 524 default: 525 val, ok := moduleInstances[addrs.NoKey] 526 if !ok { 527 // create the object if there wasn't one known 528 val = map[string]cty.Value{} 529 for k := range outputConfigs { 530 val[k] = cty.DynamicVal 531 } 532 } 533 534 ret = cty.ObjectVal(val) 535 } 536 537 // The module won't be expanded during validation, so we need to return an 538 // unknown value. This will ensure the types looks correct, since we built 539 // the objects based on the configuration. 540 if d.Operation == walkValidate { 541 // While we know the type here and it would be nice to validate whether 542 // indexes are valid or not, because tuples and objects have fixed 543 // numbers of elements we can't simply return an unknown value of the 544 // same type since we have not expanded any instances during 545 // validation. 546 // 547 // In order to validate the expression a little precisely, we'll create 548 // an unknown map or list here to get more type information. 549 ty := cty.Object(unknownMap) 550 switch { 551 case callConfig.Count != nil: 552 ret = cty.UnknownVal(cty.List(ty)) 553 case callConfig.ForEach != nil: 554 ret = cty.UnknownVal(cty.Map(ty)) 555 default: 556 ret = cty.UnknownVal(ty) 557 } 558 } 559 560 return ret, diags 561 } 562 563 func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 564 var diags tfdiags.Diagnostics 565 switch addr.Name { 566 567 case "cwd": 568 var err error 569 var wd string 570 if d.Evaluator.Meta != nil { 571 // Meta is always non-nil in the normal case, but some test cases 572 // are not so realistic. 573 wd = d.Evaluator.Meta.OriginalWorkingDir 574 } 575 if wd == "" { 576 wd, err = os.Getwd() 577 if err != nil { 578 diags = diags.Append(&hcl.Diagnostic{ 579 Severity: hcl.DiagError, 580 Summary: `Failed to get working directory`, 581 Detail: fmt.Sprintf(`The value for path.cwd cannot be determined due to a system error: %s`, err), 582 Subject: rng.ToHCL().Ptr(), 583 }) 584 return cty.DynamicVal, diags 585 } 586 } 587 // The current working directory should always be absolute, whether we 588 // just looked it up or whether we were relying on ContextMeta's 589 // (possibly non-normalized) path. 590 wd, err = filepath.Abs(wd) 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 return cty.StringVal(filepath.ToSlash(wd)), diags 602 603 case "module": 604 moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath) 605 if moduleConfig == nil { 606 // should never happen, since we can't be evaluating in a module 607 // that wasn't mentioned in configuration. 608 panic(fmt.Sprintf("module.path read from module %s, which has no configuration", d.ModulePath)) 609 } 610 sourceDir := moduleConfig.Module.SourceDir 611 return cty.StringVal(filepath.ToSlash(sourceDir)), diags 612 613 case "root": 614 sourceDir := d.Evaluator.Config.Module.SourceDir 615 return cty.StringVal(filepath.ToSlash(sourceDir)), diags 616 617 default: 618 suggestion := nameSuggestion(addr.Name, []string{"cwd", "module", "root"}) 619 if suggestion != "" { 620 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 621 } 622 diags = diags.Append(&hcl.Diagnostic{ 623 Severity: hcl.DiagError, 624 Summary: `Invalid "path" attribute`, 625 Detail: fmt.Sprintf(`The "path" object does not have an attribute named %q.%s`, addr.Name, suggestion), 626 Subject: rng.ToHCL().Ptr(), 627 }) 628 return cty.DynamicVal, diags 629 } 630 } 631 632 func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 633 var diags tfdiags.Diagnostics 634 // First we'll consult the configuration to see if an resource of this 635 // name is declared at all. 636 moduleAddr := d.ModulePath 637 moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr) 638 if moduleConfig == nil { 639 // should never happen, since we can't be evaluating in a module 640 // that wasn't mentioned in configuration. 641 panic(fmt.Sprintf("resource value read from %s, which has no configuration", moduleAddr)) 642 } 643 644 config := moduleConfig.Module.ResourceByAddr(addr) 645 if config == nil { 646 diags = diags.Append(&hcl.Diagnostic{ 647 Severity: hcl.DiagError, 648 Summary: `Reference to undeclared resource`, 649 Detail: fmt.Sprintf(`A resource %q %q has not been declared in %s`, addr.Type, addr.Name, moduleDisplayAddr(moduleAddr)), 650 Subject: rng.ToHCL().Ptr(), 651 }) 652 return cty.DynamicVal, diags 653 } 654 655 rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath)) 656 657 if rs == nil { 658 switch d.Operation { 659 case walkPlan, walkApply: 660 // During plan and apply as we evaluate each removed instance they 661 // are removed from the working state. Since we know there are no 662 // instances, return an empty container of the expected type. 663 switch { 664 case config.Count != nil: 665 return cty.EmptyTupleVal, diags 666 case config.ForEach != nil: 667 return cty.EmptyObjectVal, diags 668 default: 669 // While we can reference an expanded resource with 0 670 // instances, we cannot reference instances that do not exist. 671 // Due to the fact that we may have direct references to 672 // instances that may end up in a root output during destroy 673 // (since a planned destroy cannot yet remove root outputs), we 674 // need to return a dynamic value here to allow evaluation to 675 // continue. 676 log.Printf("[ERROR] unknown instance %q referenced during plan", addr.Absolute(d.ModulePath)) 677 return cty.DynamicVal, diags 678 } 679 680 default: 681 // We should only end up here during the validate walk, 682 // since later walks should have at least partial states populated 683 // for all resources in the configuration. 684 return cty.DynamicVal, diags 685 } 686 } 687 688 providerAddr := rs.ProviderConfig 689 690 schema := d.getResourceSchema(addr, providerAddr) 691 if schema == nil { 692 // This shouldn't happen, since validation before we get here should've 693 // taken care of it, but we'll show a reasonable error message anyway. 694 diags = diags.Append(&hcl.Diagnostic{ 695 Severity: hcl.DiagError, 696 Summary: `Missing resource type schema`, 697 Detail: fmt.Sprintf("No schema is available for %s in %s. This is a bug in Terraform and should be reported.", addr, providerAddr), 698 Subject: rng.ToHCL().Ptr(), 699 }) 700 return cty.DynamicVal, diags 701 } 702 ty := schema.ImpliedType() 703 704 // Decode all instances in the current state 705 instances := map[addrs.InstanceKey]cty.Value{} 706 pendingDestroy := d.Evaluator.Changes.IsFullDestroy() 707 for key, is := range rs.Instances { 708 if is == nil || is.Current == nil { 709 // Assume we're dealing with an instance that hasn't been created yet. 710 instances[key] = cty.UnknownVal(ty) 711 continue 712 } 713 714 instAddr := addr.Instance(key).Absolute(d.ModulePath) 715 716 change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen) 717 if change != nil { 718 // Don't take any resources that are yet to be deleted into account. 719 // If the referenced resource is CreateBeforeDestroy, then orphaned 720 // instances will be in the state, as they are not destroyed until 721 // after their dependants are updated. 722 if change.Action == plans.Delete { 723 if !pendingDestroy { 724 continue 725 } 726 } 727 } 728 729 // Planned resources are temporarily stored in state with empty values, 730 // and need to be replaced by the planned value here. 731 if is.Current.Status == states.ObjectPlanned { 732 if change == nil { 733 // If the object is in planned status then we should not get 734 // here, since we should have found a pending value in the plan 735 // above instead. 736 diags = diags.Append(&hcl.Diagnostic{ 737 Severity: hcl.DiagError, 738 Summary: "Missing pending object in plan", 739 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), 740 Subject: &config.DeclRange, 741 }) 742 continue 743 } 744 val, err := change.After.Decode(ty) 745 if err != nil { 746 diags = diags.Append(&hcl.Diagnostic{ 747 Severity: hcl.DiagError, 748 Summary: "Invalid resource instance data in plan", 749 Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err), 750 Subject: &config.DeclRange, 751 }) 752 continue 753 } 754 755 // If our provider schema contains sensitive values, mark those as sensitive 756 afterMarks := change.AfterValMarks 757 if schema.ContainsSensitive() { 758 afterMarks = append(afterMarks, schema.ValueMarks(val, nil)...) 759 } 760 761 instances[key] = val.MarkWithPaths(afterMarks) 762 continue 763 } 764 765 ios, err := is.Current.Decode(ty) 766 if err != nil { 767 // This shouldn't happen, since by the time we get here we 768 // should have upgraded the state data already. 769 diags = diags.Append(&hcl.Diagnostic{ 770 Severity: hcl.DiagError, 771 Summary: "Invalid resource instance data in state", 772 Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err), 773 Subject: &config.DeclRange, 774 }) 775 continue 776 } 777 778 val := ios.Value 779 780 // If our schema contains sensitive values, mark those as sensitive. 781 // Since decoding the instance object can also apply sensitivity marks, 782 // we must remove and combine those before remarking to avoid a double- 783 // mark error. 784 if schema.ContainsSensitive() { 785 var marks []cty.PathValueMarks 786 val, marks = val.UnmarkDeepWithPaths() 787 marks = append(marks, schema.ValueMarks(val, nil)...) 788 val = val.MarkWithPaths(marks) 789 } 790 instances[key] = val 791 } 792 793 var ret cty.Value 794 795 switch { 796 case config.Count != nil: 797 // figure out what the last index we have is 798 length := -1 799 for key := range instances { 800 intKey, ok := key.(addrs.IntKey) 801 if !ok { 802 continue 803 } 804 if int(intKey) >= length { 805 length = int(intKey) + 1 806 } 807 } 808 809 if length > 0 { 810 vals := make([]cty.Value, length) 811 for key, instance := range instances { 812 intKey, ok := key.(addrs.IntKey) 813 if !ok { 814 // old key from state, which isn't valid for evaluation 815 continue 816 } 817 818 vals[int(intKey)] = instance 819 } 820 821 // Insert unknown values where there are any missing instances 822 for i, v := range vals { 823 if v == cty.NilVal { 824 vals[i] = cty.UnknownVal(ty) 825 } 826 } 827 ret = cty.TupleVal(vals) 828 } else { 829 ret = cty.EmptyTupleVal 830 } 831 832 case config.ForEach != nil: 833 vals := make(map[string]cty.Value) 834 for key, instance := range instances { 835 strKey, ok := key.(addrs.StringKey) 836 if !ok { 837 // old key that is being dropped and not used for evaluation 838 continue 839 } 840 vals[string(strKey)] = instance 841 } 842 843 if len(vals) > 0 { 844 // We use an object rather than a map here because resource schemas 845 // may include dynamically-typed attributes, which will then cause 846 // each instance to potentially have a different runtime type even 847 // though they all conform to the static schema. 848 ret = cty.ObjectVal(vals) 849 } else { 850 ret = cty.EmptyObjectVal 851 } 852 853 default: 854 val, ok := instances[addrs.NoKey] 855 if !ok { 856 // if the instance is missing, insert an unknown value 857 val = cty.UnknownVal(ty) 858 } 859 860 ret = val 861 } 862 863 // since the plan was not yet created during validate, the values we 864 // collected here may not correspond with configuration, so they must be 865 // unknown. 866 if d.Operation == walkValidate { 867 // While we know the type here and it would be nice to validate whether 868 // indexes are valid or not, because tuples and objects have fixed 869 // numbers of elements we can't simply return an unknown value of the 870 // same type since we have not expanded any instances during 871 // validation. 872 // 873 // In order to validate the expression a little precisely, we'll create 874 // an unknown map or list here to get more type information. 875 switch { 876 case config.Count != nil: 877 ret = cty.UnknownVal(cty.List(ty)) 878 case config.ForEach != nil: 879 ret = cty.UnknownVal(cty.Map(ty)) 880 default: 881 ret = cty.UnknownVal(ty) 882 } 883 } 884 885 return ret, diags 886 } 887 888 func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.AbsProviderConfig) *configschema.Block { 889 schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr.Provider, addr.Mode, addr.Type) 890 if err != nil { 891 // We have plently other codepaths that will detect and report 892 // schema lookup errors before we'd reach this point, so we'll just 893 // treat a failure here the same as having no schema. 894 return nil 895 } 896 return schema 897 } 898 899 func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 900 var diags tfdiags.Diagnostics 901 switch addr.Name { 902 903 case "workspace": 904 workspaceName := d.Evaluator.Meta.Env 905 return cty.StringVal(workspaceName), diags 906 907 case "env": 908 // Prior to Terraform 0.12 there was an attribute "env", which was 909 // an alias name for "workspace". This was deprecated and is now 910 // removed. 911 diags = diags.Append(&hcl.Diagnostic{ 912 Severity: hcl.DiagError, 913 Summary: `Invalid "terraform" attribute`, 914 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.`, 915 Subject: rng.ToHCL().Ptr(), 916 }) 917 return cty.DynamicVal, diags 918 919 default: 920 diags = diags.Append(&hcl.Diagnostic{ 921 Severity: hcl.DiagError, 922 Summary: `Invalid "terraform" attribute`, 923 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), 924 Subject: rng.ToHCL().Ptr(), 925 }) 926 return cty.DynamicVal, diags 927 } 928 } 929 930 // nameSuggestion tries to find a name from the given slice of suggested names 931 // that is close to the given name and returns it if found. If no suggestion 932 // is close enough, returns the empty string. 933 // 934 // The suggestions are tried in order, so earlier suggestions take precedence 935 // if the given string is similar to two or more suggestions. 936 // 937 // This function is intended to be used with a relatively-small number of 938 // suggestions. It's not optimized for hundreds or thousands of them. 939 func nameSuggestion(given string, suggestions []string) string { 940 for _, suggestion := range suggestions { 941 dist := levenshtein.Distance(given, suggestion, nil) 942 if dist < 3 { // threshold determined experimentally 943 return suggestion 944 } 945 } 946 return "" 947 } 948 949 // moduleDisplayAddr returns a string describing the given module instance 950 // address that is appropriate for returning to users in situations where the 951 // root module is possible. Specifically, it returns "the root module" if the 952 // root module instance is given, or a string representation of the module 953 // address otherwise. 954 func moduleDisplayAddr(addr addrs.ModuleInstance) string { 955 switch { 956 case addr.IsRoot(): 957 return "the root module" 958 default: 959 return addr.String() 960 } 961 }