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 // 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 is a library of plugin components (providers and provisioners) // available for use during a graph walk. 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 refactoring.MoveResults } // 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, error) { return ctx.Plugins.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, err := ctx.ProviderSchema(addr) if err != nil { diags = diags.Append(fmt.Errorf("failed to read schema for %s: %s", addr, err)) return diags } 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, error) { return ctx.Plugins.ProvisionerSchema(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) SetRootModuleArgument(addr addrs.InputVariable, v cty.Value) { ctx.VariableValuesLock.Lock() defer ctx.VariableValuesLock.Unlock() log.Printf("[TRACE] BuiltinEvalContext: Storing final value for variable %s", addr.Absolute(addrs.RootModuleInstance)) key := addrs.RootModuleInstance.String() args := ctx.VariableValues[key] if args == nil { args = make(map[string]cty.Value) ctx.VariableValues[key] = args } args[addr.Name] = v } func (ctx *BuiltinEvalContext) SetModuleCallArgument(callAddr addrs.ModuleCallInstance, varAddr addrs.InputVariable, v cty.Value) { ctx.VariableValuesLock.Lock() defer ctx.VariableValuesLock.Unlock() if !ctx.pathSet { panic("context path not set") } childPath := callAddr.ModuleInstance(ctx.PathValue) log.Printf("[TRACE] BuiltinEvalContext: Storing final value for variable %s", varAddr.Absolute(childPath)) key := childPath.String() args := ctx.VariableValues[key] if args == nil { args = make(map[string]cty.Value) ctx.VariableValues[key] = args } args[varAddr.Name] = 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() refactoring.MoveResults { return ctx.MoveResultsValue }