2020-08-13 15:43:55 +02:00
|
|
|
package envtemplate
|
|
|
|
|
|
|
|
import (
|
2022-10-18 09:28:39 +02:00
|
|
|
"fmt"
|
2020-08-13 15:43:55 +02:00
|
|
|
"io"
|
2022-10-26 13:49:20 +02:00
|
|
|
"log"
|
2020-08-13 15:43:55 +02:00
|
|
|
"os"
|
2022-11-07 12:15:51 +01:00
|
|
|
"regexp"
|
2022-10-26 13:49:20 +02:00
|
|
|
"strings"
|
2020-08-13 15:43:55 +02:00
|
|
|
|
|
|
|
"github.com/valyala/fasttemplate"
|
|
|
|
)
|
|
|
|
|
2022-10-26 13:49:20 +02:00
|
|
|
// ReplaceBytes replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
|
2022-10-18 09:28:39 +02:00
|
|
|
//
|
|
|
|
// Error is returned if ENV_VAR isn't set for some `%{ENV_VAR}` placeholder.
|
2022-10-26 13:49:20 +02:00
|
|
|
func ReplaceBytes(b []byte) ([]byte, error) {
|
|
|
|
result, err := expand(envVars, string(b))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []byte(result), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReplaceString replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
|
|
|
|
//
|
|
|
|
// Error is returned if ENV_VAR isn't set for some `%{ENV_VAR}` placeholder.
|
|
|
|
func ReplaceString(s string) (string, error) {
|
|
|
|
result, err := expand(envVars, s)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2020-08-13 15:43:55 +02:00
|
|
|
}
|
2022-10-26 13:49:20 +02:00
|
|
|
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, err := expand(m, value)
|
|
|
|
if err != nil {
|
|
|
|
// Do not use lib/logger here, since it is uninitialized yet.
|
|
|
|
log.Fatalf("cannot expand %q env var value %q: %s", name, value, err)
|
|
|
|
}
|
|
|
|
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, error) {
|
|
|
|
if !strings.Contains(s, "%{") {
|
|
|
|
// Fast path - nothing to expand
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
result, err := fasttemplate.ExecuteFuncStringWithErr(s, "%{", "}", func(w io.Writer, tag string) (int, error) {
|
2022-11-07 12:15:51 +01:00
|
|
|
if !isValidEnvVarName(tag) {
|
|
|
|
return fmt.Fprintf(w, "%%{%s}", tag)
|
|
|
|
}
|
2022-10-26 13:49:20 +02:00
|
|
|
v, ok := m[tag]
|
2022-10-18 09:28:39 +02:00
|
|
|
if !ok {
|
2022-10-26 13:49:20 +02:00
|
|
|
return 0, fmt.Errorf("missing %q env var", tag)
|
2020-08-13 15:43:55 +02:00
|
|
|
}
|
2022-11-07 12:15:51 +01:00
|
|
|
return fmt.Fprintf(w, "%s", v)
|
2020-08-13 15:43:55 +02:00
|
|
|
})
|
2022-10-18 09:28:39 +02:00
|
|
|
if err != nil {
|
2022-10-26 13:49:20 +02:00
|
|
|
return "", err
|
2022-10-18 09:28:39 +02:00
|
|
|
}
|
2022-10-26 13:49:20 +02:00
|
|
|
return result, nil
|
2020-08-13 15:43:55 +02:00
|
|
|
}
|
2022-11-07 12:15:51 +01:00
|
|
|
|
|
|
|
func isValidEnvVarName(s string) bool {
|
|
|
|
return envVarNameRegex.MatchString(s)
|
|
|
|
}
|
|
|
|
|
2023-03-24 23:43:05 +01:00
|
|
|
// envVarNameRegex is used for validating environment variable names.
|
|
|
|
//
|
|
|
|
// Allow dashes and dots in env var names - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3999
|
|
|
|
var envVarNameRegex = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_\-.]*$`)
|