diff --git a/internal/dag/graph.go b/internal/dag/graph.go index e9c8376bc6..9ca6f4f301 100644 --- a/internal/dag/graph.go +++ b/internal/dag/graph.go @@ -117,6 +117,26 @@ func (g *Graph) Add(v Vertex) Vertex { return v } +// AddRoot adds a root vertex to the graph and connects all vertices that do not +// already depend on at least one other node to the root node. +func (g *Graph) AddRoot(root Vertex) Vertex { + g.init() + g.Add(root) + + // Everything that doesn't already depend on at least one other node will + // depend on the root node, except the root node itself. + for v := range g.VerticesSeq() { + if v == Vertex(root) { + continue + } + + if g.UpEdges(v).Len() == 0 { + g.Connect(BasicEdge(root, v)) + } + } + return root +} + // Remove removes a vertex from the graph. This will also remove any // edges with this vertex as a source or target. func (g *Graph) Remove(v Vertex) Vertex { diff --git a/internal/terraform/graph.go b/internal/terraform/graph.go index f3db1002a9..284c4c5d33 100644 --- a/internal/terraform/graph.go +++ b/internal/terraform/graph.go @@ -168,9 +168,10 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { 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) { + // That root node should always be a type of "graphNodeRoot". + n, err := g.Root() + _, isRoot := n.(graphNodeRoot) + if err != nil || !isRoot { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Graph node has invalid dynamic subgraph", diff --git a/internal/terraform/node_policy_eval.go b/internal/terraform/node_policy_eval.go index e574f7204d..aa13c37c55 100644 --- a/internal/terraform/node_policy_eval.go +++ b/internal/terraform/node_policy_eval.go @@ -29,7 +29,7 @@ func (n *nodePolicyEval) DynamicExpand(ctx EvalContext) (*Graph, tfdiags.Diagnos return nil, nil } // ensure the graph has a single root - addRootNodeToGraph(&policyGraph.graph) + policyGraph.graph.AddRoot(graphNodeRoot{"policy"}) return &policyGraph.graph, nil } diff --git a/internal/terraform/transform_root.go b/internal/terraform/transform_root.go index 7ee80b0662..d849264545 100644 --- a/internal/terraform/transform_root.go +++ b/internal/terraform/transform_root.go @@ -4,6 +4,8 @@ package terraform import ( + "fmt" + "github.com/hashicorp/terraform/internal/dag" ) @@ -30,22 +32,12 @@ func addRootNodeToGraph(g *Graph) { // Note that rootNode is intentionally added by value and not by pointer // so that all root nodes will be equal to one another and therefore // coalesce when two valid graphs get merged together into a single graph. - g.Add(rootNode) - - // Everything that doesn't already depend on at least one other node will - // depend on the root node, except the root node itself. - for _, v := range g.Vertices() { - if v == dag.Vertex(rootNode) { - continue - } - - if g.UpEdges(v).Len() == 0 { - g.Connect(dag.BasicEdge(rootNode, v)) - } - } + g.AddRoot(rootNode) } -type graphNodeRoot struct{} +type graphNodeRoot struct { + Prefix string +} // rootNode is the singleton value representing all root graph nodes. // @@ -56,7 +48,10 @@ type graphNodeRoot struct{} var rootNode graphNodeRoot func (n graphNodeRoot) Name() string { - return rootNodeName + if n.Prefix == "" { + return rootNodeName + } + return fmt.Sprintf("%s.%s", rootNodeName, n.Prefix) } // CloseRootModuleTransformer is a GraphTransformer that adds a root to the graph.