Skip to content

Commit

Permalink
cfgparser: add optional splitting of ENV variable values on comma via…
Browse files Browse the repository at this point in the history
… env_split
  • Loading branch information
rjocoleman committed Mar 16, 2024
1 parent 798c411 commit df4f4d4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 8 deletions.
12 changes: 10 additions & 2 deletions docs/reference/config-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ directive0 {env:VAR}
Parse is forgiving and incomplete variable placeholder (e.g. '{env:VAR') will
be left as-is. Variables are expanded inside quotes too.

Environment values that are comma separated can optionally be split to space separated strings using {env_split:COMMA_SEPARATED_VARIABLE_NAME}

```
# SEP_VAR=foo,bar,baz
directive1 {env_split:SEP_VAR}
```

In this usage the value of `directive1` would be `foo bar baz` (and `{env:SEP_VAR}` would be `foo,bar,baz`)

## Snippets & imports

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


39 changes: 33 additions & 6 deletions framework/cfgparser/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ func expandEnvironment(nodes []Node) []Node {
return nil
}

replacer := buildEnvReplacer()
envMap := buildEnvMap()
replacer := buildEnvReplacerFromMap(envMap)
newNodes := make([]Node, 0, len(nodes))
for _, node := range nodes {
node.Name = removeUnexpandedEnvvars(replacer.Replace(node.Name))
newArgs := make([]string, 0, len(node.Args))
for _, arg := range node.Args {
newArgs = append(newArgs, removeUnexpandedEnvvars(replacer.Replace(arg)))
expArg := replacer.Replace(arg)
if splitArgs, ok := handleEnvSplitWithMap(expArg, envMap); ok {
newArgs = append(newArgs, splitArgs...)
} else {
newArgs = append(newArgs, removeUnexpandedEnvvars(expArg))
}
}
node.Args = newArgs
node.Children = expandEnvironment(node.Children)
Expand All @@ -47,21 +53,42 @@ func expandEnvironment(nodes []Node) []Node {
}

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

func removeUnexpandedEnvvars(s string) string {
s = unixEnvvarRe.ReplaceAllString(s, "")
return s
}

func buildEnvReplacer() *strings.Replacer {
func buildEnvMap() map[string]string {
env := os.Environ()
pairs := make([]string, 0, len(env)*4)
envMap := make(map[string]string, len(env))
for _, entry := range env {
parts := strings.SplitN(entry, "=", 2)
key := parts[0]
value := parts[1]
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
return envMap
}

func buildEnvReplacerFromMap(envMap map[string]string) *strings.Replacer {
pairs := make([]string, 0, len(envMap)*4)
for key, value := range envMap {
pairs = append(pairs, "{env:"+key+"}", value)
}
return strings.NewReplacer(pairs...)
}

func handleEnvSplitWithMap(arg string, envMap map[string]string) ([]string, bool) {
matches := unixEnvSplitRe.FindStringSubmatch(arg)
if len(matches) == 2 {
value, exists := envMap[matches[1]]
if !exists {
return nil, false
}
splitValues := strings.Split(value, ",")
return splitValues, true
}
return nil, false
}
42 changes: 42 additions & 0 deletions framework/cfgparser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,47 @@ var cases = []struct {
},
false,
},
{
"environment variable split at comma expansion",
`a {env_split:TESTING_VARIABLE_SPLIT}`,
[]Node{
{
Name: "a",
Args: []string{"value1", "value2", "value3"},
Children: nil,
File: "test",
Line: 1,
},
},
false,
},
{
"environment variable mixed usage",
`mixed {env:TESTING_VARIABLE} {env_split:TESTING_VARIABLE_SPLIT}`,
[]Node{
{
Name: "mixed",
Args: []string{"ABCDEF", "value1", "value2", "value3"},
Children: nil,
File: "test",
Line: 1,
},
},
false,
},
{
"environment variable not split on comma",
`not_split {env:TESTING_VARIABLE_SPLIT}`,
[]Node{
{
Name: "not_split",
Args: []string{"value1,value2,value3"},
File: "test",
Line: 1,
},
},
false,
},
{
"snippet expansion",
`(foo) { a }
Expand Down Expand Up @@ -581,6 +622,7 @@ func printTree(t *testing.T, root Node, indent int) {
func TestRead(t *testing.T) {
os.Setenv("TESTING_VARIABLE", "ABCDEF")
os.Setenv("TESTING_VARIABLE2", "ABC2 DEF2")
os.Setenv("TESTING_VARIABLE_SPLIT", "value1,value2,value3")

for _, case_ := range cases {
case_ := case_
Expand Down

0 comments on commit df4f4d4

Please sign in to comment.