mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-03 20:57:09 -06:00
80b3fcf93e
In the v0.12 timeframe we made contextComponentFactory an interface with the expectation that we'd write mocks of it for tests, but in practice we ended up just always using the same "basicComponentFactory" implementation throughout. In the interests of simplification then, here we replace that interface and its sole implementation with a new concrete struct type contextPlugins. Along with the general benefit that this removes an unneeded indirection, this also means that we can add additional methods to the struct type without the usual restriction that interface types prefer to be small. In particular, in a future commit I'm planning to add methods for loading provider and provisioner schemas, working with the currently-unused new fields this commit has included in contextPlugins, as compared to its predecessor basicComponentFactory.
376 lines
11 KiB
Go
376 lines
11 KiB
Go
package terraform
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/terraform/internal/instances"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/provisioners"
|
|
"github.com/hashicorp/terraform/internal/refactoring"
|
|
"github.com/hashicorp/terraform/version"
|
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// BuiltinEvalContext is an EvalContext implementation that is used by
|
|
// Terraform by default.
|
|
type BuiltinEvalContext struct {
|
|
// StopContext is the context used to track whether we're complete
|
|
StopContext context.Context
|
|
|
|
// PathValue is the Path that this context is operating within.
|
|
PathValue addrs.ModuleInstance
|
|
|
|
// pathSet indicates that this context was explicitly created for a
|
|
// specific path, and can be safely used for evaluation. This lets us
|
|
// differentiate between PathValue being unset, and the zero value which is
|
|
// equivalent to RootModuleInstance. Path and Evaluation methods will
|
|
// panic if this is not set.
|
|
pathSet bool
|
|
|
|
// Evaluator is used for evaluating expressions within the scope of this
|
|
// eval context.
|
|
Evaluator *Evaluator
|
|
|
|
// Schemas is a repository of all of the schemas we should need to
|
|
// decode configuration blocks and expressions. This must be constructed by
|
|
// the caller to include schemas for all of the providers, resource types,
|
|
// data sources and provisioners used by the given configuration and
|
|
// state.
|
|
//
|
|
// This must not be mutated during evaluation.
|
|
Schemas *Schemas
|
|
|
|
// VariableValues contains the variable values across all modules. This
|
|
// structure is shared across the entire containing context, and so it
|
|
// may be accessed only when holding VariableValuesLock.
|
|
// The keys of the first level of VariableValues are the string
|
|
// representations of addrs.ModuleInstance values. The second-level keys
|
|
// are variable names within each module instance.
|
|
VariableValues map[string]map[string]cty.Value
|
|
VariableValuesLock *sync.Mutex
|
|
|
|
Plugins *contextPlugins
|
|
Hooks []Hook
|
|
InputValue UIInput
|
|
ProviderCache map[string]providers.Interface
|
|
ProviderInputConfig map[string]map[string]cty.Value
|
|
ProviderLock *sync.Mutex
|
|
ProvisionerCache map[string]provisioners.Interface
|
|
ProvisionerLock *sync.Mutex
|
|
ChangesValue *plans.ChangesSync
|
|
StateValue *states.SyncState
|
|
RefreshStateValue *states.SyncState
|
|
PrevRunStateValue *states.SyncState
|
|
InstanceExpanderValue *instances.Expander
|
|
MoveResultsValue map[addrs.UniqueKey]refactoring.MoveResult
|
|
}
|
|
|
|
// BuiltinEvalContext implements EvalContext
|
|
var _ EvalContext = (*BuiltinEvalContext)(nil)
|
|
|
|
func (ctx *BuiltinEvalContext) WithPath(path addrs.ModuleInstance) EvalContext {
|
|
newCtx := *ctx
|
|
newCtx.pathSet = true
|
|
newCtx.PathValue = path
|
|
return &newCtx
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} {
|
|
// This can happen during tests. During tests, we just block forever.
|
|
if ctx.StopContext == nil {
|
|
return nil
|
|
}
|
|
|
|
return ctx.StopContext.Done()
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
|
|
for _, h := range ctx.Hooks {
|
|
action, err := fn(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch action {
|
|
case HookActionContinue:
|
|
continue
|
|
case HookActionHalt:
|
|
// Return an early exit error to trigger an early exit
|
|
log.Printf("[WARN] Early exit triggered by hook: %T", h)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Input() UIInput {
|
|
return ctx.InputValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig) (providers.Interface, error) {
|
|
// If we already initialized, it is an error
|
|
if p := ctx.Provider(addr); p != nil {
|
|
return nil, fmt.Errorf("%s is already initialized", addr)
|
|
}
|
|
|
|
// Warning: make sure to acquire these locks AFTER the call to Provider
|
|
// above, since it also acquires locks.
|
|
ctx.ProviderLock.Lock()
|
|
defer ctx.ProviderLock.Unlock()
|
|
|
|
key := addr.String()
|
|
|
|
p, err := ctx.Plugins.NewProviderInstance(addr.Provider)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", addr.String(), addr)
|
|
ctx.ProviderCache[key] = p
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) providers.Interface {
|
|
ctx.ProviderLock.Lock()
|
|
defer ctx.ProviderLock.Unlock()
|
|
|
|
return ctx.ProviderCache[addr.String()]
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) *ProviderSchema {
|
|
return ctx.Schemas.ProviderSchema(addr.Provider)
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.AbsProviderConfig) error {
|
|
ctx.ProviderLock.Lock()
|
|
defer ctx.ProviderLock.Unlock()
|
|
|
|
key := addr.String()
|
|
provider := ctx.ProviderCache[key]
|
|
if provider != nil {
|
|
delete(ctx.ProviderCache, key)
|
|
return provider.Close()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.AbsProviderConfig, cfg cty.Value) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
if !addr.Module.Equal(ctx.Path().Module()) {
|
|
// This indicates incorrect use of ConfigureProvider: it should be used
|
|
// only from the module that the provider configuration belongs to.
|
|
panic(fmt.Sprintf("%s configured by wrong module %s", addr, ctx.Path()))
|
|
}
|
|
|
|
p := ctx.Provider(addr)
|
|
if p == nil {
|
|
diags = diags.Append(fmt.Errorf("%s not initialized", addr))
|
|
return diags
|
|
}
|
|
|
|
providerSchema := ctx.ProviderSchema(addr)
|
|
if providerSchema == nil {
|
|
diags = diags.Append(fmt.Errorf("schema for %s is not available", addr))
|
|
return diags
|
|
}
|
|
|
|
req := providers.ConfigureProviderRequest{
|
|
TerraformVersion: version.String(),
|
|
Config: cfg,
|
|
}
|
|
|
|
resp := p.ConfigureProvider(req)
|
|
return resp.Diagnostics
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) ProviderInput(pc addrs.AbsProviderConfig) map[string]cty.Value {
|
|
ctx.ProviderLock.Lock()
|
|
defer ctx.ProviderLock.Unlock()
|
|
|
|
if !pc.Module.Equal(ctx.Path().Module()) {
|
|
// This indicates incorrect use of InitProvider: it should be used
|
|
// only from the module that the provider configuration belongs to.
|
|
panic(fmt.Sprintf("%s initialized by wrong module %s", pc, ctx.Path()))
|
|
}
|
|
|
|
if !ctx.Path().IsRoot() {
|
|
// Only root module provider configurations can have input.
|
|
return nil
|
|
}
|
|
|
|
return ctx.ProviderInputConfig[pc.String()]
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) SetProviderInput(pc addrs.AbsProviderConfig, c map[string]cty.Value) {
|
|
absProvider := pc
|
|
if !pc.Module.IsRoot() {
|
|
// Only root module provider configurations can have input.
|
|
log.Printf("[WARN] BuiltinEvalContext: attempt to SetProviderInput for non-root module")
|
|
return
|
|
}
|
|
|
|
// Save the configuration
|
|
ctx.ProviderLock.Lock()
|
|
ctx.ProviderInputConfig[absProvider.String()] = c
|
|
ctx.ProviderLock.Unlock()
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Provisioner(n string) (provisioners.Interface, error) {
|
|
ctx.ProvisionerLock.Lock()
|
|
defer ctx.ProvisionerLock.Unlock()
|
|
|
|
p, ok := ctx.ProvisionerCache[n]
|
|
if !ok {
|
|
var err error
|
|
p, err = ctx.Plugins.NewProvisionerInstance(n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctx.ProvisionerCache[n] = p
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block {
|
|
return ctx.Schemas.ProvisionerConfig(n)
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) CloseProvisioners() error {
|
|
var diags tfdiags.Diagnostics
|
|
ctx.ProvisionerLock.Lock()
|
|
defer ctx.ProvisionerLock.Unlock()
|
|
|
|
for name, prov := range ctx.ProvisionerCache {
|
|
err := prov.Close()
|
|
if err != nil {
|
|
diags = diags.Append(fmt.Errorf("provisioner.Close %s: %s", name, err))
|
|
}
|
|
}
|
|
|
|
return diags.Err()
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, keyData InstanceKeyEvalData) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
scope := ctx.EvaluationScope(self, keyData)
|
|
body, evalDiags := scope.ExpandBlock(body, schema)
|
|
diags = diags.Append(evalDiags)
|
|
val, evalDiags := scope.EvalBlock(body, schema)
|
|
diags = diags.Append(evalDiags)
|
|
return val, body, diags
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Type, self addrs.Referenceable) (cty.Value, tfdiags.Diagnostics) {
|
|
scope := ctx.EvaluationScope(self, EvalDataForNoInstanceKey)
|
|
return scope.EvalExpr(expr, wantType)
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
|
|
if !ctx.pathSet {
|
|
panic("context path not set")
|
|
}
|
|
data := &evaluationStateData{
|
|
Evaluator: ctx.Evaluator,
|
|
ModulePath: ctx.PathValue,
|
|
InstanceKeyData: keyData,
|
|
Operation: ctx.Evaluator.Operation,
|
|
}
|
|
scope := ctx.Evaluator.Scope(data, self)
|
|
|
|
// ctx.PathValue is the path of the module that contains whatever
|
|
// expression the caller will be trying to evaluate, so this will
|
|
// activate only the experiments from that particular module, to
|
|
// be consistent with how experiment checking in the "configs"
|
|
// package itself works. The nil check here is for robustness in
|
|
// incompletely-mocked testing situations; mc should never be nil in
|
|
// real situations.
|
|
if mc := ctx.Evaluator.Config.DescendentForInstance(ctx.PathValue); mc != nil {
|
|
scope.SetActiveExperiments(mc.Module.ActiveExperiments)
|
|
}
|
|
return scope
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance {
|
|
if !ctx.pathSet {
|
|
panic("context path not set")
|
|
}
|
|
return ctx.PathValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) SetModuleCallArguments(n addrs.ModuleCallInstance, vals map[string]cty.Value) {
|
|
ctx.VariableValuesLock.Lock()
|
|
defer ctx.VariableValuesLock.Unlock()
|
|
|
|
if !ctx.pathSet {
|
|
panic("context path not set")
|
|
}
|
|
|
|
childPath := n.ModuleInstance(ctx.PathValue)
|
|
key := childPath.String()
|
|
|
|
args := ctx.VariableValues[key]
|
|
if args == nil {
|
|
ctx.VariableValues[key] = vals
|
|
return
|
|
}
|
|
|
|
for k, v := range vals {
|
|
args[k] = v
|
|
}
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) GetVariableValue(addr addrs.AbsInputVariableInstance) cty.Value {
|
|
ctx.VariableValuesLock.Lock()
|
|
defer ctx.VariableValuesLock.Unlock()
|
|
|
|
modKey := addr.Module.String()
|
|
modVars := ctx.VariableValues[modKey]
|
|
val, ok := modVars[addr.Variable.Name]
|
|
if !ok {
|
|
return cty.DynamicVal
|
|
}
|
|
return val
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) Changes() *plans.ChangesSync {
|
|
return ctx.ChangesValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) State() *states.SyncState {
|
|
return ctx.StateValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) RefreshState() *states.SyncState {
|
|
return ctx.RefreshStateValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) PrevRunState() *states.SyncState {
|
|
return ctx.PrevRunStateValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) InstanceExpander() *instances.Expander {
|
|
return ctx.InstanceExpanderValue
|
|
}
|
|
|
|
func (ctx *BuiltinEvalContext) MoveResults() map[addrs.UniqueKey]refactoring.MoveResult {
|
|
return ctx.MoveResultsValue
|
|
}
|