terraform: turn multi-counts into multiple nodes

This commit is contained in:
Mitchell Hashimoto 2014-07-03 20:42:29 -07:00
parent 5e79ddf7c6
commit e7b7644cbf
4 changed files with 137 additions and 18 deletions

View File

@ -9,6 +9,7 @@ import (
"bytes"
"fmt"
"sort"
"strings"
"sync"
"github.com/hashicorp/terraform/digraph"
@ -42,7 +43,34 @@ type ValidateError struct {
}
func (v *ValidateError) Error() string {
return "The depedency graph is not valid"
var msgs []string
if v.MissingRoot {
msgs = append(msgs, "The graph has no single root")
}
for _, n := range v.Unreachable {
msgs = append(msgs, fmt.Sprintf(
"Unreachable node: %s", n.Name))
}
for _, c := range v.Cycles {
cycleNodes := make([]string, len(c))
for i, n := range c {
cycleNodes[i] = n.Name
}
msgs = append(msgs, fmt.Sprintf(
"Cycle: %s", strings.Join(cycleNodes, " -> ")))
}
for i, m := range msgs {
msgs[i] = fmt.Sprintf("* %s", m)
}
return fmt.Sprintf(
"The dependency graph is not valid:\n\n%s",
strings.Join(msgs, "\n"))
}
// ConstraintError is used to return detailed violation

View File

@ -45,8 +45,12 @@ type GraphOpts struct {
// graph. This node is just a placemarker and has no associated functionality.
const GraphRootNode = "root"
// 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,
// this represents a _single_, _resource_ to be managed, not a set of resources
// or a component of a resource.
type GraphNodeResource struct {
Index int
Type string
Config *config.Resource
Orphan bool
@ -54,6 +58,15 @@ type GraphNodeResource struct {
ResourceProviderID string
}
// GraphNodeResourceMeta is a node type in the graph that represents the
// metadata for a resource. There will be one meta node for every resource
// in the configuration.
type GraphNodeResourceMeta struct {
Name string
Type string
Count int
}
// GraphNodeResourceProvider is a node type in the graph that represents
// the configuration for a resource provider.
type GraphNodeResourceProvider struct {
@ -152,18 +165,57 @@ func graphAddConfigResources(
}
}
noun := &depgraph.Noun{
Name: r.Id(),
Meta: &GraphNodeResource{
Type: r.Type,
Config: r,
Resource: &Resource{
Id: r.Id(),
State: state,
resourceNouns := make([]*depgraph.Noun, r.Count)
for i := 0; i < r.Count; i++ {
name := r.Id()
// If we have a count that is more than one, then make sure
// we suffix with the number of the resource that this is.
if r.Count > 1 {
name = fmt.Sprintf("%s.%d", name, i)
}
resourceNouns[i] = &depgraph.Noun{
Name: name,
Meta: &GraphNodeResource{
Type: r.Type,
Config: r,
Resource: &Resource{
Id: name,
State: state,
},
},
},
}
}
// If we have more than one, then create a meta node to track
// the resources.
if r.Count > 1 {
metaNoun := &depgraph.Noun{
Name: r.Id(),
Meta: &GraphNodeResourceMeta{
Name: r.Id(),
Type: r.Type,
Count: r.Count,
},
}
// Create the dependencies on this noun
for _, n := range resourceNouns {
metaNoun.Deps = append(metaNoun.Deps, &depgraph.Dependency{
Name: n.Name,
Source: metaNoun,
Target: n,
})
}
// Assign it to the map so that we have it
nouns[metaNoun.Name] = metaNoun
}
for _, n := range resourceNouns {
nouns[n.Name] = n
}
nouns[noun.Name] = noun
}
// Build the list of nouns that we iterate over
@ -357,7 +409,10 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
nounsList := make([]*depgraph.Noun, 0, 2)
pcNouns := make(map[string]*depgraph.Noun)
for _, noun := range g.Nouns {
resourceNode := noun.Meta.(*GraphNodeResource)
resourceNode, ok := noun.Meta.(*GraphNodeResource)
if !ok {
continue
}
// Look up the provider config for this resource
pcName := config.ProviderConfigName(resourceNode.Type, c.ProviderConfigs)
@ -401,11 +456,6 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
func graphAddRoot(g *depgraph.Graph) {
root := &depgraph.Noun{Name: GraphRootNode}
for _, n := range g.Nouns {
// The root only needs to depend on all the resources
if _, ok := n.Meta.(*GraphNodeResource); !ok {
continue
}
root.Deps = append(root.Deps, &depgraph.Dependency{
Name: n.Name,
Source: root,

View File

@ -27,6 +27,21 @@ func TestGraph_configRequired(t *testing.T) {
}
}
func TestGraph_count(t *testing.T) {
config := testConfig(t, "graph-count")
g, err := Graph(&GraphOpts{Config: config})
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTerraformGraphCountStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestGraph_cycle(t *testing.T) {
config := testConfig(t, "graph-cycle")
@ -226,6 +241,25 @@ root
root -> openstack_floating_ip.random
`
const testTerraformGraphCountStr = `
root: root
aws_instance.web
aws_instance.web -> aws_instance.web.0
aws_instance.web -> aws_instance.web.1
aws_instance.web -> aws_instance.web.2
aws_instance.web.0
aws_instance.web.1
aws_instance.web.2
aws_load_balancer.weblb
aws_load_balancer.weblb -> aws_instance.web
root
root -> aws_instance.web
root -> aws_instance.web.0
root -> aws_instance.web.1
root -> aws_instance.web.2
root -> aws_load_balancer.weblb
`
const testTerraformGraphDiffStr = `
root: root
aws_instance.foo

View File

@ -0,0 +1,7 @@
resource "aws_instance" "web" {
count = 3
}
resource "aws_load_balancer" "weblb" {
members = "${aws_instance.web.*.id}"
}