mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
797a1b339d
Implement debugInfo and the DebugGraph DebugInfo will be a global variable through which graph debug information can we written to a compressed archive. The DebugInfo methods are all safe for concurrent use, and noop with a nil receiver. The API outside of the terraform package will be to call SetDebugInfo to create the archive, and CloseDebugInfo() to properly close the file. Each write to the archive will be flushed and sync'ed individually, so in the event of a crash or a missing call to Close, the archive can still be recovered. The DebugGraph is a representation of a terraform Graph to be written to the debug archive, currently in dot format. The DebugGraph also contains an internal buffer with Printf and Write methods to add to this buffer. The buffer will be written to an accompanying file in the debug archive along with the graph. This also adds a GraphNodeDebugger interface. Any node implementing `NodeDebug() string` can output information to annotate the debug graph node, and add the data to the log. This interface may change or be removed to provide richer options for debugging graph nodes. The new graph builders all delegate the build to the BasicGraphBuilder. Having a Name field lets us differentiate the actual builder implementation in the debug graphs.
159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
package terraform
|
|
|
|
import (
|
|
"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
|
|
DebugGraph *DebugGraph
|
|
|
|
// 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
|
|
providerConfigCache map[string]*ResourceConfig
|
|
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{
|
|
PathValue: path,
|
|
Hooks: w.Context.hooks,
|
|
InputValue: w.Context.uiInput,
|
|
Components: w.Context.components,
|
|
ProviderCache: w.providerCache,
|
|
ProviderConfigCache: w.providerConfigCache,
|
|
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,
|
|
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) Debug() *DebugGraph {
|
|
return w.DebugGraph
|
|
}
|
|
|
|
func (w *ContextGraphWalker) init() {
|
|
w.contexts = make(map[string]*BuiltinEvalContext, 5)
|
|
w.providerCache = make(map[string]ResourceProvider, 5)
|
|
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
|
w.provisionerCache = make(map[string]ResourceProvisioner, 5)
|
|
w.interpolaterVars = make(map[string]map[string]interface{}, 5)
|
|
}
|