From 5ded580444e9258cb35a9c94192d3c1d63e7b74f Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Thu, 16 Feb 2023 11:14:07 -0500 Subject: cmd: Adjust documentation for commands (#5377) --- cmd/commands.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'cmd') diff --git a/cmd/commands.go b/cmd/commands.go index 9216b89..9daabd1 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -91,14 +91,15 @@ the KEY=VALUE format will be loaded into the Caddy process. 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 -using 'caddy run' instead to keep it in the foreground.`, +using 'caddy run' instead to keep it in the foreground. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("start", flag.ExitOnError) fs.String("config", "", "Configuration file") - fs.String("envfile", "", "Environment file to load") fs.String("adapter", "", "Name of config adapter to apply") - fs.String("pidfile", "", "Path of file to which to write process ID") + fs.String("envfile", "", "Environment file to load") fs.Bool("watch", false, "Reload changed config file automatically") + fs.String("pidfile", "", "Path of file to which to write process ID") return fs }(), }) @@ -138,7 +139,8 @@ save file. It is not an error if --resume is used and no autosave file exists. If --watch is specified, the config file will be loaded automatically after changes. ⚠️ This can make unintentional config changes easier; only use this -option in a local development environment.`, +option in a local development environment. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("run", flag.ExitOnError) fs.String("config", "", "Configuration file") @@ -163,7 +165,8 @@ Stops the background Caddy process as gracefully as possible. It requires that the admin API is enabled and accessible, since it will use the API's /stop endpoint. The address of this request can be customized -using the --address flag, or from the given --config, if not the default.`, +using the --address flag, or from the given --config, if not the default. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("stop", flag.ExitOnError) fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default") @@ -185,7 +188,8 @@ workflows revolving around config files. Since the admin endpoint is configurable, the endpoint configuration is loaded from the --address flag if specified; otherwise it is loaded from the given -config file; otherwise the default is assumed.`, +config file; otherwise the default is assumed. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("reload", flag.ExitOnError) fs.String("config", "", "Configuration file (required)") @@ -273,7 +277,8 @@ for human readability. 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- -zero exit status will be returned.`, +zero exit status will be returned. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("adapt", flag.ExitOnError) fs.String("config", "", "Configuration file to adapt (required)") @@ -295,7 +300,8 @@ This reveals any errors with the configuration through the loading and provisioning stages. If --envfile is specified, an environment file with environment variables in -the KEY=VALUE format will be loaded into the Caddy process.`, +the KEY=VALUE format will be loaded into the Caddy process. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("validate", flag.ExitOnError) fs.String("config", "", "Input configuration file") @@ -308,7 +314,7 @@ the KEY=VALUE format will be loaded into the Caddy process.`, RegisterCommand(Command{ Name: "fmt", Func: cmdFmt, - Usage: "[--overwrite] []", + Usage: "[--overwrite] [--diff] []", Short: "Formats a Caddyfile", Long: ` Formats the Caddyfile by adding proper indentation and spaces to improve @@ -324,7 +330,8 @@ is not a valid patch format. If you wish you use stdin instead of a regular file, use - as the path. When reading from stdin, the --overwrite flag has no effect: the result -is always printed to stdout.`, +is always printed to stdout. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("fmt", flag.ExitOnError) fs.Bool("overwrite", false, "Overwrite the input file with the results") @@ -339,7 +346,8 @@ is always printed to stdout.`, Short: "Upgrade Caddy (EXPERIMENTAL)", Long: ` Downloads an updated Caddy binary with the same modules/plugins at the -latest versions. EXPERIMENTAL: May be changed or removed.`, +latest versions. EXPERIMENTAL: May be changed or removed. +`, Flags: func() *flag.FlagSet { fs := flag.NewFlagSet("upgrade", flag.ExitOnError) fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it") -- cgit v1.2.3 From bf54892a73373637901c13e018b20cfac15878b4 Mon Sep 17 00:00:00 2001 From: Emily Lange Date: Fri, 17 Feb 2023 00:34:12 +0100 Subject: cmd: make `caddy fmt` hints more clear (#5378) --- cmd/commandfuncs.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 09accd0..62afae2 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -589,7 +589,10 @@ func cmdFmt(fl Flags) (int, error) { } if warning, diff := caddyfile.FormattingDifference(formatCmdConfigFile, input); diff { - return caddy.ExitCodeFailedStartup, fmt.Errorf("%s:%d: Caddyfile input is not formatted", warning.File, warning.Line) + 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, + ) } return caddy.ExitCodeSuccess, nil -- cgit v1.2.3 From 79de6df93d0404790c3bfecfefa9e1458ffcff75 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 22 Feb 2023 13:39:40 -0500 Subject: cmd: Strict unmarshal for validate (#5383) --- cmd/commandfuncs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 62afae2..cc5666b 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -490,7 +490,7 @@ func cmdAdaptConfig(fl Flags) (int, error) { // validate output if requested if adaptCmdValidateFlag { var cfg *caddy.Config - err = json.Unmarshal(adaptedConfig, &cfg) + err = caddy.StrictUnmarshalJSON(adaptedConfig, &cfg) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err) } @@ -523,7 +523,7 @@ func cmdValidateConfig(fl Flags) (int, error) { input = caddy.RemoveMetaFields(input) var cfg *caddy.Config - err = json.Unmarshal(input, &cfg) + err = caddy.StrictUnmarshalJSON(input, &cfg) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err) } -- cgit v1.2.3 From 9e6919550be5689628d0020ec14e90ea6f527716 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Fri, 24 Feb 2023 18:09:12 -0500 Subject: cmd: Expand cobra support, add short flags (#5379) * cmd: Expand cobra support * Convert commands to cobra, add short flags * Fix version command typo Co-authored-by: Emily Lange * Apply suggestions from code review Co-authored-by: Matt Holt --------- Co-authored-by: Emily Lange Co-authored-by: Matt Holt --- cmd/cobra.go | 21 +++-- cmd/commands.go | 233 ++++++++++++++++++++++++++------------------------------ 2 files changed, 124 insertions(+), 130 deletions(-) (limited to 'cmd') diff --git a/cmd/cobra.go b/cmd/cobra.go index 203b7bd..4339cdb 100644 --- a/cmd/cobra.go +++ b/cmd/cobra.go @@ -109,12 +109,21 @@ func caddyCmdToCobra(caddyCmd Command) *cobra.Command { Use: caddyCmd.Name, Short: caddyCmd.Short, Long: caddyCmd.Long, - RunE: func(cmd *cobra.Command, _ []string) error { - fls := cmd.Flags() - _, err := caddyCmd.Func(Flags{fls}) - return err - }, } - cmd.Flags().AddGoFlagSet(caddyCmd.Flags) + if caddyCmd.CobraFunc != nil { + caddyCmd.CobraFunc(cmd) + } else { + cmd.RunE = WrapCommandFuncForCobra(caddyCmd.Func) + cmd.Flags().AddGoFlagSet(caddyCmd.Flags) + } return cmd } + +// WrapCommandFuncForCobra wraps a Caddy CommandFunc for use +// in a cobra command's RunE field. +func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ []string) error { + return func(cmd *cobra.Command, _ []string) error { + _, err := f(Flags{cmd.Flags()}) + return err + } +} diff --git a/cmd/commands.go b/cmd/commands.go index 9daabd1..a06336d 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -34,12 +34,6 @@ type Command struct { // Required. Name string - // Func is a function that executes a subcommand using - // the parsed flags. It returns an exit code and any - // associated error. - // Required. - Func CommandFunc - // Usage is a brief message describing the syntax of // the subcommand's flags and args. Use [] to indicate // optional parameters and <> to enclose literal values @@ -60,7 +54,21 @@ type Command struct { Long string // Flags is the flagset for command. + // This is ignored if CobraFunc is set. Flags *flag.FlagSet + + // Func is a function that executes a subcommand using + // the parsed flags. It returns an exit code and any + // associated error. + // Required if CobraFunc is not set. + Func CommandFunc + + // CobraFunc allows further configuration of the command + // via cobra's APIs. If this is set, then Func and Flags + // are ignored, with the assumption that they are set in + // this function. A caddycmd.WrapCommandFuncForCobra helper + // exists to simplify porting CommandFunc to Cobra's RunE. + CobraFunc func(*cobra.Command) } // CommandFunc is a command's function. It runs the @@ -79,7 +87,6 @@ var commands = make(map[string]Command) func init() { RegisterCommand(Command{ Name: "start", - Func: cmdStart, Usage: "[--config [--adapter ]] [--envfile ] [--watch] [--pidfile ]", Short: "Starts the Caddy process in the background and then returns", Long: ` @@ -93,21 +100,19 @@ 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 using 'caddy run' instead to keep it in the foreground. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("start", 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("watch", false, "Reload changed config file automatically") - fs.String("pidfile", "", "Path of file to which to write process ID") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") + cmd.Flags().StringP("envfile", "", "", "Environment file to load") + cmd.Flags().BoolP("watch", "w", false, "Reload changed config file automatically") + cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") + cmd.RunE = WrapCommandFuncForCobra(cmdStart) + }, }) RegisterCommand(Command{ Name: "run", - Func: cmdRun, - Usage: "[--config [--adapter ]] [--envfile ] [--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, @@ -141,24 +146,22 @@ If --watch is specified, the config file will be loaded automatically after changes. ⚠️ This can make unintentional config changes easier; only use this option in a local development environment. `, - Flags: func() *flag.FlagSet { - 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") - fs.String("pidfile", "", "Path of file to which to write process ID") - fs.String("pingback", "", "Echo confirmation bytes to this address on success") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") + cmd.Flags().StringP("envfile", "", "", "Environment file to load") + cmd.Flags().BoolP("environ", "e", false, "Print environment") + cmd.Flags().BoolP("resume", "r", false, "Use saved config, if any (and prefer over --config file)") + cmd.Flags().BoolP("watch", "w", false, "Watch config file for changes and reload it automatically") + cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") + cmd.Flags().StringP("pingback", "", "", "Echo confirmation bytes to this address on success") + cmd.RunE = WrapCommandFuncForCobra(cmdRun) + }, }) RegisterCommand(Command{ Name: "stop", - Func: cmdStop, - Usage: "[--address ] [--config [--adapter ]]", + Usage: "[--config [--adapter ]] [--address ]", Short: "Gracefully stops a started Caddy process", Long: ` Stops the background Caddy process as gracefully as possible. @@ -167,18 +170,16 @@ It requires that the admin API is enabled and accessible, since it will use the API's /stop endpoint. The address of this request can be customized using the --address flag, or from the given --config, if not the default. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("stop", flag.ExitOnError) - fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default") - fs.String("config", "", "Configuration file to use to parse the admin address, if --address is not used") - fs.String("adapter", "", "Name of config adapter to apply (when --config is used)") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file to use to parse the admin address, if --address is not used") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (when --config is used)") + cmd.Flags().StringP("address", "", "", "The address to use to reach the admin API endpoint, if not the default") + cmd.RunE = WrapCommandFuncForCobra(cmdStop) + }, }) RegisterCommand(Command{ Name: "reload", - Func: cmdReload, Usage: "--config [--adapter ] [--address ]", Short: "Changes the config of the running Caddy instance", Long: ` @@ -190,19 +191,17 @@ Since the admin endpoint is configurable, the endpoint configuration is loaded from the --address flag if specified; otherwise it is loaded from the given config file; otherwise the default is assumed. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("reload", flag.ExitOnError) - fs.String("config", "", "Configuration file (required)") - fs.String("adapter", "", "Name of config adapter to apply") - fs.String("address", "", "Address of the administration listener, if different from config") - fs.Bool("force", false, "Force config reload, even if it is the same") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file (required)") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") + cmd.Flags().StringP("address", "", "", "Address of the administration listener, if different from config") + cmd.Flags().BoolP("force", "f", false, "Force config reload, even if it is the same") + cmd.RunE = WrapCommandFuncForCobra(cmdReload) + }, }) RegisterCommand(Command{ Name: "version", - Func: cmdVersion, Short: "Prints the version", Long: ` Prints the version of this Caddy binary. @@ -217,31 +216,29 @@ detailed version information is printed as given by Go modules. For more details about the full version string, see the Go module documentation: https://go.dev/doc/modules/version-numbers `, + Func: cmdVersion, }) RegisterCommand(Command{ Name: "list-modules", - Func: cmdListModules, - Usage: "[--packages] [--versions]", + Usage: "[--packages] [--versions] [--skip-standard]", Short: "Lists the installed Caddy modules", - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("list-modules", flag.ExitOnError) - fs.Bool("packages", false, "Print package paths") - fs.Bool("versions", false, "Print version information") - fs.Bool("skip-standard", false, "Skip printing standard modules") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("packages", "", false, "Print package paths") + cmd.Flags().BoolP("versions", "", false, "Print version information") + cmd.Flags().BoolP("skip-standard", "s", false, "Skip printing standard modules") + cmd.RunE = WrapCommandFuncForCobra(cmdListModules) + }, }) RegisterCommand(Command{ Name: "build-info", - Func: cmdBuildInfo, Short: "Prints information about this build", + Func: cmdBuildInfo, }) RegisterCommand(Command{ Name: "environ", - Func: cmdEnviron, Short: "Prints the environment", Long: ` Prints the environment as seen by this Caddy process. @@ -261,11 +258,11 @@ by adding the "--environ" flag. Environments may contain sensitive data. `, + Func: cmdEnviron, }) RegisterCommand(Command{ Name: "adapt", - Func: cmdAdaptConfig, Usage: "--config [--adapter ] [--pretty] [--validate]", Short: "Adapts a configuration to Caddy's native JSON", Long: ` @@ -279,19 +276,17 @@ 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- zero exit status will be returned. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("adapt", flag.ExitOnError) - fs.String("config", "", "Configuration file to adapt (required)") - fs.String("adapter", "caddyfile", "Name of config adapter") - fs.Bool("pretty", false, "Format the output for human readability") - fs.Bool("validate", false, "Validate the output") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)") + 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("validate", "", false, "Validate the output") + cmd.RunE = WrapCommandFuncForCobra(cmdAdaptConfig) + }, }) RegisterCommand(Command{ Name: "validate", - Func: cmdValidateConfig, Usage: "--config [--adapter ] [--envfile ]", Short: "Tests whether a configuration file is valid", Long: ` @@ -302,18 +297,16 @@ provisioning stages. If --envfile is specified, an environment file with environment variables in the KEY=VALUE format will be loaded into the Caddy process. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("validate", flag.ExitOnError) - fs.String("config", "", "Input configuration file") - fs.String("adapter", "", "Name of config adapter") - fs.String("envfile", "", "Environment file to load") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("config", "c", "", "Input configuration file") + cmd.Flags().StringP("adapter", "a", "", "Name of config adapter") + cmd.Flags().StringP("envfile", "", "", "Environment file to load") + cmd.RunE = WrapCommandFuncForCobra(cmdValidateConfig) + }, }) RegisterCommand(Command{ Name: "fmt", - Func: cmdFmt, Usage: "[--overwrite] [--diff] []", Short: "Formats a Caddyfile", Long: ` @@ -332,32 +325,28 @@ If you wish you use stdin instead of a regular file, use - as the path. When reading from stdin, the --overwrite flag has no effect: the result is always printed to stdout. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("fmt", flag.ExitOnError) - fs.Bool("overwrite", false, "Overwrite the input file with the results") - fs.Bool("diff", false, "Print the differences between the input file and the formatted output") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("overwrite", "w", false, "Overwrite the input file with the results") + cmd.Flags().BoolP("diff", "d", false, "Print the differences between the input file and the formatted output") + cmd.RunE = WrapCommandFuncForCobra(cmdFmt) + }, }) RegisterCommand(Command{ Name: "upgrade", - Func: cmdUpgrade, Short: "Upgrade Caddy (EXPERIMENTAL)", Long: ` Downloads an updated Caddy binary with the same modules/plugins at the latest versions. EXPERIMENTAL: May be changed or removed. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("upgrade", flag.ExitOnError) - fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") + cmd.RunE = WrapCommandFuncForCobra(cmdUpgrade) + }, }) RegisterCommand(Command{ Name: "add-package", - Func: cmdAddPackage, Usage: "", Short: "Adds Caddy packages (EXPERIMENTAL)", Long: ` @@ -365,11 +354,10 @@ Downloads an updated Caddy binary with the specified packages (module/plugin) added. Retains existing packages. Returns an error if the any of packages are already included. EXPERIMENTAL: May be changed or removed. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("add-package", flag.ExitOnError) - fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") + cmd.RunE = WrapCommandFuncForCobra(cmdAddPackage) + }, }) RegisterCommand(Command{ @@ -382,31 +370,14 @@ Downloads an updated Caddy binaries without the specified packages (module/plugi Returns an error if any of the packages are not included. EXPERIMENTAL: May be changed or removed. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("remove-package", flag.ExitOnError) - fs.Bool("keep-backup", false, "Keep the backed up binary, instead of deleting it") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") + cmd.RunE = WrapCommandFuncForCobra(cmdRemovePackage) + }, }) RegisterCommand(Command{ - Name: "manpage", - Func: func(fl Flags) (int, error) { - dir := strings.TrimSpace(fl.String("directory")) - if dir == "" { - return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") - } - if err := os.MkdirAll(dir, 0755); err != nil { - return caddy.ExitCodeFailedQuit, err - } - if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ - Title: "Caddy", - Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections - }, dir); err != nil { - return caddy.ExitCodeFailedQuit, err - } - return caddy.ExitCodeSuccess, nil - }, + Name: "manpage", Usage: "--directory ", Short: "Generates the manual pages for Caddy commands", Long: ` @@ -416,11 +387,25 @@ tagged into section 8 (System Administration). The manual page files are generated into the directory specified by the argument of --directory. If the directory does not exist, it will be created. `, - Flags: func() *flag.FlagSet { - fs := flag.NewFlagSet("manpage", flag.ExitOnError) - fs.String("directory", "", "The output directory where the manpages are generated") - return fs - }(), + CobraFunc: func(cmd *cobra.Command) { + cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated") + cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) { + dir := strings.TrimSpace(fl.String("directory")) + if dir == "" { + return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") + } + if err := os.MkdirAll(dir, 0755); err != nil { + return caddy.ExitCodeFailedQuit, err + } + if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ + Title: "Caddy", + Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections + }, dir); err != nil { + return caddy.ExitCodeFailedQuit, err + } + return caddy.ExitCodeSuccess, nil + }) + }, }) // source: https://github.com/spf13/cobra/blob/main/shell_completions.md @@ -504,7 +489,7 @@ func RegisterCommand(cmd Command) { if cmd.Name == "" { panic("command name is required") } - if cmd.Func == nil { + if cmd.Func == nil && cmd.CobraFunc == nil { panic("command function missing") } if cmd.Short == "" { -- cgit v1.2.3 From 508cf2aa228c42a0d1b9a0dbc4351a876e51b5d9 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 3 Apr 2023 11:57:16 -0600 Subject: cmd: Create pidfile before config load (close #5477) --- cmd/commandfuncs.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index cc5666b..77a4cdf 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -208,6 +208,16 @@ func cmdRun(fl Flags) (int, error) { } } + // create pidfile now, in case loading config takes a while (issue #5477) + if runCmdPidfileFlag != "" { + err := caddy.PIDFile(runCmdPidfileFlag) + if err != nil { + caddy.Log().Error("unable to write PID file", + zap.String("pidfile", runCmdPidfileFlag), + zap.Error(err)) + } + } + // run the initial config err = caddy.Load(config, true) if err != nil { @@ -242,16 +252,6 @@ func cmdRun(fl Flags) (int, error) { go watchConfigFile(configFile, runCmdConfigAdapterFlag) } - // create pidfile - if runCmdPidfileFlag != "" { - err := caddy.PIDFile(runCmdPidfileFlag) - if err != nil { - caddy.Log().Error("unable to write PID file", - zap.String("pidfile", runCmdPidfileFlag), - zap.Error(err)) - } - } - // warn if the environment does not provide enough information about the disk hasXDG := os.Getenv("XDG_DATA_HOME") != "" && os.Getenv("XDG_CONFIG_HOME") != "" && -- cgit v1.2.3 From 205b142614d5de08ffc33a04ae4cfc00e65b5dfc Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Mon, 10 Apr 2023 13:55:45 -0600 Subject: cmd: Support `'` quotes in envfile parsing (#5437) --- cmd/main.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/main.go b/cmd/main.go index 17da8bd..96debdf 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -374,18 +374,19 @@ func parseEnvFile(envInput io.Reader) (map[string]string, error) { } // quoted value: support newlines - if strings.HasPrefix(val, `"`) { - for !(strings.HasSuffix(line, `"`) && !strings.HasSuffix(line, `\"`)) { - val = strings.ReplaceAll(val, `\"`, `"`) + if strings.HasPrefix(val, `"`) || strings.HasPrefix(val, "'") { + quote := string(val[0]) + for !(strings.HasSuffix(line, quote) && !strings.HasSuffix(line, `\`+quote)) { + val = strings.ReplaceAll(val, `\`+quote, quote) if !scanner.Scan() { break } lineNumber++ - line = strings.ReplaceAll(scanner.Text(), `\"`, `"`) + line = strings.ReplaceAll(scanner.Text(), `\`+quote, quote) val += "\n" + line } - val = strings.TrimPrefix(val, `"`) - val = strings.TrimSuffix(val, `"`) + val = strings.TrimPrefix(val, quote) + val = strings.TrimSuffix(val, quote) } envMap[key] = val -- cgit v1.2.3 From 571fc034d3838d8c2a33ab8424d44eb5b30f4ecd Mon Sep 17 00:00:00 2001 From: Yehonatan Ezron <37303618+jonatan5524@users.noreply.github.com> Date: Tue, 9 May 2023 01:49:16 +0300 Subject: feature: watch include directory (#5521) Co-authored-by: Matt Holt --- cmd/main.go | 53 ++++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) (limited to 'cmd') diff --git a/cmd/main.go b/cmd/main.go index 96debdf..97326ba 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -174,6 +174,8 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) { // blocks indefinitely; it only quits if the poller has errors for // long enough time. The filename passed in must be the actual // config file used, not one to be discovered. +// Each second the config files is loaded and parsed into an object +// and is compared to the last config object that was loaded func watchConfigFile(filename, adapterName string) { defer func() { if err := recover(); err != nil { @@ -189,64 +191,37 @@ func watchConfigFile(filename, adapterName string) { With(zap.String("config_file", filename)) } - // get the initial timestamp on the config file - info, err := os.Stat(filename) + // get current config + lastCfg, _, err := LoadConfig(filename, adapterName) if err != nil { - logger().Error("cannot watch config file", zap.Error(err)) + logger().Error("unable to load latest config", zap.Error(err)) return } - lastModified := info.ModTime() logger().Info("watching config file for changes") - // if the file disappears or something, we can - // stop polling if the error lasts long enough - var lastErr time.Time - finalError := func(err error) bool { - if lastErr.IsZero() { - lastErr = time.Now() - return false - } - if time.Since(lastErr) > 30*time.Second { - logger().Error("giving up watching config file; too many errors", - zap.Error(err)) - return true - } - return false - } - // begin poller //nolint:staticcheck for range time.Tick(1 * time.Second) { - // get the file info - info, err := os.Stat(filename) + + // get current config + newCfg, _, err := LoadConfig(filename, adapterName) if err != nil { - if finalError(err) { - return - } - continue + logger().Error("unable to load latest config", zap.Error(err)) + return } - lastErr = time.Time{} // no error, so clear any memory of one // if it hasn't changed, nothing to do - if !info.ModTime().After(lastModified) { + if bytes.Equal(lastCfg, newCfg) { continue } - logger().Info("config file changed; reloading") - // remember this timestamp - lastModified = info.ModTime() - - // load the contents of the file - config, _, err := LoadConfig(filename, adapterName) - if err != nil { - logger().Error("unable to load latest config", zap.Error(err)) - continue - } + // remember the current config + lastCfg = newCfg // apply the updated config - err = caddy.Load(config, false) + err = caddy.Load(lastCfg, false) if err != nil { logger().Error("applying latest config", zap.Error(err)) continue -- cgit v1.2.3 From 5ebb7d496da765fd0263161cd306ecf86b609485 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 12 May 2023 11:04:02 -0600 Subject: cmd: Reduce spammy logs from --watch --- cmd/main.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'cmd') diff --git a/cmd/main.go b/cmd/main.go index 97326ba..7db2092 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -89,6 +89,10 @@ func handlePingbackConn(conn net.Conn, expect []byte) error { // and returns the resulting JSON config bytes along with // the name of the loaded config file (if any). func LoadConfig(configFile, adapterName string) ([]byte, string, error) { + return loadConfigWithLogger(caddy.Log(), configFile, adapterName) +} + +func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, error) { // specifying an adapter without a config file is ambiguous if adapterName != "" && configFile == "" { return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)") @@ -107,9 +111,11 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) { if err != nil { return nil, "", fmt.Errorf("reading config file: %v", err) } - caddy.Log().Info("using provided configuration", - zap.String("config_file", configFile), - zap.String("config_adapter", adapterName)) + if logger != nil { + logger.Info("using provided configuration", + zap.String("config_file", configFile), + zap.String("config_adapter", adapterName)) + } } else if adapterName == "" { // as a special case when no config file or adapter // is specified, see if the Caddyfile adapter is @@ -126,7 +132,9 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) { } else { // success reading default Caddyfile configFile = "Caddyfile" - caddy.Log().Info("using adjacent Caddyfile") + if logger != nil { + logger.Info("using adjacent Caddyfile") + } } } } @@ -161,7 +169,9 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) { if warn.Directive != "" { msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) } - caddy.Log().Warn(msg, zap.String("adapter", adapterName), zap.String("file", warn.File), zap.Int("line", warn.Line)) + if logger != nil { + logger.Warn(msg, zap.String("adapter", adapterName), zap.String("file", warn.File), zap.Int("line", warn.Line)) + } } config = adaptedConfig } @@ -192,7 +202,7 @@ func watchConfigFile(filename, adapterName string) { } // get current config - lastCfg, _, err := LoadConfig(filename, adapterName) + lastCfg, _, err := loadConfigWithLogger(nil, filename, adapterName) if err != nil { logger().Error("unable to load latest config", zap.Error(err)) return @@ -203,7 +213,6 @@ func watchConfigFile(filename, adapterName string) { // begin poller //nolint:staticcheck for range time.Tick(1 * time.Second) { - // get current config newCfg, _, err := LoadConfig(filename, adapterName) if err != nil { -- cgit v1.2.3 From 38cb587e0f1b38db2c9fb422b4892e48753f00c0 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 17 May 2023 16:13:15 -0600 Subject: cmd: Avoid spammy log messages (fix #5538) I forgot there are two calls to LoadConfig() here that needed replacing. --- cmd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/main.go b/cmd/main.go index 7db2092..bfbd0bb 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -214,7 +214,7 @@ func watchConfigFile(filename, adapterName string) { //nolint:staticcheck for range time.Tick(1 * time.Second) { // get current config - newCfg, _, err := LoadConfig(filename, adapterName) + newCfg, _, err := loadConfigWithLogger(nil, filename, adapterName) if err != nil { logger().Error("unable to load latest config", zap.Error(err)) return -- cgit v1.2.3 From 078f130a51b1546d63d770acdcbdec64cc7323a6 Mon Sep 17 00:00:00 2001 From: Cass C Date: Fri, 2 Jun 2023 15:04:31 -0400 Subject: cmd: Implement storage import/export (#5532) * cmd: Implement 'storage import' and 'storage export' CLI commands. These commands use the certmagic.Storage interface. In particular, storage implementations should ensure that their List() functions correctly enumerate all keys when called with an empty prefix and recursive == true. Also, Stat() calls on keys holding values instead of nested keys are expected to set KeyInfo.IsTerminal = true. * remove errors.Join --- cmd/commands.go | 50 ++++++++++++ cmd/storagefuncs.go | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 cmd/storagefuncs.go (limited to 'cmd') diff --git a/cmd/commands.go b/cmd/commands.go index a06336d..d1b76f4 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -305,6 +305,56 @@ the KEY=VALUE format will be loaded into the Caddy process. }, }) + RegisterCommand(Command{ + Name: "storage", + Short: "Commands for working with Caddy's storage (EXPERIMENTAL)", + Long: ` +Allows exporting and importing Caddy's storage contents. The two commands can be +combined in a pipeline to transfer directly from one storage to another: + +$ caddy storage export --config Caddyfile.old --output - | +> caddy storage import --config Caddyfile.new --input - + +The - argument refers to stdout and stdin, respectively. + +NOTE: When importing to or exporting from file_system storage (the default), the command +should be run as the user that owns the associated root path. + +EXPERIMENTAL: May be changed or removed. +`, + CobraFunc: func(cmd *cobra.Command) { + exportCmd := &cobra.Command{ + Use: "export --config --output ", + Short: "Exports storage assets as a tarball", + Long: ` +The contents of the configured storage module (TLS certificates, etc) +are exported via a tarball. + +--output is required, - can be given for stdout. +`, + RunE: WrapCommandFuncForCobra(cmdExportStorage), + } + exportCmd.Flags().StringP("config", "c", "", "Input configuration file (required)") + exportCmd.Flags().StringP("output", "o", "", "Output path") + cmd.AddCommand(exportCmd) + + importCmd := &cobra.Command{ + Use: "import --config --input ", + Short: "Imports storage assets from a tarball.", + Long: ` +Imports storage assets to the configured storage module. The import file must be +a tar archive. + +--input is required, - can be given for stdin. +`, + RunE: WrapCommandFuncForCobra(cmdImportStorage), + } + importCmd.Flags().StringP("config", "c", "", "Configuration file to load (required)") + importCmd.Flags().StringP("input", "i", "", "Tar of assets to load (required)") + cmd.AddCommand(importCmd) + }, + }) + RegisterCommand(Command{ Name: "fmt", Usage: "[--overwrite] [--diff] []", diff --git a/cmd/storagefuncs.go b/cmd/storagefuncs.go new file mode 100644 index 0000000..75790ab --- /dev/null +++ b/cmd/storagefuncs.go @@ -0,0 +1,220 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddycmd + +import ( + "archive/tar" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/certmagic" +) + +type storVal struct { + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"` +} + +// determineStorage returns the top-level storage module from the given config. +// It may return nil even if no error. +func determineStorage(configFile string, configAdapter string) (*storVal, error) { + cfg, _, err := LoadConfig(configFile, configAdapter) + if err != nil { + return nil, err + } + + // storage defaults to FileStorage if not explicitly + // defined in the config, so the config can be valid + // json but unmarshaling will fail. + if !json.Valid(cfg) { + return nil, &json.SyntaxError{} + } + var tmpStruct storVal + err = json.Unmarshal(cfg, &tmpStruct) + if err != nil { + // default case, ignore the error + var jsonError *json.SyntaxError + if errors.As(err, &jsonError) { + return nil, nil + } + return nil, err + } + + return &tmpStruct, nil +} + +func cmdImportStorage(fl Flags) (int, error) { + importStorageCmdConfigFlag := fl.String("config") + importStorageCmdImportFile := fl.String("input") + + if importStorageCmdConfigFlag == "" { + return caddy.ExitCodeFailedStartup, errors.New("--config is required") + } + if importStorageCmdImportFile == "" { + return caddy.ExitCodeFailedStartup, errors.New("--input is required") + } + + // extract storage from config if possible + storageCfg, err := determineStorage(importStorageCmdConfigFlag, "") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // load specified storage or fallback to default + var stor certmagic.Storage + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + if storageCfg != nil && storageCfg.StorageRaw != nil { + val, err := ctx.LoadModule(storageCfg, "StorageRaw") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + stor, err = val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + } else { + stor = caddy.DefaultStorage + } + + // setup input + var f *os.File + if importStorageCmdImportFile == "-" { + f = os.Stdin + } else { + f, err = os.Open(importStorageCmdImportFile) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("opening input file: %v", err) + } + defer f.Close() + } + + // store each archive element + tr := tar.NewReader(f) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err) + } + + b, err := io.ReadAll(tr) + if err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err) + } + + err = stor.Store(ctx, hdr.Name, b) + if err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err) + } + } + + fmt.Println("Successfully imported storage") + return caddy.ExitCodeSuccess, nil +} + +func cmdExportStorage(fl Flags) (int, error) { + exportStorageCmdConfigFlag := fl.String("config") + exportStorageCmdOutputFlag := fl.String("output") + + if exportStorageCmdConfigFlag == "" { + return caddy.ExitCodeFailedStartup, errors.New("--config is required") + } + if exportStorageCmdOutputFlag == "" { + return caddy.ExitCodeFailedStartup, errors.New("--output is required") + } + + // extract storage from config if possible + storageCfg, err := determineStorage(exportStorageCmdConfigFlag, "") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // load specified storage or fallback to default + var stor certmagic.Storage + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + if storageCfg != nil && storageCfg.StorageRaw != nil { + val, err := ctx.LoadModule(storageCfg, "StorageRaw") + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + stor, err = val.(caddy.StorageConverter).CertMagicStorage() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + } else { + stor = caddy.DefaultStorage + } + + // enumerate all keys + keys, err := stor.List(ctx, "", true) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // setup output + var f *os.File + if exportStorageCmdOutputFlag == "-" { + f = os.Stdout + } else { + f, err = os.Create(exportStorageCmdOutputFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("opening output file: %v", err) + } + defer f.Close() + } + + // `IsTerminal: true` keys hold the values we + // care about, write them out + tw := tar.NewWriter(f) + for _, k := range keys { + info, err := stor.Stat(ctx, k) + if err != nil { + return caddy.ExitCodeFailedQuit, err + } + + if info.IsTerminal { + v, err := stor.Load(ctx, k) + if err != nil { + return caddy.ExitCodeFailedQuit, err + } + + hdr := &tar.Header{ + Name: k, + Mode: 0600, + Size: int64(len(v)), + } + + if err = tw.WriteHeader(hdr); err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err) + } + if _, err = tw.Write(v); err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err) + } + } + } + if err = tw.Close(); err != nil { + return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err) + } + + return caddy.ExitCodeSuccess, nil +} -- cgit v1.2.3 From f66493efef4d909fdeb68a2ce8131d58e17333b3 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Wed, 2 Aug 2023 11:13:52 -0600 Subject: core: Allow loopback hosts for admin endpoint (fix #5650) (#5664) --- cmd/commandfuncs.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 77a4cdf..68b099e 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -610,7 +610,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io } origin := "http://" + parsedAddr.JoinHostPort(0) if parsedAddr.IsUnixNetwork() { - origin = "http://unixsocket" // hack so that http.NewRequest() is happy + origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy } // form the request @@ -619,20 +619,24 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io return nil, fmt.Errorf("making request: %v", err) } if parsedAddr.IsUnixNetwork() { - // When listening on a unix socket, the admin endpoint doesn't - // accept any Host header because there is no host:port for - // a unix socket's address. The server's host check is fairly - // strict for security reasons, so we don't allow just any - // Host header. For unix sockets, the Host header must be - // empty. Unfortunately, Go makes it impossible to make HTTP - // requests with an empty Host header... except with this one - // weird trick. (Hopefully they don't fix it. It's already - // hard enough to use HTTP over unix sockets.) + // We used to conform to RFC 2616 Section 14.26 which requires + // an empty host header when there is no host, as is the case + // with unix sockets. However, Go required a Host value so we + // used a hack of a space character as the host (it would see + // the Host was non-empty, then trim the space later). As of + // Go 1.20.6 (July 2023), this hack no longer works. See: + // https://github.com/golang/go/issues/60374 + // See also the discussion here: + // https://github.com/golang/go/issues/61431 // - // An equivalent curl command would be something like: - // $ curl --unix-socket caddy.sock http:/:$REQUEST_URI - req.URL.Host = " " - req.Host = "" + // After that, we now require a Host value of either 127.0.0.1 + // or ::1 if one is set. Above I choose to use 127.0.0.1. Even + // though the value should be completely irrelevant (it could be + // "srldkjfsd"), if for some reason the Host *is* used, at least + // we can have some reasonable assurance it will stay on the local + // machine and that browsers, if they ever allow access to unix + // sockets, can still enforce CORS, ensuring it is still coming + // from the local machine. } else { req.Header.Set("Origin", origin) } -- cgit v1.2.3 From 8d304a4566de36219b31e1cb5a636431362c673c Mon Sep 17 00:00:00 2001 From: Emily Date: Sun, 6 Aug 2023 02:09:16 +0200 Subject: cmd: Split unix sockets for admin endpoint addresses (#5696) * cmd: fix cli when admin endpoint uses new unix socket permission format Fixes a bug where the following Caddyfile ```Caddyfile { admin unix/admin.sock|0660 } ``` and `caddy reload --config Caddyfile` would throw the following error instead of reloading it: ``` INFO using provided configuration {"config_file": "Caddyfile", "config_adapter": ""} Error: sending configuration to instance: performing request: Post "http://127.0.0.1/load": dial unix admin.sock|0660: connect: no such file or directory [ERROR] exit status 1 ``` --- This bug also affected `caddy start` and `caddy stop`. * Move splitter function to internal --------- Co-authored-by: Matthew Holt --- cmd/commandfuncs.go | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 68b099e..dde870b 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -35,6 +35,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/internal" "go.uber.org/zap" ) @@ -611,6 +612,16 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io origin := "http://" + parsedAddr.JoinHostPort(0) if parsedAddr.IsUnixNetwork() { origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy + + // the unix address at this point might still contain the optional + // unix socket permissions, which are part of the address/host. + // those need to be removed first, as they aren't part of the + // resulting unix file path + addr, _, err := internal.SplitUnixSocketPermissionsBits(parsedAddr.Host) + if err != nil { + return nil, err + } + parsedAddr.Host = addr } // form the request -- cgit v1.2.3 From b32f265ecad60404c3818cc9d42e367a8e4eb7d4 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 8 Aug 2023 03:40:31 +0800 Subject: ci: Use gofumpt to format code (#5707) --- cmd/commandfuncs.go | 2 +- cmd/commands.go | 2 +- cmd/storagefuncs.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index dde870b..837526e 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -565,7 +565,7 @@ func cmdFmt(fl Flags) (int, error) { output := caddyfile.Format(input) if fl.Bool("overwrite") { - if err := os.WriteFile(formatCmdConfigFile, output, 0600); err != nil { + if err := os.WriteFile(formatCmdConfigFile, output, 0o600); err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("overwriting formatted file: %v", err) } return caddy.ExitCodeSuccess, nil diff --git a/cmd/commands.go b/cmd/commands.go index d1b76f4..3bf47df 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -444,7 +444,7 @@ argument of --directory. If the directory does not exist, it will be created. if dir == "" { return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") } - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { return caddy.ExitCodeFailedQuit, err } if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ diff --git a/cmd/storagefuncs.go b/cmd/storagefuncs.go index 75790ab..a9f1bca 100644 --- a/cmd/storagefuncs.go +++ b/cmd/storagefuncs.go @@ -200,7 +200,7 @@ func cmdExportStorage(fl Flags) (int, error) { hdr := &tar.Header{ Name: k, - Mode: 0600, + Mode: 0o600, Size: int64(len(v)), } -- cgit v1.2.3 From d8135505d38ca872d507418d2017f4dbf352f06a Mon Sep 17 00:00:00 2001 From: pistasjis <57069715+pistasjis@users.noreply.github.com> Date: Wed, 9 Aug 2023 19:40:37 +0200 Subject: cmd: Require config for caddy validate (fix #5612) (#5614) * Require config for caddy validate - fixes #5612 Signed-off-by: Pistasj * Try making adjacent Caddyfile check its own function Signed-off-by: Pistasj * add Francis' suggestion Co-authored-by: Francis Lavoie * Refactor * Fix borked commit, sigh --------- Signed-off-by: Pistasj Co-authored-by: Francis Lavoie Co-authored-by: Matthew Holt --- cmd/commandfuncs.go | 57 ++++++++++++++++++++++++++++++++++++++--------------- cmd/main.go | 5 ++--- 2 files changed, 43 insertions(+), 19 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 837526e..f997f06 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "io/fs" "log" "net" "net/http" @@ -423,24 +424,12 @@ func cmdAdaptConfig(fl Flags) (int, error) { adaptCmdPrettyFlag := fl.Bool("pretty") adaptCmdValidateFlag := fl.Bool("validate") - // if no input file was specified, try a default - // Caddyfile if the Caddyfile adapter is plugged in - if adaptCmdInputFlag == "" && caddyconfig.GetAdapter("caddyfile") != nil { - _, err := os.Stat("Caddyfile") - if err == nil { - // default Caddyfile exists - adaptCmdInputFlag = "Caddyfile" - caddy.Log().Info("using adjacent Caddyfile") - } else if !os.IsNotExist(err) { - // default Caddyfile exists, but error accessing it - return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing default Caddyfile: %v", err) - } + var err error + adaptCmdInputFlag, err = configFileWithRespectToDefault(caddy.Log(), adaptCmdInputFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err } - if adaptCmdInputFlag == "" { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)") - } if adaptCmdAdapterFlag == "" { return caddy.ExitCodeFailedStartup, fmt.Errorf("adapter name is required (use --adapt flag or leave unspecified for default)") @@ -517,6 +506,17 @@ func cmdValidateConfig(fl Flags) (int, error) { } } + // use default config and ensure a config file is specified + var err error + validateCmdConfigFlag, err = configFileWithRespectToDefault(caddy.Log(), validateCmdConfigFlag) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + if validateCmdConfigFlag == "" { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)") + } + input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag) if err != nil { return caddy.ExitCodeFailedStartup, err @@ -736,6 +736,31 @@ func DetermineAdminAPIAddress(address string, config []byte, configFile, configA return caddy.DefaultAdminListen, nil } +// configFileWithRespectToDefault returns the filename to use for loading the config, based +// on whether a config file is already specified and a supported default config file exists. +func configFileWithRespectToDefault(logger *zap.Logger, configFile string) (string, error) { + const defaultCaddyfile = "Caddyfile" + + // if no input file was specified, try a default Caddyfile if the Caddyfile adapter is plugged in + if configFile == "" && caddyconfig.GetAdapter("caddyfile") != nil { + _, err := os.Stat(defaultCaddyfile) + if err == nil { + // default Caddyfile exists + if logger != nil { + logger.Info("using adjacent Caddyfile") + } + return defaultCaddyfile, nil + } + if !errors.Is(err, fs.ErrNotExist) { + // problem checking + return configFile, fmt.Errorf("checking if default Caddyfile exists: %v", err) + } + } + + // default config file does not exist or is irrelevant + return configFile, nil +} + type moduleInfo struct { caddyModuleID string goModule *debug.Module diff --git a/cmd/main.go b/cmd/main.go index bfbd0bb..53b8d67 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -117,9 +117,8 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([ zap.String("config_adapter", adapterName)) } } else if adapterName == "" { - // as a special case when no config file or adapter - // is specified, see if the Caddyfile adapter is - // plugged in, and if so, try using a default Caddyfile + // if the Caddyfile adapter is plugged in, we can try using an + // adjacent Caddyfile by default cfgAdapter = caddyconfig.GetAdapter("caddyfile") if cfgAdapter != nil { config, err = os.ReadFile("Caddyfile") -- cgit v1.2.3 From d6f86cccf5fa5b4eb30141da390cf2439746c5da Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Mon, 14 Aug 2023 23:41:15 +0800 Subject: ci: use gci linter (#5708) * use gofmput to format code * use gci to format imports * reconfigure gci * linter autofixes * rearrange imports a little * export GOOS=windows golangci-lint run ./... --fix --- cmd/commandfuncs.go | 3 ++- cmd/commands.go | 3 ++- cmd/main.go | 5 +++-- cmd/packagesfuncs.go | 3 ++- cmd/storagefuncs.go | 3 ++- 5 files changed, 11 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index f997f06..f284b89 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -33,11 +33,12 @@ import ( "strings" "github.com/aryann/difflib" + "go.uber.org/zap" + "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/internal" - "go.uber.org/zap" ) func cmdStart(fl Flags) (int, error) { diff --git a/cmd/commands.go b/cmd/commands.go index 3bf47df..0885577 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -21,9 +21,10 @@ import ( "regexp" "strings" - "github.com/caddyserver/caddy/v2" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" + + "github.com/caddyserver/caddy/v2" ) // Command represents a subcommand. Name, Func, diff --git a/cmd/main.go b/cmd/main.go index 53b8d67..1d6478a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -30,11 +30,12 @@ import ( "strings" "time" - "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/certmagic" "github.com/spf13/pflag" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" ) func init() { diff --git a/cmd/packagesfuncs.go b/cmd/packagesfuncs.go index 3aed0e8..5d77e4d 100644 --- a/cmd/packagesfuncs.go +++ b/cmd/packagesfuncs.go @@ -27,8 +27,9 @@ import ( "runtime/debug" "strings" - "github.com/caddyserver/caddy/v2" "go.uber.org/zap" + + "github.com/caddyserver/caddy/v2" ) func cmdUpgrade(fl Flags) (int, error) { diff --git a/cmd/storagefuncs.go b/cmd/storagefuncs.go index a9f1bca..6a6daec 100644 --- a/cmd/storagefuncs.go +++ b/cmd/storagefuncs.go @@ -23,8 +23,9 @@ import ( "io" "os" - "github.com/caddyserver/caddy/v2" "github.com/caddyserver/certmagic" + + "github.com/caddyserver/caddy/v2" ) type storVal struct { -- cgit v1.2.3 From f2ab7099db6d8386299796e6eef8e30f65b21bcc Mon Sep 17 00:00:00 2001 From: Evan Van Dam Date: Wed, 6 Sep 2023 19:19:24 -0700 Subject: cmd: Prevent overwriting existing env vars with `--envfile` (#5803) Co-authored-by: Francis Lavoie --- cmd/commandfuncs.go | 42 +++++++++++++++++++++++++++++++----------- cmd/commands.go | 40 ++++++++++++++++++++-------------------- cmd/main.go | 8 ++++++-- 3 files changed, 57 insertions(+), 33 deletions(-) (limited to 'cmd') diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index f284b89..8963988 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -46,7 +46,14 @@ func cmdStart(fl Flags) (int, error) { startCmdConfigAdapterFlag := fl.String("adapter") startCmdPidfileFlag := fl.String("pidfile") startCmdWatchFlag := fl.Bool("watch") - startCmdEnvfileFlag := fl.String("envfile") + + var err error + var startCmdEnvfileFlag []string + startCmdEnvfileFlag, err = fl.GetStringSlice("envfile") + if err != nil { + return caddy.ExitCodeFailedStartup, + fmt.Errorf("reading envfile flag: %v", err) + } // open a listener to which the child process will connect when // it is ready to confirm that it has successfully started @@ -70,8 +77,9 @@ func cmdStart(fl Flags) (int, error) { if startCmdConfigFlag != "" { cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag) } - if startCmdEnvfileFlag != "" { - cmd.Args = append(cmd.Args, "--envfile", startCmdEnvfileFlag) + + for _, envFile := range startCmdEnvfileFlag { + cmd.Args = append(cmd.Args, "--envfile", envFile) } if startCmdConfigAdapterFlag != "" { cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag) @@ -160,15 +168,22 @@ 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") + 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 - if runCmdLoadEnvfileFlag != "" { - if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil { + for _, envFile := range runCmdLoadEnvfileFlag { + if err := loadEnvFromFile(envFile); err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("loading additional environment variables: %v", err) } @@ -181,7 +196,6 @@ func cmdRun(fl Flags) (int, error) { // load the config, depending on flags var config []byte - var err error if runCmdResumeFlag { config, err = os.ReadFile(caddy.ConfigAutosavePath) if os.IsNotExist(err) { @@ -497,18 +511,24 @@ func cmdAdaptConfig(fl Flags) (int, error) { func cmdValidateConfig(fl Flags) (int, error) { validateCmdConfigFlag := fl.String("config") validateCmdAdapterFlag := fl.String("adapter") - runCmdLoadEnvfileFlag := fl.String("envfile") + + 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 - if runCmdLoadEnvfileFlag != "" { - if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil { + for _, envFile := range runCmdLoadEnvfileFlag { + 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 - var err error validateCmdConfigFlag, err = configFileWithRespectToDefault(caddy.Log(), validateCmdConfigFlag) if err != nil { return caddy.ExitCodeFailedStartup, err diff --git a/cmd/commands.go b/cmd/commands.go index 0885577..c64ab71 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -104,7 +104,7 @@ using 'caddy run' instead to keep it in the foreground. CobraFunc: func(cmd *cobra.Command) { cmd.Flags().StringP("config", "c", "", "Configuration file") cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") - cmd.Flags().StringP("envfile", "", "", "Environment file to load") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") cmd.Flags().BoolP("watch", "w", false, "Reload changed config file automatically") cmd.Flags().StringP("pidfile", "", "", "Path of file to which to write process ID") cmd.RunE = WrapCommandFuncForCobra(cmdStart) @@ -150,7 +150,7 @@ option in a local development environment. CobraFunc: func(cmd *cobra.Command) { cmd.Flags().StringP("config", "c", "", "Configuration file") cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply") - cmd.Flags().StringP("envfile", "", "", "Environment file to load") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") cmd.Flags().BoolP("environ", "e", false, "Print environment") cmd.Flags().BoolP("resume", "r", false, "Use saved config, if any (and prefer over --config file)") cmd.Flags().BoolP("watch", "w", false, "Watch config file for changes and reload it automatically") @@ -301,7 +301,7 @@ the KEY=VALUE format will be loaded into the Caddy process. CobraFunc: func(cmd *cobra.Command) { cmd.Flags().StringP("config", "c", "", "Input configuration file") cmd.Flags().StringP("adapter", "a", "", "Name of config adapter") - cmd.Flags().StringP("envfile", "", "", "Environment file to load") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") cmd.RunE = WrapCommandFuncForCobra(cmdValidateConfig) }, }) @@ -402,7 +402,7 @@ latest versions. EXPERIMENTAL: May be changed or removed. Short: "Adds Caddy packages (EXPERIMENTAL)", Long: ` Downloads an updated Caddy binary with the specified packages (module/plugin) -added. Retains existing packages. Returns an error if the any of packages are +added. Retains existing packages. Returns an error if the any of packages are already included. EXPERIMENTAL: May be changed or removed. `, CobraFunc: func(cmd *cobra.Command) { @@ -417,8 +417,8 @@ already included. EXPERIMENTAL: May be changed or removed. Usage: "", Short: "Removes Caddy packages (EXPERIMENTAL)", Long: ` -Downloads an updated Caddy binaries without the specified packages (module/plugin). -Returns an error if any of the packages are not included. +Downloads an updated Caddy binaries without the specified packages (module/plugin). +Returns an error if any of the packages are not included. EXPERIMENTAL: May be changed or removed. `, CobraFunc: func(cmd *cobra.Command) { @@ -464,40 +464,40 @@ argument of --directory. If the directory does not exist, it will be created. Use: "completion [bash|zsh|fish|powershell]", Short: "Generate completion script", Long: fmt.Sprintf(`To load completions: - + Bash: - + $ source <(%[1]s completion bash) - + # To load completions for each session, execute once: # Linux: $ %[1]s completion bash > /etc/bash_completion.d/%[1]s # macOS: $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s - + Zsh: - + # If shell completion is not already enabled in your environment, # you will need to enable it. You can execute the following once: - + $ echo "autoload -U compinit; compinit" >> ~/.zshrc - + # To load completions for each session, execute once: $ %[1]s completion zsh > "${fpath[1]}/_%[1]s" - + # You will need to start a new shell for this setup to take effect. - + fish: - + $ %[1]s completion fish | source - + # To load completions for each session, execute once: $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish - + PowerShell: - + PS> %[1]s completion powershell | Out-String | Invoke-Expression - + # To load completions for every new session, run: PS> %[1]s completion powershell > %[1]s.ps1 # and source this file from your PowerShell profile. diff --git a/cmd/main.go b/cmd/main.go index 1d6478a..b4e3fdc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -300,8 +300,12 @@ func loadEnvFromFile(envFile string) error { } for k, v := range envMap { - if err := os.Setenv(k, v); err != nil { - return fmt.Errorf("setting environment variables: %v", err) + // do not overwrite existing environment variables + _, exists := os.LookupEnv(k) + if !exists { + if err := os.Setenv(k, v); err != nil { + return fmt.Errorf("setting environment variables: %v", err) + } } } -- cgit v1.2.3 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/cobra.go | 30 +++++++- cmd/commandfuncs.go | 207 ++++++++++++++++++++++++++++------------------------ cmd/commands.go | 27 +++++-- cmd/main.go | 5 ++ 4 files changed, 163 insertions(+), 106 deletions(-) (limited to 'cmd') diff --git a/cmd/cobra.go b/cmd/cobra.go index 4339cdb..c071f4a 100644 --- a/cmd/cobra.go +++ b/cmd/cobra.go @@ -1,7 +1,11 @@ package caddycmd import ( + "fmt" + "github.com/spf13/cobra" + + "github.com/caddyserver/caddy/v2" ) 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 // caddy has an error provisioning its modules, for instance... SilenceUsage: true, + Version: onlyVersionText(), } const fullDocsFooter = `Full documentation is available at: https://caddyserver.com/docs/command-line` func init() { + rootCmd.SetVersionTemplate("{{.Version}}") rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") } +func onlyVersionText() string { + _, f := caddy.Version() + return f +} + func caddyCmdToCobra(caddyCmd Command) *cobra.Command { cmd := &cobra.Command{ Use: caddyCmd.Name, @@ -123,7 +134,24 @@ func caddyCmdToCobra(caddyCmd Command) *cobra.Command { // in a cobra command's RunE field. func WrapCommandFuncForCobra(f CommandFunc) 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 } } + +// 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() +} 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 diff --git a/cmd/commands.go b/cmd/commands.go index c64ab71..e5e1265 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -94,8 +94,8 @@ func init() { Starts the Caddy process, optionally bootstrapped with an initial config file. This command unblocks after the server starts running or fails to run. -If --envfile is specified, an environment file with environment variables in -the KEY=VALUE format will be loaded into the Caddy process. +If --envfile is specified, an environment file with environment variables +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 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 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 --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 @@ -240,6 +240,7 @@ documentation: https://go.dev/doc/modules/version-numbers RegisterCommand(Command{ Name: "environ", + Usage: "[--envfile ]", Short: "Prints the environment", Long: ` 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 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. Environments for Caddy instances started by service managers such as systemd are often different than the environment inherited from your @@ -259,12 +263,15 @@ by adding the "--environ" flag. 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{ Name: "adapt", - Usage: "--config [--adapter ] [--pretty] [--validate]", + Usage: "--config [--adapter ] [--pretty] [--validate] [--envfile ]", Short: "Adapts a configuration to Caddy's native JSON", Long: ` 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 the config is invalid, an error will be printed to stderr and a non- 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) { cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)") 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("validate", "", false, "Validate the output") + cmd.Flags().StringSliceP("envfile", "", []string{}, "Environment file(s) to load") 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 provisioning stages. -If --envfile is specified, an environment file with environment variables in -the KEY=VALUE format will be loaded into the Caddy process. +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) { cmd.Flags().StringP("config", "c", "", "Input configuration file") diff --git a/cmd/main.go b/cmd/main.go index b4e3fdc..fa15c08 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,6 +17,7 @@ package caddycmd import ( "bufio" "bytes" + "errors" "flag" "fmt" "io" @@ -63,6 +64,10 @@ func Main() { } if err := rootCmd.Execute(); err != nil { + var exitError *exitError + if errors.As(err, &exitError) { + os.Exit(exitError.ExitCode) + } os.Exit(1) } } -- cgit v1.2.3