1
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-16 21:56:40 -05:00

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 <msaa1990@gmail.com>
This commit is contained in:
Francis Lavoie 2023-10-11 11:46:18 -04:00 committed by GitHub
parent b245ecd325
commit 9c419f1e1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 167 additions and 109 deletions

1
.gitignore vendored
View file

@ -12,6 +12,7 @@ Caddyfile.*
cmd/caddy/caddy cmd/caddy/caddy
cmd/caddy/caddy.exe cmd/caddy/caddy.exe
cmd/caddy/tmp/*.exe cmd/caddy/tmp/*.exe
cmd/caddy/.env
# mac specific # mac specific
.DS_Store .DS_Store

View file

@ -1,7 +1,11 @@
package caddycmd package caddycmd
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/caddyserver/caddy/v2"
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -95,15 +99,22 @@ https://caddyserver.com/docs/running
// kind of annoying to have all the help text printed out if // kind of annoying to have all the help text printed out if
// caddy has an error provisioning its modules, for instance... // caddy has an error provisioning its modules, for instance...
SilenceUsage: true, SilenceUsage: true,
Version: onlyVersionText(),
} }
const fullDocsFooter = `Full documentation is available at: const fullDocsFooter = `Full documentation is available at:
https://caddyserver.com/docs/command-line` https://caddyserver.com/docs/command-line`
func init() { func init() {
rootCmd.SetVersionTemplate("{{.Version}}")
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
} }
func onlyVersionText() string {
_, f := caddy.Version()
return f
}
func caddyCmdToCobra(caddyCmd Command) *cobra.Command { func caddyCmdToCobra(caddyCmd Command) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: caddyCmd.Name, Use: caddyCmd.Name,
@ -123,7 +134,24 @@ func caddyCmdToCobra(caddyCmd Command) *cobra.Command {
// in a cobra command's RunE field. // in a cobra command's RunE field.
func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ []string) error { func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ []string) error {
return func(cmd *cobra.Command, _ []string) error { return func(cmd *cobra.Command, _ []string) error {
_, err := f(Flags{cmd.Flags()}) status, err := f(Flags{cmd.Flags()})
if status > 1 {
cmd.SilenceErrors = true
return &exitError{ExitCode: status, Err: err}
}
return err return err
} }
} }
// exitError carries the exit code from CommandFunc to Main()
type exitError struct {
ExitCode int
Err error
}
func (e *exitError) Error() string {
if e.Err == nil {
return fmt.Sprintf("exiting with status %d", e.ExitCode)
}
return e.Err.Error()
}

View file

@ -42,14 +42,14 @@ import (
) )
func cmdStart(fl Flags) (int, error) { func cmdStart(fl Flags) (int, error) {
startCmdConfigFlag := fl.String("config") configFlag := fl.String("config")
startCmdConfigAdapterFlag := fl.String("adapter") configAdapterFlag := fl.String("adapter")
startCmdPidfileFlag := fl.String("pidfile") pidfileFlag := fl.String("pidfile")
startCmdWatchFlag := fl.Bool("watch") watchFlag := fl.Bool("watch")
var err error var err error
var startCmdEnvfileFlag []string var envfileFlag []string
startCmdEnvfileFlag, err = fl.GetStringSlice("envfile") envfileFlag, err = fl.GetStringSlice("envfile")
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading envfile flag: %v", err) 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 // sure by giving it some random bytes and having it echo
// them back to us) // them back to us)
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String()) cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
if startCmdConfigFlag != "" { if configFlag != "" {
cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag) cmd.Args = append(cmd.Args, "--config", configFlag)
} }
for _, envFile := range startCmdEnvfileFlag { for _, envfile := range envfileFlag {
cmd.Args = append(cmd.Args, "--envfile", envFile) cmd.Args = append(cmd.Args, "--envfile", envfile)
} }
if startCmdConfigAdapterFlag != "" { if configAdapterFlag != "" {
cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag) cmd.Args = append(cmd.Args, "--adapter", configAdapterFlag)
} }
if startCmdWatchFlag { if watchFlag {
cmd.Args = append(cmd.Args, "--watch") cmd.Args = append(cmd.Args, "--watch")
} }
if startCmdPidfileFlag != "" { if pidfileFlag != "" {
cmd.Args = append(cmd.Args, "--pidfile", startCmdPidfileFlag) cmd.Args = append(cmd.Args, "--pidfile", pidfileFlag)
} }
stdinpipe, err := cmd.StdinPipe() stdinPipe, err := cmd.StdinPipe()
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("creating stdin pipe: %v", err) fmt.Errorf("creating stdin pipe: %v", err)
@ -102,7 +102,8 @@ func cmdStart(fl Flags) (int, error) {
expect := make([]byte, 32) expect := make([]byte, 32)
_, err = rand.Read(expect) _, err = rand.Read(expect)
if err != nil { 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 // 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 // started yet, and writing synchronously would result
// in a deadlock // in a deadlock
go func() { go func() {
_, _ = stdinpipe.Write(expect) _, _ = stdinPipe.Write(expect)
stdinpipe.Close() stdinPipe.Close()
}() }()
// start the process // start the process
err = cmd.Start() err = cmd.Start()
if err != nil { 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 // 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) { func cmdRun(fl Flags) (int, error) {
caddy.TrapSignals() caddy.TrapSignals()
runCmdConfigFlag := fl.String("config") configFlag := fl.String("config")
runCmdConfigAdapterFlag := fl.String("adapter") configAdapterFlag := fl.String("adapter")
runCmdResumeFlag := fl.Bool("resume") resumeFlag := fl.Bool("resume")
runCmdPrintEnvFlag := fl.Bool("environ") printEnvFlag := fl.Bool("environ")
runCmdWatchFlag := fl.Bool("watch") watchFlag := fl.Bool("watch")
runCmdPidfileFlag := fl.String("pidfile") pidfileFlag := fl.String("pidfile")
runCmdPingbackFlag := fl.String("pingback") pingbackFlag := 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)
}
// load all additional envs as soon as possible // load all additional envs as soon as possible
for _, envFile := range runCmdLoadEnvfileFlag { err := handleEnvFileFlag(fl)
if err := loadEnvFromFile(envFile); err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup, err
fmt.Errorf("loading additional environment variables: %v", err)
}
} }
// if we are supposed to print the environment, do that first // if we are supposed to print the environment, do that first
if runCmdPrintEnvFlag { if printEnvFlag {
printEnvironment() printEnvironment()
} }
// load the config, depending on flags // load the config, depending on flags
var config []byte var config []byte
if runCmdResumeFlag { if resumeFlag {
config, err = os.ReadFile(caddy.ConfigAutosavePath) config, err = os.ReadFile(caddy.ConfigAutosavePath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
// not a bad error; just can't resume if autosave file doesn't exist // 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)) caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
runCmdResumeFlag = false resumeFlag = false
} else if err != nil { } else if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} else { } else {
if runCmdConfigFlag == "" { if configFlag == "" {
caddy.Log().Info("resuming from last configuration", caddy.Log().Info("resuming from last configuration",
zap.String("autosave_file", caddy.ConfigAutosavePath)) zap.String("autosave_file", caddy.ConfigAutosavePath))
} else { } 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 // we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
var configFile string var configFile string
if !runCmdResumeFlag { if !resumeFlag {
config, configFile, err = LoadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag) config, configFile, err = LoadConfig(configFlag, configAdapterFlag)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} }
} }
// create pidfile now, in case loading config takes a while (issue #5477) // create pidfile now, in case loading config takes a while (issue #5477)
if runCmdPidfileFlag != "" { if pidfileFlag != "" {
err := caddy.PIDFile(runCmdPidfileFlag) err := caddy.PIDFile(pidfileFlag)
if err != nil { if err != nil {
caddy.Log().Error("unable to write PID file", caddy.Log().Error("unable to write PID file",
zap.String("pidfile", runCmdPidfileFlag), zap.String("pidfile", pidfileFlag),
zap.Error(err)) zap.Error(err))
} }
} }
@ -244,13 +236,13 @@ func cmdRun(fl Flags) (int, error) {
// if we are to report to another process the successful start // if we are to report to another process the successful start
// of the server, do so now by echoing back contents of stdin // of the server, do so now by echoing back contents of stdin
if runCmdPingbackFlag != "" { if pingbackFlag != "" {
confirmationBytes, err := io.ReadAll(os.Stdin) confirmationBytes, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading confirmation bytes from stdin: %v", err) fmt.Errorf("reading confirmation bytes from stdin: %v", err)
} }
conn, err := net.Dial("tcp", runCmdPingbackFlag) conn, err := net.Dial("tcp", pingbackFlag)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("dialing confirmation address: %v", err) fmt.Errorf("dialing confirmation address: %v", err)
@ -259,14 +251,14 @@ func cmdRun(fl Flags) (int, error) {
_, err = conn.Write(confirmationBytes) _, err = conn.Write(confirmationBytes)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, 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 // if enabled, reload config file automatically on changes
// (this better only be used in dev!) // (this better only be used in dev!)
if runCmdWatchFlag { if watchFlag {
go watchConfigFile(configFile, runCmdConfigAdapterFlag) go watchConfigFile(configFile, configAdapterFlag)
} }
// warn if the environment does not provide enough information about the disk // 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) { func cmdStop(fl Flags) (int, error) {
addrFlag := fl.String("address") addressFlag := fl.String("address")
configFlag := fl.String("config") configFlag := fl.String("config")
configAdapterFlag := fl.String("adapter") configAdapterFlag := fl.String("adapter")
adminAddr, err := DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag) adminAddr, err := DetermineAdminAPIAddress(addressFlag, nil, configFlag, configAdapterFlag)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) 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) { func cmdReload(fl Flags) (int, error) {
configFlag := fl.String("config") configFlag := fl.String("config")
configAdapterFlag := fl.String("adapter") configAdapterFlag := fl.String("adapter")
addrFlag := fl.String("address") addressFlag := fl.String("address")
forceFlag := fl.Bool("force") forceFlag := fl.Bool("force")
// get the config in caddy's native format // 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") 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 { if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err) 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 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() printEnvironment()
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
} }
func cmdAdaptConfig(fl Flags) (int, error) { func cmdAdaptConfig(fl Flags) (int, error) {
adaptCmdInputFlag := fl.String("config") inputFlag := fl.String("config")
adaptCmdAdapterFlag := fl.String("adapter") adapterFlag := fl.String("adapter")
adaptCmdPrettyFlag := fl.Bool("pretty") prettyFlag := fl.Bool("pretty")
adaptCmdValidateFlag := fl.Bool("validate") validateFlag := fl.Bool("validate")
var err error var err error
adaptCmdInputFlag, err = configFileWithRespectToDefault(caddy.Log(), adaptCmdInputFlag) inputFlag, err = configFileWithRespectToDefault(caddy.Log(), inputFlag)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err 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, return caddy.ExitCodeFailedStartup,
fmt.Errorf("adapter name is required (use --adapt flag or leave unspecified for default)") 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 { if cfgAdapter == nil {
return caddy.ExitCodeFailedStartup, 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 { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading input file: %v", err) 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) adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} }
if adaptCmdPrettyFlag { if prettyFlag {
var prettyBuf bytes.Buffer var prettyBuf bytes.Buffer
err = json.Indent(&prettyBuf, adaptedConfig, "", "\t") err = json.Indent(&prettyBuf, adaptedConfig, "", "\t")
if err != nil { if err != nil {
@ -487,13 +491,13 @@ func cmdAdaptConfig(fl Flags) (int, error) {
if warn.Directive != "" { if warn.Directive != "" {
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) 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.String("file", warn.File),
zap.Int("line", warn.Line)) zap.Int("line", warn.Line))
} }
// validate output if requested // validate output if requested
if adaptCmdValidateFlag { if validateFlag {
var cfg *caddy.Config var cfg *caddy.Config
err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg) err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg)
if err != nil { if err != nil {
@ -509,36 +513,26 @@ func cmdAdaptConfig(fl Flags) (int, error) {
} }
func cmdValidateConfig(fl Flags) (int, error) { func cmdValidateConfig(fl Flags) (int, error) {
validateCmdConfigFlag := fl.String("config") configFlag := fl.String("config")
validateCmdAdapterFlag := fl.String("adapter") adapterFlag := 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)
}
// load all additional envs as soon as possible // load all additional envs as soon as possible
for _, envFile := range runCmdLoadEnvfileFlag { err := handleEnvFileFlag(fl)
if err := loadEnvFromFile(envFile); err != nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("loading additional environment variables: %v", err)
}
}
// use default config and ensure a config file is specified
validateCmdConfigFlag, err = configFileWithRespectToDefault(caddy.Log(), validateCmdConfigFlag)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} }
if validateCmdConfigFlag == "" {
// use default config and ensure a config file is specified
configFlag, err = configFileWithRespectToDefault(caddy.Log(), configFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
if configFlag == "" {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)") 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 { if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} }
@ -561,13 +555,13 @@ func cmdValidateConfig(fl Flags) (int, error) {
} }
func cmdFmt(fl Flags) (int, error) { func cmdFmt(fl Flags) (int, error) {
formatCmdConfigFile := fl.Arg(0) configFile := fl.Arg(0)
if formatCmdConfigFile == "" { if configFile == "" {
formatCmdConfigFile = "Caddyfile" configFile = "Caddyfile"
} }
// as a special case, read from stdin if the file name is "-" // as a special case, read from stdin if the file name is "-"
if formatCmdConfigFile == "-" { if configFile == "-" {
input, err := io.ReadAll(os.Stdin) input, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
@ -577,7 +571,7 @@ func cmdFmt(fl Flags) (int, error) {
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
} }
input, err := os.ReadFile(formatCmdConfigFile) input, err := os.ReadFile(configFile)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading input file: %v", err) fmt.Errorf("reading input file: %v", err)
@ -586,7 +580,7 @@ func cmdFmt(fl Flags) (int, error) {
output := caddyfile.Format(input) output := caddyfile.Format(input)
if fl.Bool("overwrite") { 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.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err)
} }
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
@ -610,7 +604,7 @@ func cmdFmt(fl Flags) (int, error) {
fmt.Print(string(output)) 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`, 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.File,
warning.Line, warning.Line,
@ -620,6 +614,25 @@ func cmdFmt(fl Flags) (int, error) {
return caddy.ExitCodeSuccess, nil 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, // 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 // 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 // be assumed to be Content-Type application/json. The caller should close

View file

@ -94,8 +94,8 @@ func init() {
Starts the Caddy process, optionally bootstrapped with an initial config file. Starts the Caddy process, optionally bootstrapped with an initial config file.
This command unblocks after the server starts running or fails to run. This command unblocks after the server starts running or fails to run.
If --envfile is specified, an environment file with environment variables in If --envfile is specified, an environment file with environment variables
the KEY=VALUE format will be loaded into the Caddy process. in the KEY=VALUE format will be loaded into the Caddy process.
On Windows, the spawned child process will remain attached to the terminal, so On Windows, the spawned child process will remain attached to the terminal, so
closing the window will forcefully stop Caddy; to avoid forgetting this, try closing the window will forcefully stop Caddy; to avoid forgetting this, try
@ -133,8 +133,8 @@ 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 that file will be loaded and used to configure Caddy, even without any command
line flags. line flags.
If --envfile is specified, an environment file with environment variables in If --envfile is specified, an environment file with environment variables
the KEY=VALUE format will be loaded into the Caddy process. 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 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 be printed before starting. This is the same as the environ command but does
@ -240,6 +240,7 @@ documentation: https://go.dev/doc/modules/version-numbers
RegisterCommand(Command{ RegisterCommand(Command{
Name: "environ", Name: "environ",
Usage: "[--envfile <path>]",
Short: "Prints the environment", Short: "Prints the environment",
Long: ` Long: `
Prints the environment as seen by this Caddy process. Prints the environment as seen by this Caddy process.
@ -249,6 +250,9 @@ configuration uses environment variables (e.g. "{env.VARIABLE}") then
this command can be useful for verifying that the variables will have this command can be useful for verifying that the variables will have
the values you expect in your config. the values you expect in your config.
If --envfile is specified, an environment file with environment variables
in the KEY=VALUE format will be loaded into the Caddy process.
Note that environments may be different depending on how you run Caddy. Note that environments may be different depending on how you run Caddy.
Environments for Caddy instances started by service managers such as Environments for Caddy instances started by service managers such as
systemd are often different than the environment inherited from your systemd are often different than the environment inherited from your
@ -259,12 +263,15 @@ by adding the "--environ" flag.
Environments may contain sensitive data. Environments may contain sensitive data.
`, `,
Func: cmdEnviron, CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
cmd.RunE = WrapCommandFuncForCobra(cmdEnviron)
},
}) })
RegisterCommand(Command{ RegisterCommand(Command{
Name: "adapt", Name: "adapt",
Usage: "--config <path> [--adapter <name>] [--pretty] [--validate]", Usage: "--config <path> [--adapter <name>] [--pretty] [--validate] [--envfile <path>]",
Short: "Adapts a configuration to Caddy's native JSON", Short: "Adapts a configuration to Caddy's native JSON",
Long: ` Long: `
Adapts a configuration to Caddy's native JSON format and writes the Adapts a configuration to Caddy's native JSON format and writes the
@ -276,12 +283,16 @@ for human readability.
If --validate is used, the adapted config will be checked for validity. If --validate is used, the adapted config will be checked for validity.
If the config is invalid, an error will be printed to stderr and a non- If the config is invalid, an error will be printed to stderr and a non-
zero exit status will be returned. zero exit status will be returned.
If --envfile is specified, an environment file with environment variables
in the KEY=VALUE format will be loaded into the Caddy process.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)") cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)")
cmd.Flags().StringP("adapter", "a", "caddyfile", "Name of config adapter") cmd.Flags().StringP("adapter", "a", "caddyfile", "Name of config adapter")
cmd.Flags().BoolP("pretty", "p", false, "Format the output for human readability") cmd.Flags().BoolP("pretty", "p", false, "Format the output for human readability")
cmd.Flags().BoolP("validate", "", false, "Validate the output") cmd.Flags().BoolP("validate", "", false, "Validate the output")
cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load")
cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig) cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig)
}, },
}) })
@ -295,8 +306,8 @@ Loads and provisions the provided config, but does not start running it.
This reveals any errors with the configuration through the loading and This reveals any errors with the configuration through the loading and
provisioning stages. provisioning stages.
If --envfile is specified, an environment file with environment variables in If --envfile is specified, an environment file with environment variables
the KEY=VALUE format will be loaded into the Caddy process. in the KEY=VALUE format will be loaded into the Caddy process.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("config", "c", "", "Input configuration file") cmd.Flags().StringP("config", "c", "", "Input configuration file")

View file

@ -17,6 +17,7 @@ package caddycmd
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -63,6 +64,10 @@ func Main() {
} }
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
var exitError *exitError
if errors.As(err, &exitError) {
os.Exit(exitError.ExitCode)
}
os.Exit(1) os.Exit(1)
} }
} }