Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: Migrate to spf13/cobra, remove single-dash arg support #4565

Merged
merged 12 commits into from
Aug 30, 2022
33 changes: 33 additions & 0 deletions cmd/cobra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package caddycmd

import (
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "caddy",
}

const docsHeader = `Caddy is an extensible server platform.`
mohammed90 marked this conversation as resolved.
Show resolved Hide resolved
const fullDocsFooter = `Full documentation is available at:
https://caddyserver.com/docs/command-line
`

func init() {
rootCmd.SetHelpTemplate(docsHeader + "\n\n" + rootCmd.HelpTemplate() + "\n" + fullDocsFooter)
}

func caddyCmdToCoral(caddyCmd Command) *cobra.Command {
cmd := &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)
return cmd
}
65 changes: 0 additions & 65 deletions cmd/commandfuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"os/exec"
"runtime"
"runtime/debug"
"sort"
"strings"

"github.com/aryann/difflib"
Expand Down Expand Up @@ -580,70 +579,6 @@ func cmdFmt(fl Flags) (int, error) {
return caddy.ExitCodeSuccess, nil
}

func cmdHelp(fl Flags) (int, error) {
const fullDocs = `Full documentation is available at:
https://caddyserver.com/docs/command-line`
mohammed90 marked this conversation as resolved.
Show resolved Hide resolved

args := fl.Args()
if len(args) == 0 {
s := `Caddy is an extensible server platform.

usage:
caddy <command> [<args...>]

commands:
`
keys := make([]string, 0, len(commands))
for k := range commands {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
cmd := commands[k]
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"
s += "\n" + fullDocs + "\n"

fmt.Print(s)

return caddy.ExitCodeSuccess, nil
} else if len(args) > 1 {
return caddy.ExitCodeFailedStartup, fmt.Errorf("can only give help with one command")
}

subcommand, ok := commands[args[0]]
if !ok {
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",
helpText,
subcommand.Name,
strings.TrimSpace(subcommand.Usage),
)

if help := flagHelp(subcommand.Flags); help != "" {
result += fmt.Sprintf("\nflags: (NOTE: prefix flags with `--` instead of `-`)\n%s", help)
}

result += "\n" + fullDocs + "\n"

fmt.Print(result)

return caddy.ExitCodeSuccess, nil
}

// AdminAPIRequest makes an API request according to the CLI flags given,
// with the given HTTP method and request URI. If body is non-nil, it will
// be assumed to be Content-Type application/json. The caller should close
Expand Down
50 changes: 42 additions & 8 deletions cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ package caddycmd

import (
"flag"
"fmt"
"os"
"regexp"
"strings"

"github.com/caddyserver/caddy/v2"
"github.com/spf13/cobra/doc"
)

// Command represents a subcommand. Name, Func,
Expand Down Expand Up @@ -70,13 +76,6 @@ func Commands() map[string]Command {
var commands = make(map[string]Command)

func init() {
RegisterCommand(Command{
Name: "help",
Func: cmdHelp,
Usage: "<command>",
Short: "Shows help for a Caddy subcommand",
})

RegisterCommand(Command{
Name: "start",
Func: cmdStart,
Expand Down Expand Up @@ -346,6 +345,41 @@ EXPERIMENTAL: May be changed or removed.
}(),
})

RegisterCommand(Command{
Name: "manpage",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool, how do I actually use this on my system so I can do man caddy and have it show me the manpages?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is it would look something like this (not tested):

caddy manpage | gzip | sudo tee /usr/share/man/man1/caddy.1.gz && sudo mandb

But really, package installers will do this on install/update. I'll probably update the package install script for the apt repo after playing around with it for a while.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed this isn't quite right, I assumed caddy manpage would spit out to stdout but it takes a --directory instead. But this is probably pretty close to being correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This worked:

$ mkdir man
$ ./caddy manpage --directory man
$ gzip -r man/
$ sudo cp man/* /usr/share/man/man8/
$ sudo mandb

Will need to add this to the deb package installation later.

Func: func(fl Flags) (int, error) {
dir := strings.TrimSpace(fl.String("directory"))
section := strings.TrimSpace(fl.String("section"))
if dir == "" || section == "" {
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: section,
}, dir); err != nil {
return caddy.ExitCodeFailedQuit, err
}
return caddy.ExitCodeSuccess, nil
},
Usage: "--directory <directory path> --section <section number>",
Short: "Generates the manual pages of Caddy commands",
Long: `
Generates the manual pages of Caddy commands into the designated directory tagged into the specified section.

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.

The manual pages are sorted into the section specified by the argument of --section.
`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("manpage", flag.ExitOnError)
fs.String("directory", "", "The output directory where the manpages are generated")
fs.String("section", "", "The section number of the generated manual pages")
return fs
}(),
})
}

// RegisterCommand registers the command cmd.
Expand Down Expand Up @@ -378,7 +412,7 @@ func RegisterCommand(cmd Command) {
if !commandNameRegex.MatchString(cmd.Name) {
panic("invalid command name")
}
commands[cmd.Name] = cmd
rootCmd.AddCommand(caddyCmdToCoral(cmd))
}

var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
53 changes: 8 additions & 45 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/certmagic"
"github.com/spf13/pflag"
"go.uber.org/zap"
)

Expand All @@ -58,35 +59,9 @@ func Main() {
os.Args = append(os.Args, "help")
}

subcommandName := os.Args[1]
subcommand, ok := commands[subcommandName]
if !ok {
if strings.HasPrefix(os.Args[1], "-") {
// user probably forgot to type the subcommand
fmt.Println("[ERROR] first argument must be a subcommand; see 'caddy help'")
} else {
fmt.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'\n", os.Args[1])
}
os.Exit(caddy.ExitCodeFailedStartup)
}

fs := subcommand.Flags
if fs == nil {
fs = flag.NewFlagSet(subcommand.Name, flag.ExitOnError)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}

err := fs.Parse(os.Args[2:])
if err != nil {
fmt.Println(err)
os.Exit(caddy.ExitCodeFailedStartup)
}

exitCode, err := subcommand.Func(Flags{fs})
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", subcommand.Name, err)
}

os.Exit(exitCode)
}

// handlePingbackConn reads from conn and ensures it matches
Expand Down Expand Up @@ -280,7 +255,7 @@ func watchConfigFile(filename, adapterName string) {
// Flags wraps a FlagSet so that typed values
// from flags can be easily retrieved.
type Flags struct {
*flag.FlagSet
*pflag.FlagSet
}

// String returns the string representation of the
Expand Down Expand Up @@ -326,22 +301,6 @@ func (f Flags) Duration(name string) time.Duration {
return val
}

// flagHelp returns the help text for fs.
func flagHelp(fs *flag.FlagSet) string {
if fs == nil {
return ""
}

// temporarily redirect output
out := fs.Output()
defer fs.SetOutput(out)

buf := new(bytes.Buffer)
fs.SetOutput(buf)
fs.PrintDefaults()
return buf.String()
}

func loadEnvFromFile(envFile string) error {
file, err := os.Open(envFile)
if err != nil {
Expand Down Expand Up @@ -470,5 +429,9 @@ func (ss *StringSlice) Set(value string) error {
return nil
}

// TODO: should it implement either of those?
// var _ pflag.Value = (*StringSlice)(nil)
// var _ pflag.SliceValue = (*StringSlice)(nil)
francislavoie marked this conversation as resolved.
Show resolved Hide resolved

// Interface guard
var _ flag.Value = (*StringSlice)(nil)
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ require (
github.com/smallstep/cli v0.21.0
github.com/smallstep/nosql v0.4.0
github.com/smallstep/truststore v0.11.0
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
github.com/yuin/goldmark v1.4.13
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
Expand All @@ -40,6 +42,7 @@ require (
require (
github.com/golang/mock v1.6.0 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

require (
Expand All @@ -53,7 +56,7 @@ require (
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect
Expand All @@ -72,6 +75,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.10.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
Expand Down Expand Up @@ -101,7 +105,7 @@ require (
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rs/xid v1.2.1 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
Expand Down