cmd: CLI improvements; add --validate to adapt command

This commit is contained in:
Matthew Holt 2019-10-01 11:02:13 -06:00
parent 5b36424cf0
commit 2c3657bb8a
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
2 changed files with 107 additions and 61 deletions

View file

@ -351,10 +351,11 @@ func cmdAdaptConfig(fl Flags) (int, error) {
adaptCmdInputFlag := fl.String("config")
adaptCmdAdapterFlag := fl.String("adapter")
adaptCmdPrettyFlag := fl.Bool("pretty")
adaptCmdValidateFlag := fl.Bool("validate")
if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("usage: caddy adapt --adapter <name> --input <file>")
fmt.Errorf("--adapter and --config flags are required")
}
cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag)
@ -391,6 +392,19 @@ func cmdAdaptConfig(fl Flags) (int, error) {
// print result to stdout
fmt.Println(string(adaptedConfig))
// validate output if requested
if adaptCmdValidateFlag {
var cfg *caddy.Config
err = json.Unmarshal(adaptedConfig, &cfg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
}
err = caddy.Validate(cfg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("validation: %v", err)
}
}
return caddy.ExitCodeSuccess, nil
}
@ -457,7 +471,8 @@ usage:
commands:
`
for _, cmd := range commands {
s += fmt.Sprintf(" %-15s %s\n", cmd.Name, cmd.Short)
short := strings.TrimSuffix(cmd.Short, ".")
s += fmt.Sprintf(" %-15s %s\n", cmd.Name, short)
}
s += "\nUse 'caddy help <command>' for more information about a command.\n"
@ -475,8 +490,16 @@ commands:
return caddy.ExitCodeFailedStartup, fmt.Errorf("unknown command: %s", args[0])
}
helpText := strings.TrimSpace(subcommand.Long)
if helpText == "" {
helpText = subcommand.Short
if !strings.HasSuffix(helpText, ".") {
helpText += "."
}
}
result := fmt.Sprintf("%s\n\nusage:\n caddy %s %s\n",
strings.TrimSpace(subcommand.Long),
helpText,
subcommand.Name,
strings.TrimSpace(subcommand.Usage),
)

View file

@ -19,29 +19,40 @@ import (
"regexp"
)
// Command represents a subcommand. All fields
// are required to be set except for Flags if
// there are no flags and Usage if there are
// no flags or arguments.
// Command represents a subcommand. Name, Func,
// and Short are required.
type Command struct {
// The name of the subcommand. Must conform to the
// format described by the RegisterCommand() godoc.
// Required.
Name string
// Run is a function that executes a subcommand.
// It returns an exit code and any associated error.
// Takes non-flag commandline arguments as args.
// Flag must be parsed before Run is executed.
// Run 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 the one-line message explaining args, flags.
// 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
// intended to be replaced by the user. Do not prefix
// the string with "caddy" or the name of the command
// since these will be prepended for you; only include
// the actual parameters for this command.
Usage string
// Short is the short description for command.
// Short is a one-line message explaining what the
// command does. Should not end with punctuation.
// Required.
Short string
// Long is the message for 'caddy help <command>'
// Long is the full help text shown to the user.
// Will be trimmed of whitespace on both ends before
// being printed.
Long string
// Flags is flagset for command.
// Flags is the flagset for command.
Flags *flag.FlagSet
}
@ -54,14 +65,15 @@ var commands = map[string]Command{
"start": {
Name: "start",
Func: cmdStart,
Usage: "[--config <path>] [--adapter <name>]",
Short: "Starts the Caddy process and returns after server has started.",
Usage: "[--config <path> [[--adapter <name>]]",
Short: "Starts the Caddy process in the background and then returns",
Long: `
Starts the Caddy process, optionally bootstrapped with an initial
config file. Blocks until server is successfully running (or fails to run),
then returns. On Windows, the child process will remain attached to the
terminal, so closing the window will forcefully stop Caddy. See run for more
details.`,
Starts the Caddy process, optionally bootstrapped with an initial config file.
This command unblocks after the server starts running or fails to run.
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")
@ -73,13 +85,12 @@ details.`,
"run": {
Name: "run",
Func: cmdRun,
Usage: "[--config <path>] [--adapter <name>] [--print-env]",
Short: `Starts the Caddy process and blocks indefinitely.`,
Usage: "[--config <path> [--adapter <name>]] [--environ]",
Short: `Starts the Caddy process and blocks indefinitely`,
Long: `
Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On
Windows, this is recommended over caddy start when running Caddy manually since
it will be more obvious that Caddy is still running and bound to the terminal
window.
Starts the Caddy process, optionally bootstrapped with an initial config file,
and blocks indefinitely until the server is stopped; i.e. runs Caddy in
"daemon" mode (foreground).
If a config file is specified, it will be applied immediately after the process
is running. If the config file is not in Caddy's native JSON format, you can
@ -90,13 +101,13 @@ errors will immediately be used. If you want to review the results of the
adaptation first, use the 'adapt' subcommand.
As a special case, if the current working directory has a file called
"Caddyfile" and the caddyfile config adapter is plugged in (default), then that
file will be loaded and used to configure Caddy, even without any command line
flags.
"Caddyfile" and the caddyfile config adapter is plugged in (default), then
that file will be loaded and used to configure Caddy, even without any command
line flags.
If --environ is specified, the environment as seen by the Caddy process will
be printed before starting. This is the same as the environ command but does
not quit after printing.`,
not quit after printing, and can be useful for troubleshooting.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("run", flag.ExitOnError)
fs.String("config", "", "Configuration file")
@ -111,26 +122,32 @@ not quit after printing.`,
Name: "stop",
Func: cmdStop,
Short: "Gracefully stops the running Caddy process",
Long: `Gracefully stops the running Caddy process. (Note: this will stop any process
named the same as the executable.) On Windows, this stop is forceful and Caddy
will not have an opportunity to clean up any active locks; for a graceful
shutdown on Windows, use Ctrl+C or the /stop endpoint.`,
Long: `
Stops the running Caddy process as gracefully as possible.
On Windows, this stop is forceful and Caddy will not have an opportunity to
clean up any active locks; for a graceful shutdown on Windows, use Ctrl+C
or the /stop API endpoint.
Note: this will stop any process named the same as the executable (os.Args[0]).`,
},
"reload": {
Name: "reload",
Func: cmdReload,
Usage: "--config <path> [--adapter <name>] [--address <interface>]",
Short: "Gives the running Caddy instance a new configuration",
Long: `Gives the running Caddy instance a new configuration. This has the same effect
as POSTing a document to the /load endpoint, but is convenient for simple
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.`,
Short: "Changes the config of the running Caddy instance",
Long: `
Gives the running Caddy instance a new configuration. This has the same effect
as POSTing a document to the /load API endpoint, but is convenient for simple
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.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("reload", flag.ExitOnError)
fs.String("config", "", "Configuration file")
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")
return fs
@ -140,15 +157,14 @@ default is assumed.`,
"version": {
Name: "version",
Func: cmdVersion,
Short: "Prints the version.",
Long: `Prints the version.`,
Short: "Prints the version",
},
"list-modules": {
Name: "list-modules",
Func: cmdListModules,
Short: "List installed Caddy modules.",
Long: `List installed Caddy modules.`,
Usage: "[--versions]",
Short: "Lists the installed Caddy modules",
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
fs.Bool("versions", false, "Print version information")
@ -159,24 +175,30 @@ default is assumed.`,
"environ": {
Name: "environ",
Func: cmdEnviron,
Short: "Prints the environment as seen by Caddy.",
Long: `Prints the environment as seen by Caddy.`,
Short: "Prints the environment",
},
"adapt": {
Name: "adapt",
Func: cmdAdaptConfig,
Usage: "--config <path> --adapter <name> [--pretty]",
Short: "Adapts a configuration to Caddy's native JSON config structure",
Usage: "--config <path> --adapter <name> [--pretty] [--validate]",
Short: "Adapts a configuration to Caddy's native JSON",
Long: `
Adapts a configuration to Caddy's native JSON config structure and writes the
output to stdout, along with any warnings to stderr. If --pretty is specified,
the output will be formatted with indentation for human readability.`,
Adapts a configuration to Caddy's native JSON format and writes the
output to stdout, along with any warnings to stderr.
If --pretty is specified, the output will be formatted with indentation
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.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("adapt", flag.ExitOnError)
fs.String("config", "", "Configuration file to adapt")
fs.String("adapter", "", "Name of config adapter")
fs.String("config", "", "Configuration file to adapt (required)")
fs.String("adapter", "", "Name of config adapter (required)")
fs.Bool("pretty", false, "Format the output for human readability")
fs.Bool("validate", false, "Validate the output")
return fs
}(),
},
@ -185,10 +207,11 @@ the output will be formatted with indentation for human readability.`,
Name: "validate",
Func: cmdValidateConfig,
Usage: "--config <path> [--adapter <name>]",
Short: "Tests whether a configuration file is valid.",
Short: "Tests whether a configuration file is valid",
Long: `
Loads and provisions the provided config, but does not start
running it.`,
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.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("load", flag.ExitOnError)
fs.String("config", "", "Input configuration file")
@ -208,7 +231,7 @@ func init() {
Name: "help",
Func: cmdHelp,
Usage: "<command>",
Short: "Shows help for a Caddy subcommand.",
Short: "Shows help for a Caddy subcommand",
}
}