terraform: modules are put into the graph

This commit is contained in:
Mitchell Hashimoto 2014-09-22 15:11:57 -07:00
parent 6f7c3caab3
commit 1d106d3fa4
3 changed files with 104 additions and 12 deletions

View File

@ -50,6 +50,12 @@ type GraphOpts struct {
// graph. This node is just a placemarker and has no associated functionality. // graph. This node is just a placemarker and has no associated functionality.
const GraphRootNode = "root" 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 // GraphNodeResource is a node type in the graph that represents a resource
// that will be created or managed. Unlike the GraphNodeResourceMeta node, // that will be created or managed. Unlike the GraphNodeResourceMeta node,
// this represents a _single_, _resource_ to be managed, not a set of resources // 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). // and not "orphans" (that are in the state, but not in the config).
graphAddConfigResources(g, opts.Config, opts.State) graphAddConfigResources(g, opts.Config, opts.State)
// Add the modules that are in the configuration.
graphAddConfigModules(g, opts)
// Add explicit dependsOn dependencies to the graph // Add explicit dependsOn dependencies to the graph
graphAddExplicitDeps(g) 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. // configGraph turns a configuration structure into a dependency graph.
func graphAddConfigResources( func graphAddConfigResources(
g *depgraph.Graph, c *config.Config, s *State) { g *depgraph.Graph, c *config.Config, s *State) {
@ -752,18 +788,21 @@ func graphAddRoot(g *depgraph.Graph) {
// based on variable values. // based on variable values.
func graphAddVariableDeps(g *depgraph.Graph) { func graphAddVariableDeps(g *depgraph.Graph) {
for _, n := range g.Nouns { for _, n := range g.Nouns {
var vars map[string]config.InterpolatedVariable
switch m := n.Meta.(type) { switch m := n.Meta.(type) {
case *GraphNodeModule:
vars := m.Config.RawConfig.Variables
nounAddVariableDeps(g, n, vars, false)
case *GraphNodeResource: case *GraphNodeResource:
if m.Config != nil { if m.Config != nil {
// Handle the resource variables // Handle the resource variables
vars = m.Config.RawConfig.Variables vars := m.Config.RawConfig.Variables
nounAddVariableDeps(g, n, vars, false) nounAddVariableDeps(g, n, vars, false)
} }
// Handle the variables of the resource provisioners // Handle the variables of the resource provisioners
for _, p := range m.Resource.Provisioners { for _, p := range m.Resource.Provisioners {
vars = p.RawConfig.Variables vars := p.RawConfig.Variables
nounAddVariableDeps(g, n, vars, true) nounAddVariableDeps(g, n, vars, true)
vars = p.ConnInfo.Variables vars = p.ConnInfo.Variables
@ -771,10 +810,11 @@ func graphAddVariableDeps(g *depgraph.Graph) {
} }
case *GraphNodeResourceProvider: case *GraphNodeResourceProvider:
vars = m.Config.RawConfig.Variables vars := m.Config.RawConfig.Variables
nounAddVariableDeps(g, n, vars, false) nounAddVariableDeps(g, n, vars, false)
default: default:
// Other node types don't have dependencies or we don't support it
continue continue
} }
} }
@ -854,15 +894,20 @@ func nounAddVariableDeps(
n *depgraph.Noun, n *depgraph.Noun,
vars map[string]config.InterpolatedVariable, vars map[string]config.InterpolatedVariable,
removeSelf bool) { removeSelf bool) {
for _, v := range vars { for _, rawV := range vars {
// Only resource variables impose dependencies var name string
rv, ok := v.(*config.ResourceVariable) var target *depgraph.Noun
if !ok {
continue 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 { if target == nil {
continue continue
} }
@ -875,7 +920,7 @@ func nounAddVariableDeps(
// Build the dependency // Build the dependency
dep := &depgraph.Dependency{ dep := &depgraph.Dependency{
Name: rv.ResourceId(), Name: name,
Source: n, Source: n,
Target: target, Target: target,
} }

View File

@ -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) { func TestGraph_state(t *testing.T) {
config := testConfig(t, "graph-basic") config := testConfig(t, "graph-basic")
state := &State{ state := &State{
@ -767,6 +782,23 @@ root
root -> aws_load_balancer.weblb 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 = ` const testTerraformGraphStateStr = `
root: root root: root
aws_instance.old aws_instance.old

View File

@ -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}"
]
}