terraform/terraform/transform_flatten.go

102 lines
2.6 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeFlatGraph must be implemented by nodes that have subgraphs
// that they want flattened into the graph.
type GraphNodeFlatGraph interface {
FlattenGraph() *Graph
}
// GraphNodeFlattenable must be implemented by all nodes that can be
// flattened. If a FlattenGraph returns any nodes that can't be flattened,
// it will be an error.
//
// If Flatten returns nil for the Vertex along with a nil error, it will
// removed from the graph.
type GraphNodeFlattenable interface {
Flatten(path []string) (dag.Vertex, error)
}
// FlattenTransformer is a transformer that goes through the graph, finds
// subgraphs that can be flattened, and flattens them into this graph,
// removing the prior subgraph node.
type FlattenTransformer struct{}
func (t *FlattenTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
fn, ok := v.(GraphNodeFlatGraph)
if !ok {
continue
}
// If we don't want to be flattened, don't do it
subgraph := fn.FlattenGraph()
if subgraph == nil {
continue
}
// Get all the things that depend on this node. We'll re-connect
// dependents later. We have to copy these here since the UpEdges
// value will be deleted after the Remove below.
dependents := make([]dag.Vertex, 0, 5)
for _, v := range g.UpEdges(v).List() {
dependents = append(dependents, v)
}
// Remove the old node
g.Remove(v)
// Go through the subgraph and flatten all the nodes
for _, sv := range subgraph.Vertices() {
fn, ok := sv.(GraphNodeFlattenable)
if !ok {
return fmt.Errorf(
"unflattenable node: %s %T",
dag.VertexName(sv), sv)
}
v, err := fn.Flatten(subgraph.Path)
if err != nil {
return fmt.Errorf(
"error flattening %s (%T): %s",
dag.VertexName(sv), sv, err)
}
if v == nil {
subgraph.Remove(v)
} else {
subgraph.Replace(sv, v)
}
}
// Now that we've handled any changes to the graph that are
// needed, we can add them all to our graph along with their edges.
for _, sv := range subgraph.Vertices() {
g.Add(sv)
}
for _, se := range subgraph.Edges() {
g.Connect(se)
}
// Connect the dependencies for all the new nodes that we added.
// This will properly connect variables to their sources, for example.
for _, sv := range subgraph.Vertices() {
g.ConnectDependent(sv)
}
// Re-connect all the things that dependend on the graph
// we just flattened. This should connect them back into the
// correct nodes if their DependentOn() is setup correctly.
for _, v := range dependents {
g.ConnectDependent(v)
}
}
return nil
}