Skip to content

Commit

Permalink
Merge pull request #13 from hashicorp/f-count
Browse files Browse the repository at this point in the history
Count Meta-Parameter
  • Loading branch information
mitchellh committed Jul 4, 2014
2 parents 17074e5 + 3b3c9e1 commit 338bafc
Show file tree
Hide file tree
Showing 17 changed files with 533 additions and 33 deletions.
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

0 comments on commit 338bafc

Please sign in to comment.