terraform: turn multi-counts into multiple nodes
This commit is contained in:
parent
5e79ddf7c6
commit
e7b7644cbf
|
@ -9,6 +9,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/digraph"
|
"github.com/hashicorp/terraform/digraph"
|
||||||
|
@ -42,7 +43,34 @@ type ValidateError struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ValidateError) Error() string {
|
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
|
// ConstraintError is used to return detailed violation
|
||||||
|
|
|
@ -45,8 +45,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"
|
||||||
|
|
||||||
// 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 {
|
type GraphNodeResource struct {
|
||||||
|
Index int
|
||||||
Type string
|
Type string
|
||||||
Config *config.Resource
|
Config *config.Resource
|
||||||
Orphan bool
|
Orphan bool
|
||||||
|
@ -54,6 +58,15 @@ type GraphNodeResource struct {
|
||||||
ResourceProviderID string
|
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
|
// GraphNodeResourceProvider is a node type in the graph that represents
|
||||||
// the configuration for a resource provider.
|
// the configuration for a resource provider.
|
||||||
type GraphNodeResourceProvider struct {
|
type GraphNodeResourceProvider struct {
|
||||||
|
@ -152,18 +165,57 @@ func graphAddConfigResources(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noun := &depgraph.Noun{
|
resourceNouns := make([]*depgraph.Noun, r.Count)
|
||||||
Name: r.Id(),
|
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{
|
Meta: &GraphNodeResource{
|
||||||
Type: r.Type,
|
Type: r.Type,
|
||||||
Config: r,
|
Config: r,
|
||||||
Resource: &Resource{
|
Resource: &Resource{
|
||||||
Id: r.Id(),
|
Id: name,
|
||||||
State: state,
|
State: state,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
nouns[noun.Name] = noun
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the list of nouns that we iterate over
|
// 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)
|
nounsList := make([]*depgraph.Noun, 0, 2)
|
||||||
pcNouns := make(map[string]*depgraph.Noun)
|
pcNouns := make(map[string]*depgraph.Noun)
|
||||||
for _, noun := range g.Nouns {
|
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
|
// Look up the provider config for this resource
|
||||||
pcName := config.ProviderConfigName(resourceNode.Type, c.ProviderConfigs)
|
pcName := config.ProviderConfigName(resourceNode.Type, c.ProviderConfigs)
|
||||||
|
@ -401,11 +456,6 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
|
||||||
func graphAddRoot(g *depgraph.Graph) {
|
func graphAddRoot(g *depgraph.Graph) {
|
||||||
root := &depgraph.Noun{Name: GraphRootNode}
|
root := &depgraph.Noun{Name: GraphRootNode}
|
||||||
for _, n := range g.Nouns {
|
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{
|
root.Deps = append(root.Deps, &depgraph.Dependency{
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Source: root,
|
Source: root,
|
||||||
|
|
|
@ -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) {
|
func TestGraph_cycle(t *testing.T) {
|
||||||
config := testConfig(t, "graph-cycle")
|
config := testConfig(t, "graph-cycle")
|
||||||
|
|
||||||
|
@ -226,6 +241,25 @@ root
|
||||||
root -> openstack_floating_ip.random
|
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 = `
|
const testTerraformGraphDiffStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.foo
|
aws_instance.foo
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_load_balancer" "weblb" {
|
||||||
|
members = "${aws_instance.web.*.id}"
|
||||||
|
}
|
Loading…
Reference in New Issue