Skip to content

Commit

Permalink
fix(config): Move bracketed environment variable substitution to doub…
Browse files Browse the repository at this point in the history
…le-dollar
  • Loading branch information
srebhan committed Jun 15, 2023
1 parent fc5412d commit 050f1a2
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 19 deletions.
13 changes: 11 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,22 @@ import (
"github.com/influxdata/telegraf/plugins/serializers"
)

// envVarPattern is a regex to determine environment variables in the
// config file for substitution. Those should start with a dollar signs.
// Expression modified from
// https://github.com/compose-spec/compose-go/blob/v1.14.0/template/template.go
const envVarPattern = `\$(?i:(?P<escaped>\\)|(?P<named>[_a-z][_a-z0-9]*)|\${(?:(?P<braced>[_a-z][_a-z0-9]*(?::?[-+?](.*))?)}|(?P<invalid>)))`

var (
httpLoadConfigRetryInterval = 10 * time.Second

// fetchURLRe is a regex to determine whether the requested file should
// be fetched from a remote or read from the filesystem.
fetchURLRe = regexp.MustCompile(`^\w+://`)

// envVarRe is the compiled regex of envVarPattern
envVarRe = regexp.MustCompile(envVarPattern)

// Password specified via command-line
Password Secret
)
Expand Down Expand Up @@ -850,12 +859,12 @@ func removeComments(contents []byte) ([]byte, error) {

func substituteEnvironment(contents []byte) ([]byte, error) {
envMap := utils.GetAsEqualsMap(os.Environ())
retVal, err := template.Substitute(string(contents), func(k string) (string, bool) {
retVal, err := template.SubstituteWith(string(contents), func(k string) (string, bool) {
if v, ok := envMap[k]; ok {
return v, ok
}
return "", false
})
}, envVarRe)
var invalidTmplError *template.InvalidTemplateError
if err != nil && !errors.As(err, &invalidTmplError) {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestConfig_LoadSingleInputWithEnvVars(t *testing.T) {

input := inputs.Inputs["memcached"]().(*MockupInputPlugin)
input.Servers = []string{"192.168.1.1"}
input.Command = `Raw command which may or may not contain # in it
input.Command = `Raw command which may or may not contain # or ${var} in it
# is unique`

filter := models.Filter{
Expand Down
26 changes: 17 additions & 9 deletions config/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ func TestEnvironmentSubstitution(t *testing.T) {
t.Setenv("TEST_ENV1", "VALUE1")
t.Setenv("TEST_ENV2", "VALUE2")
},
contents: "A string with ${TEST_ENV1}, $TEST_ENV2 and $TEST_ENV1 as repeated",
contents: "A string with $${TEST_ENV1}, $TEST_ENV2 and $TEST_ENV1 as repeated",
expected: "A string with VALUE1, VALUE2 and VALUE1 as repeated",
},
{
name: "Env not set",
contents: "Env variable ${NOT_SET} will be empty",
contents: "Env variable $${NOT_SET} will be empty",
expected: "Env variable will be empty", // Two spaces present
},
{
name: "Env not set, fallback to default",
contents: "Env variable ${THIS_IS_ABSENT:-Fallback}",
contents: "Env variable $${THIS_IS_ABSENT:-Fallback}",
expected: "Env variable Fallback",
},
{
name: "No fallback",
setEnv: func(t *testing.T) {
t.Setenv("MY_ENV1", "VALUE1")
},
contents: "Env variable ${MY_ENV1:-Fallback}",
contents: "Env variable $${MY_ENV1:-Fallback}",
expected: "Env variable VALUE1",
},
{
Expand All @@ -52,17 +52,17 @@ func TestEnvironmentSubstitution(t *testing.T) {
t.Setenv("MY_VAR", "VALUE")
t.Setenv("MY_VAR2", "VALUE2")
},
contents: "Env var ${MY_VAR} is set, with $MY_VAR syntax and default on this ${MY_VAR1:-Substituted}, no default on this ${MY_VAR2:-NoDefault}",
contents: "Env var $${MY_VAR} is set, with $MY_VAR syntax and default on this $${MY_VAR1:-Substituted}, no default on this $${MY_VAR2:-NoDefault}",
expected: "Env var VALUE is set, with VALUE syntax and default on this Substituted, no default on this VALUE2",
},
{
name: "Default has special chars",
contents: `Not recommended but supported ${MY_VAR:-Default with special chars Supported#$\"}`,
contents: `Not recommended but supported $${MY_VAR:-Default with special chars Supported#$\"}`,
expected: `Not recommended but supported Default with special chars Supported#$\"`, // values are escaped
},
{
name: "unset error",
contents: "Contains ${THIS_IS_NOT_SET?unset-error}",
contents: "Contains $${THIS_IS_NOT_SET?unset-error}",
wantErr: true,
errSubstring: "unset-error",
},
Expand All @@ -71,7 +71,7 @@ func TestEnvironmentSubstitution(t *testing.T) {
setEnv: func(t *testing.T) {
t.Setenv("ENV_EMPTY", "")
},
contents: "Contains ${ENV_EMPTY:?empty-error}",
contents: "Contains $${ENV_EMPTY:?empty-error}",
wantErr: true,
errSubstring: "empty-error",
},
Expand All @@ -80,9 +80,17 @@ func TestEnvironmentSubstitution(t *testing.T) {
setEnv: func(t *testing.T) {
t.Setenv("FALLBACK", "my-fallback")
},
contents: "Should output ${NOT_SET:-${FALLBACK}}",
contents: "Should output $${NOT_SET:-${FALLBACK}}",
expected: "Should output my-fallback",
},
{
name: "leave alone single dollar expressions #13432",
setEnv: func(t *testing.T) {
t.Setenv("MYVAR", "my-variable")
},
contents: "Should output ${MYVAR}",
expected: "Should output ${MYVAR}",
},
}

for _, tt := range tests {
Expand Down
13 changes: 6 additions & 7 deletions config/testdata/single_plugin_env_vars.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,26 @@
# file would generate.
#
# Environment variables can be used anywhere in this config file, simply surround
# them with ${}. For strings the variable must be within quotes (ie, "${STR_VAR}"),
# for numbers and booleans they should be plain (ie, ${INT_VAR}, ${BOOL_VAR})
# them with $${}. For strings the variable must be within quotes (ie, "$${STR_VAR}"),
# for numbers and booleans they should be plain (ie, $${INT_VAR}, $${BOOL_VAR})

[[inputs.memcached]]
# this comment line will be ignored by the parser
servers = ["$MY_TEST_SERVER"]
namepass = ["metricname1", "ip_${MY_TEST_SERVER}_name"] # this comment will be ignored as well
servers = ["$MY_TEST_SERVER"]
namepass = ["metricname1", "ip_$${MY_TEST_SERVER}_name"] # this comment will be ignored as well
namedrop = ["metricname2"]
fieldpass = ["some", "strings"]
fielddrop = ["other", "stuff"]
interval = "$TEST_INTERVAL"
##### this input is provided to test multiline strings
command = """
Raw command which may or may not contain # in it
Raw command which may or may not contain # or ${var} in it
# is unique""" # Multiline comment black starting with #
[inputs.memcached.tagpass]
goodtag = ["mytag", """tagwith#value""",
goodtag = ["mytag", """tagwith#value""",
# comment in between array items
# should ignore "quotes" in comments
'''TagWithMultilineSyntax''', ## ignore this comment
] # hastag
[inputs.memcached.tagdrop]
badtag = ["othertag"]

0 comments on commit 050f1a2

Please sign in to comment.