diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index fbbe441ed..22acfb3d4 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -56,10 +56,11 @@ func interpolationFuncConcat() ast.Function { if IsStringList(argument) { isDeprecated = false + finalList = append(finalList, StringList(argument).Slice()...) + } else { + finalList = append(finalList, argument) } - finalList = append(finalList, argument) - // Deprecated concat behaviour b.WriteString(argument) } @@ -131,11 +132,26 @@ func interpolationFuncFormatList() ast.Function { if !ok { continue } - parts := StringList(s).Slice() - if len(parts) <= 1 { + if !IsStringList(s) { continue } + + parts := StringList(s).Slice() + + // 0 or 1 length lists are treated as scalars and repeated + switch len(parts) { + case 0: + varargs[i-1] = "" + continue + case 1: + varargs[i-1] = parts[0] + continue + } + + // otherwise the list is sent down to be indexed varargs[i-1] = parts + + // Check length if n == 0 { // first list we've seen n = len(parts) diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index dc6c93339..e6379fc01 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -221,7 +221,8 @@ func TestInterpolateFuncJoin(t *testing.T) { }, { - `${join(",", "foo")}`, + fmt.Sprintf(`${join(",", "%s")}`, + NewStringList([]string{"foo"}).String()), "foo", false, }, @@ -354,9 +355,15 @@ func TestInterpolateFuncSplit(t *testing.T) { true, }, + { + `${split(",", "")}`, + NewStringList([]string{""}).String(), + false, + }, + { `${split(",", "foo")}`, - "foo", + NewStringList([]string{"foo"}).String(), false, }, @@ -524,7 +531,8 @@ func TestInterpolateFuncElement(t *testing.T) { }, { - `${element("foo", "0")}`, + fmt.Sprintf(`${element("%s", "0")}`, + NewStringList([]string{"foo"}).String()), "foo", false, }, diff --git a/config/interpolate_walk.go b/config/interpolate_walk.go index a8d8ac7f0..adcb5e32c 100644 --- a/config/interpolate_walk.go +++ b/config/interpolate_walk.go @@ -144,7 +144,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error { // set if it is computed. This behavior is different if we're // splitting (in a SliceElem) or not. remove := false - if w.loc == reflectwalk.SliceElem { + if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal) { parts := StringList(replaceVal).Slice() for _, p := range parts { if p == UnknownVariableValue { @@ -265,10 +265,15 @@ func (w *interpolationWalker) splitSlice() { continue } - // Split on the delimiter - for _, p := range StringList(sv).Slice() { - result = append(result, p) + if IsStringList(sv) { + for _, p := range StringList(sv).Slice() { + result = append(result, p) + } + continue } + + // Not a string list, so just set it + result = append(result, sv) } // Our slice is now done, we have to replace the slice now diff --git a/config/interpolate_walk_test.go b/config/interpolate_walk_test.go index bc17dbddd..fc7c8b549 100644 --- a/config/interpolate_walk_test.go +++ b/config/interpolate_walk_test.go @@ -183,7 +183,7 @@ func TestInterpolationWalker_replace(t *testing.T) { } if !reflect.DeepEqual(tc.Input, tc.Output) { - t.Fatalf("%d: bad:\n\n%#v", i, tc.Input) + t.Fatalf("%d: bad:\n\nexpected:%#v\ngot:%#v", i, tc.Output, tc.Input) } } } diff --git a/config/string_list.go b/config/string_list.go index 1a4ac6a7d..70d43d1e4 100644 --- a/config/string_list.go +++ b/config/string_list.go @@ -1,37 +1,48 @@ package config -import "strings" +import ( + "fmt" + "strings" +) // StringList represents the "poor man's list" that terraform uses // internally type StringList string // This is the delimiter used to recognize and split StringLists -const StringListDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6` +// +// It plays two semantic roles: +// * It introduces a list +// * It terminates each element +// +// Example representations: +// [] => SLD +// [""] => SLDSLD +// [" "] => SLD SLD +// ["foo"] => SLDfooSLD +// ["foo", "bar"] => SLDfooSLDbarSLD +// ["", ""] => SLDSLDSLD +const stringListDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6` // Build a StringList from a slice func NewStringList(parts []string) StringList { - // FOR NOW: - return StringList(strings.Join(parts, StringListDelim)) - // EVENTUALLY: - // var sl StringList - // for _, p := range parts { - // sl = sl.Append(p) - // } - // return sl -} - -// Returns a new StringList with the item appended -func (sl StringList) Append(s string) StringList { - // FOR NOW: - return StringList(strings.Join(append(sl.Slice(), s), StringListDelim)) - // EVENTUALLY: - // return StringList(fmt.Sprintf("%s%s%s", sl, s, StringListDelim)) + // We have to special case the empty list representation + if len(parts) == 0 { + return StringList(stringListDelim) + } + return StringList(fmt.Sprintf("%s%s%s", + stringListDelim, + strings.Join(parts, stringListDelim), + stringListDelim, + )) } // Returns an element at the index, wrapping around the length of the string // when index > list length func (sl StringList) Element(index int) string { + if sl.Length() == 0 { + return "" + } return sl.Slice()[index%sl.Length()] } @@ -42,17 +53,17 @@ func (sl StringList) Length() int { // Returns a slice of strings as represented by this StringList func (sl StringList) Slice() []string { - parts := strings.Split(string(sl), StringListDelim) + parts := strings.Split(string(sl), stringListDelim) - // FOR NOW: - if sl.String() == "" { + switch len(parts) { + case 0, 1: return []string{} - } else { - return parts + case 2: + return []string{""} } - // EVENTUALLY: - // StringLists always have a trailing StringListDelim - // return parts[:len(parts)-1] + + // strip empty elements generated by leading and trailing delimiters + return parts[1 : len(parts)-1] } func (sl StringList) String() string { @@ -61,5 +72,5 @@ func (sl StringList) String() string { // Determines if a given string represents a StringList func IsStringList(s string) bool { - return strings.Contains(s, StringListDelim) + return strings.Contains(s, stringListDelim) } diff --git a/terraform/context_test.go b/terraform/context_test.go index 55580cb9a..53092d72d 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -1672,29 +1672,6 @@ func TestContext2Plan_provider(t *testing.T) { } } -func TestContext2Plan_varMultiCountOne(t *testing.T) { - m := testModule(t, "plan-var-multi-count-one") - p := testProvider("aws") - p.DiffFn = testDiffFn - ctx := testContext2(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, - }) - - plan, err := ctx.Plan() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(plan.String()) - expected := strings.TrimSpace(testTerraformPlanVarMultiCountOneStr) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } -} - func TestContext2Plan_varListErr(t *testing.T) { m := testModule(t, "plan-var-list-err") p := testProvider("aws") diff --git a/terraform/test-fixtures/plan-var-multi-count-one/main.tf b/terraform/test-fixtures/plan-var-multi-count-one/main.tf deleted file mode 100644 index 6fd84932f..000000000 --- a/terraform/test-fixtures/plan-var-multi-count-one/main.tf +++ /dev/null @@ -1,9 +0,0 @@ -resource "aws_instance" "foo" { - num = "2" - - count = 1 -} - -resource "aws_instance" "bar" { - foo = "${aws_instance.foo.*.num}" -}