Skip to content

Commit a6715bc

Browse files
committed
Fix default values on variables interpolation
Signed-off-by: Ulysses Souza <[email protected]>
1 parent 0621f17 commit a6715bc

File tree

2 files changed

+100
-3
lines changed

2 files changed

+100
-3
lines changed

interpolation/interpolation_test.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
package interpolation
1818

1919
import (
20-
"testing"
21-
2220
"strconv"
21+
"testing"
2322

2423
"gotest.tools/v3/assert"
2524
is "gotest.tools/v3/assert/cmp"
@@ -95,6 +94,75 @@ func TestInterpolateWithDefaults(t *testing.T) {
9594
assert.Check(t, is.DeepEqual(expected, result))
9695
}
9796

97+
func TestValidUnexistentInterpolation(t *testing.T) {
98+
var testcases = []struct {
99+
test string
100+
expected string
101+
}{
102+
{test: "{{{ ${FOO:-foo_} }}}", expected: "{{{ foo_ }}}"},
103+
{test: "{{{ ${FOO:-foo-bar-value} }}}", expected: "{{{ foo-bar-value }}}"},
104+
{test: "{{{ ${FOO:-foo} ${BAR:-DEFAULT_VALUE} }}}", expected: "{{{ foo DEFAULT_VALUE }}}"},
105+
{test: "{{{ ${BAR} }}}", expected: "{{{ }}}"},
106+
{test: "${FOO:-baz} }}}", expected: "baz }}}"},
107+
{test: "${FOO-baz} }}}", expected: "baz }}}"},
108+
}
109+
110+
getServiceConfig := func(val string) map[string]interface{} {
111+
return map[string]interface{}{
112+
"myservice": map[string]interface{}{
113+
"environment": map[string]interface{}{
114+
"TESTVAR": val,
115+
},
116+
},
117+
}
118+
}
119+
120+
for _, testcase := range testcases {
121+
result, err := Interpolate(getServiceConfig(testcase.test), Options{})
122+
assert.NilError(t, err)
123+
assert.Check(t, is.DeepEqual(getServiceConfig(testcase.expected), result))
124+
}
125+
}
126+
127+
func TestValidExistentInterpolation(t *testing.T) {
128+
var testcases = []struct {
129+
test string
130+
expected string
131+
}{
132+
// Only FOO is set
133+
{test: "{{{ ${FOO:-foo_} }}}", expected: "{{{ bar }}}"},
134+
{test: "{{{ ${FOO:-foo-bar-value} }}}", expected: "{{{ bar }}}"},
135+
{test: "{{{ ${FOO:-foo} ${BAR:-DEFAULT_VALUE} }}}", expected: "{{{ bar DEFAULT_VALUE }}}"},
136+
{test: "{{{ ${BAR} }}}", expected: "{{{ }}}"},
137+
{test: "${FOO:-baz} }}}", expected: "bar }}}"},
138+
{test: "${FOO-baz} }}}", expected: "bar }}}"},
139+
140+
// Both FOO and USER are set
141+
{test: "{{{ ${FOO:-foo_} }}}", expected: "{{{ bar }}}"},
142+
{test: "{{{ ${FOO:-foo-bar-value} }}}", expected: "{{{ bar }}}"},
143+
{test: "{{{ ${FOO:-foo} ${USER:-bar} }}}", expected: "{{{ bar jenny }}}"},
144+
{test: "{{{ ${USER} }}}", expected: "{{{ jenny }}}"},
145+
{test: "${FOO:-baz} }}}", expected: "bar }}}"},
146+
{test: "${FOO-baz} }}}", expected: "bar }}}"},
147+
}
148+
149+
getServiceConfig := func(val string) map[string]interface{} {
150+
return map[string]interface{}{
151+
"myservice": map[string]interface{}{
152+
"environment": map[string]interface{}{
153+
"TESTVAR": val,
154+
},
155+
},
156+
}
157+
}
158+
159+
for _, testcase := range testcases {
160+
result, err := Interpolate(getServiceConfig(testcase.test), Options{LookupValue: defaultMapping})
161+
assert.NilError(t, err)
162+
assert.Check(t, is.DeepEqual(getServiceConfig(testcase.expected), result))
163+
}
164+
}
165+
98166
func TestInterpolateWithCast(t *testing.T) {
99167
config := map[string]interface{}{
100168
"foo": map[string]interface{}{

template/template.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
var delimiter = "\\$"
2828
var substitutionNamed = "[_a-z][_a-z0-9]*"
29+
2930
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?"
3031

3132
var patternString = fmt.Sprintf(
@@ -69,6 +70,13 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
6970
}
7071
var err error
7172
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
73+
closingBraceIndex := getFirstBraceClosingIndex(substring)
74+
rest := ""
75+
if closingBraceIndex > -1 {
76+
rest = substring[closingBraceIndex+1:]
77+
substring = substring[0 : closingBraceIndex+1]
78+
}
79+
7280
matches := pattern.FindStringSubmatch(substring)
7381
groups := matchGroups(matches, pattern)
7482
if escaped := groups["escaped"]; escaped != "" {
@@ -100,7 +108,11 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
100108
if !applied {
101109
continue
102110
}
103-
return value
111+
interpolatedNested, err := SubstituteWith(rest, mapping, pattern, subsFuncs...)
112+
if err != nil {
113+
return ""
114+
}
115+
return value + interpolatedNested
104116
}
105117
}
106118

@@ -114,6 +126,23 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
114126
return result, err
115127
}
116128

129+
func getFirstBraceClosingIndex(s string) int {
130+
openVariableBraces := 0
131+
for i := 0; i < len(s); i++ {
132+
if s[i] == '}' {
133+
openVariableBraces--
134+
if openVariableBraces == 0 {
135+
return i
136+
}
137+
}
138+
if strings.HasPrefix(s[i:], "${") {
139+
openVariableBraces++
140+
i++
141+
}
142+
}
143+
return -1
144+
}
145+
117146
// Substitute variables in the string with their values
118147
func Substitute(template string, mapping Mapping) (string, error) {
119148
return SubstituteWith(template, mapping, defaultPattern)

0 commit comments

Comments
 (0)