Skip to content

Commit 2c3657b

Browse files
committed
cmd: CLI improvements; add --validate to adapt command
1 parent 5b36424 commit 2c3657b

File tree

2 files changed

+107
-61
lines changed

2 files changed

+107
-61
lines changed

cmd/commandfuncs.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,11 @@ func cmdAdaptConfig(fl Flags) (int, error) {
351351
adaptCmdInputFlag := fl.String("config")
352352
adaptCmdAdapterFlag := fl.String("adapter")
353353
adaptCmdPrettyFlag := fl.Bool("pretty")
354+
adaptCmdValidateFlag := fl.Bool("validate")
354355

355356
if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" {
356357
return caddy.ExitCodeFailedStartup,
357-
fmt.Errorf("usage: caddy adapt --adapter <name> --input <file>")
358+
fmt.Errorf("--adapter and --config flags are required")
358359
}
359360

360361
cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag)
@@ -391,6 +392,19 @@ func cmdAdaptConfig(fl Flags) (int, error) {
391392
// print result to stdout
392393
fmt.Println(string(adaptedConfig))
393394

395+
// validate output if requested
396+
if adaptCmdValidateFlag {
397+
var cfg *caddy.Config
398+
err = json.Unmarshal(adaptedConfig, &cfg)
399+
if err != nil {
400+
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
401+
}
402+
err = caddy.Validate(cfg)
403+
if err != nil {
404+
return caddy.ExitCodeFailedStartup, fmt.Errorf("validation: %v", err)
405+
}
406+
}
407+
394408
return caddy.ExitCodeSuccess, nil
395409
}
396410

@@ -457,7 +471,8 @@ usage:
457471
commands:
458472
`
459473
for _, cmd := range commands {
460-
s += fmt.Sprintf(" %-15s %s\n", cmd.Name, cmd.Short)
474+
short := strings.TrimSuffix(cmd.Short, ".")
475+
s += fmt.Sprintf(" %-15s %s\n", cmd.Name, short)
461476
}
462477

463478
s += "\nUse 'caddy help <command>' for more information about a command.\n"
@@ -475,8 +490,16 @@ commands:
475490
return caddy.ExitCodeFailedStartup, fmt.Errorf("unknown command: %s", args[0])
476491
}
477492

493+
helpText := strings.TrimSpace(subcommand.Long)
494+
if helpText == "" {
495+
helpText = subcommand.Short
496+
if !strings.HasSuffix(helpText, ".") {
497+
helpText += "."
498+
}
499+
}
500+
478501
result := fmt.Sprintf("%s\n\nusage:\n caddy %s %s\n",
479-
strings.TrimSpace(subcommand.Long),
502+
helpText,
480503
subcommand.Name,
481504
strings.TrimSpace(subcommand.Usage),
482505
)

cmd/commands.go

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,40 @@ import (
1919
"regexp"
2020
)
2121

22-
// Command represents a subcommand. All fields
23-
// are required to be set except for Flags if
24-
// there are no flags and Usage if there are
25-
// no flags or arguments.
22+
// Command represents a subcommand. Name, Func,
23+
// and Short are required.
2624
type Command struct {
25+
// The name of the subcommand. Must conform to the
26+
// format described by the RegisterCommand() godoc.
27+
// Required.
2728
Name string
2829

29-
// Run is a function that executes a subcommand.
30-
// It returns an exit code and any associated error.
31-
// Takes non-flag commandline arguments as args.
32-
// Flag must be parsed before Run is executed.
30+
// Run is a function that executes a subcommand using
31+
// the parsed flags. It returns an exit code and any
32+
// associated error.
33+
// Required.
3334
Func CommandFunc
3435

35-
// Usage is the one-line message explaining args, flags.
36+
// Usage is a brief message describing the syntax of
37+
// the subcommand's flags and args. Use [] to indicate
38+
// optional parameters and <> to enclose literal values
39+
// intended to be replaced by the user. Do not prefix
40+
// the string with "caddy" or the name of the command
41+
// since these will be prepended for you; only include
42+
// the actual parameters for this command.
3643
Usage string
3744

38-
// Short is the short description for command.
45+
// Short is a one-line message explaining what the
46+
// command does. Should not end with punctuation.
47+
// Required.
3948
Short string
4049

41-
// Long is the message for 'caddy help <command>'
50+
// Long is the full help text shown to the user.
51+
// Will be trimmed of whitespace on both ends before
52+
// being printed.
4253
Long string
4354

44-
// Flags is flagset for command.
55+
// Flags is the flagset for command.
4556
Flags *flag.FlagSet
4657
}
4758

@@ -54,14 +65,15 @@ var commands = map[string]Command{
5465
"start": {
5566
Name: "start",
5667
Func: cmdStart,
57-
Usage: "[--config <path>] [--adapter <name>]",
58-
Short: "Starts the Caddy process and returns after server has started.",
68+
Usage: "[--config <path> [[--adapter <name>]]",
69+
Short: "Starts the Caddy process in the background and then returns",
5970
Long: `
60-
Starts the Caddy process, optionally bootstrapped with an initial
61-
config file. Blocks until server is successfully running (or fails to run),
62-
then returns. On Windows, the child process will remain attached to the
63-
terminal, so closing the window will forcefully stop Caddy. See run for more
64-
details.`,
71+
Starts the Caddy process, optionally bootstrapped with an initial config file.
72+
This command unblocks after the server starts running or fails to run.
73+
74+
On Windows, the spawned child process will remain attached to the terminal, so
75+
closing the window will forcefully stop Caddy; to avoid forgetting this, try
76+
using 'caddy run' instead to keep it in the foreground.`,
6577
Flags: func() *flag.FlagSet {
6678
fs := flag.NewFlagSet("start", flag.ExitOnError)
6779
fs.String("config", "", "Configuration file")
@@ -73,13 +85,12 @@ details.`,
7385
"run": {
7486
Name: "run",
7587
Func: cmdRun,
76-
Usage: "[--config <path>] [--adapter <name>] [--print-env]",
77-
Short: `Starts the Caddy process and blocks indefinitely.`,
88+
Usage: "[--config <path> [--adapter <name>]] [--environ]",
89+
Short: `Starts the Caddy process and blocks indefinitely`,
7890
Long: `
79-
Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On
80-
Windows, this is recommended over caddy start when running Caddy manually since
81-
it will be more obvious that Caddy is still running and bound to the terminal
82-
window.
91+
Starts the Caddy process, optionally bootstrapped with an initial config file,
92+
and blocks indefinitely until the server is stopped; i.e. runs Caddy in
93+
"daemon" mode (foreground).
8394
8495
If a config file is specified, it will be applied immediately after the process
8596
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
90101
adaptation first, use the 'adapt' subcommand.
91102
92103
As a special case, if the current working directory has a file called
93-
"Caddyfile" and the caddyfile config adapter is plugged in (default), then that
94-
file will be loaded and used to configure Caddy, even without any command line
95-
flags.
104+
"Caddyfile" and the caddyfile config adapter is plugged in (default), then
105+
that file will be loaded and used to configure Caddy, even without any command
106+
line flags.
96107
97108
If --environ is specified, the environment as seen by the Caddy process will
98109
be printed before starting. This is the same as the environ command but does
99-
not quit after printing.`,
110+
not quit after printing, and can be useful for troubleshooting.`,
100111
Flags: func() *flag.FlagSet {
101112
fs := flag.NewFlagSet("run", flag.ExitOnError)
102113
fs.String("config", "", "Configuration file")
@@ -111,26 +122,32 @@ not quit after printing.`,
111122
Name: "stop",
112123
Func: cmdStop,
113124
Short: "Gracefully stops the running Caddy process",
114-
Long: `Gracefully stops the running Caddy process. (Note: this will stop any process
115-
named the same as the executable.) On Windows, this stop is forceful and Caddy
116-
will not have an opportunity to clean up any active locks; for a graceful
117-
shutdown on Windows, use Ctrl+C or the /stop endpoint.`,
125+
Long: `
126+
Stops the running Caddy process as gracefully as possible.
127+
128+
On Windows, this stop is forceful and Caddy will not have an opportunity to
129+
clean up any active locks; for a graceful shutdown on Windows, use Ctrl+C
130+
or the /stop API endpoint.
131+
132+
Note: this will stop any process named the same as the executable (os.Args[0]).`,
118133
},
119134

120135
"reload": {
121136
Name: "reload",
122137
Func: cmdReload,
123138
Usage: "--config <path> [--adapter <name>] [--address <interface>]",
124-
Short: "Gives the running Caddy instance a new configuration",
125-
Long: `Gives the running Caddy instance a new configuration. This has the same effect
126-
as POSTing a document to the /load endpoint, but is convenient for simple
127-
workflows revolving around config files. Since the admin endpoint is
128-
configurable, the endpoint configuration is loaded from the --address flag if
129-
specified; otherwise it is loaded from the given config file; otherwise the
130-
default is assumed.`,
139+
Short: "Changes the config of the running Caddy instance",
140+
Long: `
141+
Gives the running Caddy instance a new configuration. This has the same effect
142+
as POSTing a document to the /load API endpoint, but is convenient for simple
143+
workflows revolving around config files.
144+
145+
Since the admin endpoint is configurable, the endpoint configuration is loaded
146+
from the --address flag if specified; otherwise it is loaded from the given
147+
config file; otherwise the default is assumed.`,
131148
Flags: func() *flag.FlagSet {
132149
fs := flag.NewFlagSet("reload", flag.ExitOnError)
133-
fs.String("config", "", "Configuration file")
150+
fs.String("config", "", "Configuration file (required)")
134151
fs.String("adapter", "", "Name of config adapter to apply")
135152
fs.String("address", "", "Address of the administration listener, if different from config")
136153
return fs
@@ -140,15 +157,14 @@ default is assumed.`,
140157
"version": {
141158
Name: "version",
142159
Func: cmdVersion,
143-
Short: "Prints the version.",
144-
Long: `Prints the version.`,
160+
Short: "Prints the version",
145161
},
146162

147163
"list-modules": {
148164
Name: "list-modules",
149165
Func: cmdListModules,
150-
Short: "List installed Caddy modules.",
151-
Long: `List installed Caddy modules.`,
166+
Usage: "[--versions]",
167+
Short: "Lists the installed Caddy modules",
152168
Flags: func() *flag.FlagSet {
153169
fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
154170
fs.Bool("versions", false, "Print version information")
@@ -159,24 +175,30 @@ default is assumed.`,
159175
"environ": {
160176
Name: "environ",
161177
Func: cmdEnviron,
162-
Short: "Prints the environment as seen by Caddy.",
163-
Long: `Prints the environment as seen by Caddy.`,
178+
Short: "Prints the environment",
164179
},
165180

166181
"adapt": {
167182
Name: "adapt",
168183
Func: cmdAdaptConfig,
169-
Usage: "--config <path> --adapter <name> [--pretty]",
170-
Short: "Adapts a configuration to Caddy's native JSON config structure",
184+
Usage: "--config <path> --adapter <name> [--pretty] [--validate]",
185+
Short: "Adapts a configuration to Caddy's native JSON",
171186
Long: `
172-
Adapts a configuration to Caddy's native JSON config structure and writes the
173-
output to stdout, along with any warnings to stderr. If --pretty is specified,
174-
the output will be formatted with indentation for human readability.`,
187+
Adapts a configuration to Caddy's native JSON format and writes the
188+
output to stdout, along with any warnings to stderr.
189+
190+
If --pretty is specified, the output will be formatted with indentation
191+
for human readability.
192+
193+
If --validate is used, the adapted config will be checked for validity.
194+
If the config is invalid, an error will be printed to stderr and a non-
195+
zero exit status will be returned.`,
175196
Flags: func() *flag.FlagSet {
176197
fs := flag.NewFlagSet("adapt", flag.ExitOnError)
177-
fs.String("config", "", "Configuration file to adapt")
178-
fs.String("adapter", "", "Name of config adapter")
198+
fs.String("config", "", "Configuration file to adapt (required)")
199+
fs.String("adapter", "", "Name of config adapter (required)")
179200
fs.Bool("pretty", false, "Format the output for human readability")
201+
fs.Bool("validate", false, "Validate the output")
180202
return fs
181203
}(),
182204
},
@@ -185,10 +207,11 @@ the output will be formatted with indentation for human readability.`,
185207
Name: "validate",
186208
Func: cmdValidateConfig,
187209
Usage: "--config <path> [--adapter <name>]",
188-
Short: "Tests whether a configuration file is valid.",
210+
Short: "Tests whether a configuration file is valid",
189211
Long: `
190-
Loads and provisions the provided config, but does not start
191-
running it.`,
212+
Loads and provisions the provided config, but does not start running it.
213+
This reveals any errors with the configuration through the loading and
214+
provisioning stages.`,
192215
Flags: func() *flag.FlagSet {
193216
fs := flag.NewFlagSet("load", flag.ExitOnError)
194217
fs.String("config", "", "Input configuration file")
@@ -208,7 +231,7 @@ func init() {
208231
Name: "help",
209232
Func: cmdHelp,
210233
Usage: "<command>",
211-
Short: "Shows help for a Caddy subcommand.",
234+
Short: "Shows help for a Caddy subcommand",
212235
}
213236
}
214237

0 commit comments

Comments
 (0)