opentofu/terraform/graph_walk_context.go
2017-11-07 10:15:11 -05:00

155 lines
4.1 KiB
Go

package terraform
import (
"context"
"fmt"
"log"
"sync"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/dag"
)
// ContextGraphWalker is the GraphWalker implementation used with the
// Context struct to walk and evaluate the graph.
type ContextGraphWalker struct {
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
}
func (w *ContextGraphWalker) EnterPath(path []string) 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)
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
}
}
w.interpolaterVars[key] = variables
w.interpolaterVarLock.Unlock()
ctx := &BuiltinEvalContext{
StopContext: w.StopContext,
PathValue: path,
Hooks: w.Context.hooks,
InputValue: w.Context.uiInput,
Components: w.Context.components,
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,
}
w.contexts[key] = ctx
return ctx
}
func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode {
log.Printf("[TRACE] [%s] Entering eval tree: %s",
w.Operation, dag.VertexName(v))
// Acquire a lock on the semaphore
w.Context.parallelSem.Acquire()
// We want to filter the evaluation tree to only include operations
// that belong in this operation.
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))
// Release the semaphore
w.Context.parallelSem.Release()
if err == nil {
return nil
}
// Acquire the lock because anything is going to require a lock.
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))
}
return nil
}
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)
}