diff options
| -rw-r--r-- | cmd/commandfuncs.go | 122 | ||||
| -rw-r--r-- | cmd/commands.go | 56 | 
2 files changed, 154 insertions, 24 deletions
| diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index be9da93..715f064 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -26,6 +26,8 @@ import (  	"net/http"  	"os"  	"os/exec" +	"reflect" +	"runtime/debug"  	"strings"  	"github.com/caddyserver/caddy/v2" @@ -36,7 +38,7 @@ import (  func cmdStart(fl Flags) (int, error) {  	startCmdConfigFlag := fl.String("config") -	startCmdConfigAdapterFlag := fl.String("config-adapter") +	startCmdConfigAdapterFlag := fl.String("adapter")  	// open a listener to which the child process will connect when  	// 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)  	}  	if startCmdConfigAdapterFlag != "" { -		cmd.Args = append(cmd.Args, "--config-adapter", startCmdConfigAdapterFlag) +		cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag)  	}  	stdinpipe, err := cmd.StdinPipe()  	if err != nil { @@ -137,8 +139,8 @@ func cmdStart(fl Flags) (int, error) {  func cmdRun(fl Flags) (int, error) {  	runCmdConfigFlag := fl.String("config") -	runCmdConfigAdapterFlag := fl.String("config-adapter") -	runCmdPrintEnvFlag := fl.Bool("print-env") +	runCmdConfigAdapterFlag := fl.String("adapter") +	runCmdPrintEnvFlag := fl.Bool("environ")  	runCmdPingbackFlag := fl.String("pingback")  	// 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) {  	reloadCmdConfigFlag := fl.String("config") -	reloadCmdConfigAdapterFlag := fl.String("config-adapter") +	reloadCmdConfigAdapterFlag := fl.String("adapter")  	reloadCmdAddrFlag := fl.String("address")  	// a configuration is required @@ -282,10 +284,61 @@ func cmdVersion(_ Flags) (int, error) {  	return caddy.ExitCodeSuccess, nil  } -func cmdListModules(_ Flags) (int, error) { -	for _, m := range caddy.Modules() { -		fmt.Println(m) +func cmdListModules(fl Flags) (int, error) { +	versions := fl.Bool("versions") + +	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  } @@ -295,13 +348,13 @@ func cmdEnviron(_ Flags) (int, error) {  }  func cmdAdaptConfig(fl Flags) (int, error) { +	adaptCmdInputFlag := fl.String("config")  	adaptCmdAdapterFlag := fl.String("adapter") -	adaptCmdInputFlag := fl.String("input")  	adaptCmdPrettyFlag := fl.Bool("pretty")  	if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" {  		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) @@ -341,6 +394,55 @@ func cmdAdaptConfig(fl Flags) (int, error) {  	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) {  	const fullDocs = `Full documentation is available at:  https://github.com/caddyserver/caddy/wiki/v2:-Documentation` diff --git a/cmd/commands.go b/cmd/commands.go index 8a9d482..731e813 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -54,7 +54,7 @@ var commands = map[string]Command{  	"start": {  		Name:  "start",  		Func:  cmdStart, -		Usage: "[--config <path>] [--config-adapter <name>]", +		Usage: "[--config <path>] [--adapter <name>]",  		Short: "Starts the Caddy process and returns after server has started.",  		Long: `  Starts the Caddy process, optionally bootstrapped with an initial @@ -65,7 +65,7 @@ details.`,  		Flags: func() *flag.FlagSet {  			fs := flag.NewFlagSet("start", flag.ExitOnError)  			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  		}(),  	}, @@ -73,7 +73,7 @@ details.`,  	"run": {  		Name:  "run",  		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.`,  		Long: `  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  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  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 -adaptation first, use adapt-config. +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. -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  not quit after printing.`,  		Flags: func() *flag.FlagSet {  			fs := flag.NewFlagSet("run", flag.ExitOnError)  			fs.String("config", "", "Configuration file") -			fs.String("config-adapter", "", "Name of config adapter to apply") -			fs.Bool("print-env", false, "Print environment") +			fs.String("adapter", "", "Name of config adapter to apply") +			fs.Bool("environ", false, "Print environment")  			fs.String("pingback", "", "Echo confirmation bytes to this address on success")  			return fs  		}(), @@ -120,7 +120,7 @@ shutdown on Windows, use Ctrl+C or the /stop endpoint.`,  	"reload": {  		Name:  "reload",  		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",  		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 @@ -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  default is assumed.`,  		Flags: func() *flag.FlagSet { -			fs := flag.NewFlagSet("load", flag.ExitOnError) +			fs := flag.NewFlagSet("reload", flag.ExitOnError)  			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")  			return fs  		}(), @@ -149,6 +149,11 @@ default is assumed.`,  		Func:  cmdListModules,  		Short: "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": { @@ -158,15 +163,38 @@ default is assumed.`,  		Long:  `Prints the environment as seen by Caddy.`,  	}, -	"adapt-config": { -		Name:  "adapt-config", +	"adapt": { +		Name:  "adapt",  		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",  		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.`, +		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 +		}(),  	},  } | 
