github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/graph_walk_context.go (about) 1 package terraform 2 3 import ( 4 "context" 5 "log" 6 "sync" 7 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/dag" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/plans" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/provisioners" 16 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 17 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 18 ) 19 20 // ContextGraphWalker is the GraphWalker implementation used with the 21 // Context struct to walk and evaluate the graph. 22 type ContextGraphWalker struct { 23 NullGraphWalker 24 25 // Configurable values 26 Context *Context 27 State *states.SyncState // Used for safe concurrent access to state 28 Changes *plans.ChangesSync // Used for safe concurrent writes to changes 29 Operation walkOperation 30 StopContext context.Context 31 RootVariableValues InputValues 32 33 // This is an output. Do not set this, nor read it while a graph walk 34 // is in progress. 35 NonFatalDiagnostics tfdiags.Diagnostics 36 37 errorLock sync.Mutex 38 once sync.Once 39 contexts map[string]*BuiltinEvalContext 40 contextLock sync.Mutex 41 variableValues map[string]map[string]cty.Value 42 variableValuesLock sync.Mutex 43 providerCache map[string]providers.Interface 44 providerSchemas map[string]*ProviderSchema 45 providerLock sync.Mutex 46 provisionerCache map[string]provisioners.Interface 47 provisionerSchemas map[string]*configschema.Block 48 provisionerLock sync.Mutex 49 } 50 51 func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext { 52 w.once.Do(w.init) 53 54 w.contextLock.Lock() 55 defer w.contextLock.Unlock() 56 57 // If we already have a context for this path cached, use that 58 key := path.String() 59 if ctx, ok := w.contexts[key]; ok { 60 return ctx 61 } 62 63 // Our evaluator shares some locks with the main context and the walker 64 // so that we can safely run multiple evaluations at once across 65 // different modules. 66 evaluator := &Evaluator{ 67 Meta: w.Context.meta, 68 Config: w.Context.config, 69 Operation: w.Operation, 70 State: w.State, 71 Changes: w.Changes, 72 Schemas: w.Context.schemas, 73 VariableValues: w.variableValues, 74 VariableValuesLock: &w.variableValuesLock, 75 } 76 77 ctx := &BuiltinEvalContext{ 78 StopContext: w.StopContext, 79 PathValue: path, 80 Hooks: w.Context.hooks, 81 InputValue: w.Context.uiInput, 82 Components: w.Context.components, 83 Schemas: w.Context.schemas, 84 ProviderCache: w.providerCache, 85 ProviderInputConfig: w.Context.providerInputConfig, 86 ProviderLock: &w.providerLock, 87 ProvisionerCache: w.provisionerCache, 88 ProvisionerLock: &w.provisionerLock, 89 ChangesValue: w.Changes, 90 StateValue: w.State, 91 Evaluator: evaluator, 92 VariableValues: w.variableValues, 93 VariableValuesLock: &w.variableValuesLock, 94 } 95 96 w.contexts[key] = ctx 97 return ctx 98 } 99 100 func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { 101 log.Printf("[TRACE] [%s] Entering eval tree: %s", w.Operation, dag.VertexName(v)) 102 103 // Acquire a lock on the semaphore 104 w.Context.parallelSem.Acquire() 105 106 // We want to filter the evaluation tree to only include operations 107 // that belong in this operation. 108 return EvalFilter(n, EvalNodeFilterOp(w.Operation)) 109 } 110 111 func (w *ContextGraphWalker) ExitEvalTree(v dag.Vertex, output interface{}, err error) tfdiags.Diagnostics { 112 log.Printf("[TRACE] [%s] Exiting eval tree: %s", w.Operation, dag.VertexName(v)) 113 114 // Release the semaphore 115 w.Context.parallelSem.Release() 116 117 if err == nil { 118 return nil 119 } 120 121 // Acquire the lock because anything is going to require a lock. 122 w.errorLock.Lock() 123 defer w.errorLock.Unlock() 124 125 // If the error is non-fatal then we'll accumulate its diagnostics in our 126 // non-fatal list, rather than returning it directly, so that the graph 127 // walk can continue. 128 if nferr, ok := err.(tfdiags.NonFatalError); ok { 129 log.Printf("[WARN] %s: %s", dag.VertexName(v), nferr) 130 w.NonFatalDiagnostics = w.NonFatalDiagnostics.Append(nferr.Diagnostics) 131 return nil 132 } 133 134 // Otherwise, we'll let our usual diagnostics machinery figure out how to 135 // unpack this as one or more diagnostic messages and return that. If we 136 // get down here then the returned diagnostics will contain at least one 137 // error, causing the graph walk to halt. 138 var diags tfdiags.Diagnostics 139 diags = diags.Append(err) 140 return diags 141 } 142 143 func (w *ContextGraphWalker) init() { 144 w.contexts = make(map[string]*BuiltinEvalContext) 145 w.providerCache = make(map[string]providers.Interface) 146 w.providerSchemas = make(map[string]*ProviderSchema) 147 w.provisionerCache = make(map[string]provisioners.Interface) 148 w.provisionerSchemas = make(map[string]*configschema.Block) 149 w.variableValues = make(map[string]map[string]cty.Value) 150 151 // Populate root module variable values. Other modules will be populated 152 // during the graph walk. 153 w.variableValues[""] = make(map[string]cty.Value) 154 for k, iv := range w.RootVariableValues { 155 w.variableValues[""][k] = iv.Value 156 } 157 }