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