diff --git a/terraform/transform_expand.go b/terraform/transform_expand.go new file mode 100644 index 000000000..ada9775f2 --- /dev/null +++ b/terraform/transform_expand.go @@ -0,0 +1,61 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform/dag" +) + +// GraphNodeExapndable is an interface that nodes can implement to +// signal that they can be expanded. Expanded nodes turn into +// GraphNodeSubgraph nodes within the graph. +type GraphNodeExpandable interface { + Expand(GraphBuilder) (*Graph, error) +} + +// GraphNodeSubgraph is an interface a node can implement if it has +// a larger subgraph that should be walked. +type GraphNodeSubgraph interface { + Subgraph() *Graph +} + +// ExpandTransform is a transformer that does a subgraph expansion +// at graph transform time (vs. at eval time). The benefit of earlier +// subgraph expansion is that errors with the graph build can be detected +// at an earlier stage. +type ExpandTransform struct { + Builder GraphBuilder +} + +func (t *ExpandTransform) Transform(v dag.Vertex) (dag.Vertex, error) { + ev, ok := v.(GraphNodeExpandable) + if !ok { + // This isn't an expandable vertex, so just ignore it. + return v, nil + } + + // Expand the subgraph! + g, err := ev.Expand(t.Builder) + if err != nil { + return nil, err + } + + // Replace with our special node + return &graphNodeExpanded{ + Graph: g, + OriginalName: dag.VertexName(v), + }, nil +} + +type graphNodeExpanded struct { + Graph *Graph + OriginalName string +} + +func (n *graphNodeExpanded) Name() string { + return fmt.Sprintf("%s (expanded)", n.OriginalName) +} + +func (n *graphNodeExpanded) Subgraph() *Graph { + return n.Graph +} diff --git a/terraform/transform_expand_test.go b/terraform/transform_expand_test.go new file mode 100644 index 000000000..b63205557 --- /dev/null +++ b/terraform/transform_expand_test.go @@ -0,0 +1,69 @@ +package terraform + +import ( + "strings" + "testing" + + "github.com/hashicorp/terraform/dag" +) + +func TestExpandTransform_impl(t *testing.T) { + var _ GraphVertexTransformer = new(ExpandTransform) +} + +func TestExpandTransform(t *testing.T) { + var g Graph + g.Add(1) + g.Add(2) + g.Connect(dag.BasicEdge(1, 2)) + + tf := &ExpandTransform{} + out, err := tf.Transform(&testExpandable{ + Result: &g, + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + sn, ok := out.(GraphNodeSubgraph) + if !ok { + t.Fatalf("not subgraph: %#v", out) + } + + actual := strings.TrimSpace(sn.Subgraph().String()) + expected := strings.TrimSpace(testExpandTransformStr) + if actual != expected { + t.Fatalf("bad: %s", actual) + } +} + +func TestExpandTransform_nonExpandable(t *testing.T) { + tf := &ExpandTransform{} + out, err := tf.Transform(42) + if err != nil { + t.Fatalf("err: %s", err) + } + if out != 42 { + t.Fatalf("bad: %#v", out) + } +} + +type testExpandable struct { + // Inputs + Result *Graph + ResultError error + + // Outputs + Builder GraphBuilder +} + +func (n *testExpandable) Expand(b GraphBuilder) (*Graph, error) { + n.Builder = b + return n.Result, n.ResultError +} + +const testExpandTransformStr = ` +1 + 2 +2 +`