Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Count Meta-Parameter #13

Merged
merged 7 commits into from
Jul 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type ProviderConfig struct {
type Resource struct {
Name string
Type string
Count int
RawConfig *RawConfig
}

Expand Down
18 changes: 18 additions & 0 deletions config/loader_libucl.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
err)
}

// Remove the "count" from the config, since we treat that special
delete(config, "count")

rawConfig, err := NewRawConfig(config)
if err != nil {
return nil, fmt.Errorf(
Expand All @@ -262,9 +265,24 @@ func loadResourcesLibucl(o *libucl.Object) ([]*Resource, error) {
err)
}

// If we have a count, then figure it out
var count int = 1
if o := r.Get("count"); o != nil {
err = o.Decode(&count)
o.Close()
if err != nil {
return nil, fmt.Errorf(
"Error parsing count for %s[%s]: %s",
t.Key(),
r.Key(),
err)
}
}

result = append(result, &Resource{
Name: r.Key(),
Type: t.Key(),
Count: count,
RawConfig: rawConfig,
})
}
Expand Down
17 changes: 11 additions & 6 deletions config/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,10 @@ func resourcesStr(rs []*Resource) string {
result := ""
for _, r := range rs {
result += fmt.Sprintf(
"%s[%s]\n",
"%s[%s] (x%d)\n",
r.Type,
r.Name)
r.Name,
r.Count)

ks := make([]string, 0, len(r.RawConfig.Raw))
for k, _ := range r.RawConfig.Raw {
Expand Down Expand Up @@ -229,14 +230,18 @@ do
`

const basicResourcesStr = `
aws_security_group[firewall]
aws_instance[web]
aws_security_group[firewall] (x5)
aws_instance[web] (x1)
ami
network_interface
security_groups
vars
resource: aws_security_group.firewall.foo
user: var.foo
aws_instance[db] (x1)
security_groups
vars
resource: aws_security_group.firewall.*.id
`

const basicVariablesStr = `
Expand All @@ -251,8 +256,8 @@ aws
`

const importResourcesStr = `
aws_security_group[db]
aws_security_group[web]
aws_security_group[db] (x1)
aws_security_group[web] (x1)
`

const importVariablesStr = `
Expand Down
5 changes: 5 additions & 0 deletions config/test-fixtures/basic.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ provider "do" {
}

resource "aws_security_group" "firewall" {
count = 5
}

resource aws_instance "web" {
Expand All @@ -27,3 +28,7 @@ resource aws_instance "web" {
description = "Main network interface"
}
}

resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
}
2 changes: 1 addition & 1 deletion config/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
var varRegexp *regexp.Regexp

func init() {
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([-.a-z0-9_]+)\}`)
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
}

// ReplaceVariables takes a configuration and a mapping of variables
Expand Down
16 changes: 16 additions & 0 deletions config/variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ func TestVariableDetectWalker_resource(t *testing.T) {
}
}

func TestVariableDetectWalker_resourceMulti(t *testing.T) {
w := new(variableDetectWalker)

str := `foo ${ec2.foo.*.bar}`
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
t.Fatalf("err: %s", err)
}

if len(w.Variables) != 1 {
t.Fatalf("bad: %#v", w.Variables)
}
if w.Variables["ec2.foo.*.bar"].(*ResourceVariable).FullKey() != "ec2.foo.*.bar" {
t.Fatalf("bad: %#v", w.Variables)
}
}

func TestVariableDetectWalker_bad(t *testing.T) {
w := new(variableDetectWalker)

Expand Down
30 changes: 29 additions & 1 deletion depgraph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"bytes"
"fmt"
"sort"
"strings"
"sync"

"github.com/hashicorp/terraform/digraph"
Expand Down Expand Up @@ -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
Expand Down
83 changes: 83 additions & 0 deletions terraform/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package terraform
import (
"fmt"
"log"
"strings"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -509,6 +510,9 @@ func (c *Context) genericWalkFn(
vars[fmt.Sprintf("var.%s", k)] = v
}

// This will keep track of the counts of multi-count resources
counts := make(map[string]int)

// This will keep track of whether we're stopped or not
var stop uint32 = 0

Expand All @@ -523,8 +527,20 @@ func (c *Context) genericWalkFn(
return nil
}

// Calculate any aggregate interpolated variables if we have to.
// Aggregate variables (such as "test_instance.foo.*.id") are not
// pre-computed since the fanout would be expensive. We calculate
// them on-demand here.
computeAggregateVars(&l, n, counts, vars)

switch m := n.Meta.(type) {
case *GraphNodeResource:
case *GraphNodeResourceMeta:
// Record the count and then just ignore
l.Lock()
counts[m.ID] = m.Count
l.Unlock()
return nil
case *GraphNodeResourceProvider:
var rc *ResourceConfig
if m.Config != nil {
Expand All @@ -543,6 +559,8 @@ func (c *Context) genericWalkFn(
}

return nil
default:
panic(fmt.Sprintf("unknown graph node: %#v", n.Meta))
}

rn := n.Meta.(*GraphNodeResource)
Expand Down Expand Up @@ -603,3 +621,68 @@ func (c *Context) genericWalkFn(
return nil
}
}

func computeAggregateVars(
l *sync.RWMutex,
n *depgraph.Noun,
cs map[string]int,
vs map[string]string) {
var ivars map[string]config.InterpolatedVariable
switch m := n.Meta.(type) {
case *GraphNodeResource:
if m.Config != nil {
ivars = m.Config.RawConfig.Variables
}
case *GraphNodeResourceProvider:
if m.Config != nil {
ivars = m.Config.RawConfig.Variables
}
}
if len(ivars) == 0 {
return
}

for _, v := range ivars {
rv, ok := v.(*config.ResourceVariable)
if !ok {
continue
}

idx := strings.Index(rv.Field, ".")
if idx == -1 {
// It isn't an aggregated var
continue
}
if rv.Field[:idx] != "*" {
// It isn't an aggregated var
continue
}
field := rv.Field[idx+1:]

// Get the meta node so that we can determine the count
key := fmt.Sprintf("%s.%s", rv.Type, rv.Name)
l.RLock()
count, ok := cs[key]
l.RUnlock()
if !ok {
// This should never happen due to semantic checks
panic(fmt.Sprintf(
"non-existent resource variable access: %s\n\n%#v", key, rv))
}

var values []string
for i := 0; i < count; i++ {
key := fmt.Sprintf(
"%s.%s.%d.%s",
rv.Type,
rv.Name,
i,
field)
if v, ok := vs[key]; ok {
values = append(values, v)
}
}

vs[rv.FullKey()] = strings.Join(values, ",")
}
}
Loading