import (
"context"
- "fmt"
"log"
"sync"
- "github.com/hashicorp/errwrap"
+ "github.com/zclconf/go-cty/cty"
+
+ "github.com/hashicorp/terraform/addrs"
+ "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/dag"
+ "github.com/hashicorp/terraform/plans"
+ "github.com/hashicorp/terraform/providers"
+ "github.com/hashicorp/terraform/provisioners"
+ "github.com/hashicorp/terraform/states"
+ "github.com/hashicorp/terraform/tfdiags"
)
// ContextGraphWalker is the GraphWalker implementation used with the
NullGraphWalker
// Configurable values
- Context *Context
- Operation walkOperation
- StopContext context.Context
-
- // Outputs, do not set these. Do not read these while the graph
- // is being walked.
- ValidationWarnings []string
- ValidationErrors []error
-
- errorLock sync.Mutex
- once sync.Once
- contexts map[string]*BuiltinEvalContext
- contextLock sync.Mutex
- interpolaterVars map[string]map[string]interface{}
- interpolaterVarLock sync.Mutex
- providerCache map[string]ResourceProvider
- providerLock sync.Mutex
- provisionerCache map[string]ResourceProvisioner
- provisionerLock sync.Mutex
+ Context *Context
+ State *states.SyncState // Used for safe concurrent access to state
+ Changes *plans.ChangesSync // Used for safe concurrent writes to changes
+ Operation walkOperation
+ StopContext context.Context
+ RootVariableValues InputValues
+
+ // This is an output. Do not set this, nor read it while a graph walk
+ // is in progress.
+ NonFatalDiagnostics tfdiags.Diagnostics
+
+ errorLock sync.Mutex
+ once sync.Once
+ contexts map[string]*BuiltinEvalContext
+ contextLock sync.Mutex
+ variableValues map[string]map[string]cty.Value
+ variableValuesLock sync.Mutex
+ providerCache map[string]providers.Interface
+ providerSchemas map[string]*ProviderSchema
+ providerLock sync.Mutex
+ provisionerCache map[string]provisioners.Interface
+ provisionerSchemas map[string]*configschema.Block
+ provisionerLock sync.Mutex
}
-func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
+func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
w.once.Do(w.init)
w.contextLock.Lock()
defer w.contextLock.Unlock()
// If we already have a context for this path cached, use that
- key := PathCacheKey(path)
+ key := path.String()
if ctx, ok := w.contexts[key]; ok {
return ctx
}
- // Setup the variables for this interpolater
- variables := make(map[string]interface{})
- if len(path) <= 1 {
- for k, v := range w.Context.variables {
- variables[k] = v
- }
- }
- w.interpolaterVarLock.Lock()
- if m, ok := w.interpolaterVars[key]; ok {
- for k, v := range m {
- variables[k] = v
- }
+ // Our evaluator shares some locks with the main context and the walker
+ // so that we can safely run multiple evaluations at once across
+ // different modules.
+ evaluator := &Evaluator{
+ Meta: w.Context.meta,
+ Config: w.Context.config,
+ Operation: w.Operation,
+ State: w.State,
+ Changes: w.Changes,
+ Schemas: w.Context.schemas,
+ VariableValues: w.variableValues,
+ VariableValuesLock: &w.variableValuesLock,
}
- w.interpolaterVars[key] = variables
- w.interpolaterVarLock.Unlock()
ctx := &BuiltinEvalContext{
StopContext: w.StopContext,
Hooks: w.Context.hooks,
InputValue: w.Context.uiInput,
Components: w.Context.components,
+ Schemas: w.Context.schemas,
ProviderCache: w.providerCache,
ProviderInputConfig: w.Context.providerInputConfig,
ProviderLock: &w.providerLock,
ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock,
- DiffValue: w.Context.diff,
- DiffLock: &w.Context.diffLock,
- StateValue: w.Context.state,
- StateLock: &w.Context.stateLock,
- Interpolater: &Interpolater{
- Operation: w.Operation,
- Meta: w.Context.meta,
- Module: w.Context.module,
- State: w.Context.state,
- StateLock: &w.Context.stateLock,
- VariableValues: variables,
- VariableValuesLock: &w.interpolaterVarLock,
- },
- InterpolaterVars: w.interpolaterVars,
- InterpolaterVarLock: &w.interpolaterVarLock,
+ ChangesValue: w.Changes,
+ StateValue: w.State,
+ Evaluator: evaluator,
+ VariableValues: w.variableValues,
+ VariableValuesLock: &w.variableValuesLock,
}
w.contexts[key] = ctx
}
func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode {
- log.Printf("[TRACE] [%s] Entering eval tree: %s",
- w.Operation, dag.VertexName(v))
+ log.Printf("[TRACE] [%s] Entering eval tree: %s", w.Operation, dag.VertexName(v))
// Acquire a lock on the semaphore
w.Context.parallelSem.Acquire()
return EvalFilter(n, EvalNodeFilterOp(w.Operation))
}
-func (w *ContextGraphWalker) ExitEvalTree(
- v dag.Vertex, output interface{}, err error) error {
- log.Printf("[TRACE] [%s] Exiting eval tree: %s",
- w.Operation, dag.VertexName(v))
+func (w *ContextGraphWalker) ExitEvalTree(v dag.Vertex, output interface{}, err error) tfdiags.Diagnostics {
+ log.Printf("[TRACE] [%s] Exiting eval tree: %s", w.Operation, dag.VertexName(v))
// Release the semaphore
w.Context.parallelSem.Release()
w.errorLock.Lock()
defer w.errorLock.Unlock()
- // Try to get a validation error out of it. If its not a validation
- // error, then just record the normal error.
- verr, ok := err.(*EvalValidateError)
- if !ok {
- return err
- }
-
- for _, msg := range verr.Warnings {
- w.ValidationWarnings = append(
- w.ValidationWarnings,
- fmt.Sprintf("%s: %s", dag.VertexName(v), msg))
- }
- for _, e := range verr.Errors {
- w.ValidationErrors = append(
- w.ValidationErrors,
- errwrap.Wrapf(fmt.Sprintf("%s: {{err}}", dag.VertexName(v)), e))
+ // If the error is non-fatal then we'll accumulate its diagnostics in our
+ // non-fatal list, rather than returning it directly, so that the graph
+ // walk can continue.
+ if nferr, ok := err.(tfdiags.NonFatalError); ok {
+ log.Printf("[WARN] %s: %s", dag.VertexName(v), nferr)
+ w.NonFatalDiagnostics = w.NonFatalDiagnostics.Append(nferr.Diagnostics)
+ return nil
}
- return nil
+ // Otherwise, we'll let our usual diagnostics machinery figure out how to
+ // unpack this as one or more diagnostic messages and return that. If we
+ // get down here then the returned diagnostics will contain at least one
+ // error, causing the graph walk to halt.
+ var diags tfdiags.Diagnostics
+ diags = diags.Append(err)
+ return diags
}
func (w *ContextGraphWalker) init() {
- w.contexts = make(map[string]*BuiltinEvalContext, 5)
- w.providerCache = make(map[string]ResourceProvider, 5)
- w.provisionerCache = make(map[string]ResourceProvisioner, 5)
- w.interpolaterVars = make(map[string]map[string]interface{}, 5)
+ w.contexts = make(map[string]*BuiltinEvalContext)
+ w.providerCache = make(map[string]providers.Interface)
+ w.providerSchemas = make(map[string]*ProviderSchema)
+ w.provisionerCache = make(map[string]provisioners.Interface)
+ w.provisionerSchemas = make(map[string]*configschema.Block)
+ w.variableValues = make(map[string]map[string]cty.Value)
+
+ // Populate root module variable values. Other modules will be populated
+ // during the graph walk.
+ w.variableValues[""] = make(map[string]cty.Value)
+ for k, iv := range w.RootVariableValues {
+ w.variableValues[""][k] = iv.Value
+ }
}