From 62c9f2cf3e86f8aaea7a1583f09adb6b8007cfe6 Mon Sep 17 00:00:00 2001 From: elcore Date: Fri, 15 May 2020 23:49:51 +0200 Subject: cmd: Add --envfile flag to run command (#3278) * run: Add the possibility to load an env file * run: change envfile flag var * run: do not ignore err values * Apply suggestions from code review Co-authored-by: Matt Holt Co-authored-by: Matt Holt --- cmd/commandfuncs.go | 9 +++++++ cmd/commands.go | 6 ++++- cmd/main.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index f9e1033..a824789 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -151,11 +151,20 @@ func cmdRun(fl Flags) (int, error) { runCmdConfigFlag := fl.String("config") runCmdConfigAdapterFlag := fl.String("adapter") runCmdResumeFlag := fl.Bool("resume") + runCmdLoadEnvfileFlag := fl.String("envfile") runCmdPrintEnvFlag := fl.Bool("environ") runCmdWatchFlag := fl.Bool("watch") runCmdPidfileFlag := fl.String("pidfile") runCmdPingbackFlag := fl.String("pingback") + // load all additional envs as soon as possible + if runCmdLoadEnvfileFlag != "" { + if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("loading additional environment variables: %v", err) + } + } + // if we are supposed to print the environment, do that first if runCmdPrintEnvFlag { printEnvironment() diff --git a/cmd/commands.go b/cmd/commands.go index 5a40b1d..99d5245 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -96,7 +96,7 @@ using 'caddy run' instead to keep it in the foreground.`, RegisterCommand(Command{ Name: "run", Func: cmdRun, - Usage: "[--config [--adapter ]] [--environ] [--resume] [--watch] [--pidfile ]", + Usage: "[--config [--adapter ]] [--envfile ] [--environ] [--resume] [--watch] [--pidfile ]", Short: `Starts the Caddy process and blocks indefinitely`, Long: ` Starts the Caddy process, optionally bootstrapped with an initial config file, @@ -116,6 +116,9 @@ As a special case, if the current working directory has a file called that file will be loaded and used to configure Caddy, even without any command line flags. +If --envfile is specified, an environment file with environment variables in +the KEY=VALUE format will be loaded into the Caddy process. + If --environ is specified, the environment as seen by the Caddy process will be printed before starting. This is the same as the environ command but does not quit after printing, and can be useful for troubleshooting. @@ -130,6 +133,7 @@ development environment.`, fs := flag.NewFlagSet("run", flag.ExitOnError) fs.String("config", "", "Configuration file") fs.String("adapter", "", "Name of config adapter to apply") + fs.String("envfile", "", "Environment file to load") fs.Bool("environ", false, "Print environment") fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)") fs.Bool("watch", false, "Watch config file for changes and reload it automatically") diff --git a/cmd/main.go b/cmd/main.go index 7e8d9b7..4fd8d68 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -15,6 +15,7 @@ package caddycmd import ( + "bufio" "bytes" "flag" "fmt" @@ -339,6 +340,73 @@ func flagHelp(fs *flag.FlagSet) string { return buf.String() } +func loadEnvFromFile(envFile string) error { + file, err := os.Open(envFile) + if err != nil { + return fmt.Errorf("reading environment file: %v", err) + } + defer file.Close() + + envMap, err := parseEnvFile(file) + if err != nil { + return fmt.Errorf("parsing environment file: %v", err) + } + + for k, v := range envMap { + if err := os.Setenv(k, v); err != nil { + return fmt.Errorf("setting environment variables: %v", err) + } + } + + return nil +} + +func parseEnvFile(envInput io.Reader) (map[string]string, error) { + envMap := make(map[string]string) + + scanner := bufio.NewScanner(envInput) + var line string + lineNumber := 0 + + for scanner.Scan() { + line = strings.TrimSpace(scanner.Text()) + lineNumber++ + + // skip lines starting with comment + if strings.HasPrefix(line, "#") { + continue + } + + // skip empty line + if len(line) == 0 { + continue + } + + fields := strings.SplitN(line, "=", 2) + if len(fields) != 2 { + return nil, fmt.Errorf("can't parse line %d; line should be in KEY=VALUE format", lineNumber) + } + + if strings.Contains(fields[0], " ") { + return nil, fmt.Errorf("bad key on line %d: contains whitespace", lineNumber) + } + + key := fields[0] + val := fields[1] + + if key == "" { + return nil, fmt.Errorf("missing or empty key on line %d", lineNumber) + } + envMap[key] = val + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return envMap, nil +} + func printEnvironment() { fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir()) fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir()) -- cgit v1.2.3