mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-03 20:57:09 -06:00
4bc1696fd1
The graph walking mechanism is specified as requiring a graph with a single root, which in practice means there's exactly one node in the graph which doesn't have any dependencies. However, we previously weren't verifying that invariant is true for subgraphs returned from DynamicExpand. It was working anyway, but it's not ideal to be relying on a behavior that isn't guaranteed by our underlying infrastructure. We also previously had the RootTransformer being a bit clever and trying to avoid adding a new node if there is already only a single graph with no dependencies. That special case isn't particularly valuable since there's no harm in turning a one-node graph into a two-node graph with an explicit separate root node, and doing that allows us to assume that the root node is always present and is always exactly terraform.rootNode. Many existing DynamicExpand implementations were not producing valid graphs and were previously getting away with it. All of them now produce properly-rooted graphs that should pass validation, and we will guarantee that with an explicit check of the DynamicExpand return value before we try to walk that subgraph. For good measure we also verify that the root node is exactly terraform.rootNode, even though that isn't strictly required by our graph walker, just to help us catch potential future bugs where a DynamicExpand implementation neglects to add our singleton root node.
136 lines
4.4 KiB
Go
136 lines
4.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/internal/logging"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
|
|
"github.com/hashicorp/terraform/internal/dag"
|
|
)
|
|
|
|
// Graph represents the graph that Terraform uses to represent resources
|
|
// and their dependencies.
|
|
type Graph struct {
|
|
// Graph is the actual DAG. This is embedded so you can call the DAG
|
|
// methods directly.
|
|
dag.AcyclicGraph
|
|
|
|
// Path is the path in the module tree that this Graph represents.
|
|
Path addrs.ModuleInstance
|
|
}
|
|
|
|
func (g *Graph) DirectedGraph() dag.Grapher {
|
|
return &g.AcyclicGraph
|
|
}
|
|
|
|
// Walk walks the graph with the given walker for callbacks. The graph
|
|
// will be walked with full parallelism, so the walker should expect
|
|
// to be called in concurrently.
|
|
func (g *Graph) Walk(walker GraphWalker) tfdiags.Diagnostics {
|
|
return g.walk(walker)
|
|
}
|
|
|
|
func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
|
|
// The callbacks for enter/exiting a graph
|
|
ctx := walker.EvalContext()
|
|
|
|
// Walk the graph.
|
|
walkFn := func(v dag.Vertex) (diags tfdiags.Diagnostics) {
|
|
// the walkFn is called asynchronously, and needs to be recovered
|
|
// separately in the case of a panic.
|
|
defer logging.PanicHandler()
|
|
|
|
log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v)
|
|
|
|
defer func() {
|
|
if diags.HasErrors() {
|
|
for _, diag := range diags {
|
|
if diag.Severity() == tfdiags.Error {
|
|
desc := diag.Description()
|
|
log.Printf("[ERROR] vertex %q error: %s", dag.VertexName(v), desc.Summary)
|
|
}
|
|
}
|
|
log.Printf("[TRACE] vertex %q: visit complete, with errors", dag.VertexName(v))
|
|
} else {
|
|
log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v))
|
|
}
|
|
}()
|
|
|
|
// vertexCtx is the context that we use when evaluating. This
|
|
// is normally the context of our graph but can be overridden
|
|
// with a GraphNodeModuleInstance impl.
|
|
vertexCtx := ctx
|
|
if pn, ok := v.(GraphNodeModuleInstance); ok {
|
|
vertexCtx = walker.EnterPath(pn.Path())
|
|
defer walker.ExitPath(pn.Path())
|
|
}
|
|
|
|
// If the node is exec-able, then execute it.
|
|
if ev, ok := v.(GraphNodeExecutable); ok {
|
|
diags = diags.Append(walker.Execute(vertexCtx, ev))
|
|
if diags.HasErrors() {
|
|
return
|
|
}
|
|
}
|
|
|
|
// If the node is dynamically expanded, then expand it
|
|
if ev, ok := v.(GraphNodeDynamicExpandable); ok {
|
|
log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v))
|
|
|
|
g, err := ev.DynamicExpand(vertexCtx)
|
|
diags = diags.Append(err)
|
|
if diags.HasErrors() {
|
|
log.Printf("[TRACE] vertex %q: failed expanding dynamic subgraph: %s", dag.VertexName(v), err)
|
|
return
|
|
}
|
|
if g != nil {
|
|
// The subgraph should always be valid, per our normal acyclic
|
|
// graph validation rules.
|
|
if err := g.Validate(); err != nil {
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Graph node has invalid dynamic subgraph",
|
|
fmt.Sprintf("The internal logic for %q generated an invalid dynamic subgraph: %s.\n\nThis is a bug in Terraform. Please report it!", dag.VertexName(v), err),
|
|
))
|
|
return
|
|
}
|
|
// If we passed validation then there is exactly one root node.
|
|
// That root node should always be "rootNode", the singleton
|
|
// root node value.
|
|
if n, err := g.Root(); err != nil || n != dag.Vertex(rootNode) {
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Graph node has invalid dynamic subgraph",
|
|
fmt.Sprintf("The internal logic for %q generated an invalid dynamic subgraph: the root node is %T, which is not a suitable root node type.\n\nThis is a bug in Terraform. Please report it!", dag.VertexName(v), v),
|
|
))
|
|
return
|
|
}
|
|
|
|
// Walk the subgraph
|
|
log.Printf("[TRACE] vertex %q: entering dynamic subgraph", dag.VertexName(v))
|
|
subDiags := g.walk(walker)
|
|
diags = diags.Append(subDiags)
|
|
if subDiags.HasErrors() {
|
|
var errs []string
|
|
for _, d := range subDiags {
|
|
errs = append(errs, d.Description().Summary)
|
|
}
|
|
log.Printf("[TRACE] vertex %q: dynamic subgraph encountered errors: %s", dag.VertexName(v), strings.Join(errs, ","))
|
|
return
|
|
}
|
|
log.Printf("[TRACE] vertex %q: dynamic subgraph completed successfully", dag.VertexName(v))
|
|
} else {
|
|
log.Printf("[TRACE] vertex %q: produced no dynamic subgraph", dag.VertexName(v))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
return g.AcyclicGraph.Walk(walkFn)
|
|
}
|