cmd: Use a factory to create the caddy root command (#6533)
Some checks are pending
Tests / test (./cmd/caddy/caddy, ~1.21.0, macos-14, 0, 1.21, mac) (push) Waiting to run
Tests / test (./cmd/caddy/caddy, ~1.21.0, ubuntu-latest, 0, 1.21, linux) (push) Waiting to run
Tests / test (./cmd/caddy/caddy, ~1.22.3, macos-14, 0, 1.22, mac) (push) Waiting to run
Tests / test (./cmd/caddy/caddy, ~1.22.3, ubuntu-latest, 0, 1.22, linux) (push) Waiting to run
Tests / test (./cmd/caddy/caddy.exe, ~1.21.0, windows-latest, True, 1.21, windows) (push) Waiting to run
Tests / test (./cmd/caddy/caddy.exe, ~1.22.3, windows-latest, True, 1.22, windows) (push) Waiting to run
Tests / test (s390x on IBM Z) (push) Waiting to run
Tests / goreleaser-check (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, aix) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, darwin) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, dragonfly) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, freebsd) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, illumos) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, linux) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, netbsd) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, openbsd) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, solaris) (push) Waiting to run
Cross-Build / build (~1.22.3, 1.22, windows) (push) Waiting to run
Lint / lint (macos-14, mac) (push) Waiting to run
Lint / lint (ubuntu-latest, linux) (push) Waiting to run
Lint / lint (windows-latest, windows) (push) Waiting to run
Lint / govulncheck (push) Waiting to run

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
This commit is contained in:
a 2024-08-21 22:29:42 -05:00 committed by GitHub
parent 2bb2ecc549
commit 8ccfedf2bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 96 additions and 60 deletions

View File

@ -8,9 +8,10 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
var rootCmd = &cobra.Command{ var defaultFactory = newRootCommandFactory(func() *cobra.Command {
Use: "caddy", return &cobra.Command{
Long: `Caddy is an extensible server platform written in Go. Use: "caddy",
Long: `Caddy is an extensible server platform written in Go.
At its core, Caddy merely manages configuration. Modules are plugged At its core, Caddy merely manages configuration. Modules are plugged
in statically at compile-time to provide useful functionality. Caddy's in statically at compile-time to provide useful functionality. Caddy's
@ -91,23 +92,26 @@ package installers: https://caddyserver.com/docs/install
Instructions for running Caddy in production are also available: Instructions for running Caddy in production are also available:
https://caddyserver.com/docs/running https://caddyserver.com/docs/running
`, `,
Example: ` $ caddy run Example: ` $ caddy run
$ caddy run --config caddy.json $ caddy run --config caddy.json
$ caddy reload --config caddy.json $ caddy reload --config caddy.json
$ caddy stop`, $ caddy stop`,
// kind of annoying to have all the help text printed out if // kind of annoying to have all the help text printed out if
// caddy has an error provisioning its modules, for instance... // caddy has an error provisioning its modules, for instance...
SilenceUsage: true, SilenceUsage: true,
Version: onlyVersionText(), Version: onlyVersionText(),
} }
})
const fullDocsFooter = `Full documentation is available at: const fullDocsFooter = `Full documentation is available at:
https://caddyserver.com/docs/command-line` https://caddyserver.com/docs/command-line`
func init() { func init() {
rootCmd.SetVersionTemplate("{{.Version}}\n") defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n") rootCmd.SetVersionTemplate("{{.Version}}\n")
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
})
} }
func onlyVersionText() string { func onlyVersionText() string {

28
cmd/commandfactory.go Normal file
View File

@ -0,0 +1,28 @@
package caddycmd
import (
"github.com/spf13/cobra"
)
type rootCommandFactory struct {
constructor func() *cobra.Command
options []func(*cobra.Command)
}
func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory {
return &rootCommandFactory{
constructor: fn,
}
}
func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) {
f.options = append(f.options, fn)
}
func (f *rootCommandFactory) Build() *cobra.Command {
o := f.constructor()
for _, v := range f.options {
v(o)
}
return o
}

View File

@ -438,43 +438,44 @@ EXPERIMENTAL: May be changed or removed.
}, },
}) })
RegisterCommand(Command{ defaultFactory.Use(func(rootCmd *cobra.Command) {
Name: "manpage", RegisterCommand(Command{
Usage: "--directory <path>", Name: "manpage",
Short: "Generates the manual pages for Caddy commands", Usage: "--directory <path>",
Long: ` Short: "Generates the manual pages for Caddy commands",
Long: `
Generates the manual pages for Caddy commands into the designated directory Generates the manual pages for Caddy commands into the designated directory
tagged into section 8 (System Administration). tagged into section 8 (System Administration).
The manual page files are generated into the directory specified by the 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. argument of --directory. If the directory does not exist, it will be created.
`, `,
CobraFunc: func(cmd *cobra.Command) { CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated") cmd.Flags().StringP("directory", "o", "", "The output directory where the manpages are generated")
cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) { cmd.RunE = WrapCommandFuncForCobra(func(fl Flags) (int, error) {
dir := strings.TrimSpace(fl.String("directory")) dir := strings.TrimSpace(fl.String("directory"))
if dir == "" { if dir == "" {
return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required") return caddy.ExitCodeFailedQuit, fmt.Errorf("designated output directory and specified section are required")
} }
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0o755); err != nil {
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
if err := doc.GenManTree(rootCmd, &doc.GenManHeader{ if err := doc.GenManTree(rootCmd, &doc.GenManHeader{
Title: "Caddy", Title: "Caddy",
Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections Section: "8", // https://en.wikipedia.org/wiki/Man_page#Manual_sections
}, dir); err != nil { }, dir); err != nil {
return caddy.ExitCodeFailedQuit, err return caddy.ExitCodeFailedQuit, err
} }
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
}) })
}, },
}) })
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md // source: https://github.com/spf13/cobra/blob/main/shell_completions.md
rootCmd.AddCommand(&cobra.Command{ rootCmd.AddCommand(&cobra.Command{
Use: "completion [bash|zsh|fish|powershell]", Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script", Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions: Long: fmt.Sprintf(`To load completions:
Bash: Bash:
@ -513,23 +514,24 @@ argument of --directory. If the directory does not exist, it will be created.
PS> %[1]s completion powershell > %[1]s.ps1 PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile. # and source this file from your PowerShell profile.
`, rootCmd.Root().Name()), `, rootCmd.Root().Name()),
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] { switch args[0] {
case "bash": case "bash":
return cmd.Root().GenBashCompletion(os.Stdout) return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh": case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout) return cmd.Root().GenZshCompletion(os.Stdout)
case "fish": case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true) return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell": case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default: default:
return fmt.Errorf("unrecognized shell: %s", args[0]) return fmt.Errorf("unrecognized shell: %s", args[0])
} }
}, },
})
}) })
} }
@ -563,7 +565,9 @@ func RegisterCommand(cmd Command) {
if !commandNameRegex.MatchString(cmd.Name) { if !commandNameRegex.MatchString(cmd.Name) {
panic("invalid command name") panic("invalid command name")
} }
rootCmd.AddCommand(caddyCmdToCobra(cmd)) defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.AddCommand(caddyCmdToCobra(cmd))
})
} }
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)

View File

@ -72,7 +72,7 @@ func Main() {
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err)) caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
} }
if err := rootCmd.Execute(); err != nil { if err := defaultFactory.Build().Execute(); err != nil {
var exitError *exitError var exitError *exitError
if errors.As(err, &exitError) { if errors.As(err, &exitError) {
os.Exit(exitError.ExitCode) os.Exit(exitError.ExitCode)