core: allow distinguishing between empty lists and strings
Had to handle a lot of implicit leaning on a few properties of the old representation: * Old representation allowed plain strings to be treated as lists without problem (i.e. shoved into strings.Split), now strings need to be checked whether they are a list before they are treated as one (i.e. shoved into StringList(s).Slice()). * Tested behavior of 0 and 1 length lists in formatlist() was a side effect of the representation. Needs to be special cased now to maintain the behavior. * Found a pretty old context test failure that was wrong in several different ways. It's covered by TestContext2Apply_multiVar so I removed it.
This commit is contained in:
parent
7238b3b4af
commit
e88aeede9b
|
@ -56,9 +56,10 @@ func interpolationFuncConcat() ast.Function {
|
||||||
|
|
||||||
if IsStringList(argument) {
|
if IsStringList(argument) {
|
||||||
isDeprecated = false
|
isDeprecated = false
|
||||||
}
|
finalList = append(finalList, StringList(argument).Slice()...)
|
||||||
|
} else {
|
||||||
finalList = append(finalList, argument)
|
finalList = append(finalList, argument)
|
||||||
|
}
|
||||||
|
|
||||||
// Deprecated concat behaviour
|
// Deprecated concat behaviour
|
||||||
b.WriteString(argument)
|
b.WriteString(argument)
|
||||||
|
@ -131,11 +132,26 @@ func interpolationFuncFormatList() ast.Function {
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
parts := StringList(s).Slice()
|
if !IsStringList(s) {
|
||||||
if len(parts) <= 1 {
|
|
||||||
continue
|
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
|
varargs[i-1] = parts
|
||||||
|
|
||||||
|
// Check length
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
// first list we've seen
|
// first list we've seen
|
||||||
n = len(parts)
|
n = len(parts)
|
||||||
|
|
|
@ -221,7 +221,8 @@ func TestInterpolateFuncJoin(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
`${join(",", "foo")}`,
|
fmt.Sprintf(`${join(",", "%s")}`,
|
||||||
|
NewStringList([]string{"foo"}).String()),
|
||||||
"foo",
|
"foo",
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -354,9 +355,15 @@ func TestInterpolateFuncSplit(t *testing.T) {
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`${split(",", "")}`,
|
||||||
|
NewStringList([]string{""}).String(),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
`${split(",", "foo")}`,
|
`${split(",", "foo")}`,
|
||||||
"foo",
|
NewStringList([]string{"foo"}).String(),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -524,7 +531,8 @@ func TestInterpolateFuncElement(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
`${element("foo", "0")}`,
|
fmt.Sprintf(`${element("%s", "0")}`,
|
||||||
|
NewStringList([]string{"foo"}).String()),
|
||||||
"foo",
|
"foo",
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -144,7 +144,7 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
||||||
// set if it is computed. This behavior is different if we're
|
// set if it is computed. This behavior is different if we're
|
||||||
// splitting (in a SliceElem) or not.
|
// splitting (in a SliceElem) or not.
|
||||||
remove := false
|
remove := false
|
||||||
if w.loc == reflectwalk.SliceElem {
|
if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal) {
|
||||||
parts := StringList(replaceVal).Slice()
|
parts := StringList(replaceVal).Slice()
|
||||||
for _, p := range parts {
|
for _, p := range parts {
|
||||||
if p == UnknownVariableValue {
|
if p == UnknownVariableValue {
|
||||||
|
@ -265,10 +265,15 @@ func (w *interpolationWalker) splitSlice() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split on the delimiter
|
if IsStringList(sv) {
|
||||||
for _, p := range StringList(sv).Slice() {
|
for _, p := range StringList(sv).Slice() {
|
||||||
result = append(result, p)
|
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
|
// Our slice is now done, we have to replace the slice now
|
||||||
|
|
|
@ -183,7 +183,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,48 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// StringList represents the "poor man's list" that terraform uses
|
// StringList represents the "poor man's list" that terraform uses
|
||||||
// internally
|
// internally
|
||||||
type StringList string
|
type StringList string
|
||||||
|
|
||||||
// This is the delimiter used to recognize and split StringLists
|
// 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
|
// Build a StringList from a slice
|
||||||
func NewStringList(parts []string) StringList {
|
func NewStringList(parts []string) StringList {
|
||||||
// FOR NOW:
|
// We have to special case the empty list representation
|
||||||
return StringList(strings.Join(parts, StringListDelim))
|
if len(parts) == 0 {
|
||||||
// EVENTUALLY:
|
return StringList(stringListDelim)
|
||||||
// var sl StringList
|
}
|
||||||
// for _, p := range parts {
|
return StringList(fmt.Sprintf("%s%s%s",
|
||||||
// sl = sl.Append(p)
|
stringListDelim,
|
||||||
// }
|
strings.Join(parts, stringListDelim),
|
||||||
// return sl
|
stringListDelim,
|
||||||
}
|
))
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an element at the index, wrapping around the length of the string
|
// Returns an element at the index, wrapping around the length of the string
|
||||||
// when index > list length
|
// when index > list length
|
||||||
func (sl StringList) Element(index int) string {
|
func (sl StringList) Element(index int) string {
|
||||||
|
if sl.Length() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return sl.Slice()[index%sl.Length()]
|
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
|
// Returns a slice of strings as represented by this StringList
|
||||||
func (sl StringList) Slice() []string {
|
func (sl StringList) Slice() []string {
|
||||||
parts := strings.Split(string(sl), StringListDelim)
|
parts := strings.Split(string(sl), stringListDelim)
|
||||||
|
|
||||||
// FOR NOW:
|
switch len(parts) {
|
||||||
if sl.String() == "" {
|
case 0, 1:
|
||||||
return []string{}
|
return []string{}
|
||||||
} else {
|
case 2:
|
||||||
return parts
|
return []string{""}
|
||||||
}
|
}
|
||||||
// EVENTUALLY:
|
|
||||||
// StringLists always have a trailing StringListDelim
|
// strip empty elements generated by leading and trailing delimiters
|
||||||
// return parts[:len(parts)-1]
|
return parts[1 : len(parts)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sl StringList) String() string {
|
func (sl StringList) String() string {
|
||||||
|
@ -61,5 +72,5 @@ func (sl StringList) String() string {
|
||||||
|
|
||||||
// Determines if a given string represents a StringList
|
// Determines if a given string represents a StringList
|
||||||
func IsStringList(s string) bool {
|
func IsStringList(s string) bool {
|
||||||
return strings.Contains(s, StringListDelim)
|
return strings.Contains(s, stringListDelim)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
func TestContext2Plan_varListErr(t *testing.T) {
|
||||||
m := testModule(t, "plan-var-list-err")
|
m := testModule(t, "plan-var-list-err")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
resource "aws_instance" "foo" {
|
|
||||||
num = "2"
|
|
||||||
|
|
||||||
count = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "aws_instance" "bar" {
|
|
||||||
foo = "${aws_instance.foo.*.num}"
|
|
||||||
}
|
|
Loading…
Reference in New Issue