github.com/opentofu/opentofu@v1.7.1/internal/tofu/eval_context_builtin.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "context" 10 "fmt" 11 "log" 12 "sync" 13 14 "github.com/hashicorp/hcl/v2" 15 "github.com/hashicorp/hcl/v2/hclsyntax" 16 "github.com/zclconf/go-cty/cty" 17 "github.com/zclconf/go-cty/cty/function" 18 19 "github.com/opentofu/opentofu/internal/addrs" 20 "github.com/opentofu/opentofu/internal/checks" 21 "github.com/opentofu/opentofu/internal/configs/configschema" 22 "github.com/opentofu/opentofu/internal/encryption" 23 "github.com/opentofu/opentofu/internal/instances" 24 "github.com/opentofu/opentofu/internal/lang" 25 "github.com/opentofu/opentofu/internal/lang/marks" 26 "github.com/opentofu/opentofu/internal/plans" 27 "github.com/opentofu/opentofu/internal/providers" 28 "github.com/opentofu/opentofu/internal/provisioners" 29 "github.com/opentofu/opentofu/internal/refactoring" 30 "github.com/opentofu/opentofu/internal/states" 31 "github.com/opentofu/opentofu/internal/tfdiags" 32 "github.com/opentofu/opentofu/version" 33 ) 34 35 // BuiltinEvalContext is an EvalContext implementation that is used by 36 // OpenTofu by default. 37 type BuiltinEvalContext struct { 38 // StopContext is the context used to track whether we're complete 39 StopContext context.Context 40 41 // PathValue is the Path that this context is operating within. 42 PathValue addrs.ModuleInstance 43 44 // pathSet indicates that this context was explicitly created for a 45 // specific path, and can be safely used for evaluation. This lets us 46 // differentiate between PathValue being unset, and the zero value which is 47 // equivalent to RootModuleInstance. Path and Evaluation methods will 48 // panic if this is not set. 49 pathSet bool 50 51 // Evaluator is used for evaluating expressions within the scope of this 52 // eval context. 53 Evaluator *Evaluator 54 55 // VariableValues contains the variable values across all modules. This 56 // structure is shared across the entire containing context, and so it 57 // may be accessed only when holding VariableValuesLock. 58 // The keys of the first level of VariableValues are the string 59 // representations of addrs.ModuleInstance values. The second-level keys 60 // are variable names within each module instance. 61 VariableValues map[string]map[string]cty.Value 62 VariableValuesLock *sync.Mutex 63 64 // Plugins is a library of plugin components (providers and provisioners) 65 // available for use during a graph walk. 66 Plugins *contextPlugins 67 68 Hooks []Hook 69 InputValue UIInput 70 ProviderCache map[string]providers.Interface 71 ProviderInputConfig map[string]map[string]cty.Value 72 ProviderLock *sync.Mutex 73 ProvisionerCache map[string]provisioners.Interface 74 ProvisionerLock *sync.Mutex 75 ChangesValue *plans.ChangesSync 76 StateValue *states.SyncState 77 ChecksValue *checks.State 78 RefreshStateValue *states.SyncState 79 PrevRunStateValue *states.SyncState 80 InstanceExpanderValue *instances.Expander 81 MoveResultsValue refactoring.MoveResults 82 ImportResolverValue *ImportResolver 83 Encryption encryption.Encryption 84 } 85 86 // BuiltinEvalContext implements EvalContext 87 var _ EvalContext = (*BuiltinEvalContext)(nil) 88 89 func (ctx *BuiltinEvalContext) WithPath(path addrs.ModuleInstance) EvalContext { 90 newCtx := *ctx 91 newCtx.pathSet = true 92 newCtx.PathValue = path 93 return &newCtx 94 } 95 96 func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} { 97 // This can happen during tests. During tests, we just block forever. 98 if ctx.StopContext == nil { 99 return nil 100 } 101 102 return ctx.StopContext.Done() 103 } 104 105 func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error { 106 for _, h := range ctx.Hooks { 107 action, err := fn(h) 108 if err != nil { 109 return err 110 } 111 112 switch action { 113 case HookActionContinue: 114 continue 115 case HookActionHalt: 116 // Return an early exit error to trigger an early exit 117 log.Printf("[WARN] Early exit triggered by hook: %T", h) 118 return nil 119 } 120 } 121 122 return nil 123 } 124 125 func (ctx *BuiltinEvalContext) Input() UIInput { 126 return ctx.InputValue 127 } 128 129 func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig) (providers.Interface, error) { 130 ctx.ProviderLock.Lock() 131 defer ctx.ProviderLock.Unlock() 132 133 key := addr.String() 134 135 // If we have already initialized, it is an error 136 if _, ok := ctx.ProviderCache[key]; ok { 137 return nil, fmt.Errorf("%s is already initialized", addr) 138 } 139 140 p, err := ctx.Plugins.NewProviderInstance(addr.Provider) 141 if err != nil { 142 return nil, err 143 } 144 145 log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", addr.String(), addr) 146 ctx.ProviderCache[key] = p 147 148 return p, nil 149 } 150 151 func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Interface { 152 ctx.ProviderLock.Lock() 153 defer ctx.ProviderLock.Unlock() 154 155 return ctx.ProviderCache[addr.String()] 156 } 157 158 func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (providers.ProviderSchema, error) { 159 return ctx.Plugins.ProviderSchema(addr.Provider) 160 } 161 162 func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.AbsProviderConfig) error { 163 ctx.ProviderLock.Lock() 164 defer ctx.ProviderLock.Unlock() 165 166 key := addr.String() 167 provider := ctx.ProviderCache[key] 168 if provider != nil { 169 delete(ctx.ProviderCache, key) 170 return provider.Close() 171 } 172 173 return nil 174 } 175 176 func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, cfg cty.Value) tfdiags.Diagnostics { 177 var diags tfdiags.Diagnostics 178 if !addr.Module.Equal(ctx.Path().Module()) { 179 // This indicates incorrect use of ConfigureProvider: it should be used 180 // only from the module that the provider configuration belongs to. 181 panic(fmt.Sprintf("%s configured by wrong module %s", addr, ctx.Path())) 182 } 183 184 p := ctx.Provider(addr) 185 if p == nil { 186 diags = diags.Append(fmt.Errorf("%s not initialized", addr)) 187 return diags 188 } 189 190 req := providers.ConfigureProviderRequest{ 191 TerraformVersion: version.String(), 192 Config: cfg, 193 } 194 195 resp := p.ConfigureProvider(req) 196 return resp.Diagnostics 197 } 198 199 func (ctx *BuiltinEvalContext) ProviderInput(pc addrs.AbsProviderConfig) map[string]cty.Value { 200 ctx.ProviderLock.Lock() 201 defer ctx.ProviderLock.Unlock() 202 203 if !pc.Module.Equal(ctx.Path().Module()) { 204 // This indicates incorrect use of InitProvider: it should be used 205 // only from the module that the provider configuration belongs to. 206 panic(fmt.Sprintf("%s initialized by wrong module %s", pc, ctx.Path())) 207 } 208 209 if !ctx.Path().IsRoot() { 210 // Only root module provider configurations can have input. 211 return nil 212 } 213 214 return ctx.ProviderInputConfig[pc.String()] 215 } 216 217 func (ctx *BuiltinEvalContext) SetProviderInput(pc addrs.AbsProviderConfig, c map[string]cty.Value) { 218 absProvider := pc 219 if !pc.Module.IsRoot() { 220 // Only root module provider configurations can have input. 221 log.Printf("[WARN] BuiltinEvalContext: attempt to SetProviderInput for non-root module") 222 return 223 } 224 225 // Save the configuration 226 ctx.ProviderLock.Lock() 227 ctx.ProviderInputConfig[absProvider.String()] = c 228 ctx.ProviderLock.Unlock() 229 } 230 231 func (ctx *BuiltinEvalContext) Provisioner(n string) (provisioners.Interface, error) { 232 ctx.ProvisionerLock.Lock() 233 defer ctx.ProvisionerLock.Unlock() 234 235 p, ok := ctx.ProvisionerCache[n] 236 if !ok { 237 var err error 238 p, err = ctx.Plugins.NewProvisionerInstance(n) 239 if err != nil { 240 return nil, err 241 } 242 243 ctx.ProvisionerCache[n] = p 244 } 245 246 return p, nil 247 } 248 249 func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) (*configschema.Block, error) { 250 return ctx.Plugins.ProvisionerSchema(n) 251 } 252 253 func (ctx *BuiltinEvalContext) CloseProvisioners() error { 254 var diags tfdiags.Diagnostics 255 ctx.ProvisionerLock.Lock() 256 defer ctx.ProvisionerLock.Unlock() 257 258 for name, prov := range ctx.ProvisionerCache { 259 err := prov.Close() 260 if err != nil { 261 diags = diags.Append(fmt.Errorf("provisioner.Close %s: %w", name, err)) 262 } 263 } 264 265 return diags.Err() 266 } 267 268 func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) { 269 var diags tfdiags.Diagnostics 270 scope := ctx.EvaluationScope(self, nil, keyData) 271 body, evalDiags := scope.ExpandBlock(body, schema) 272 diags = diags.Append(evalDiags) 273 val, evalDiags := scope.EvalBlock(body, schema) 274 diags = diags.Append(evalDiags) 275 return val, body, diags 276 } 277 278 func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) { 279 scope := ctx.EvaluationScope(self, nil, EvalDataForNoInstanceKey) 280 return scope.EvalExpr(expr, wantType) 281 } 282 283 func (ctx *BuiltinEvalContext) EvaluateReplaceTriggeredBy(expr hcl.Expression, repData instances.RepetitionData) (*addrs.Reference, bool, tfdiags.Diagnostics) { 284 285 // get the reference to lookup changes in the plan 286 ref, diags := evalReplaceTriggeredByExpr(expr, repData) 287 if diags.HasErrors() { 288 return nil, false, diags 289 } 290 291 var changes []*plans.ResourceInstanceChangeSrc 292 // store the address once we get it for validation 293 var resourceAddr addrs.Resource 294 295 // The reference is either a resource or resource instance 296 switch sub := ref.Subject.(type) { 297 case addrs.Resource: 298 resourceAddr = sub 299 rc := sub.Absolute(ctx.Path()) 300 changes = ctx.Changes().GetChangesForAbsResource(rc) 301 case addrs.ResourceInstance: 302 resourceAddr = sub.ContainingResource() 303 rc := sub.Absolute(ctx.Path()) 304 change := ctx.Changes().GetResourceInstanceChange(rc, states.CurrentGen) 305 if change != nil { 306 // we'll generate an error below if there was no change 307 changes = append(changes, change) 308 } 309 } 310 311 // Do some validation to make sure we are expecting a change at all 312 cfg := ctx.Evaluator.Config.Descendent(ctx.Path().Module()) 313 resCfg := cfg.Module.ResourceByAddr(resourceAddr) 314 if resCfg == nil { 315 diags = diags.Append(&hcl.Diagnostic{ 316 Severity: hcl.DiagError, 317 Summary: `Reference to undeclared resource`, 318 Detail: fmt.Sprintf(`A resource %s has not been declared in %s`, ref.Subject, moduleDisplayAddr(ctx.Path())), 319 Subject: expr.Range().Ptr(), 320 }) 321 return nil, false, diags 322 } 323 324 if len(changes) == 0 { 325 // If the resource is valid there should always be at least one change. 326 diags = diags.Append(fmt.Errorf("no change found for %s in %s", ref.Subject, moduleDisplayAddr(ctx.Path()))) 327 return nil, false, diags 328 } 329 330 // If we don't have a traversal beyond the resource, then we can just look 331 // for any change. 332 if len(ref.Remaining) == 0 { 333 for _, c := range changes { 334 switch c.ChangeSrc.Action { 335 // Only immediate changes to the resource will trigger replacement. 336 case plans.Update, plans.DeleteThenCreate, plans.CreateThenDelete: 337 return ref, true, diags 338 } 339 } 340 341 // no change triggered 342 return nil, false, diags 343 } 344 345 // This must be an instances to have a remaining traversal, which means a 346 // single change. 347 change := changes[0] 348 349 // Make sure the change is actionable. A create or delete action will have 350 // a change in value, but are not valid for our purposes here. 351 switch change.ChangeSrc.Action { 352 case plans.Update, plans.DeleteThenCreate, plans.CreateThenDelete: 353 // OK 354 default: 355 return nil, false, diags 356 } 357 358 // Since we have a traversal after the resource reference, we will need to 359 // decode the changes, which means we need a schema. 360 providerAddr := change.ProviderAddr 361 schema, err := ctx.ProviderSchema(providerAddr) 362 if err != nil { 363 diags = diags.Append(err) 364 return nil, false, diags 365 } 366 367 resAddr := change.Addr.ContainingResource().Resource 368 resSchema, _ := schema.SchemaForResourceType(resAddr.Mode, resAddr.Type) 369 ty := resSchema.ImpliedType() 370 371 before, err := change.ChangeSrc.Before.Decode(ty) 372 if err != nil { 373 diags = diags.Append(err) 374 return nil, false, diags 375 } 376 377 after, err := change.ChangeSrc.After.Decode(ty) 378 if err != nil { 379 diags = diags.Append(err) 380 return nil, false, diags 381 } 382 383 path := traversalToPath(ref.Remaining) 384 attrBefore, _ := path.Apply(before) 385 attrAfter, _ := path.Apply(after) 386 387 if attrBefore == cty.NilVal || attrAfter == cty.NilVal { 388 replace := attrBefore != attrAfter 389 return ref, replace, diags 390 } 391 392 replace := !attrBefore.RawEquals(attrAfter) 393 394 return ref, replace, diags 395 } 396 397 // EvaluateImportAddress takes the raw reference expression of the import address 398 // from the config, and returns the evaluated address addrs.AbsResourceInstance 399 // 400 // The implementation is inspired by config.AbsTraversalForImportToExpr, but this time we can evaluate the expression 401 // in the indexes of expressions. If we encounter a hclsyntax.IndexExpr, we can evaluate the Key expression and create 402 // an Index Traversal, adding it to the Traverser 403 // TODO move this function into eval_import.go 404 func (ctx *BuiltinEvalContext) EvaluateImportAddress(expr hcl.Expression, keyData instances.RepetitionData) (addrs.AbsResourceInstance, tfdiags.Diagnostics) { 405 traversal, diags := ctx.traversalForImportExpr(expr, keyData) 406 if diags.HasErrors() { 407 return addrs.AbsResourceInstance{}, diags 408 } 409 410 return addrs.ParseAbsResourceInstance(traversal) 411 } 412 413 func (ctx *BuiltinEvalContext) traversalForImportExpr(expr hcl.Expression, keyData instances.RepetitionData) (traversal hcl.Traversal, diags tfdiags.Diagnostics) { 414 switch e := expr.(type) { 415 case *hclsyntax.IndexExpr: 416 t, d := ctx.traversalForImportExpr(e.Collection, keyData) 417 diags = diags.Append(d) 418 traversal = append(traversal, t...) 419 420 tIndex, dIndex := ctx.parseImportIndexKeyExpr(e.Key, keyData) 421 diags = diags.Append(dIndex) 422 traversal = append(traversal, tIndex) 423 case *hclsyntax.RelativeTraversalExpr: 424 t, d := ctx.traversalForImportExpr(e.Source, keyData) 425 diags = diags.Append(d) 426 traversal = append(traversal, t...) 427 traversal = append(traversal, e.Traversal...) 428 case *hclsyntax.ScopeTraversalExpr: 429 traversal = append(traversal, e.Traversal...) 430 default: 431 // This should not happen, as it should have failed validation earlier, in config.AbsTraversalForImportToExpr 432 diags = diags.Append(&hcl.Diagnostic{ 433 Severity: hcl.DiagError, 434 Summary: "Invalid import address expression", 435 Detail: "Import address must be a reference to a resource's address, and only allows for indexing with dynamic keys. For example: module.my_module[expression1].aws_s3_bucket.my_buckets[expression2] for resources inside of modules, or simply aws_s3_bucket.my_bucket for a resource in the root module", 436 Subject: expr.Range().Ptr(), 437 }) 438 } 439 return 440 } 441 442 // parseImportIndexKeyExpr parses an expression that is used as a key in an index, of an HCL expression representing an 443 // import target address, into a traversal of type hcl.TraverseIndex. 444 // After evaluation, the expression must be known, not null, not sensitive, and must be a string (for_each) or a number 445 // (count) 446 func (ctx *BuiltinEvalContext) parseImportIndexKeyExpr(expr hcl.Expression, keyData instances.RepetitionData) (hcl.TraverseIndex, tfdiags.Diagnostics) { 447 idx := hcl.TraverseIndex{ 448 SrcRange: expr.Range(), 449 } 450 451 // evaluate and take into consideration the for_each key (if exists) 452 val, diags := evaluateExprWithRepetitionData(ctx, expr, cty.DynamicPseudoType, keyData) 453 if diags.HasErrors() { 454 return idx, diags 455 } 456 457 if !val.IsKnown() { 458 diags = diags.Append(&hcl.Diagnostic{ 459 Severity: hcl.DiagError, 460 Summary: "Import block 'to' address contains an invalid key", 461 Detail: "Import block contained a resource address using an index that will only be known after apply. Please ensure to use expressions that are known at plan time for the index of an import target address", 462 Subject: expr.Range().Ptr(), 463 }) 464 return idx, diags 465 } 466 467 if val.IsNull() { 468 diags = diags.Append(&hcl.Diagnostic{ 469 Severity: hcl.DiagError, 470 Summary: "Import block 'to' address contains an invalid key", 471 Detail: "Import block contained a resource address using an index which is null. Please ensure the expression for the index is not null", 472 Subject: expr.Range().Ptr(), 473 }) 474 return idx, diags 475 } 476 477 if val.Type() != cty.String && val.Type() != cty.Number { 478 diags = diags.Append(&hcl.Diagnostic{ 479 Severity: hcl.DiagError, 480 Summary: "Import block 'to' address contains an invalid key", 481 Detail: "Import block contained a resource address using an index which is not valid for a resource instance (not a string or a number). Please ensure the expression for the index is correct, and returns either a string or a number", 482 Subject: expr.Range().Ptr(), 483 }) 484 return idx, diags 485 } 486 487 unmarkedVal, valMarks := val.Unmark() 488 if _, sensitive := valMarks[marks.Sensitive]; sensitive { 489 diags = diags.Append(&hcl.Diagnostic{ 490 Severity: hcl.DiagError, 491 Summary: "Import block 'to' address contains an invalid key", 492 Detail: "Import block contained a resource address using an index which is sensitive. Please ensure indexes used in the resource address of an import target are not sensitive", 493 Subject: expr.Range().Ptr(), 494 }) 495 } 496 497 idx.Key = unmarkedVal 498 return idx, diags 499 } 500 501 func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope { 502 if !ctx.pathSet { 503 panic("context path not set") 504 } 505 data := &evaluationStateData{ 506 Evaluator: ctx.Evaluator, 507 ModulePath: ctx.PathValue, 508 InstanceKeyData: keyData, 509 Operation: ctx.Evaluator.Operation, 510 } 511 512 // ctx.PathValue is the path of the module that contains whatever 513 // expression the caller will be trying to evaluate, so this will 514 // activate only the experiments from that particular module, to 515 // be consistent with how experiment checking in the "configs" 516 // package itself works. The nil check here is for robustness in 517 // incompletely-mocked testing situations; mc should never be nil in 518 // real situations. 519 mc := ctx.Evaluator.Config.DescendentForInstance(ctx.PathValue) 520 521 if mc == nil || mc.Module.ProviderRequirements == nil { 522 return ctx.Evaluator.Scope(data, self, source, nil) 523 } 524 525 scope := ctx.Evaluator.Scope(data, self, source, func(pf addrs.ProviderFunction, rng tfdiags.SourceRange) (*function.Function, tfdiags.Diagnostics) { 526 return evalContextProviderFunction(ctx.Provider, mc, ctx.Evaluator.Operation, pf, rng) 527 }) 528 scope.SetActiveExperiments(mc.Module.ActiveExperiments) 529 530 return scope 531 } 532 533 func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance { 534 if !ctx.pathSet { 535 panic("context path not set") 536 } 537 return ctx.PathValue 538 } 539 540 func (ctx *BuiltinEvalContext) SetRootModuleArgument(addr addrs.InputVariable, v cty.Value) { 541 ctx.VariableValuesLock.Lock() 542 defer ctx.VariableValuesLock.Unlock() 543 544 log.Printf("[TRACE] BuiltinEvalContext: Storing final value for variable %s", addr.Absolute(addrs.RootModuleInstance)) 545 key := addrs.RootModuleInstance.String() 546 args := ctx.VariableValues[key] 547 if args == nil { 548 args = make(map[string]cty.Value) 549 ctx.VariableValues[key] = args 550 } 551 args[addr.Name] = v 552 } 553 554 func (ctx *BuiltinEvalContext) SetModuleCallArgument(callAddr addrs.ModuleCallInstance, varAddr addrs.InputVariable, v cty.Value) { 555 ctx.VariableValuesLock.Lock() 556 defer ctx.VariableValuesLock.Unlock() 557 558 if !ctx.pathSet { 559 panic("context path not set") 560 } 561 562 childPath := callAddr.ModuleInstance(ctx.PathValue) 563 log.Printf("[TRACE] BuiltinEvalContext: Storing final value for variable %s", varAddr.Absolute(childPath)) 564 key := childPath.String() 565 args := ctx.VariableValues[key] 566 if args == nil { 567 args = make(map[string]cty.Value) 568 ctx.VariableValues[key] = args 569 } 570 args[varAddr.Name] = v 571 } 572 573 func (ctx *BuiltinEvalContext) GetVariableValue(addr addrs.AbsInputVariableInstance) cty.Value { 574 ctx.VariableValuesLock.Lock() 575 defer ctx.VariableValuesLock.Unlock() 576 577 modKey := addr.Module.String() 578 modVars := ctx.VariableValues[modKey] 579 val, ok := modVars[addr.Variable.Name] 580 if !ok { 581 return cty.DynamicVal 582 } 583 return val 584 } 585 586 func (ctx *BuiltinEvalContext) Changes() *plans.ChangesSync { 587 return ctx.ChangesValue 588 } 589 590 func (ctx *BuiltinEvalContext) State() *states.SyncState { 591 return ctx.StateValue 592 } 593 594 func (ctx *BuiltinEvalContext) Checks() *checks.State { 595 return ctx.ChecksValue 596 } 597 598 func (ctx *BuiltinEvalContext) RefreshState() *states.SyncState { 599 return ctx.RefreshStateValue 600 } 601 602 func (ctx *BuiltinEvalContext) PrevRunState() *states.SyncState { 603 return ctx.PrevRunStateValue 604 } 605 606 func (ctx *BuiltinEvalContext) InstanceExpander() *instances.Expander { 607 return ctx.InstanceExpanderValue 608 } 609 610 func (ctx *BuiltinEvalContext) MoveResults() refactoring.MoveResults { 611 return ctx.MoveResultsValue 612 } 613 614 func (ctx *BuiltinEvalContext) ImportResolver() *ImportResolver { 615 return ctx.ImportResolverValue 616 } 617 618 func (ctx *BuiltinEvalContext) GetEncryption() encryption.Encryption { 619 return ctx.Encryption 620 }