VictoriaMetrics/lib/envflag/envflag.go

89 lines
2.9 KiB
Go

package envflag
import (
"flag"
"log"
"os"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
)
var (
enable = flag.Bool("envflag.enable", false, "Whether to enable reading flags from environment variables in addition to the command line. "+
"Command line flag values have priority over values from environment vars. "+
"Flags are read only from the command line if this flag isn't set. See https://docs.victoriametrics.com/#environment-variables for more details")
prefix = flag.String("envflag.prefix", "", "Prefix for environment variables if -envflag.enable is set")
)
// Parse parses environment vars and command-line flags.
//
// Flags set via command-line override flags set via environment vars.
//
// This function must be called instead of flag.Parse() before using any flags in the program.
func Parse() {
ParseFlagSet(flag.CommandLine, os.Args[1:])
}
// ParseFlagSet parses the given args into the given fs.
func ParseFlagSet(fs *flag.FlagSet, args []string) {
args = expandArgs(args)
if err := fs.Parse(args); err != nil {
// Do not use lib/logger here, since it is uninitialized yet.
log.Fatalf("cannot parse flags %q: %s", args, err)
}
if fs.NArg() > 0 {
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4845
log.Fatalf("unprocessed command-line args left: %s; the most likely reason is missing `=` between boolean flag name and value; "+
"see https://pkg.go.dev/flag#hdr-Command_line_flag_syntax", fs.Args())
}
if !*enable {
return
}
// Remember explicitly set command-line flags.
flagsSet := make(map[string]bool)
fs.Visit(func(f *flag.Flag) {
flagsSet[f.Name] = true
})
// Obtain the remaining flag values from environment vars.
fs.VisitAll(func(f *flag.Flag) {
if flagsSet[f.Name] {
// The flag is explicitly set via command-line.
return
}
// Get flag value from environment var.
fname := getEnvFlagName(f.Name)
if v, ok := envtemplate.LookupEnv(fname); ok {
if err := fs.Set(f.Name, v); err != nil {
// Do not use lib/logger here, since it is uninitialized yet.
log.Fatalf("cannot set flag %s to %q, which is read from env var %q: %s", f.Name, v, fname, err)
}
}
})
}
// expandArgs substitutes %{ENV_VAR} placeholders inside args
// with the corresponding environment variable values.
func expandArgs(args []string) []string {
dstArgs := make([]string, 0, len(args))
for _, arg := range args {
s, err := envtemplate.ReplaceString(arg)
if err != nil {
// Do not use lib/logger here, since it is uninitialized yet.
log.Fatalf("cannot process arg %q: %s", arg, err)
}
if len(s) > 0 {
dstArgs = append(dstArgs, s)
}
}
return dstArgs
}
func getEnvFlagName(s string) string {
// Substitute dots with underscores, since env var names cannot contain dots.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/311#issuecomment-586354129 for details.
s = strings.ReplaceAll(s, ".", "_")
return *prefix + s
}