Merge pull request #9883 from hashicorp/b-multi-order
terraform: multi-var ordering is by count
This commit is contained in:
commit
b010f8c864
|
@ -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) {
|
func TestContext2Apply_nilDiff(t *testing.T) {
|
||||||
m := testModule(t, "apply-good")
|
m := testModule(t, "apply-good")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -491,13 +492,13 @@ func (i *Interpolater) computeResourceMultiVariable(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the keys for all the resources that are created for this resource
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If count is zero, we return an empty list
|
// 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
|
return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,7 +508,9 @@ func (i *Interpolater) computeResourceMultiVariable(
|
||||||
}
|
}
|
||||||
|
|
||||||
var values []interface{}
|
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
|
// 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
|
// without a trailing index is found we prefer that. This choice
|
||||||
// is for legacy reasons: older versions of TF preferred it.
|
// is for legacy reasons: older versions of TF preferred it.
|
||||||
|
@ -678,10 +681,10 @@ func (i *Interpolater) resourceVariableInfo(
|
||||||
return module, cr, nil
|
return module, cr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Interpolater) resourceCountKeys(
|
func (i *Interpolater) resourceCountMax(
|
||||||
ms *ModuleState,
|
ms *ModuleState,
|
||||||
cr *config.Resource,
|
cr *config.Resource,
|
||||||
v *config.ResourceVariable) ([]string, error) {
|
v *config.ResourceVariable) (int, error) {
|
||||||
id := v.ResourceId()
|
id := v.ResourceId()
|
||||||
|
|
||||||
// If we're NOT applying, then we assume we can read the count
|
// If we're NOT applying, then we assume we can read the count
|
||||||
|
@ -690,31 +693,58 @@ func (i *Interpolater) resourceCountKeys(
|
||||||
if i.Operation != walkApply {
|
if i.Operation != walkApply {
|
||||||
count, err := cr.Count()
|
count, err := cr.Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]string, count)
|
return count, nil
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
result[i] = fmt.Sprintf("%s.%d", id, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to determine the list of resource keys to get values from.
|
// 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
|
// 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
|
// 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.
|
// and we can't guarantee that so we instead depend on the state.
|
||||||
var resourceKeys []string
|
max := -1
|
||||||
for k, _ := range ms.Resources {
|
for k, _ := range ms.Resources {
|
||||||
// If we don't have the right prefix then ignore it
|
// Get the index number for this resource
|
||||||
if k != id && !strings.HasPrefix(k, id+".") {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add it to the list
|
// Turn the index into an int
|
||||||
resourceKeys = append(resourceKeys, k)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)}"
|
||||||
|
}
|
|
@ -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)}"
|
||||||
|
}
|
Loading…
Reference in New Issue