diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index abaa9817c132..992171914ae0 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -20,21 +20,38 @@ var Funcs map[string]ast.Function func init() { Funcs = map[string]ast.Function{ - "concat": interpolationFuncConcat(), - "element": interpolationFuncElement(), - "file": interpolationFuncFile(), - "format": interpolationFuncFormat(), - "formatlist": interpolationFuncFormatList(), - "index": interpolationFuncIndex(), - "join": interpolationFuncJoin(), - "length": interpolationFuncLength(), - "replace": interpolationFuncReplace(), - "split": interpolationFuncSplit(), + "compact": interpolationFuncCompact(), + "concat": interpolationFuncConcat(), + "element": interpolationFuncElement(), + "file": interpolationFuncFile(), + "format": interpolationFuncFormat(), + "formatlist": interpolationFuncFormatList(), + "index": interpolationFuncIndex(), + "join": interpolationFuncJoin(), + "length": interpolationFuncLength(), + "replace": interpolationFuncReplace(), + "split": interpolationFuncSplit(), "base64encode": interpolationFuncBase64Encode(), "base64decode": interpolationFuncBase64Decode(), } } +// interpolationFuncCompact strips a list of multi-variable values +// (e.g. as returned by "split") of any empty strings. +func interpolationFuncCompact() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Variadic: false, + Callback: func(args []interface{}) (interface{}, error) { + if !IsStringList(args[0].(string)) { + return args[0].(string), nil + } + return StringList(args[0].(string)).Compact().String(), nil + }, + } +} + // interpolationFuncConcat implements the "concat" function that // concatenates multiple strings. This isn't actually necessary anymore // since our language supports string concat natively, but for backwards diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index e4d275e5809d..8a33169a6751 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -11,6 +11,34 @@ import ( "github.com/hashicorp/terraform/config/lang/ast" ) + +func TestInterpolateFuncCompact(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + // empty string within array + { + `${compact(split(",", "a,,b"))}`, + NewStringList([]string{"a", "b"}).String(), + false, + }, + + // empty string at the end of array + { + `${compact(split(",", "a,b,"))}`, + NewStringList([]string{"a", "b"}).String(), + false, + }, + + // single empty string + { + `${compact(split(",", ""))}`, + NewStringList([]string{}).String(), + false, + }, + }, + }) +} + func TestInterpolateFuncDeprecatedConcat(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/config/string_list.go b/config/string_list.go index 70d43d1e4bc4..ba41d0256d94 100644 --- a/config/string_list.go +++ b/config/string_list.go @@ -24,6 +24,20 @@ type StringList string // ["", ""] => SLDSLDSLD const stringListDelim = `B780FFEC-B661-4EB8-9236-A01737AD98B6` +// Takes a Stringlist and returns one without empty strings in it +func (sl StringList) Compact() StringList { + parts := sl.Slice() + + newlist := []string{} + // drop the empty strings + for i := range parts { + if parts[i] != "" { + newlist = append(newlist, parts[i]) + } + } + return NewStringList(newlist) +} + // Build a StringList from a slice func NewStringList(parts []string) StringList { // We have to special case the empty list representation @@ -55,11 +69,10 @@ func (sl StringList) Length() int { func (sl StringList) Slice() []string { parts := strings.Split(string(sl), stringListDelim) - switch len(parts) { - case 0, 1: + // split on an empty StringList will have a length of 2, since there is + // always at least one deliminator + if len(parts) <= 2 { return []string{} - case 2: - return []string{""} } // strip empty elements generated by leading and trailing delimiters diff --git a/config/string_list_test.go b/config/string_list_test.go index 64049eb502e5..3fe57dfe2846 100644 --- a/config/string_list_test.go +++ b/config/string_list_test.go @@ -27,3 +27,26 @@ func TestStringList_element(t *testing.T) { list, expected, actual) } } + +func TestStringList_empty_slice(t *testing.T) { + expected := []string{} + l := NewStringList(expected) + actual := l.Slice() + + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("Expected %q, got %q", expected, actual) + } +} + +func TestStringList_empty_slice_length(t *testing.T) { + list := []string{} + l := NewStringList([]string{}) + actual := l.Length() + + expected := 0 + + if actual != expected { + t.Fatalf("Expected length of %q to be %d, got %d", + list, expected, actual) + } +} diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index bbbb1024a43a..fbce848eaf64 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -330,11 +330,6 @@ func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) { Value: config.NewStringList([]string{}).String(), Type: ast.TypeString, }) - // Zero + zero elements - testInterpolate(t, i, scope, "aws_route53_zone.terra.*.nothing", ast.Variable{ - Value: config.NewStringList([]string{"", ""}).String(), - Type: ast.TypeString, - }) // Zero + 1 element testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", ast.Variable{ Value: config.NewStringList([]string{"extra"}).String(),