Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 0fc04d3

Browse files
author
Vincent Demeester
authored
Merge pull request #492 from vito-c/parser
adding ability to parse default bash variables such as ${foo:-bar}
2 parents 68ce73f + 0e8984f commit 0fc04d3

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

config/interpolation.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ import (
88
"github.com/Sirupsen/logrus"
99
)
1010

11+
var defaultValues = make(map[string]string)
12+
1113
func isNum(c uint8) bool {
1214
return c >= '0' && c <= '9'
1315
}
1416

17+
func validVariableDefault(c uint8, line string, pos int) bool {
18+
return (c == ':' && line[pos+1] == '-') || (c == '-')
19+
}
20+
1521
func validVariableNameChar(c uint8) bool {
1622
return c == '_' ||
1723
c >= 'A' && c <= 'Z' ||
@@ -36,6 +42,30 @@ func parseVariable(line string, pos int, mapping func(string) string) (string, i
3642
return mapping(buffer.String()), pos, true
3743
}
3844

45+
func parseDefaultValue(line string, pos int) (string, int, bool) {
46+
var buffer bytes.Buffer
47+
48+
// only skip :, :- and - at the beginning
49+
for ; pos < len(line); pos++ {
50+
c := line[pos]
51+
if c == ':' || c == '-' {
52+
continue
53+
}
54+
break
55+
}
56+
for ; pos < len(line); pos++ {
57+
c := line[pos]
58+
if c == '}' {
59+
return buffer.String(), pos - 1, true
60+
}
61+
err := buffer.WriteByte(c)
62+
if err != nil {
63+
return "", pos, false
64+
}
65+
}
66+
return "", 0, false
67+
}
68+
3969
func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) {
4070
var buffer bytes.Buffer
4171

@@ -49,10 +79,13 @@ func parseVariableWithBraces(line string, pos int, mapping func(string) string)
4979
if bufferString == "" {
5080
return "", 0, false
5181
}
52-
5382
return mapping(buffer.String()), pos, true
5483
case validVariableNameChar(c):
5584
buffer.WriteByte(c)
85+
case validVariableDefault(c, line, pos):
86+
defaultValue := ""
87+
defaultValue, pos, _ = parseDefaultValue(line, pos)
88+
defaultValues[buffer.String()] = defaultValue
5689
default:
5790
return "", 0, false
5891
}
@@ -143,10 +176,19 @@ func Interpolate(key string, data *interface{}, environmentLookup EnvironmentLoo
143176
values := environmentLookup.Lookup(s, nil)
144177

145178
if len(values) == 0 {
179+
if val, ok := defaultValues[s]; ok {
180+
return val
181+
}
146182
logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
147183
return ""
148184
}
149185

186+
if strings.SplitN(values[0], "=", 2)[1] == "" {
187+
if val, ok := defaultValues[s]; ok {
188+
return val
189+
}
190+
}
191+
150192
// Use first result if many are given
151193
value := values[0]
152194

config/interpolation_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"fmt"
55
"os"
6+
"strings"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
@@ -25,6 +26,14 @@ func testInvalidInterpolatedLine(t *testing.T, line string) {
2526
assert.Equal(t, false, success)
2627
}
2728

29+
func testInterpolatedDefault(t *testing.T, line string, delim string, expectedVar string, expectedVal string) {
30+
envVar, _ := parseLine(line, func(env string) string { return env })
31+
pos := strings.Index(line, delim)
32+
envDefault, _, _ := parseDefaultValue(line, pos)
33+
assert.Equal(t, expectedVal, envDefault)
34+
assert.Equal(t, expectedVar, envVar)
35+
}
36+
2837
func TestParseLine(t *testing.T) {
2938
variables := map[string]string{
3039
"A": "ABC",
@@ -35,11 +44,20 @@ func TestParseLine(t *testing.T) {
3544
"split_VaLue": "WORKED",
3645
"9aNumber": "WORKED",
3746
"a9Number": "WORKED",
47+
"defTest": "WORKED",
3848
}
3949

50+
testInterpolatedDefault(t, "${defVar:-defVal}", ":-", "defVar", "defVal")
51+
testInterpolatedDefault(t, "${defVar2-defVal2}", "-", "defVar2", "defVal2")
52+
testInterpolatedDefault(t, "${defVar:-def:Val}", ":-", "defVar", "def:Val")
53+
testInterpolatedDefault(t, "${defVar:-def-Val}", ":-", "defVar", "def-Val")
54+
4055
testInterpolatedLine(t, "WORKED", "$lower", variables)
4156
testInterpolatedLine(t, "WORKED", "${MiXeD}", variables)
4257
testInterpolatedLine(t, "WORKED", "${split_VaLue}", variables)
58+
// make sure variable name is parsed correctly with default value
59+
testInterpolatedLine(t, "WORKED", "${defTest:-sometest}", variables)
60+
testInterpolatedLine(t, "WORKED", "${defTest-sometest}", variables)
4361
// Starting with a number isn't valid
4462
testInterpolatedLine(t, "", "$9aNumber", variables)
4563
testInterpolatedLine(t, "WORKED", "$a9Number", variables)
@@ -67,6 +85,7 @@ func TestParseLine(t *testing.T) {
6785
testInterpolatedLine(t, "", "$E", variables)
6886
testInterpolatedLine(t, "", "${E}", variables)
6987

88+
testInvalidInterpolatedLine(t, "${df:val}")
7089
testInvalidInterpolatedLine(t, "${")
7190
testInvalidInterpolatedLine(t, "$}")
7291
testInvalidInterpolatedLine(t, "${}")
@@ -198,6 +217,43 @@ func TestInterpolate(t *testing.T) {
198217
"HOST_PORT": "=",
199218
"LABEL_VALUE": "myvalue==",
200219
})
220+
// same as above but with default values
221+
testInterpolatedConfig(t,
222+
`web:
223+
# unbracketed name
224+
image: busybox
225+
226+
# array element
227+
ports:
228+
- "80:8000"
229+
230+
# dictionary item value
231+
labels:
232+
mylabel: "my-val:ue"
233+
234+
# unset value
235+
hostname: "host-"
236+
237+
# escaped interpolation
238+
command: "${ESCAPED}"`,
239+
240+
`web:
241+
# unbracketed name
242+
image: ${IMAGE:-busybox}
243+
244+
# array element
245+
ports:
246+
- "${HOST_PORT:-80}:8000"
247+
248+
# dictionary item value
249+
labels:
250+
mylabel: "${LABEL_VALUE-my-val:ue}"
251+
252+
# unset value
253+
hostname: "host-${UNSET_VALUE}"
254+
255+
# escaped interpolation
256+
command: "$${ESCAPED}"`, map[string]string{})
201257

202258
testInvalidInterpolatedConfig(t,
203259
`web:

0 commit comments

Comments
 (0)