github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/evaluate.go (about) 1 package durgaform 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 14 "github.com/eliastor/durgaform/internal/addrs" 15 "github.com/eliastor/durgaform/internal/configs" 16 "github.com/eliastor/durgaform/internal/configs/configschema" 17 "github.com/eliastor/durgaform/internal/instances" 18 "github.com/eliastor/durgaform/internal/lang" 19 "github.com/eliastor/durgaform/internal/lang/marks" 20 "github.com/eliastor/durgaform/internal/plans" 21 "github.com/eliastor/durgaform/internal/states" 22 "github.com/eliastor/durgaform/internal/tfdiags" 23 ) 24 25 // Evaluator provides the necessary contextual data for evaluating expressions 26 // for a particular walk operation. 27 type Evaluator struct { 28 // Operation defines what type of operation this evaluator is being used 29 // for. 30 Operation walkOperation 31 32 // Meta is contextual metadata about the current operation. 33 Meta *ContextMeta 34 35 // Config is the root node in the configuration tree. 36 Config *configs.Config 37 38 // VariableValues is a map from variable names to their associated values, 39 // within the module indicated by ModulePath. VariableValues is modified 40 // concurrently, and so it must be accessed only while holding 41 // VariableValuesLock. 42 // 43 // The first map level is string representations of addr.ModuleInstance 44 // values, while the second level is variable names. 45 VariableValues map[string]map[string]cty.Value 46 VariableValuesLock *sync.Mutex 47 48 // Plugins is the library of available plugin components (providers and 49 // provisioners) that we have available to help us evaluate expressions 50 // that interact with plugin-provided objects. 51 // 52 // From this we only access the schemas of the plugins, and don't otherwise 53 // interact with plugin instances. 54 Plugins *contextPlugins 55 56 // State is the current state, embedded in a wrapper that ensures that 57 // it can be safely accessed and modified concurrently. 58 State *states.SyncState 59 60 // Changes is the set of proposed changes, embedded in a wrapper that 61 // ensures they can be safely accessed and modified concurrently. 62 Changes *plans.ChangesSync 63 } 64 65 // Scope creates an evaluation scope for the given module path and optional 66 // resource. 67 // 68 // If the "self" argument is nil then the "self" object is not available 69 // in evaluated expressions. Otherwise, it behaves as an alias for the given 70 // address. 71 func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope { 72 return &lang.Scope{ 73 Data: data, 74 SelfAddr: self, 75 PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval, 76 BaseDir: ".", // Always current working directory for now. 77 } 78 } 79 80 // evaluationStateData is an implementation of lang.Data that resolves 81 // references primarily (but not exclusively) using information from a State. 82 type evaluationStateData struct { 83 Evaluator *Evaluator 84 85 // ModulePath is the path through the dynamic module tree to the module 86 // that references will be resolved relative to. 87 ModulePath addrs.ModuleInstance 88 89 // InstanceKeyData describes the values, if any, that are accessible due 90 // to repetition of a containing object using "count" or "for_each" 91 // arguments. (It is _not_ used for the for_each inside "dynamic" blocks, 92 // since the user specifies in that case which variable name to locally 93 // shadow.) 94 InstanceKeyData InstanceKeyEvalData 95 96 // Operation records the type of walk the evaluationStateData is being used 97 // for. 98 Operation walkOperation 99 } 100 101 // InstanceKeyEvalData is the old name for instances.RepetitionData, aliased 102 // here for compatibility. In new code, use instances.RepetitionData instead. 103 type InstanceKeyEvalData = instances.RepetitionData 104 105 // EvalDataForInstanceKey constructs a suitable InstanceKeyEvalData for 106 // evaluating in a context that has the given instance key. 107 // 108 // The forEachMap argument can be nil when preparing for evaluation 109 // in a context where each.value is prohibited, such as a destroy-time 110 // provisioner. In that case, the returned EachValue will always be 111 // cty.NilVal. 112 func EvalDataForInstanceKey(key addrs.InstanceKey, forEachMap map[string]cty.Value) InstanceKeyEvalData { 113 var evalData InstanceKeyEvalData 114 if key == nil { 115 return evalData 116 } 117 118 keyValue := key.Value() 119 switch keyValue.Type() { 120 case cty.String: 121 evalData.EachKey = keyValue 122 evalData.EachValue = forEachMap[keyValue.AsString()] 123 case cty.Number: 124 evalData.CountIndex = keyValue 125 } 126 return evalData 127 } 128 129 // EvalDataForNoInstanceKey is a value of InstanceKeyData that sets no instance 130 // key values at all, suitable for use in contexts where no keyed instance 131 // is relevant. 132 var EvalDataForNoInstanceKey = InstanceKeyEvalData{} 133 134 // evaluationStateData must implement lang.Data 135 var _ lang.Data = (*evaluationStateData)(nil) 136 137 func (d *evaluationStateData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 138 var diags tfdiags.Diagnostics 139 switch addr.Name { 140 141 case "index": 142 idxVal := d.InstanceKeyData.CountIndex 143 if idxVal == cty.NilVal { 144 diags = diags.Append(&hcl.Diagnostic{ 145 Severity: hcl.DiagError, 146 Summary: `Reference to "count" in non-counted context`, 147 Detail: `The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set.`, 148 Subject: rng.ToHCL().Ptr(), 149 }) 150 return cty.UnknownVal(cty.Number), diags 151 } 152 return idxVal, diags 153 154 default: 155 diags = diags.Append(&hcl.Diagnostic{ 156 Severity: hcl.DiagError, 157 Summary: `Invalid "count" attribute`, 158 Detail: fmt.Sprintf(`The "count" object does not have an attribute named %q. The only supported attribute is count.index, which is the index of each instance of a resource block that has the "count" argument set.`, addr.Name), 159 Subject: rng.ToHCL().Ptr(), 160 }) 161 return cty.DynamicVal, diags 162 } 163 } 164 165 func (d *evaluationStateData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 166 var diags tfdiags.Diagnostics 167 var returnVal cty.Value 168 switch addr.Name { 169 170 case "key": 171 returnVal = d.InstanceKeyData.EachKey 172 case "value": 173 returnVal = d.InstanceKeyData.EachValue 174 175 if returnVal == cty.NilVal { 176 diags = diags.Append(&hcl.Diagnostic{ 177 Severity: hcl.DiagError, 178 Summary: `each.value cannot be used in this context`, 179 Detail: `A reference to "each.value" has been used in a context in which it unavailable, such as when the configuration no longer contains the value in its "for_each" expression. Remove this reference to each.value in your configuration to work around this error.`, 180 Subject: rng.ToHCL().Ptr(), 181 }) 182 return cty.UnknownVal(cty.DynamicPseudoType), diags 183 } 184 default: 185 diags = diags.Append(&hcl.Diagnostic{ 186 Severity: hcl.DiagError, 187 Summary: `Invalid "each" attribute`, 188 Detail: fmt.Sprintf(`The "each" object does not have an attribute named %q. The supported attributes are each.key and each.value, the current key and value pair of the "for_each" attribute set.`, addr.Name), 189 Subject: rng.ToHCL().Ptr(), 190 }) 191 return cty.DynamicVal, diags 192 } 193 194 if returnVal == cty.NilVal { 195 diags = diags.Append(&hcl.Diagnostic{ 196 Severity: hcl.DiagError, 197 Summary: `Reference to "each" in context without for_each`, 198 Detail: `The "each" object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set.`, 199 Subject: rng.ToHCL().Ptr(), 200 }) 201 return cty.UnknownVal(cty.DynamicPseudoType), diags 202 } 203 return returnVal, diags 204 } 205 206 func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 207 var diags tfdiags.Diagnostics 208 209 // First we'll make sure the requested value is declared in configuration, 210 // so we can produce a nice message if not. 211 moduleConfig := d.Evaluator.Config.DescendentForInstance(d.ModulePath) 212 if moduleConfig == nil { 213 // should never happen, since we can't be evaluating in a module 214 // that wasn't mentioned in configuration. 215 panic(fmt.Sprintf("input variable read from %s, which has no configuration", d.ModulePath)) 216 } 217 218 config := moduleConfig.Module.Variables[addr.Name] 219 if config == nil { 220 var suggestions []string 221 for k := range moduleConfig.Module.Variables { 222 suggestions = append(suggestions, k) 223 } 224 suggestion := nameSuggestion(addr.Name, suggestions) 225 if suggestion != "" { 226 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) 227 } else { 228 suggestion = fmt.Sprintf(" This variable can be declared with a variable %q {} block.", addr.Name) 229 } 230 231 diags = diags.Append(&hcl.Diagnostic{ 232 Severity: hcl.DiagError, 233 Summary: `Reference to undeclared input variable`, 234 Detail: fmt.Sprintf(`An input variable with the name %q has not been declared.%s`, addr.Name, suggestion), 235 Subject: rng.ToHCL().Ptr(), 236 }) 237 return cty.DynamicVal, diags 238 } 239 d.Evaluator.VariableValuesLock.Lock() 240 defer d.Evaluator.VariableValuesLock.Unlock() 241 242 // During the validate walk, input variables are always unknown so 243 // that we are validating the configuration for all possible input values 244 // rather than for a specific set. Checking against a specific set of 245 // input values then happens during the plan walk. 246 // 247 // This is important because otherwise the validation walk will tend to be 248 // overly strict, requiring expressions throughout the configuration to 249 // be complicated to accommodate all possible inputs, whereas returning 250 // unknown here allows for simpler patterns like using input values as 251 // guards to broadly enable/disable resources, avoid processing things 252 // that are disabled, etc. Durgaform's static validation leans towards 253 // being liberal in what it accepts because the subsequent plan walk has 254 // more information available and so can be more conservative. 255 if d.Operation == walkValidate { 256 // Ensure variable sensitivity is captured in the validate walk 257 if config.Sensitive { 258 return cty.UnknownVal(config.Type).Mark(marks.Sensitive), diags 259 } 260 return cty.UnknownVal(config.Type), diags 261 } 262 263 moduleAddrStr := d.ModulePath.String() 264 vals := d.Evaluator.VariableValues[moduleAddrStr] 265 if vals == nil { 266 return cty.UnknownVal(config.Type), diags 267 } 268 269 // d.Evaluator.VariableValues should always contain valid "final values" 270 // for variables, which is to say that they have already had type 271 // conversions, validations, and default value handling applied to them. 272 // Those are the responsibility of the graph notes representing the 273 // variable declarations. Therefore here we just trust that we already 274 // have a correct value. 275 276 val, isSet := vals[addr.Name] 277 if !isSet { 278 // We should not be able to get here without having a valid value 279 // for every variable, so this always indicates a bug in either 280 // the graph builder (not including all the needed nodes) or in 281 // the graph nodes representing variables. 282 diags = diags.Append(&hcl.Diagnostic{ 283 Severity: hcl.DiagError, 284 Summary: `Reference to unresolved input variable`, 285 Detail: fmt.Sprintf( 286 `The final value for %s is missing in Durgaform's evaluation context. This is a bug in Terraform; please report it!`, 287 addr.Absolute(d.ModulePath), 288 ), 289 Subject: rng.ToHCL().Ptr(), 290 }) 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 // Build the provider address from configuration, since we may not have 656 // state available in all cases. 657 // We need to build an abs provider address, but we can use a default 658 // instance since we're only interested in the schema. 659 schema := d.getResourceSchema(addr, config.Provider) 660 if schema == nil { 661 // This shouldn't happen, since validation before we get here should've 662 // taken care of it, but we'll show a reasonable error message anyway. 663 diags = diags.Append(&hcl.Diagnostic{ 664 Severity: hcl.DiagError, 665 Summary: `Missing resource type schema`, 666 Detail: fmt.Sprintf("No schema is available for %s in %s. This is a bug in Durgaform and should be reported.", addr, config.Provider), 667 Subject: rng.ToHCL().Ptr(), 668 }) 669 return cty.DynamicVal, diags 670 } 671 ty := schema.ImpliedType() 672 673 rs := d.Evaluator.State.Resource(addr.Absolute(d.ModulePath)) 674 675 if rs == nil { 676 switch d.Operation { 677 case walkPlan, walkApply: 678 // During plan and apply as we evaluate each removed instance they 679 // are removed from the working state. Since we know there are no 680 // instances, return an empty container of the expected type. 681 switch { 682 case config.Count != nil: 683 return cty.EmptyTupleVal, diags 684 case config.ForEach != nil: 685 return cty.EmptyObjectVal, diags 686 default: 687 // While we can reference an expanded resource with 0 688 // instances, we cannot reference instances that do not exist. 689 // Due to the fact that we may have direct references to 690 // instances that may end up in a root output during destroy 691 // (since a planned destroy cannot yet remove root outputs), we 692 // need to return a dynamic value here to allow evaluation to 693 // continue. 694 log.Printf("[ERROR] unknown instance %q referenced during %s", addr.Absolute(d.ModulePath), d.Operation) 695 return cty.DynamicVal, diags 696 } 697 698 default: 699 // We should only end up here during the validate walk, 700 // since later walks should have at least partial states populated 701 // for all resources in the configuration. 702 return cty.DynamicVal, diags 703 } 704 } 705 706 // Decode all instances in the current state 707 instances := map[addrs.InstanceKey]cty.Value{} 708 pendingDestroy := d.Evaluator.Changes.IsFullDestroy() 709 for key, is := range rs.Instances { 710 if is == nil || is.Current == nil { 711 // Assume we're dealing with an instance that hasn't been created yet. 712 instances[key] = cty.UnknownVal(ty) 713 continue 714 } 715 716 instAddr := addr.Instance(key).Absolute(d.ModulePath) 717 718 change := d.Evaluator.Changes.GetResourceInstanceChange(instAddr, states.CurrentGen) 719 if change != nil { 720 // Don't take any resources that are yet to be deleted into account. 721 // If the referenced resource is CreateBeforeDestroy, then orphaned 722 // instances will be in the state, as they are not destroyed until 723 // after their dependants are updated. 724 if change.Action == plans.Delete { 725 if !pendingDestroy { 726 continue 727 } 728 } 729 } 730 731 // Planned resources are temporarily stored in state with empty values, 732 // and need to be replaced by the planned value here. 733 if is.Current.Status == states.ObjectPlanned { 734 if change == nil { 735 // If the object is in planned status then we should not get 736 // here, since we should have found a pending value in the plan 737 // above instead. 738 diags = diags.Append(&hcl.Diagnostic{ 739 Severity: hcl.DiagError, 740 Summary: "Missing pending object in plan", 741 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 Durgaform; please report it.", instAddr), 742 Subject: &config.DeclRange, 743 }) 744 continue 745 } 746 val, err := change.After.Decode(ty) 747 if err != nil { 748 diags = diags.Append(&hcl.Diagnostic{ 749 Severity: hcl.DiagError, 750 Summary: "Invalid resource instance data in plan", 751 Detail: fmt.Sprintf("Instance %s data could not be decoded from the plan: %s.", instAddr, err), 752 Subject: &config.DeclRange, 753 }) 754 continue 755 } 756 757 // If our provider schema contains sensitive values, mark those as sensitive 758 afterMarks := change.AfterValMarks 759 if schema.ContainsSensitive() { 760 afterMarks = append(afterMarks, schema.ValueMarks(val, nil)...) 761 } 762 763 instances[key] = val.MarkWithPaths(afterMarks) 764 continue 765 } 766 767 ios, err := is.Current.Decode(ty) 768 if err != nil { 769 // This shouldn't happen, since by the time we get here we 770 // should have upgraded the state data already. 771 diags = diags.Append(&hcl.Diagnostic{ 772 Severity: hcl.DiagError, 773 Summary: "Invalid resource instance data in state", 774 Detail: fmt.Sprintf("Instance %s data could not be decoded from the state: %s.", instAddr, err), 775 Subject: &config.DeclRange, 776 }) 777 continue 778 } 779 780 val := ios.Value 781 782 // If our schema contains sensitive values, mark those as sensitive. 783 // Since decoding the instance object can also apply sensitivity marks, 784 // we must remove and combine those before remarking to avoid a double- 785 // mark error. 786 if schema.ContainsSensitive() { 787 var marks []cty.PathValueMarks 788 val, marks = val.UnmarkDeepWithPaths() 789 marks = append(marks, schema.ValueMarks(val, nil)...) 790 val = val.MarkWithPaths(marks) 791 } 792 instances[key] = val 793 } 794 795 // ret should be populated with a valid value in all cases below 796 var ret cty.Value 797 798 switch { 799 case config.Count != nil: 800 // figure out what the last index we have is 801 length := -1 802 for key := range instances { 803 intKey, ok := key.(addrs.IntKey) 804 if !ok { 805 continue 806 } 807 if int(intKey) >= length { 808 length = int(intKey) + 1 809 } 810 } 811 812 if length > 0 { 813 vals := make([]cty.Value, length) 814 for key, instance := range instances { 815 intKey, ok := key.(addrs.IntKey) 816 if !ok { 817 // old key from state, which isn't valid for evaluation 818 continue 819 } 820 821 vals[int(intKey)] = instance 822 } 823 824 // Insert unknown values where there are any missing instances 825 for i, v := range vals { 826 if v == cty.NilVal { 827 vals[i] = cty.UnknownVal(ty) 828 } 829 } 830 ret = cty.TupleVal(vals) 831 } else { 832 ret = cty.EmptyTupleVal 833 } 834 835 case config.ForEach != nil: 836 vals := make(map[string]cty.Value) 837 for key, instance := range instances { 838 strKey, ok := key.(addrs.StringKey) 839 if !ok { 840 // old key that is being dropped and not used for evaluation 841 continue 842 } 843 vals[string(strKey)] = instance 844 } 845 846 if len(vals) > 0 { 847 // We use an object rather than a map here because resource schemas 848 // may include dynamically-typed attributes, which will then cause 849 // each instance to potentially have a different runtime type even 850 // though they all conform to the static schema. 851 ret = cty.ObjectVal(vals) 852 } else { 853 ret = cty.EmptyObjectVal 854 } 855 856 default: 857 val, ok := instances[addrs.NoKey] 858 if !ok { 859 // if the instance is missing, insert an unknown value 860 val = cty.UnknownVal(ty) 861 } 862 863 ret = val 864 } 865 866 return ret, diags 867 } 868 869 func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.Provider) *configschema.Block { 870 schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr, addr.Mode, addr.Type) 871 if err != nil { 872 // We have plently other codepaths that will detect and report 873 // schema lookup errors before we'd reach this point, so we'll just 874 // treat a failure here the same as having no schema. 875 return nil 876 } 877 return schema 878 } 879 880 func (d *evaluationStateData) GetDurgaformAttr(addr addrs.DurgaformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { 881 var diags tfdiags.Diagnostics 882 switch addr.Name { 883 884 case "workspace": 885 workspaceName := d.Evaluator.Meta.Env 886 return cty.StringVal(workspaceName), diags 887 888 case "env": 889 // Prior to Durgaform 0.12 there was an attribute "env", which was 890 // an alias name for "workspace". This was deprecated and is now 891 // removed. 892 diags = diags.Append(&hcl.Diagnostic{ 893 Severity: hcl.DiagError, 894 Summary: `Invalid "durgaform" attribute`, 895 Detail: `The durgaform.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.`, 896 Subject: rng.ToHCL().Ptr(), 897 }) 898 return cty.DynamicVal, diags 899 900 default: 901 diags = diags.Append(&hcl.Diagnostic{ 902 Severity: hcl.DiagError, 903 Summary: `Invalid "durgaform" attribute`, 904 Detail: fmt.Sprintf(`The "durgaform" object does not have an attribute named %q. The only supported attribute is terraform.workspace, the name of the currently-selected workspace.`, addr.Name), 905 Subject: rng.ToHCL().Ptr(), 906 }) 907 return cty.DynamicVal, diags 908 } 909 } 910 911 // nameSuggestion tries to find a name from the given slice of suggested names 912 // that is close to the given name and returns it if found. If no suggestion 913 // is close enough, returns the empty string. 914 // 915 // The suggestions are tried in order, so earlier suggestions take precedence 916 // if the given string is similar to two or more suggestions. 917 // 918 // This function is intended to be used with a relatively-small number of 919 // suggestions. It's not optimized for hundreds or thousands of them. 920 func nameSuggestion(given string, suggestions []string) string { 921 for _, suggestion := range suggestions { 922 dist := levenshtein.Distance(given, suggestion, nil) 923 if dist < 3 { // threshold determined experimentally 924 return suggestion 925 } 926 } 927 return "" 928 } 929 930 // moduleDisplayAddr returns a string describing the given module instance 931 // address that is appropriate for returning to users in situations where the 932 // root module is possible. Specifically, it returns "the root module" if the 933 // root module instance is given, or a string representation of the module 934 // address otherwise. 935 func moduleDisplayAddr(addr addrs.ModuleInstance) string { 936 switch { 937 case addr.IsRoot(): 938 return "the root module" 939 default: 940 return addr.String() 941 } 942 }