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 <git@indeednotjames.com>

* Apply suggestions from code review

Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

---------

Co-authored-by: Emily Lange <git@indeednotjames.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
This commit is contained in:
Francis Lavoie 2023-02-24 18:09:12 -05:00 committed by GitHub
parent 167981d258
commit 9e6919550b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 199 additions and 210 deletions

View File

@ -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
}
}

View File

@ -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 <path> [--adapter <name>]] [--envfile <path>] [--watch] [--pidfile <file>]",
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 <path> [--adapter <name>]] [--envfile <path>] [--environ] [--resume] [--watch] [--pidfile <fil>]",
Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--environ] [--resume] [--watch] [--pidfile <file>]",
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 <interface>] [--config <path> [--adapter <name>]]",
Usage: "[--config <path> [--adapter <name>]] [--address <interface>]",
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 <path> [--adapter <name>] [--address <interface>]",
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 <path> [--adapter <name>] [--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 <path> [--adapter <name>] [--envfile <path>]",
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] [<path>]",
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: "<packages...>",
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 <path>",
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 == "" {

View File

@ -18,20 +18,19 @@ import (
"bufio"
"bytes"
"encoding/base64"
"flag"
"fmt"
"os"
"os/signal"
"github.com/caddyserver/caddy/v2"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/spf13/cobra"
"golang.org/x/term"
)
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "hash-password",
Func: cmdHashPassword,
Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
Short: "Hashes a password and writes base64",
Long: `
@ -50,13 +49,12 @@ be provided (scrypt).
Note that scrypt is deprecated. Please use 'bcrypt' instead.
`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("hash-password", flag.ExitOnError)
fs.String("algorithm", "bcrypt", "Name of the hash algorithm")
fs.String("plaintext", "", "The plaintext password")
fs.String("salt", "", "The password salt")
return fs
}(),
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
cmd.Flags().StringP("salt", "s", "", "The password salt")
cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
},
})
}

View File

@ -16,7 +16,6 @@ package fileserver
import (
"encoding/json"
"flag"
"log"
"strconv"
"time"
@ -27,13 +26,13 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
"github.com/caddyserver/certmagic"
"github.com/spf13/cobra"
"go.uber.org/zap"
)
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "file-server",
Func: cmdFileServer,
Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--browse] [--access-log]",
Short: "Spins up a production-ready file server",
Long: `
@ -49,17 +48,16 @@ using this option.
If --browse is enabled, requests for folders without an index file will
respond with a file listing.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("file-server", flag.ExitOnError)
fs.String("domain", "", "Domain name at which to serve the files")
fs.String("root", "", "The path to the root of the site")
fs.String("listen", "", "The address to which to bind the listener")
fs.Bool("browse", false, "Enable directory browsing")
fs.Bool("templates", false, "Enable template rendering")
fs.Bool("access-log", false, "Enable the access log")
fs.Bool("debug", false, "Enable verbose debug logs")
return fs
}(),
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("domain", "d", "", "Domain name at which to serve the files")
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
cmd.Flags().StringP("listen", "", "", "The address to which to bind the listener")
cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing")
cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
cmd.Flags().BoolP("access-log", "", false, "Enable the access log")
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
},
})
}

View File

@ -16,7 +16,6 @@ package reverseproxy
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"strconv"
@ -28,14 +27,14 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/spf13/cobra"
"go.uber.org/zap"
)
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "reverse-proxy",
Func: cmdReverseProxy,
Usage: "[--from <addr>] [--to <addr>] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects]",
Usage: "[--from <addr>] [--to <addr>] [--change-host-header] [--insecure] [--internal-certs] [--disable-redirects] [--access-log]",
Short: "A quick and production-ready reverse proxy",
Long: `
A simple but production-ready reverse proxy. Useful for quick deployments,
@ -63,17 +62,17 @@ For proxying:
--insecure disables TLS verification with the upstream. WARNING: THIS
DISABLES SECURITY BY NOT VERIFYING THE UPSTREAM'S CERTIFICATE.
`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("reverse-proxy", flag.ExitOnError)
fs.String("from", "localhost", "Address on which to receive traffic")
fs.Var(&reverseProxyCmdTo, "to", "Upstream address(es) to which traffic should be sent")
fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)")
fs.Bool("internal-certs", false, "Use internal CA for issuing certs")
fs.Bool("debug", false, "Enable verbose debug logs")
fs.Bool("disable-redirects", false, "Disable HTTP->HTTPS redirects")
return fs
}(),
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("from", "f", "localhost", "Address on which to receive traffic")
cmd.Flags().StringSliceP("to", "t", []string{}, "Upstream address(es) to which traffic should be sent")
cmd.Flags().BoolP("change-host-header", "c", false, "Set upstream Host header to address of upstream")
cmd.Flags().BoolP("insecure", "", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)")
cmd.Flags().BoolP("disable-redirects", "r", false, "Disable HTTP->HTTPS redirects")
cmd.Flags().BoolP("internal-certs", "i", false, "Use internal CA for issuing certs")
cmd.Flags().BoolP("access-log", "", false, "Enable the access log")
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdReverseProxy)
},
})
}
@ -83,14 +82,19 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
from := fs.String("from")
changeHost := fs.Bool("change-host-header")
insecure := fs.Bool("insecure")
internalCerts := fs.Bool("internal-certs")
debug := fs.Bool("debug")
disableRedir := fs.Bool("disable-redirects")
internalCerts := fs.Bool("internal-certs")
accessLog := fs.Bool("access-log")
debug := fs.Bool("debug")
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if len(reverseProxyCmdTo) == 0 {
to, err := fs.GetStringSlice("to")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid to flag: %v", err)
}
if len(to) == 0 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("--to is required")
}
@ -119,9 +123,9 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
// set up the upstream address; assume missing information from given parts
// mixing schemes isn't supported, so use first defined (if available)
toAddresses := make([]string, len(reverseProxyCmdTo))
toAddresses := make([]string, len(to))
var toScheme string
for i, toLoc := range reverseProxyCmdTo {
for i, toLoc := range to {
addr, scheme, err := parseUpstreamDialAddress(toLoc)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", toLoc, err)
@ -180,6 +184,9 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
Routes: caddyhttp.RouteList{route},
Listen: []string{":" + fromAddr.Port},
}
if accessLog {
server.Logs = &caddyhttp.ServerLogConfig{}
}
if fromAddr.Scheme == "http" {
server.AutoHTTPS = &caddyhttp.AutoHTTPSConfig{Disabled: true}
@ -238,6 +245,3 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
select {}
}
// reverseProxyCmdTo holds the parsed values from repeated use of the --to flag.
var reverseProxyCmdTo caddycmd.StringSlice

View File

@ -17,7 +17,6 @@ package caddyhttp
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
@ -32,6 +31,7 @@ import (
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/spf13/cobra"
"go.uber.org/zap"
)
@ -39,7 +39,6 @@ func init() {
caddy.RegisterModule(StaticResponse{})
caddycmd.RegisterCommand(caddycmd.Command{
Name: "respond",
Func: cmdRespond,
Usage: `[--status <code>] [--body <content>] [--listen <addr>] [--access-log] [--debug] [--header "Field: value"] <body|status>`,
Short: "Simple, hard-coded HTTP responses for development and testing",
Long: `
@ -71,16 +70,15 @@ Access/request logging and more verbose debug logging can also be enabled.
Response headers may be added using the --header flag for each header field.
`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("respond", flag.ExitOnError)
fs.String("listen", ":0", "The address to which to bind the listener")
fs.Int("status", http.StatusOK, "The response status code")
fs.String("body", "", "The body of the HTTP response")
fs.Bool("access-log", false, "Enable the access log")
fs.Bool("debug", false, "Enable more verbose debug-level logging")
fs.Var(&respondCmdHeaders, "header", "Set a header on the response (format: \"Field: value\"")
return fs
}(),
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("listen", "l", ":0", "The address to which to bind the listener")
cmd.Flags().IntP("status", "s", http.StatusOK, "The response status code")
cmd.Flags().StringP("body", "b", "", "The body of the HTTP response")
cmd.Flags().BoolP("access-log", "", false, "Enable the access log")
cmd.Flags().BoolP("debug", "v", false, "Enable more verbose debug-level logging")
cmd.Flags().StringSliceP("header", "H", []string{}, "Set a header on the response (format: \"Field: value\")")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdRespond)
},
})
}
@ -318,8 +316,12 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
}
// build headers map
headers, err := fl.GetStringSlice("header")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err)
}
hdr := make(http.Header)
for i, h := range respondCmdHeaders {
for i, h := range headers {
key, val, found := strings.Cut(h, ":")
key, val = strings.TrimSpace(key), strings.TrimSpace(val)
if !found || key == "" || val == "" {
@ -432,9 +434,6 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
select {}
}
// respondCmdHeaders holds the parsed values from repeated use of the --header flag.
var respondCmdHeaders caddycmd.StringSlice
// Interface guards
var (
_ MiddlewareHandler = (*StaticResponse)(nil)

View File

@ -18,7 +18,6 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"net/http"
"os"
@ -27,12 +26,12 @@ import (
"github.com/caddyserver/caddy/v2"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/smallstep/truststore"
"github.com/spf13/cobra"
)
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "trust",
Func: cmdTrust,
Usage: "[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]",
Short: "Installs a CA certificate into local trust stores",
Long: `
@ -53,19 +52,17 @@ This command will attempt to connect to Caddy's admin API running at
'` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may
explicitly specify the --address, or use the --config flag to load
the admin address from your config, if not using the default.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("trust", flag.ExitOnError)
fs.String("ca", "", "The ID of the CA to trust (defaults to 'local')")
fs.String("address", "", "Address of the administration API listener (if --config is not used)")
fs.String("config", "", "Configuration file (if --address is not used)")
fs.String("adapter", "", "Name of config adapter to apply (if --config is used)")
return fs
}(),
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("ca", "", "", "The ID of the CA to trust (defaults to 'local')")
cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdTrust)
},
})
caddycmd.RegisterCommand(caddycmd.Command{
Name: "untrust",
Func: cmdUntrust,
Usage: "[--cert <path>] | [[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]]",
Short: "Untrusts a locally-trusted CA certificate",
Long: `
@ -89,15 +86,14 @@ will attempt to connect to the Caddy's admin API running at
'` + caddy.DefaultAdminListen + `' to fetch the root certificate.
You may explicitly specify the --address, or use the --config flag
to load the admin address from your config, if not using the default.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("untrust", flag.ExitOnError)
fs.String("cert", "", "The path to the CA certificate to untrust")
fs.String("ca", "", "The ID of the CA to untrust (defaults to 'local')")
fs.String("address", "", "Address of the administration API listener (if --config is not used)")
fs.String("config", "", "Configuration file (if --address is not used)")
fs.String("adapter", "", "Name of config adapter to apply (if --config is used)")
return fs
}(),
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("cert", "p", "", "The path to the CA certificate to untrust")
cmd.Flags().StringP("ca", "", "", "The ID of the CA to untrust (defaults to 'local')")
cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdUntrust)
},
})
}