cmd: Add validate subcommand; list-modules --versions; some renaming

Renames --config-adapter flag to --adapter, adapt-config command to
adapt, --print-env flag to --environ, and --input flag to --config.
This commit is contained in:
Matthew Holt 2019-09-30 23:43:39 -06:00
parent 0006df6026
commit 5b36424cf0
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
2 changed files with 154 additions and 24 deletions

View file

@ -26,6 +26,8 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"reflect"
"runtime/debug"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -36,7 +38,7 @@ import (
func cmdStart(fl Flags) (int, error) { func cmdStart(fl Flags) (int, error) {
startCmdConfigFlag := fl.String("config") startCmdConfigFlag := fl.String("config")
startCmdConfigAdapterFlag := fl.String("config-adapter") startCmdConfigAdapterFlag := fl.String("adapter")
// open a listener to which the child process will connect when // open a listener to which the child process will connect when
// it is ready to confirm that it has successfully started // it is ready to confirm that it has successfully started
@ -61,7 +63,7 @@ func cmdStart(fl Flags) (int, error) {
cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag) cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag)
} }
if startCmdConfigAdapterFlag != "" { if startCmdConfigAdapterFlag != "" {
cmd.Args = append(cmd.Args, "--config-adapter", startCmdConfigAdapterFlag) cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag)
} }
stdinpipe, err := cmd.StdinPipe() stdinpipe, err := cmd.StdinPipe()
if err != nil { if err != nil {
@ -137,8 +139,8 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) { func cmdRun(fl Flags) (int, error) {
runCmdConfigFlag := fl.String("config") runCmdConfigFlag := fl.String("config")
runCmdConfigAdapterFlag := fl.String("config-adapter") runCmdConfigAdapterFlag := fl.String("adapter")
runCmdPrintEnvFlag := fl.Bool("print-env") runCmdPrintEnvFlag := fl.Bool("environ")
runCmdPingbackFlag := fl.String("pingback") runCmdPingbackFlag := fl.String("pingback")
// if we are supposed to print the environment, do that first // if we are supposed to print the environment, do that first
@ -216,7 +218,7 @@ func cmdStop(_ Flags) (int, error) {
func cmdReload(fl Flags) (int, error) { func cmdReload(fl Flags) (int, error) {
reloadCmdConfigFlag := fl.String("config") reloadCmdConfigFlag := fl.String("config")
reloadCmdConfigAdapterFlag := fl.String("config-adapter") reloadCmdConfigAdapterFlag := fl.String("adapter")
reloadCmdAddrFlag := fl.String("address") reloadCmdAddrFlag := fl.String("address")
// a configuration is required // a configuration is required
@ -282,10 +284,61 @@ func cmdVersion(_ Flags) (int, error) {
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
} }
func cmdListModules(_ Flags) (int, error) { func cmdListModules(fl Flags) (int, error) {
for _, m := range caddy.Modules() { versions := fl.Bool("versions")
fmt.Println(m)
bi, ok := debug.ReadBuildInfo()
if !ok || !versions {
// if there's no build information,
// just print out the modules
for _, m := range caddy.Modules() {
fmt.Println(m)
}
return caddy.ExitCodeSuccess, nil
} }
for _, modName := range caddy.Modules() {
modInfo, err := caddy.GetModule(modName)
if err != nil {
// that's weird
fmt.Println(modName)
continue
}
// to get the Caddy plugin's version info, we need to know
// the package that the Caddy module's value comes from; we
// can use reflection but we need a non-pointer value (I'm
// not sure why), and since New() should return a pointer
// value, we need to dereference it first
iface := interface{}(modInfo.New())
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
}
modPkgPath := reflect.TypeOf(iface).PkgPath()
// now we find the Go module that the Caddy module's package
// belongs to; we assume the Caddy module package path will
// be prefixed by its Go module path, and we will choose the
// longest matching prefix in case there are nested modules
var matched *debug.Module
for _, dep := range bi.Deps {
if strings.HasPrefix(modPkgPath, dep.Path) {
if matched == nil || len(dep.Path) > len(matched.Path) {
matched = dep
}
}
}
// if we could find no matching module, just print out
// the module name instead
if matched == nil {
fmt.Println(modName)
continue
}
fmt.Printf("%s %s\n", modName, matched.Version)
}
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
} }
@ -295,13 +348,13 @@ func cmdEnviron(_ Flags) (int, error) {
} }
func cmdAdaptConfig(fl Flags) (int, error) { func cmdAdaptConfig(fl Flags) (int, error) {
adaptCmdInputFlag := fl.String("config")
adaptCmdAdapterFlag := fl.String("adapter") adaptCmdAdapterFlag := fl.String("adapter")
adaptCmdInputFlag := fl.String("input")
adaptCmdPrettyFlag := fl.Bool("pretty") adaptCmdPrettyFlag := fl.Bool("pretty")
if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" { if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("usage: caddy adapt-config --adapter <name> --input <file>") fmt.Errorf("usage: caddy adapt --adapter <name> --input <file>")
} }
cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag) cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag)
@ -341,6 +394,55 @@ func cmdAdaptConfig(fl Flags) (int, error) {
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
} }
func cmdValidateConfig(fl Flags) (int, error) {
validateCmdConfigFlag := fl.String("config")
validateCmdAdapterFlag := fl.String("adapter")
input, err := ioutil.ReadFile(validateCmdConfigFlag)
if err != nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading input file: %v", err)
}
if validateCmdAdapterFlag != "" {
cfgAdapter := caddyconfig.GetAdapter(validateCmdAdapterFlag)
if cfgAdapter == nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("unrecognized config adapter: %s", validateCmdAdapterFlag)
}
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, nil)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
// print warnings to stderr
for _, warn := range warnings {
msg := warn.Message
if warn.Directive != "" {
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
}
log.Printf("[WARNING][%s] %s:%d: %s", validateCmdAdapterFlag, warn.File, warn.Line, msg)
}
input = adaptedConfig
}
var cfg *caddy.Config
err = json.Unmarshal(input, &cfg)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("decoding config: %v", err)
}
err = caddy.Validate(cfg)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
fmt.Println("Valid configuration")
return caddy.ExitCodeSuccess, nil
}
func cmdHelp(fl Flags) (int, error) { func cmdHelp(fl Flags) (int, error) {
const fullDocs = `Full documentation is available at: const fullDocs = `Full documentation is available at:
https://github.com/caddyserver/caddy/wiki/v2:-Documentation` https://github.com/caddyserver/caddy/wiki/v2:-Documentation`

View file

@ -54,7 +54,7 @@ var commands = map[string]Command{
"start": { "start": {
Name: "start", Name: "start",
Func: cmdStart, Func: cmdStart,
Usage: "[--config <path>] [--config-adapter <name>]", Usage: "[--config <path>] [--adapter <name>]",
Short: "Starts the Caddy process and returns after server has started.", Short: "Starts the Caddy process and returns after server has started.",
Long: ` Long: `
Starts the Caddy process, optionally bootstrapped with an initial Starts the Caddy process, optionally bootstrapped with an initial
@ -65,7 +65,7 @@ details.`,
Flags: func() *flag.FlagSet { Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("start", flag.ExitOnError) fs := flag.NewFlagSet("start", flag.ExitOnError)
fs.String("config", "", "Configuration file") fs.String("config", "", "Configuration file")
fs.String("config-adapter", "", "Name of config adapter to apply") fs.String("adapter", "", "Name of config adapter to apply")
return fs return fs
}(), }(),
}, },
@ -73,7 +73,7 @@ details.`,
"run": { "run": {
Name: "run", Name: "run",
Func: cmdRun, Func: cmdRun,
Usage: "[--config <path>] [--config-adapter <name>] [--print-env]", Usage: "[--config <path>] [--adapter <name>] [--print-env]",
Short: `Starts the Caddy process and blocks indefinitely.`, Short: `Starts the Caddy process and blocks indefinitely.`,
Long: ` Long: `
Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On
@ -83,25 +83,25 @@ window.
If a config file is specified, it will be applied immediately after the process 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 is running. If the config file is not in Caddy's native JSON format, you can
specify an adapter with --config-adapter to adapt the given config file to specify an adapter with --adapter to adapt the given config file to
Caddy's native format. The config adapter must be a registered module. Any Caddy's native format. The config adapter must be a registered module. Any
warnings will be printed to the log, but beware that any adaptation without warnings will be printed to the log, but beware that any adaptation without
errors will immediately be used. If you want to review the results of the errors will immediately be used. If you want to review the results of the
adaptation first, use adapt-config. adaptation first, use the 'adapt' subcommand.
As a special case, if the current working directory has a file called 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 "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 file will be loaded and used to configure Caddy, even without any command line
flags. flags.
If --print-env is specified, the environment as seen by the Caddy process will If --environ is specified, the environment as seen by the Caddy process will
be printed before starting. This is the same as the environ command but does be printed before starting. This is the same as the environ command but does
not quit after printing.`, not quit after printing.`,
Flags: func() *flag.FlagSet { Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("run", flag.ExitOnError) fs := flag.NewFlagSet("run", flag.ExitOnError)
fs.String("config", "", "Configuration file") fs.String("config", "", "Configuration file")
fs.String("config-adapter", "", "Name of config adapter to apply") fs.String("adapter", "", "Name of config adapter to apply")
fs.Bool("print-env", false, "Print environment") fs.Bool("environ", false, "Print environment")
fs.String("pingback", "", "Echo confirmation bytes to this address on success") fs.String("pingback", "", "Echo confirmation bytes to this address on success")
return fs return fs
}(), }(),
@ -120,7 +120,7 @@ shutdown on Windows, use Ctrl+C or the /stop endpoint.`,
"reload": { "reload": {
Name: "reload", Name: "reload",
Func: cmdReload, Func: cmdReload,
Usage: "--config <path> [--config-adapter <name>] [--address <interface>]", Usage: "--config <path> [--adapter <name>] [--address <interface>]",
Short: "Gives the running Caddy instance a new configuration", Short: "Gives the running Caddy instance a new configuration",
Long: `Gives the running Caddy instance a new configuration. This has the same effect 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 as POSTing a document to the /load endpoint, but is convenient for simple
@ -129,9 +129,9 @@ configurable, the endpoint configuration is loaded from the --address flag if
specified; otherwise it is loaded from the given config file; otherwise the specified; otherwise it is loaded from the given config file; otherwise the
default is assumed.`, default is assumed.`,
Flags: func() *flag.FlagSet { Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("load", flag.ExitOnError) fs := flag.NewFlagSet("reload", flag.ExitOnError)
fs.String("config", "", "Configuration file") fs.String("config", "", "Configuration file")
fs.String("config-adapter", "", "Name of config adapter to apply") fs.String("adapter", "", "Name of config adapter to apply")
fs.String("address", "", "Address of the administration listener, if different from config") fs.String("address", "", "Address of the administration listener, if different from config")
return fs return fs
}(), }(),
@ -149,6 +149,11 @@ default is assumed.`,
Func: cmdListModules, Func: cmdListModules,
Short: "List installed Caddy modules.", Short: "List installed Caddy modules.",
Long: `List installed Caddy modules.`, Long: `List installed Caddy modules.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("list-modules", flag.ExitOnError)
fs.Bool("versions", false, "Print version information")
return fs
}(),
}, },
"environ": { "environ": {
@ -158,15 +163,38 @@ default is assumed.`,
Long: `Prints the environment as seen by Caddy.`, Long: `Prints the environment as seen by Caddy.`,
}, },
"adapt-config": { "adapt": {
Name: "adapt-config", Name: "adapt",
Func: cmdAdaptConfig, Func: cmdAdaptConfig,
Usage: "--input <path> --adapter <name> [--pretty]", Usage: "--config <path> --adapter <name> [--pretty]",
Short: "Adapts a configuration to Caddy's native JSON config structure", Short: "Adapts a configuration to Caddy's native JSON config structure",
Long: ` Long: `
Adapts a configuration to Caddy's native JSON config structure and writes the 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, output to stdout, along with any warnings to stderr. If --pretty is specified,
the output will be formatted with indentation for human readability.`, the output will be formatted with indentation for human readability.`,
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.Bool("pretty", false, "Format the output for human readability")
return fs
}(),
},
"validate": {
Name: "validate",
Func: cmdValidateConfig,
Usage: "--config <path> [--adapter <name>]",
Short: "Tests whether a configuration file is valid.",
Long: `
Loads and provisions the provided config, but does not start
running it.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("load", flag.ExitOnError)
fs.String("config", "", "Input configuration file")
fs.String("adapter", "", "Name of config adapter")
return fs
}(),
}, },
} }