Files
VictoriaMetrics/lib/envtemplate/envtemplate.go
Aliaksandr Valialkin cb16774bcb lib/envtemplate: allow referring non-existing environment variables in config files and in command-line flags
A few users reported unexpected errors when environment variables referred other environment variables
at VictoriaMetrics startup. This resulted in the following fatal error on startup:

    cannot expand "..." env var value "...%{SOME_NON_EXISTING_ENV_VAR}..."

Fix this by leaving placeholders with non-existing env vars as is.
This improves the general usability of environment variables by VictoriaMetrics components
inside command-line flags and inside config files. User can easily notice placeholders with non-existing
environment variables by looking at the corresponding command-line flag or at the corresponding config option value.

While at it, replace duplicate docs about environment variables at the https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#environment-variables
with the link to the same docs at https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#environment-variables .

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3999
2025-08-09 21:07:18 +02:00

84 lines
1.9 KiB
Go

package envtemplate
import (
"fmt"
"io"
"os"
"strings"
"github.com/valyala/fasttemplate"
)
// ReplaceBytes replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
func ReplaceBytes(b []byte) []byte {
result := expand(envVars, string(b))
return []byte(result)
}
// ReplaceString replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
func ReplaceString(s string) string {
return expand(envVars, s)
}
// LookupEnv returns the expanded environment variable value for the given name.
//
// The expanded means that `%{ENV_VAR}` placeholders in env var value are replaced
// with the corresponding ENV_VAR values (recursively).
//
// false is returned if environment variable isn't found.
func LookupEnv(name string) (string, bool) {
value, ok := envVars[name]
return value, ok
}
var envVars = func() map[string]string {
envs := os.Environ()
m := parseEnvVars(envs)
return expandTemplates(m)
}()
func parseEnvVars(envs []string) map[string]string {
m := make(map[string]string, len(envs))
for _, env := range envs {
n := strings.IndexByte(env, '=')
if n < 0 {
m[env] = ""
continue
}
name := env[:n]
value := env[n+1:]
m[name] = value
}
return m
}
func expandTemplates(m map[string]string) map[string]string {
for i := 0; i < len(m); i++ {
mExpanded := make(map[string]string, len(m))
expands := 0
for name, value := range m {
valueExpanded := expand(m, value)
mExpanded[name] = valueExpanded
if valueExpanded != value {
expands++
}
}
if expands == 0 {
return mExpanded
}
m = mExpanded
}
return m
}
func expand(m map[string]string, s string) string {
return fasttemplate.ExecuteFuncString(s, "%{", "}", func(w io.Writer, tag string) (int, error) {
v, ok := m[tag]
if !ok {
// Cannot find the tag in m. Leave it as is.
return fmt.Fprintf(w, "%%{%s}", tag)
}
return fmt.Fprintf(w, "%s", v)
})
}