opentofu/terraform/eval_context_builtin.go
Martin Atkins 2002fee32e core: Context.Input as config walk, rather than graph walk
Now that core has access to the provider configuration schema, our input
logic can be implemented entirely within Context.Input, removing the need
to execute a full graph walk to gather input.

This commit replaces the graph walk call with instead just visiting the
provider configurations (explicit and implied) in the root module, using
the schema to prompt.

The code to manage the input graph walk is not yet removed by this commit,
and will be cleaned up in a subsequent commit once we've made sure there
aren't any other callers/tests depending on parts of it.
2018-10-16 18:49:20 -07:00

362 lines
9.8 KiB
Go

package terraform
import (
"context"
"fmt"
"log"
"sync"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/terraform/lang"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/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
// 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
Components contextComponentFactory
Hooks []Hook
InputValue UIInput
ProviderCache map[string]ResourceProvider
ProviderSchemas map[string]*ProviderSchema
ProviderInputConfig map[string]map[string]cty.Value
ProviderLock *sync.Mutex
ProvisionerCache map[string]ResourceProvisioner
ProvisionerSchemas map[string]*configschema.Block
ProvisionerLock *sync.Mutex
DiffValue *Diff
DiffLock *sync.RWMutex
StateValue *State
StateLock *sync.RWMutex
once sync.Once
}
// BuiltinEvalContext implements EvalContext
var _ EvalContext = (*BuiltinEvalContext)(nil)
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 EvalEarlyExitError{}
}
}
return nil
}
func (ctx *BuiltinEvalContext) Input() UIInput {
return ctx.InputValue
}
func (ctx *BuiltinEvalContext) InitProvider(typeName string, addr addrs.ProviderConfig) (ResourceProvider, error) {
ctx.once.Do(ctx.init)
absAddr := addr.Absolute(ctx.Path())
// If we already initialized, it is an error
if p := ctx.Provider(absAddr); 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 := absAddr.String()
p, err := ctx.Components.ResourceProvider(typeName, key)
if err != nil {
return nil, err
}
log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", typeName, absAddr)
ctx.ProviderCache[key] = p
// Also fetch and cache the provider's schema.
// FIXME: This is using a non-ideal provider API that requires us to
// request specific resource types, but we actually just want _all_ the
// resource types, so we'll list these first. Once the provider API is
// updated we'll get enough data to populate this whole structure in
// a single call.
resourceTypes := p.Resources()
dataSources := p.DataSources()
resourceTypeNames := make([]string, len(resourceTypes))
for i, t := range resourceTypes {
resourceTypeNames[i] = t.Name
}
dataSourceNames := make([]string, len(dataSources))
for i, t := range dataSources {
dataSourceNames[i] = t.Name
}
schema, err := p.GetSchema(&ProviderSchemaRequest{
DataSources: dataSourceNames,
ResourceTypes: resourceTypeNames,
})
if err != nil {
return nil, fmt.Errorf("error fetching schema for %s: %s", key, err)
}
if ctx.ProviderSchemas == nil {
ctx.ProviderSchemas = make(map[string]*ProviderSchema)
}
ctx.ProviderSchemas[typeName] = schema
return p, nil
}
func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) ResourceProvider {
ctx.once.Do(ctx.init)
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
return ctx.ProviderCache[addr.String()]
}
func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) *ProviderSchema {
ctx.once.Do(ctx.init)
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
return ctx.ProviderSchemas[addr.ProviderConfig.Type]
}
func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.ProviderConfig) error {
ctx.once.Do(ctx.init)
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
key := addr.String()
var provider interface{}
provider = ctx.ProviderCache[key]
if provider != nil {
if p, ok := provider.(ResourceProviderCloser); ok {
delete(ctx.ProviderCache, key)
return p.Close()
}
}
return nil
}
func (ctx *BuiltinEvalContext) ConfigureProvider(addr addrs.ProviderConfig, cfg cty.Value) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
absAddr := addr.Absolute(ctx.Path())
p := ctx.Provider(absAddr)
if p == nil {
diags = diags.Append(fmt.Errorf("%s not initialized", addr))
return diags
}
providerSchema := ctx.ProviderSchema(absAddr)
if providerSchema == nil {
diags = diags.Append(fmt.Errorf("schema for %s is not available", absAddr))
return diags
}
// FIXME: The provider API isn't yet updated to take a cty.Value directly.
rc := NewResourceConfigShimmed(cfg, providerSchema.Provider)
err := p.Configure(rc)
if err != nil {
diags = diags.Append(err)
}
return diags
}
func (ctx *BuiltinEvalContext) ProviderInput(pc addrs.ProviderConfig) map[string]cty.Value {
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
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.ProviderConfig, c map[string]cty.Value) {
absProvider := pc.Absolute(ctx.Path())
// Save the configuration
ctx.ProviderLock.Lock()
ctx.ProviderInputConfig[absProvider.String()] = c
ctx.ProviderLock.Unlock()
}
func (ctx *BuiltinEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) {
ctx.once.Do(ctx.init)
// If we already initialized, it is an error
if p := ctx.Provisioner(n); p != nil {
return nil, fmt.Errorf("Provisioner '%s' already initialized", n)
}
// Warning: make sure to acquire these locks AFTER the call to Provisioner
// above, since it also acquires locks.
ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock()
key := PathObjectCacheKey(ctx.Path(), n)
p, err := ctx.Components.ResourceProvisioner(n, key)
if err != nil {
return nil, err
}
ctx.ProvisionerCache[key] = p
// Also fetch the provisioner's schema
schema, err := p.GetConfigSchema()
if err != nil {
return nil, fmt.Errorf("error getting schema for provisioner %q: %s", n, err)
}
if ctx.ProvisionerSchemas == nil {
ctx.ProvisionerSchemas = make(map[string]*configschema.Block)
}
ctx.ProvisionerSchemas[n] = schema
return p, nil
}
func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
ctx.once.Do(ctx.init)
ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock()
key := PathObjectCacheKey(ctx.Path(), n)
return ctx.ProvisionerCache[key]
}
func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block {
ctx.once.Do(ctx.init)
ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock()
return ctx.ProvisionerSchemas[n]
}
func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {
ctx.once.Do(ctx.init)
ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock()
key := PathObjectCacheKey(ctx.Path(), n)
var prov interface{}
prov = ctx.ProvisionerCache[key]
if prov != nil {
if p, ok := prov.(ResourceProvisionerCloser); ok {
delete(ctx.ProvisionerCache, key)
return p.Close()
}
}
return nil
}
func (ctx *BuiltinEvalContext) EvaluateBlock(body hcl.Body, schema *configschema.Block, self addrs.Referenceable, key addrs.InstanceKey) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
scope := ctx.EvaluationScope(self, key)
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, addrs.NoKey)
return scope.EvalExpr(expr, wantType)
}
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, key addrs.InstanceKey) *lang.Scope {
data := &evaluationStateData{
Evaluator: ctx.Evaluator,
ModulePath: ctx.PathValue,
InstanceKey: key,
}
return ctx.Evaluator.Scope(data, self)
}
func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance {
return ctx.PathValue
}
func (ctx *BuiltinEvalContext) SetModuleCallArguments(n addrs.ModuleCallInstance, vals map[string]cty.Value) {
ctx.VariableValuesLock.Lock()
defer ctx.VariableValuesLock.Unlock()
childPath := n.ModuleInstance(ctx.PathValue)
key := childPath.String()
args := ctx.VariableValues[key]
if args == nil {
args = make(map[string]cty.Value)
ctx.VariableValues[key] = vals
return
}
for k, v := range vals {
args[k] = v
}
}
func (ctx *BuiltinEvalContext) Diff() (*Diff, *sync.RWMutex) {
return ctx.DiffValue, ctx.DiffLock
}
func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) {
return ctx.StateValue, ctx.StateLock
}
func (ctx *BuiltinEvalContext) init() {
}