Skip to content

Commit df4f4d4

Browse files
committed
cfgparser: add optional splitting of ENV variable values on comma via env_split
1 parent 798c411 commit df4f4d4

File tree

3 files changed

+85
-8
lines changed

3 files changed

+85
-8
lines changed

docs/reference/config-syntax.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ directive0 {env:VAR}
8787
Parse is forgiving and incomplete variable placeholder (e.g. '{env:VAR') will
8888
be left as-is. Variables are expanded inside quotes too.
8989

90+
Environment values that are comma separated can optionally be split to space separated strings using {env_split:COMMA_SEPARATED_VARIABLE_NAME}
91+
92+
```
93+
# SEP_VAR=foo,bar,baz
94+
95+
directive1 {env_split:SEP_VAR}
96+
```
97+
98+
In this usage the value of `directive1` would be `foo bar baz` (and `{env:SEP_VAR}` would be `foo,bar,baz`)
99+
90100
## Snippets & imports
91101

92102
You can reuse blocks of configuration by defining them as "snippets". Snippet
@@ -196,5 +206,3 @@ No-op module. It doesn't need to be configured explicitly and can be referenced
196206
using "dummy" name. It can act as a delivery target or auth.
197207
provider. In the latter case, it will accept any credentials, allowing any
198208
client to authenticate using any username and password (use with care!).
199-
200-

framework/cfgparser/env.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,19 @@ func expandEnvironment(nodes []Node) []Node {
3131
return nil
3232
}
3333

34-
replacer := buildEnvReplacer()
34+
envMap := buildEnvMap()
35+
replacer := buildEnvReplacerFromMap(envMap)
3536
newNodes := make([]Node, 0, len(nodes))
3637
for _, node := range nodes {
3738
node.Name = removeUnexpandedEnvvars(replacer.Replace(node.Name))
3839
newArgs := make([]string, 0, len(node.Args))
3940
for _, arg := range node.Args {
40-
newArgs = append(newArgs, removeUnexpandedEnvvars(replacer.Replace(arg)))
41+
expArg := replacer.Replace(arg)
42+
if splitArgs, ok := handleEnvSplitWithMap(expArg, envMap); ok {
43+
newArgs = append(newArgs, splitArgs...)
44+
} else {
45+
newArgs = append(newArgs, removeUnexpandedEnvvars(expArg))
46+
}
4147
}
4248
node.Args = newArgs
4349
node.Children = expandEnvironment(node.Children)
@@ -47,21 +53,42 @@ func expandEnvironment(nodes []Node) []Node {
4753
}
4854

4955
var unixEnvvarRe = regexp.MustCompile(`{env:([^\$]+)}`)
56+
var unixEnvSplitRe = regexp.MustCompile(`{env_split:([^\$]+)}`)
5057

5158
func removeUnexpandedEnvvars(s string) string {
5259
s = unixEnvvarRe.ReplaceAllString(s, "")
5360
return s
5461
}
5562

56-
func buildEnvReplacer() *strings.Replacer {
63+
func buildEnvMap() map[string]string {
5764
env := os.Environ()
58-
pairs := make([]string, 0, len(env)*4)
65+
envMap := make(map[string]string, len(env))
5966
for _, entry := range env {
6067
parts := strings.SplitN(entry, "=", 2)
61-
key := parts[0]
62-
value := parts[1]
68+
if len(parts) == 2 {
69+
envMap[parts[0]] = parts[1]
70+
}
71+
}
72+
return envMap
73+
}
6374

75+
func buildEnvReplacerFromMap(envMap map[string]string) *strings.Replacer {
76+
pairs := make([]string, 0, len(envMap)*4)
77+
for key, value := range envMap {
6478
pairs = append(pairs, "{env:"+key+"}", value)
6579
}
6680
return strings.NewReplacer(pairs...)
6781
}
82+
83+
func handleEnvSplitWithMap(arg string, envMap map[string]string) ([]string, bool) {
84+
matches := unixEnvSplitRe.FindStringSubmatch(arg)
85+
if len(matches) == 2 {
86+
value, exists := envMap[matches[1]]
87+
if !exists {
88+
return nil, false
89+
}
90+
splitValues := strings.Split(value, ",")
91+
return splitValues, true
92+
}
93+
return nil, false
94+
}

framework/cfgparser/parse_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,47 @@ var cases = []struct {
342342
},
343343
false,
344344
},
345+
{
346+
"environment variable split at comma expansion",
347+
`a {env_split:TESTING_VARIABLE_SPLIT}`,
348+
[]Node{
349+
{
350+
Name: "a",
351+
Args: []string{"value1", "value2", "value3"},
352+
Children: nil,
353+
File: "test",
354+
Line: 1,
355+
},
356+
},
357+
false,
358+
},
359+
{
360+
"environment variable mixed usage",
361+
`mixed {env:TESTING_VARIABLE} {env_split:TESTING_VARIABLE_SPLIT}`,
362+
[]Node{
363+
{
364+
Name: "mixed",
365+
Args: []string{"ABCDEF", "value1", "value2", "value3"},
366+
Children: nil,
367+
File: "test",
368+
Line: 1,
369+
},
370+
},
371+
false,
372+
},
373+
{
374+
"environment variable not split on comma",
375+
`not_split {env:TESTING_VARIABLE_SPLIT}`,
376+
[]Node{
377+
{
378+
Name: "not_split",
379+
Args: []string{"value1,value2,value3"},
380+
File: "test",
381+
Line: 1,
382+
},
383+
},
384+
false,
385+
},
345386
{
346387
"snippet expansion",
347388
`(foo) { a }
@@ -581,6 +622,7 @@ func printTree(t *testing.T, root Node, indent int) {
581622
func TestRead(t *testing.T) {
582623
os.Setenv("TESTING_VARIABLE", "ABCDEF")
583624
os.Setenv("TESTING_VARIABLE2", "ABC2 DEF2")
625+
os.Setenv("TESTING_VARIABLE_SPLIT", "value1,value2,value3")
584626

585627
for _, case_ := range cases {
586628
case_ := case_

0 commit comments

Comments
 (0)