blob: c9950f608d1bb8bf60f4dd7e1849d1ae5d63eba9 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package terraform
import (
"context"
"fmt"
"log"
"sync"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/experiments"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/namedvals"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/deferring"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
"github.com/hashicorp/terraform/internal/refactoring"
"github.com/hashicorp/terraform/internal/resources/ephemeral"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/version"
)
// BuiltinEvalContext is an EvalContext implementation that is used by
// Terraform by default.
type BuiltinEvalContext struct {
// scope is the scope (module instance or set of possible module instances)
// that this context is operating within.
//
// Note: this can be evalContextGlobal (i.e. nil) when visiting a graph
// node that doesn't belong to a particular module, in which case any
// method using it will panic.
scope evalContextScope
// StopContext is the context used to track whether we're complete
StopContext context.Context
// Evaluator is used for evaluating expressions within the scope of this
// eval context.
Evaluator *Evaluator
// NamedValuesValue is where we keep the values of already-evaluated input
// variables, local values, and output values.
NamedValuesValue *namedvals.State
// Plugins is a library of plugin components (providers and provisioners)
// available for use during a graph walk.
Plugins *contextPlugins
// ExternalProviderConfigs are pre-configured provider instances passed
// in by the caller, for situations like Stack components where the
// root module isn't designed to be planned and applied in isolation and
// instead expects to recieve certain provider configurations from the
// stack configuration.
ExternalProviderConfigs map[addrs.RootProviderConfig]providers.Interface
// DeferralsValue is the object returned by [BuiltinEvalContext.Deferrals].
DeferralsValue *deferring.Deferred
// forget if set to true will cause the plan to forget all resources. This is
// only allowd in the context of a destroy plan.
forget bool
Hooks []Hook
InputValue UIInput
ProviderCache map[string]providers.Interface
ProviderFuncCache map[string]providers.Interface
ProviderFuncResults *providers.FunctionResults
ProviderInputConfig map[string]map[string]cty.Value
ProviderLock *sync.Mutex
ProvisionerCache map[string]provisioners.Interface
ProvisionerLock *sync.Mutex
ChangesValue *plans.ChangesSync
StateValue *states.SyncState
ChecksValue *checks.State
EphemeralResourcesValue *ephemeral.Resources
RefreshStateValue *states.SyncState
PrevRunStateValue *states.SyncState
InstanceExpanderValue *instances.Expander
MoveResultsValue refactoring.MoveResults
OverrideValues *mocking.Overrides
}
// BuiltinEvalContext implements EvalContext
var _ EvalContext = (*BuiltinEvalContext)(nil)
func (ctx *BuiltinEvalContext) withScope(scope evalContextScope) EvalContext {
newCtx := *ctx
newCtx.scope = scope
return &newCtx
}
func (ctx *BuiltinEvalContext) StopCtx() context.Context {
// This can happen during tests. During tests, we just block forever.
if ctx.StopContext == nil {
return context.TODO()
}
return ctx.StopContext
}
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, config *configs.Provider) (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()
if addr.Module.IsRoot() {
rootAddr := addrs.RootProviderConfig{
Provider: addr.Provider,
Alias: addr.Alias,
}
if external, isExternal := ctx.ExternalProviderConfigs[rootAddr]; isExternal {
// External providers should always be pre-configured by the
// external caller, and so we'll wrap them in a type that
// makes operations like ConfigureProvider and Close be no-op.
wrapped := externalProviderWrapper{external}
ctx.ProviderCache[key] = wrapped
return wrapped, nil
}
}
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)
// The config might be nil, if there was no config block defined for this
// provider.
if config != nil && config.Mock {
log.Printf("[TRACE] BuiltinEvalContext: Mocked %q provider for %s", addr.String(), addr)
p = &providers.Mock{
Provider: p,
Data: config.MockData,
}
}
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) (providers.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
}
req := providers.ConfigureProviderRequest{
TerraformVersion: version.String(),
Config: cfg,
ClientCapabilities: providers.ClientCapabilities{
DeferralAllowed: ctx.Deferrals().DeferralAllowed(),
WriteOnlyAttributesAllowed: true,
},
}
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) ClosePlugins() 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))
}
delete(ctx.ProvisionerCache, name)
}
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
for name, prov := range ctx.ProviderFuncCache {
err := prov.Close()
if err != nil {
diags = diags.Append(fmt.Errorf("provider.Close %s: %s", name, err))
}
delete(ctx.ProviderFuncCache, name)
}
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, nil, 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, nil, EvalDataForNoInstanceKey)
return scope.EvalExpr(expr, wantType)
}
func (ctx *BuiltinEvalContext) EvaluateReplaceTriggeredBy(expr hcl.Expression, repData instances.RepetitionData) (*addrs.Reference, bool, tfdiags.Diagnostics) {
// get the reference to lookup changes in the plan
ref, diags := evalReplaceTriggeredByExpr(expr, repData)
if diags.HasErrors() {
return nil, false, diags
}
var changes []*plans.ResourceInstanceChange
// store the address once we get it for validation
var resourceAddr addrs.Resource
// The reference is either a resource or resource instance
switch sub := ref.Subject.(type) {
case addrs.Resource:
resourceAddr = sub
rc := sub.Absolute(ctx.Path())
changes = ctx.Changes().GetChangesForAbsResource(rc)
case addrs.ResourceInstance:
resourceAddr = sub.ContainingResource()
rc := sub.Absolute(ctx.Path())
change := ctx.Changes().GetResourceInstanceChange(rc, addrs.NotDeposed)
if change != nil {
// we'll generate an error below if there was no change
changes = append(changes, change)
}
}
// Do some validation to make sure we are expecting a change at all
cfg := ctx.Evaluator.Config.Descendant(ctx.Path().Module())
resCfg := cfg.Module.ResourceByAddr(resourceAddr)
if resCfg == nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Reference to undeclared resource`,
Detail: fmt.Sprintf(`A resource %s has not been declared in %s`, ref.Subject, moduleDisplayAddr(ctx.Path())),
Subject: expr.Range().Ptr(),
})
return nil, false, diags
}
if len(changes) == 0 {
// If the resource is valid there should always be at least one change.
diags = diags.Append(fmt.Errorf("no change found for %s in %s", ref.Subject, moduleDisplayAddr(ctx.Path())))
return nil, false, diags
}
// If we don't have a traversal beyond the resource, then we can just look
// for any change.
if len(ref.Remaining) == 0 {
for _, c := range changes {
switch c.Change.Action {
// Only immediate changes to the resource will trigger replacement.
case plans.Update, plans.DeleteThenCreate, plans.CreateThenDelete:
return ref, true, diags
}
}
// no change triggered
return nil, false, diags
}
// This must be an instances to have a remaining traversal, which means a
// single change.
change := changes[0]
// Make sure the change is actionable. A create or delete action will have
// a change in value, but are not valid for our purposes here.
switch change.Change.Action {
case plans.Update, plans.DeleteThenCreate, plans.CreateThenDelete:
// OK
default:
return nil, false, diags
}
path, _ := traversalToPath(ref.Remaining)
attrBefore, _ := path.Apply(change.Before)
attrAfter, _ := path.Apply(change.After)
if attrBefore == cty.NilVal || attrAfter == cty.NilVal {
replace := attrBefore != attrAfter
return ref, replace, diags
}
replace := !attrBefore.RawEquals(attrAfter)
return ref, replace, diags
}
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, source addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
switch scope := ctx.scope.(type) {
case evalContextModuleInstance:
data := &evaluationStateData{
evaluationData: &evaluationData{
Evaluator: ctx.Evaluator,
Module: scope.Addr.Module(),
},
ModulePath: scope.Addr,
InstanceKeyData: keyData,
Operation: ctx.Evaluator.Operation,
}
evalScope := ctx.Evaluator.Scope(data, self, source, ctx.evaluationExternalFunctions())
// 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.DescendantForInstance(scope.Addr); mc != nil {
evalScope.SetActiveExperiments(mc.Module.ActiveExperiments)
}
return evalScope
case evalContextPartialExpandedModule:
data := &evaluationPlaceholderData{
evaluationData: &evaluationData{
Evaluator: ctx.Evaluator,
Module: scope.Addr.Module(),
},
ModulePath: scope.Addr,
CountAvailable: keyData.CountIndex != cty.NilVal,
EachAvailable: keyData.EachKey != cty.NilVal,
Operation: ctx.Evaluator.Operation,
}
evalScope := ctx.Evaluator.Scope(data, self, source, ctx.evaluationExternalFunctions())
if mc := ctx.Evaluator.Config.Descendant(scope.Addr.Module()); mc != nil {
evalScope.SetActiveExperiments(mc.Module.ActiveExperiments)
}
return evalScope
default:
// This method is valid only for module-scoped EvalContext objects.
panic("no evaluation scope available: not in module context")
}
}
// evaluationExternalFunctions is a helper for method EvaluationScope which
// determines the set of external functions that should be available for
// evaluation in this EvalContext, based on declarations in the configuration.
func (ctx *BuiltinEvalContext) evaluationExternalFunctions() lang.ExternalFuncs {
// The set of functions in scope includes the functions contributed by
// every provider that the current module has as a requirement.
//
// We expose them under the local name for each provider that was selected
// by the module author.
ret := lang.ExternalFuncs{}
cfg := ctx.Evaluator.Config.Descendant(ctx.scope.evalContextScopeModule())
if cfg == nil {
// It's weird to not have a configuration by this point, but we'll
// tolerate it for robustness and just return no functions at all.
return ret
}
if cfg.Module.ProviderRequirements == nil {
// A module with no provider requirements can't have any
// provider-contributed functions.
return ret
}
reqs := cfg.Module.ProviderRequirements.RequiredProviders
ret.Provider = make(map[string]map[string]function.Function, len(reqs))
for localName, req := range reqs {
providerAddr := req.Type
funcDecls, err := ctx.Plugins.ProviderFunctionDecls(providerAddr)
if err != nil {
// If a particular provider can't return schema then we'll catch
// it in plenty other places where it's more reasonable for us
// to return an error, so here we'll just treat it as having
// no functions.
log.Printf("[WARN] Error loading schema for %s to determine its functions: %s", providerAddr, err)
continue
}
ret.Provider[localName] = make(map[string]function.Function, len(funcDecls))
funcs := ret.Provider[localName]
for name, decl := range funcDecls {
funcs[name] = decl.BuildFunction(providerAddr, name, ctx.ProviderFuncResults, func() (providers.Interface, error) {
return ctx.functionProvider(providerAddr)
})
}
}
return ret
}
// functionProvider fetches a running provider instance for evaluating
// functions from the cache, or starts a new instance and adds it to the cache.
func (ctx *BuiltinEvalContext) functionProvider(addr addrs.Provider) (providers.Interface, error) {
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
p, ok := ctx.ProviderFuncCache[addr.String()]
if ok {
return p, nil
}
log.Printf("[TRACE] starting function provider instance for %s", addr)
p, err := ctx.Plugins.NewProviderInstance(addr)
if err == nil {
ctx.ProviderFuncCache[addr.String()] = p
}
return p, err
}
func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance {
if scope, ok := ctx.scope.(evalContextModuleInstance); ok {
return scope.Addr
}
panic("not evaluating in the scope of a fully-expanded module")
}
func (ctx *BuiltinEvalContext) LanguageExperimentActive(experiment experiments.Experiment) bool {
if ctx.Evaluator == nil || ctx.Evaluator.Config == nil {
// Should not get here in normal code, but might get here in test code
// if the context isn't fully populated.
return false
}
scope := ctx.scope
if scope == evalContextGlobal {
// If we're not associated with a specific module then there can't
// be any language experiments in play, because experiment activation
// is module-scoped.
return false
}
cfg := ctx.Evaluator.Config.Descendant(scope.evalContextScopeModule())
if cfg == nil {
return false
}
return cfg.Module.ActiveExperiments.Has(experiment)
}
func (ctx *BuiltinEvalContext) NamedValues() *namedvals.State {
return ctx.NamedValuesValue
}
func (ctx *BuiltinEvalContext) Deferrals() *deferring.Deferred {
return ctx.DeferralsValue
}
func (ctx *BuiltinEvalContext) Changes() *plans.ChangesSync {
return ctx.ChangesValue
}
func (ctx *BuiltinEvalContext) State() *states.SyncState {
return ctx.StateValue
}
func (ctx *BuiltinEvalContext) Checks() *checks.State {
return ctx.ChecksValue
}
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
}
func (ctx *BuiltinEvalContext) Overrides() *mocking.Overrides {
return ctx.OverrideValues
}
func (ctx *BuiltinEvalContext) Forget() bool {
return ctx.forget
}
func (ctx *BuiltinEvalContext) EphemeralResources() *ephemeral.Resources {
return ctx.EphemeralResourcesValue
}
func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities {
return providers.ClientCapabilities{
DeferralAllowed: ctx.Deferrals().DeferralAllowed(),
WriteOnlyAttributesAllowed: true,
}
}