diff --git a/terraform/graph_config_node_module.go b/terraform/graph_config_node_module.go index 22ed3493f..cc6a87b2d 100644 --- a/terraform/graph_config_node_module.go +++ b/terraform/graph_config_node_module.go @@ -2,6 +2,7 @@ package terraform import ( "fmt" + "strings" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" @@ -152,7 +153,75 @@ func (n *graphNodeModuleExpanded) EvalTree() EvalNode { } } +// GraphNodeFlattenable impl. +func (n *graphNodeModuleExpanded) FlattenGraph() *Graph { + graph := n.Subgraph() + + // Build the string that represents the path. We do this once here + // so that we only have to compute it once. The block below is in {} + // so that parts drops out of scope immediately. + var pathStr string + { + parts := make([]string, 0, len(graph.Path)*2) + for _, p := range graph.Path[1:] { + parts = append(parts, "module", p) + } + + pathStr = strings.Join(parts, ".") + } + + // Go over each vertex in the graph and wrap the configuration + // items so that the dependencies properly map to the modules. + // See the docs for graphNodeModuleWrappable for more info. + for _, v := range graph.Vertices() { + if sn, ok := v.(graphNodeModuleSkippable); ok && sn.FlattenSkip() { + graph.Remove(v) + continue + } + + wn, ok := v.(graphNodeModuleWrappable) + if !ok { + panic("unwrappable node: " + dag.VertexName(v)) + } + + graph.Replace(v, &graphNodeModuleFlatWrap{ + graphNodeModuleWrappable: wn, + + Path: graph.Path, + PathString: pathStr, + }) + } + + return graph +} + // GraphNodeSubgraph impl. func (n *graphNodeModuleExpanded) Subgraph() *Graph { return n.Graph } + +// This interface can be implemented to be skipped/ignored when +// flattening the module graph. +type graphNodeModuleSkippable interface { + FlattenSkip() bool +} + +// This is the interface that all nodes within a module graph must +// implement to be wrapped for flattening. +type graphNodeModuleWrappable interface { + graphNodeConfig +} + +// graphNodeModuleFlatWrap wraps elements of the module graph +// when they are flattened so that the dependency information is +// correct. +type graphNodeModuleFlatWrap struct { + graphNodeModuleWrappable + + Path []string + PathString string +} + +func (n *graphNodeModuleFlatWrap) Name() string { + return fmt.Sprintf("%s.%s", n.PathString, n.graphNodeModuleWrappable.Name()) +} diff --git a/terraform/graph_config_node_module_test.go b/terraform/graph_config_node_module_test.go index f30f422a1..35c3cd7a2 100644 --- a/terraform/graph_config_node_module_test.go +++ b/terraform/graph_config_node_module_test.go @@ -39,3 +39,42 @@ func TestGraphNodeConfigModuleExpand(t *testing.T) { t.Fatalf("bad:\n\n%s", actual) } } + +func TestGraphNodeConfigModuleExpandFlatten(t *testing.T) { + mod := testModule(t, "graph-node-module-flatten") + + node := &GraphNodeConfigModule{ + Path: []string{RootModuleName, "child"}, + Module: &config.Module{}, + Tree: nil, + } + + g, err := node.Expand(&BasicGraphBuilder{ + Steps: []GraphTransformer{ + &ConfigTransformer{Module: mod}, + }, + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + fg := g.(GraphNodeFlattenable) + + actual := strings.TrimSpace(fg.FlattenGraph().String()) + expected := strings.TrimSpace(testGraphNodeModuleExpandFlattenStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +const testGraphNodeModuleExpandStr = ` +aws_instance.bar + aws_instance.foo +aws_instance.foo + module inputs +module inputs +` + +const testGraphNodeModuleExpandFlattenStr = ` +module.child.aws_instance.foo +` diff --git a/terraform/graph_config_node_test.go b/terraform/graph_config_node_test.go index 279313516..c2b016ece 100644 --- a/terraform/graph_config_node_test.go +++ b/terraform/graph_config_node_test.go @@ -113,11 +113,3 @@ func TestGraphNodeConfigVariable_impl(t *testing.T) { var _ dag.NamedVertex = new(GraphNodeConfigVariable) var _ graphNodeConfig = new(GraphNodeConfigVariable) } - -const testGraphNodeModuleExpandStr = ` -aws_instance.bar - aws_instance.foo -aws_instance.foo - module inputs -module inputs -` diff --git a/terraform/test-fixtures/graph-node-module-flatten/child/main.tf b/terraform/test-fixtures/graph-node-module-flatten/child/main.tf new file mode 100644 index 000000000..919f140bb --- /dev/null +++ b/terraform/test-fixtures/graph-node-module-flatten/child/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/graph-node-module-flatten/main.tf b/terraform/test-fixtures/graph-node-module-flatten/main.tf new file mode 100644 index 000000000..0f6991c53 --- /dev/null +++ b/terraform/test-fixtures/graph-node-module-flatten/main.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +} diff --git a/terraform/transform_module.go b/terraform/transform_module.go index 816ccc455..7733fe578 100644 --- a/terraform/transform_module.go +++ b/terraform/transform_module.go @@ -45,3 +45,8 @@ func (n *graphNodeModuleInput) Name() string { func (n *graphNodeModuleInput) EvalTree() EvalNode { return &EvalSetVariables{Variables: n.Variables} } + +// graphNodeModuleSkippable impl. +func (n *graphNodeModuleInput) FlattenSkip() bool { + return true +}