fix(config): Move bracketed environment variable substitution to double-dollar (#13451)
This commit is contained in:
parent
291fea8998
commit
4d22b9106e
|
|
@ -40,6 +40,12 @@ import (
|
||||||
"github.com/influxdata/telegraf/plugins/serializers"
|
"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 = `\\(?P<escaped>\$)|\$(?i:(?P<named>[_a-z][_a-z0-9]*)|\${(?:(?P<braced>[_a-z][_a-z0-9]*(?::?[-+?](.*))?)}|(?P<invalid>)))`
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpLoadConfigRetryInterval = 10 * time.Second
|
httpLoadConfigRetryInterval = 10 * time.Second
|
||||||
|
|
||||||
|
|
@ -47,6 +53,9 @@ var (
|
||||||
// be fetched from a remote or read from the filesystem.
|
// be fetched from a remote or read from the filesystem.
|
||||||
fetchURLRe = regexp.MustCompile(`^\w+://`)
|
fetchURLRe = regexp.MustCompile(`^\w+://`)
|
||||||
|
|
||||||
|
// envVarRe is the compiled regex of envVarPattern
|
||||||
|
envVarRe = regexp.MustCompile(envVarPattern)
|
||||||
|
|
||||||
// Password specified via command-line
|
// Password specified via command-line
|
||||||
Password Secret
|
Password Secret
|
||||||
)
|
)
|
||||||
|
|
@ -850,12 +859,12 @@ func removeComments(contents []byte) ([]byte, error) {
|
||||||
|
|
||||||
func substituteEnvironment(contents []byte) ([]byte, error) {
|
func substituteEnvironment(contents []byte) ([]byte, error) {
|
||||||
envMap := utils.GetAsEqualsMap(os.Environ())
|
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 {
|
if v, ok := envMap[k]; ok {
|
||||||
return v, ok
|
return v, ok
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
})
|
}, envVarRe)
|
||||||
var invalidTmplError *template.InvalidTemplateError
|
var invalidTmplError *template.InvalidTemplateError
|
||||||
if err != nil && !errors.As(err, &invalidTmplError) {
|
if err != nil && !errors.As(err, &invalidTmplError) {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func TestConfig_LoadSingleInputWithEnvVars(t *testing.T) {
|
||||||
|
|
||||||
input := inputs.Inputs["memcached"]().(*MockupInputPlugin)
|
input := inputs.Inputs["memcached"]().(*MockupInputPlugin)
|
||||||
input.Servers = []string{"192.168.1.1"}
|
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`
|
# is unique`
|
||||||
|
|
||||||
filter := models.Filter{
|
filter := models.Filter{
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,17 @@ func TestEnvironmentSubstitution(t *testing.T) {
|
||||||
t.Setenv("TEST_ENV1", "VALUE1")
|
t.Setenv("TEST_ENV1", "VALUE1")
|
||||||
t.Setenv("TEST_ENV2", "VALUE2")
|
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",
|
expected: "A string with VALUE1, VALUE2 and VALUE1 as repeated",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Env not set",
|
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
|
expected: "Env variable will be empty", // Two spaces present
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Env not set, fallback to default",
|
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",
|
expected: "Env variable Fallback",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +43,7 @@ func TestEnvironmentSubstitution(t *testing.T) {
|
||||||
setEnv: func(t *testing.T) {
|
setEnv: func(t *testing.T) {
|
||||||
t.Setenv("MY_ENV1", "VALUE1")
|
t.Setenv("MY_ENV1", "VALUE1")
|
||||||
},
|
},
|
||||||
contents: "Env variable ${MY_ENV1:-Fallback}",
|
contents: "Env variable $${MY_ENV1:-Fallback}",
|
||||||
expected: "Env variable VALUE1",
|
expected: "Env variable VALUE1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -52,17 +52,17 @@ func TestEnvironmentSubstitution(t *testing.T) {
|
||||||
t.Setenv("MY_VAR", "VALUE")
|
t.Setenv("MY_VAR", "VALUE")
|
||||||
t.Setenv("MY_VAR2", "VALUE2")
|
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",
|
expected: "Env var VALUE is set, with VALUE syntax and default on this Substituted, no default on this VALUE2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Default has special chars",
|
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
|
expected: `Not recommended but supported Default with special chars Supported#$\"`, // values are escaped
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unset error",
|
name: "unset error",
|
||||||
contents: "Contains ${THIS_IS_NOT_SET?unset-error}",
|
contents: "Contains $${THIS_IS_NOT_SET?unset-error}",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errSubstring: "unset-error",
|
errSubstring: "unset-error",
|
||||||
},
|
},
|
||||||
|
|
@ -71,7 +71,7 @@ func TestEnvironmentSubstitution(t *testing.T) {
|
||||||
setEnv: func(t *testing.T) {
|
setEnv: func(t *testing.T) {
|
||||||
t.Setenv("ENV_EMPTY", "")
|
t.Setenv("ENV_EMPTY", "")
|
||||||
},
|
},
|
||||||
contents: "Contains ${ENV_EMPTY:?empty-error}",
|
contents: "Contains $${ENV_EMPTY:?empty-error}",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
errSubstring: "empty-error",
|
errSubstring: "empty-error",
|
||||||
},
|
},
|
||||||
|
|
@ -80,9 +80,33 @@ func TestEnvironmentSubstitution(t *testing.T) {
|
||||||
setEnv: func(t *testing.T) {
|
setEnv: func(t *testing.T) {
|
||||||
t.Setenv("FALLBACK", "my-fallback")
|
t.Setenv("FALLBACK", "my-fallback")
|
||||||
},
|
},
|
||||||
contents: "Should output ${NOT_SET:-${FALLBACK}}",
|
contents: "Should output $${NOT_SET:-${FALLBACK}}",
|
||||||
expected: "Should output my-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}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "leave alone escaped expressions (backslash)",
|
||||||
|
setEnv: func(t *testing.T) {
|
||||||
|
t.Setenv("MYVAR", "my-variable")
|
||||||
|
},
|
||||||
|
contents: `Should output \$MYVAR`,
|
||||||
|
expected: "Should output $MYVAR",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "double dollar no brackets",
|
||||||
|
setEnv: func(t *testing.T) {
|
||||||
|
t.Setenv("MYVAR", "my-variable")
|
||||||
|
},
|
||||||
|
contents: `Should output $$MYVAR`,
|
||||||
|
expected: "Should output $my-variable",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,26 @@
|
||||||
# file would generate.
|
# file would generate.
|
||||||
#
|
#
|
||||||
# Environment variables can be used anywhere in this config file, simply surround
|
# 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}"),
|
# 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})
|
# for numbers and booleans they should be plain (ie, $${INT_VAR}, $${BOOL_VAR})
|
||||||
|
|
||||||
[[inputs.memcached]]
|
[[inputs.memcached]]
|
||||||
# this comment line will be ignored by the parser
|
# this comment line will be ignored by the parser
|
||||||
servers = ["$MY_TEST_SERVER"]
|
servers = ["$MY_TEST_SERVER"]
|
||||||
namepass = ["metricname1", "ip_${MY_TEST_SERVER}_name"] # this comment will be ignored as well
|
namepass = ["metricname1", "ip_$${MY_TEST_SERVER}_name"] # this comment will be ignored as well
|
||||||
namedrop = ["metricname2"]
|
namedrop = ["metricname2"]
|
||||||
fieldpass = ["some", "strings"]
|
fieldpass = ["some", "strings"]
|
||||||
fielddrop = ["other", "stuff"]
|
fielddrop = ["other", "stuff"]
|
||||||
interval = "$TEST_INTERVAL"
|
interval = "$TEST_INTERVAL"
|
||||||
##### this input is provided to test multiline strings
|
##### this input is provided to test multiline strings
|
||||||
command = """
|
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 #
|
# is unique""" # Multiline comment black starting with #
|
||||||
[inputs.memcached.tagpass]
|
[inputs.memcached.tagpass]
|
||||||
goodtag = ["mytag", """tagwith#value""",
|
goodtag = ["mytag", """tagwith#value""",
|
||||||
# comment in between array items
|
# comment in between array items
|
||||||
# should ignore "quotes" in comments
|
# should ignore "quotes" in comments
|
||||||
'''TagWithMultilineSyntax''', ## ignore this comment
|
'''TagWithMultilineSyntax''', ## ignore this comment
|
||||||
] # hastag
|
] # hastag
|
||||||
[inputs.memcached.tagdrop]
|
[inputs.memcached.tagdrop]
|
||||||
badtag = ["othertag"]
|
badtag = ["othertag"]
|
||||||
|
|
||||||
Loading…
Reference in New Issue