From 9c419f1e1a4a82a8ed49bac3d54050890cb3e58e Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 11 Oct 2023 11:46:18 -0400 Subject: cmd: Fix exiting with custom status code, add `caddy -v` (#5874) * Simplify variables for commands * Add --envfile support for adapt command * Carry custom status code for commands to os.Exit() * cmd: add `-v` and `--version` to root caddy command * Add `--envfile` to `caddy environ`, extract flag parsing to func --------- Co-authored-by: Mohammed Al Sahaf --- cmd/commandfuncs.go | 207 ++++++++++++++++++++++++++++------------------------ 1 file changed, 110 insertions(+), 97 deletions(-) (limited to 'cmd/commandfuncs.go') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 8963988..b0c576a 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -42,14 +42,14 @@ import ( ) func cmdStart(fl Flags) (int, error) { - startCmdConfigFlag := fl.String("config") - startCmdConfigAdapterFlag := fl.String("adapter") - startCmdPidfileFlag := fl.String("pidfile") - startCmdWatchFlag := fl.Bool("watch") + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + pidfileFlag := fl.String("pidfile") + watchFlag := fl.Bool("watch") var err error - var startCmdEnvfileFlag []string - startCmdEnvfileFlag, err = fl.GetStringSlice("envfile") + var envfileFlag []string + envfileFlag, err = fl.GetStringSlice("envfile") if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("reading envfile flag: %v", err) @@ -74,23 +74,23 @@ func cmdStart(fl Flags) (int, error) { // sure by giving it some random bytes and having it echo // them back to us) cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String()) - if startCmdConfigFlag != "" { - cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag) + if configFlag != "" { + cmd.Args = append(cmd.Args, "--config", configFlag) } - for _, envFile := range startCmdEnvfileFlag { - cmd.Args = append(cmd.Args, "--envfile", envFile) + for _, envfile := range envfileFlag { + cmd.Args = append(cmd.Args, "--envfile", envfile) } - if startCmdConfigAdapterFlag != "" { - cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag) + if configAdapterFlag != "" { + cmd.Args = append(cmd.Args, "--adapter", configAdapterFlag) } - if startCmdWatchFlag { + if watchFlag { cmd.Args = append(cmd.Args, "--watch") } - if startCmdPidfileFlag != "" { - cmd.Args = append(cmd.Args, "--pidfile", startCmdPidfileFlag) + if pidfileFlag != "" { + cmd.Args = append(cmd.Args, "--pidfile", pidfileFlag) } - stdinpipe, err := cmd.StdinPipe() + stdinPipe, err := cmd.StdinPipe() if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("creating stdin pipe: %v", err) @@ -102,7 +102,8 @@ func cmdStart(fl Flags) (int, error) { expect := make([]byte, 32) _, err = rand.Read(expect) if err != nil { - return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err) + return caddy.ExitCodeFailedStartup, + fmt.Errorf("generating random confirmation bytes: %v", err) } // begin writing the confirmation bytes to the child's @@ -110,14 +111,15 @@ func cmdStart(fl Flags) (int, error) { // started yet, and writing synchronously would result // in a deadlock go func() { - _, _ = stdinpipe.Write(expect) - stdinpipe.Close() + _, _ = stdinPipe.Write(expect) + stdinPipe.Close() }() // start the process err = cmd.Start() if err != nil { - return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err) + return caddy.ExitCodeFailedStartup, + fmt.Errorf("starting caddy process: %v", err) } // there are two ways we know we're done: either @@ -165,47 +167,37 @@ func cmdStart(fl Flags) (int, error) { func cmdRun(fl Flags) (int, error) { caddy.TrapSignals() - runCmdConfigFlag := fl.String("config") - runCmdConfigAdapterFlag := fl.String("adapter") - runCmdResumeFlag := fl.Bool("resume") - runCmdPrintEnvFlag := fl.Bool("environ") - runCmdWatchFlag := fl.Bool("watch") - runCmdPidfileFlag := fl.String("pidfile") - runCmdPingbackFlag := fl.String("pingback") - - var err error - var runCmdLoadEnvfileFlag []string - runCmdLoadEnvfileFlag, err = fl.GetStringSlice("envfile") - if err != nil { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("reading envfile flag: %v", err) - } + configFlag := fl.String("config") + configAdapterFlag := fl.String("adapter") + resumeFlag := fl.Bool("resume") + printEnvFlag := fl.Bool("environ") + watchFlag := fl.Bool("watch") + pidfileFlag := fl.String("pidfile") + pingbackFlag := fl.String("pingback") // load all additional envs as soon as possible - for _, envFile := range runCmdLoadEnvfileFlag { - if err := loadEnvFromFile(envFile); err != nil { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("loading additional environment variables: %v", err) - } + err := handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err } // if we are supposed to print the environment, do that first - if runCmdPrintEnvFlag { + if printEnvFlag { printEnvironment() } // load the config, depending on flags var config []byte - if runCmdResumeFlag { + if resumeFlag { config, err = os.ReadFile(caddy.ConfigAutosavePath) if os.IsNotExist(err) { // not a bad error; just can't resume if autosave file doesn't exist caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath)) - runCmdResumeFlag = false + resumeFlag = false } else if err != nil { return caddy.ExitCodeFailedStartup, err } else { - if runCmdConfigFlag == "" { + if configFlag == "" { caddy.Log().Info("resuming from last configuration", zap.String("autosave_file", caddy.ConfigAutosavePath)) } else { @@ -218,19 +210,19 @@ func cmdRun(fl Flags) (int, error) { } // we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive var configFile string - if !runCmdResumeFlag { - config, configFile, err = LoadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag) + if !resumeFlag { + config, configFile, err = LoadConfig(configFlag, configAdapterFlag) if err != nil { return caddy.ExitCodeFailedStartup, err } } // create pidfile now, in case loading config takes a while (issue #5477) - if runCmdPidfileFlag != "" { - err := caddy.PIDFile(runCmdPidfileFlag) + if pidfileFlag != "" { + err := caddy.PIDFile(pidfileFlag) if err != nil { caddy.Log().Error("unable to write PID file", - zap.String("pidfile", runCmdPidfileFlag), + zap.String("pidfile", pidfileFlag), zap.Error(err)) } } @@ -244,13 +236,13 @@ func cmdRun(fl Flags) (int, error) { // if we are to report to another process the successful start // of the server, do so now by echoing back contents of stdin - if runCmdPingbackFlag != "" { + if pingbackFlag != "" { confirmationBytes, err := io.ReadAll(os.Stdin) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("reading confirmation bytes from stdin: %v", err) } - conn, err := net.Dial("tcp", runCmdPingbackFlag) + conn, err := net.Dial("tcp", pingbackFlag) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("dialing confirmation address: %v", err) @@ -259,14 +251,14 @@ func cmdRun(fl Flags) (int, error) { _, err = conn.Write(confirmationBytes) if err != nil { return caddy.ExitCodeFailedStartup, - fmt.Errorf("writing confirmation bytes to %s: %v", runCmdPingbackFlag, err) + fmt.Errorf("writing confirmation bytes to %s: %v", pingbackFlag, err) } } // if enabled, reload config file automatically on changes // (this better only be used in dev!) - if runCmdWatchFlag { - go watchConfigFile(configFile, runCmdConfigAdapterFlag) + if watchFlag { + go watchConfigFile(configFile, configAdapterFlag) } // warn if the environment does not provide enough information about the disk @@ -292,11 +284,11 @@ func cmdRun(fl Flags) (int, error) { } func cmdStop(fl Flags) (int, error) { - addrFlag := fl.String("address") + addressFlag := fl.String("address") configFlag := fl.String("config") configAdapterFlag := fl.String("adapter") - adminAddr, err := DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag) + adminAddr, err := DetermineAdminAPIAddress(addressFlag, nil, configFlag, configAdapterFlag) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) } @@ -314,7 +306,7 @@ func cmdStop(fl Flags) (int, error) { func cmdReload(fl Flags) (int, error) { configFlag := fl.String("config") configAdapterFlag := fl.String("adapter") - addrFlag := fl.String("address") + addressFlag := fl.String("address") forceFlag := fl.Bool("force") // get the config in caddy's native format @@ -326,7 +318,7 @@ func cmdReload(fl Flags) (int, error) { return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load") } - adminAddr, err := DetermineAdminAPIAddress(addrFlag, config, configFlag, configAdapterFlag) + adminAddr, err := DetermineAdminAPIAddress(addressFlag, config, configFlag, configAdapterFlag) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) } @@ -428,48 +420,60 @@ func cmdListModules(fl Flags) (int, error) { return caddy.ExitCodeSuccess, nil } -func cmdEnviron(_ Flags) (int, error) { +func cmdEnviron(fl Flags) (int, error) { + // load all additional envs as soon as possible + err := handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + printEnvironment() return caddy.ExitCodeSuccess, nil } func cmdAdaptConfig(fl Flags) (int, error) { - adaptCmdInputFlag := fl.String("config") - adaptCmdAdapterFlag := fl.String("adapter") - adaptCmdPrettyFlag := fl.Bool("pretty") - adaptCmdValidateFlag := fl.Bool("validate") + inputFlag := fl.String("config") + adapterFlag := fl.String("adapter") + prettyFlag := fl.Bool("pretty") + validateFlag := fl.Bool("validate") var err error - adaptCmdInputFlag, err = configFileWithRespectToDefault(caddy.Log(), adaptCmdInputFlag) + inputFlag, err = configFileWithRespectToDefault(caddy.Log(), inputFlag) if err != nil { return caddy.ExitCodeFailedStartup, err } - if adaptCmdAdapterFlag == "" { + // load all additional envs as soon as possible + err = handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + if adapterFlag == "" { return caddy.ExitCodeFailedStartup, fmt.Errorf("adapter name is required (use --adapt flag or leave unspecified for default)") } - cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag) + cfgAdapter := caddyconfig.GetAdapter(adapterFlag) if cfgAdapter == nil { return caddy.ExitCodeFailedStartup, - fmt.Errorf("unrecognized config adapter: %s", adaptCmdAdapterFlag) + fmt.Errorf("unrecognized config adapter: %s", adapterFlag) } - input, err := os.ReadFile(adaptCmdInputFlag) + input, err := os.ReadFile(inputFlag) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("reading input file: %v", err) } - opts := map[string]any{"filename": adaptCmdInputFlag} + opts := map[string]any{"filename": inputFlag} adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts) if err != nil { return caddy.ExitCodeFailedStartup, err } - if adaptCmdPrettyFlag { + if prettyFlag { var prettyBuf bytes.Buffer err = json.Indent(&prettyBuf, adaptedConfig, "", "\t") if err != nil { @@ -487,13 +491,13 @@ func cmdAdaptConfig(fl Flags) (int, error) { if warn.Directive != "" { msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) } - caddy.Log().Named(adaptCmdAdapterFlag).Warn(msg, + caddy.Log().Named(adapterFlag).Warn(msg, zap.String("file", warn.File), zap.Int("line", warn.Line)) } // validate output if requested - if adaptCmdValidateFlag { + if validateFlag { var cfg *caddy.Config err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg) if err != nil { @@ -509,36 +513,26 @@ func cmdAdaptConfig(fl Flags) (int, error) { } func cmdValidateConfig(fl Flags) (int, error) { - validateCmdConfigFlag := fl.String("config") - validateCmdAdapterFlag := fl.String("adapter") - - var err error - var runCmdLoadEnvfileFlag []string - runCmdLoadEnvfileFlag, err = fl.GetStringSlice("envfile") - if err != nil { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("reading envfile flag: %v", err) - } + configFlag := fl.String("config") + adapterFlag := fl.String("adapter") // load all additional envs as soon as possible - for _, envFile := range runCmdLoadEnvfileFlag { - if err := loadEnvFromFile(envFile); err != nil { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("loading additional environment variables: %v", err) - } + err := handleEnvFileFlag(fl) + if err != nil { + return caddy.ExitCodeFailedStartup, err } // use default config and ensure a config file is specified - validateCmdConfigFlag, err = configFileWithRespectToDefault(caddy.Log(), validateCmdConfigFlag) + configFlag, err = configFileWithRespectToDefault(caddy.Log(), configFlag) if err != nil { return caddy.ExitCodeFailedStartup, err } - if validateCmdConfigFlag == "" { + if configFlag == "" { return caddy.ExitCodeFailedStartup, fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)") } - input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag) + input, _, err := LoadConfig(configFlag, adapterFlag) if err != nil { return caddy.ExitCodeFailedStartup, err } @@ -561,13 +555,13 @@ func cmdValidateConfig(fl Flags) (int, error) { } func cmdFmt(fl Flags) (int, error) { - formatCmdConfigFile := fl.Arg(0) - if formatCmdConfigFile == "" { - formatCmdConfigFile = "Caddyfile" + configFile := fl.Arg(0) + if configFile == "" { + configFile = "Caddyfile" } // as a special case, read from stdin if the file name is "-" - if formatCmdConfigFile == "-" { + if configFile == "-" { input, err := io.ReadAll(os.Stdin) if err != nil { return caddy.ExitCodeFailedStartup, @@ -577,7 +571,7 @@ func cmdFmt(fl Flags) (int, error) { return caddy.ExitCodeSuccess, nil } - input, err := os.ReadFile(formatCmdConfigFile) + input, err := os.ReadFile(configFile) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("reading input file: %v", err) @@ -586,7 +580,7 @@ func cmdFmt(fl Flags) (int, error) { output := caddyfile.Format(input) if fl.Bool("overwrite") { - if err := os.WriteFile(formatCmdConfigFile, output, 0o600); err != nil { + if err := os.WriteFile(configFile, output, 0o600); err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err) } return caddy.ExitCodeSuccess, nil @@ -610,7 +604,7 @@ func cmdFmt(fl Flags) (int, error) { fmt.Print(string(output)) } - if warning, diff := caddyfile.FormattingDifference(formatCmdConfigFile, input); diff { + if warning, diff := caddyfile.FormattingDifference(configFile, input); diff { return caddy.ExitCodeFailedStartup, fmt.Errorf(`%s:%d: Caddyfile input is not formatted; Tip: use '--overwrite' to update your Caddyfile in-place instead of previewing it. Consult '--help' for more options`, warning.File, warning.Line, @@ -620,6 +614,25 @@ func cmdFmt(fl Flags) (int, error) { return caddy.ExitCodeSuccess, nil } +// handleEnvFileFlag loads the environment variables from the given --envfile +// flag if specified. This should be called as early in the command function. +func handleEnvFileFlag(fl Flags) error { + var err error + var envfileFlag []string + envfileFlag, err = fl.GetStringSlice("envfile") + if err != nil { + return fmt.Errorf("reading envfile flag: %v", err) + } + + for _, envfile := range envfileFlag { + if err := loadEnvFromFile(envfile); err != nil { + return fmt.Errorf("loading additional environment variables: %v", err) + } + } + + return nil +} + // AdminAPIRequest makes an API request according to the CLI flags given, // with the given HTTP method and request URI. If body is non-nil, it will // be assumed to be Content-Type application/json. The caller should close -- cgit v1.2.3