From 1d106d3fa43df7ab65e63ecb66ffc9744c9cd5f2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 22 Sep 2014 15:11:57 -0700 Subject: [PATCH] terraform: modules are put into the graph --- terraform/graph.go | 69 +++++++++++++++---- terraform/graph_test.go | 32 +++++++++ terraform/test-fixtures/graph-modules/main.tf | 15 ++++ 3 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 terraform/test-fixtures/graph-modules/main.tf diff --git a/terraform/graph.go b/terraform/graph.go index 4fcb5f1dc7..d6724bcbed 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -50,6 +50,12 @@ type GraphOpts struct { // graph. This node is just a placemarker and has no associated functionality. const GraphRootNode = "root" +// GraphNodeModule is a node type in the graph that represents a module +// that will be created/managed. +type GraphNodeModule struct { + Config *config.Module +} + // GraphNodeResource is a node type in the graph that represents a resource // that will be created or managed. Unlike the GraphNodeResourceMeta node, // this represents a _single_, _resource_ to be managed, not a set of resources @@ -108,6 +114,9 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) { // and not "orphans" (that are in the state, but not in the config). graphAddConfigResources(g, opts.Config, opts.State) + // Add the modules that are in the configuration. + graphAddConfigModules(g, opts) + // Add explicit dependsOn dependencies to the graph graphAddExplicitDeps(g) @@ -220,6 +229,33 @@ func graphInitState(s *State, g *depgraph.Graph) { } } +// graphAddConfigModules adds the modules from a configuration structure +// into the graph, expanding each to their own sub-graph. +func graphAddConfigModules(g *depgraph.Graph, opts *GraphOpts) { + c := opts.Config + + // Just short-circuit the whole thing if we don't have modules + if len(c.Modules) == 0 { + return + } + + // Build the list of nouns to add to the graph + nounsList := make([]*depgraph.Noun, 0, len(c.Modules)) + for _, m := range c.Modules { + name := fmt.Sprintf("module.%s", m.Name) + n := &depgraph.Noun{ + Name: name, + Meta: &GraphNodeModule{ + Config: m, + }, + } + + nounsList = append(nounsList, n) + } + + g.Nouns = append(g.Nouns, nounsList...) +} + // configGraph turns a configuration structure into a dependency graph. func graphAddConfigResources( g *depgraph.Graph, c *config.Config, s *State) { @@ -752,18 +788,21 @@ func graphAddRoot(g *depgraph.Graph) { // based on variable values. func graphAddVariableDeps(g *depgraph.Graph) { for _, n := range g.Nouns { - var vars map[string]config.InterpolatedVariable switch m := n.Meta.(type) { + case *GraphNodeModule: + vars := m.Config.RawConfig.Variables + nounAddVariableDeps(g, n, vars, false) + case *GraphNodeResource: if m.Config != nil { // Handle the resource variables - vars = m.Config.RawConfig.Variables + vars := m.Config.RawConfig.Variables nounAddVariableDeps(g, n, vars, false) } // Handle the variables of the resource provisioners for _, p := range m.Resource.Provisioners { - vars = p.RawConfig.Variables + vars := p.RawConfig.Variables nounAddVariableDeps(g, n, vars, true) vars = p.ConnInfo.Variables @@ -771,10 +810,11 @@ func graphAddVariableDeps(g *depgraph.Graph) { } case *GraphNodeResourceProvider: - vars = m.Config.RawConfig.Variables + vars := m.Config.RawConfig.Variables nounAddVariableDeps(g, n, vars, false) default: + // Other node types don't have dependencies or we don't support it continue } } @@ -854,15 +894,20 @@ func nounAddVariableDeps( n *depgraph.Noun, vars map[string]config.InterpolatedVariable, removeSelf bool) { - for _, v := range vars { - // Only resource variables impose dependencies - rv, ok := v.(*config.ResourceVariable) - if !ok { - continue + for _, rawV := range vars { + var name string + var target *depgraph.Noun + + switch v := rawV.(type) { + case *config.ModuleVariable: + name = fmt.Sprintf("module.%s", v.Name) + target = g.Noun(name) + case *config.ResourceVariable: + name = v.ResourceId() + target = g.Noun(v.ResourceId()) + default: } - // Find the target - target := g.Noun(rv.ResourceId()) if target == nil { continue } @@ -875,7 +920,7 @@ func nounAddVariableDeps( // Build the dependency dep := &depgraph.Dependency{ - Name: rv.ResourceId(), + Name: name, Source: n, Target: target, } diff --git a/terraform/graph_test.go b/terraform/graph_test.go index a9170f6f95..e138efb769 100644 --- a/terraform/graph_test.go +++ b/terraform/graph_test.go @@ -81,6 +81,21 @@ func TestGraph_dependsOnCount(t *testing.T) { } } +func TestGraph_modules(t *testing.T) { + config := testConfig(t, "graph-modules") + + g, err := Graph(&GraphOpts{Config: config}) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTerraformGraphModulesStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + func TestGraph_state(t *testing.T) { config := testConfig(t, "graph-basic") state := &State{ @@ -767,6 +782,23 @@ root root -> aws_load_balancer.weblb ` +const testTerraformGraphModulesStr = ` +root: root +aws_instance.web + aws_instance.web -> aws_security_group.firewall + aws_instance.web -> module.consul + aws_instance.web -> provider.aws +aws_security_group.firewall + aws_security_group.firewall -> provider.aws +module.consul + module.consul -> aws_security_group.firewall +provider.aws +root + root -> aws_instance.web + root -> aws_security_group.firewall + root -> module.consul +` + const testTerraformGraphStateStr = ` root: root aws_instance.old diff --git a/terraform/test-fixtures/graph-modules/main.tf b/terraform/test-fixtures/graph-modules/main.tf new file mode 100644 index 0000000000..dbd6fcd983 --- /dev/null +++ b/terraform/test-fixtures/graph-modules/main.tf @@ -0,0 +1,15 @@ +module "consul" { + foo = "${aws_security_group.firewall.foo}" +} + +provider "aws" {} + +resource "aws_security_group" "firewall" {} + +resource "aws_instance" "web" { + security_groups = [ + "foo", + "${aws_security_group.firewall.foo}", + "${module.consul.security_group}" + ] +}