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

terraform: multi-var ordering is by count #9883

Merged
merged 1 commit into from
Nov 7, 2016
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
68 changes: 68 additions & 0 deletions terraform/context_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,74 @@ func TestContext2Apply_multiVar(t *testing.T) {
}
}

// Test that multi-var (splat) access is ordered by count, not by
// value.
func TestContext2Apply_multiVarOrder(t *testing.T) {
m := testModule(t, "apply-multi-var-order")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn

// First, apply with a count of 3
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})

if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}

state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}

t.Logf("State: %s", state.String())

actual := state.RootModule().Outputs["should-be-11"]
expected := "index-11"
if actual == nil || actual.Value != expected {
t.Fatalf("bad: \n%s", actual)
}
}

// Test that multi-var (splat) access is ordered by count, not by
// value, through interpolations.
func TestContext2Apply_multiVarOrderInterp(t *testing.T) {
m := testModule(t, "apply-multi-var-order-interp")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn

// First, apply with a count of 3
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})

if _, err := ctx.Plan(); err != nil {
t.Fatalf("err: %s", err)
}

state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}

t.Logf("State: %s", state.String())

actual := state.RootModule().Outputs["should-be-11"]
expected := "baz-index-11"
if actual == nil || actual.Value != expected {
t.Fatalf("bad: \n%s", actual)
}
}

func TestContext2Apply_nilDiff(t *testing.T) {
m := testModule(t, "apply-good")
p := testProvider("aws")
Expand Down
68 changes: 49 additions & 19 deletions terraform/interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"regexp"
"sort"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -491,13 +492,13 @@ func (i *Interpolater) computeResourceMultiVariable(
}

// Get the keys for all the resources that are created for this resource
resourceKeys, err := i.resourceCountKeys(module, cr, v)
countMax, err := i.resourceCountMax(module, cr, v)
if err != nil {
return nil, err
}

// If count is zero, we return an empty list
if len(resourceKeys) == 0 {
if countMax == 0 {
return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
}

Expand All @@ -507,7 +508,9 @@ func (i *Interpolater) computeResourceMultiVariable(
}

var values []interface{}
for _, id := range resourceKeys {
for idx := 0; idx < countMax; idx++ {
id := fmt.Sprintf("%s.%d", v.ResourceId(), idx)

// ID doesn't have a trailing index. We try both here, but if a value
// without a trailing index is found we prefer that. This choice
// is for legacy reasons: older versions of TF preferred it.
Expand Down Expand Up @@ -678,10 +681,10 @@ func (i *Interpolater) resourceVariableInfo(
return module, cr, nil
}

func (i *Interpolater) resourceCountKeys(
func (i *Interpolater) resourceCountMax(
ms *ModuleState,
cr *config.Resource,
v *config.ResourceVariable) ([]string, error) {
v *config.ResourceVariable) (int, error) {
id := v.ResourceId()

// If we're NOT applying, then we assume we can read the count
Expand All @@ -690,31 +693,58 @@ func (i *Interpolater) resourceCountKeys(
if i.Operation != walkApply {
count, err := cr.Count()
if err != nil {
return nil, err
}

result := make([]string, count)
for i := 0; i < count; i++ {
result[i] = fmt.Sprintf("%s.%d", id, i)
return 0, err
}

return result, nil
return count, nil
}

// We need to determine the list of resource keys to get values from.
// This needs to be sorted so the order is deterministic. We used to
// use "cr.Count()" but that doesn't work if the count is interpolated
// and we can't guarantee that so we instead depend on the state.
var resourceKeys []string
max := -1
for k, _ := range ms.Resources {
// If we don't have the right prefix then ignore it
if k != id && !strings.HasPrefix(k, id+".") {
// Get the index number for this resource
index := ""
if k == id {
// If the key is the id, then its just 0 (no explicit index)
index = "0"
} else if strings.HasPrefix(k, id+".") {
// Grab the index number out of the state
index = k[len(id+"."):]
if idx := strings.IndexRune(index, '.'); idx >= 0 {
index = index[:idx]
}
}

// If there was no index then this resource didn't match
// the one we're looking for, exit.
if index == "" {
continue
}

// Add it to the list
resourceKeys = append(resourceKeys, k)
// Turn the index into an int
raw, err := strconv.ParseInt(index, 0, 0)
if err != nil {
return 0, fmt.Errorf(
"%s: error parsing index %q as int: %s",
id, index, err)
}

// Keep track of this index if its the max
if new := int(raw); new > max {
max = new
}
}
sort.Strings(resourceKeys)
return resourceKeys, nil

// If we never found any matching resources in the state, we
// have zero.
if max == -1 {
return 0, nil
}

// The result value is "max+1" because we're returning the
// max COUNT, not the max INDEX, and we zero-index.
return max + 1, nil
}
15 changes: 15 additions & 0 deletions terraform/test-fixtures/apply-multi-var-order-interp/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "count" { default = 15 }

resource "aws_instance" "bar" {
count = "${var.count}"
foo = "index-${count.index}"
}

resource "aws_instance" "baz" {
count = "${var.count}"
foo = "baz-${element(aws_instance.bar.*.foo, count.index)}"
}

output "should-be-11" {
value = "${element(aws_instance.baz.*.foo, 11)}"
}
10 changes: 10 additions & 0 deletions terraform/test-fixtures/apply-multi-var-order/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
variable "count" { default = 15 }

resource "aws_instance" "bar" {
count = "${var.count}"
foo = "index-${count.index}"
}

output "should-be-11" {
value = "${element(aws_instance.bar.*.foo, 11)}"
}