Skip to content

Commit e633d01

Browse files
authored
cmd: fix Commands function not returning all registered commands (#7059)
1 parent fe26751 commit e633d01

File tree

2 files changed

+85
-21
lines changed

2 files changed

+85
-21
lines changed

cmd/commands.go

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"os"
2121
"regexp"
2222
"strings"
23+
"sync"
2324

2425
"github.com/spf13/cobra"
2526
"github.com/spf13/cobra/doc"
@@ -80,10 +81,16 @@ type CommandFunc func(Flags) (int, error)
8081
// Commands returns a list of commands initialised by
8182
// RegisterCommand
8283
func Commands() map[string]Command {
84+
commandsMu.RLock()
85+
defer commandsMu.RUnlock()
86+
8387
return commands
8488
}
8589

86-
var commands = make(map[string]Command)
90+
var (
91+
commandsMu sync.RWMutex
92+
commands = make(map[string]Command)
93+
)
8794

8895
func init() {
8996
RegisterCommand(Command{
@@ -441,7 +448,7 @@ EXPERIMENTAL: May be changed or removed.
441448
})
442449

443450
defaultFactory.Use(func(rootCmd *cobra.Command) {
444-
rootCmd.AddCommand(caddyCmdToCobra(Command{
451+
manpageCommand := Command{
445452
Name: "manpage",
446453
Usage: "--directory <path>",
447454
Short: "Generates the manual pages for Caddy commands",
@@ -471,11 +478,12 @@ argument of --directory. If the directory does not exist, it will be created.
471478
return caddy.ExitCodeSuccess, nil
472479
})
473480
},
474-
}))
481+
}
475482

476483
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md
477-
rootCmd.AddCommand(&cobra.Command{
478-
Use: "completion [bash|zsh|fish|powershell]",
484+
completionCommand := Command{
485+
Name: "completion",
486+
Usage: "[bash|zsh|fish|powershell]",
479487
Short: "Generate completion script",
480488
Long: fmt.Sprintf(`To load completions:
481489
@@ -516,24 +524,37 @@ argument of --directory. If the directory does not exist, it will be created.
516524
PS> %[1]s completion powershell > %[1]s.ps1
517525
# and source this file from your PowerShell profile.
518526
`, rootCmd.Root().Name()),
519-
DisableFlagsInUseLine: true,
520-
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
521-
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
522-
RunE: func(cmd *cobra.Command, args []string) error {
523-
switch args[0] {
524-
case "bash":
525-
return cmd.Root().GenBashCompletion(os.Stdout)
526-
case "zsh":
527-
return cmd.Root().GenZshCompletion(os.Stdout)
528-
case "fish":
529-
return cmd.Root().GenFishCompletion(os.Stdout, true)
530-
case "powershell":
531-
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
532-
default:
533-
return fmt.Errorf("unrecognized shell: %s", args[0])
527+
CobraFunc: func(cmd *cobra.Command) {
528+
cmd.DisableFlagsInUseLine = true
529+
cmd.ValidArgs = []string{"bash", "zsh", "fish", "powershell"}
530+
cmd.Args = cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)
531+
cmd.RunE = func(cmd *cobra.Command, args []string) error {
532+
switch args[0] {
533+
case "bash":
534+
return cmd.Root().GenBashCompletion(os.Stdout)
535+
case "zsh":
536+
return cmd.Root().GenZshCompletion(os.Stdout)
537+
case "fish":
538+
return cmd.Root().GenFishCompletion(os.Stdout, true)
539+
case "powershell":
540+
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
541+
default:
542+
return fmt.Errorf("unrecognized shell: %s", args[0])
543+
}
534544
}
535545
},
536-
})
546+
}
547+
548+
rootCmd.AddCommand(caddyCmdToCobra(manpageCommand))
549+
rootCmd.AddCommand(caddyCmdToCobra(completionCommand))
550+
551+
// add manpage and completion commands to the map of
552+
// available commands, because they're not registered
553+
// through RegisterCommand.
554+
commandsMu.Lock()
555+
commands[manpageCommand.Name] = manpageCommand
556+
commands[completionCommand.Name] = completionCommand
557+
commandsMu.Unlock()
537558
})
538559
}
539560

@@ -552,6 +573,9 @@ argument of --directory. If the directory does not exist, it will be created.
552573
//
553574
// This function should be used in init().
554575
func RegisterCommand(cmd Command) {
576+
commandsMu.Lock()
577+
defer commandsMu.Unlock()
578+
555579
if cmd.Name == "" {
556580
panic("command name is required")
557581
}
@@ -570,6 +594,7 @@ func RegisterCommand(cmd Command) {
570594
defaultFactory.Use(func(rootCmd *cobra.Command) {
571595
rootCmd.AddCommand(caddyCmdToCobra(cmd))
572596
})
597+
commands[cmd.Name] = cmd
573598
}
574599

575600
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)

cmd/commands_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package caddycmd
2+
3+
import (
4+
"maps"
5+
"reflect"
6+
"slices"
7+
"testing"
8+
)
9+
10+
func TestCommandsAreAvailable(t *testing.T) {
11+
// trigger init, and build the default factory, so that
12+
// all commands from this package are available
13+
cmd := defaultFactory.Build()
14+
if cmd == nil {
15+
t.Fatal("default factory failed to build")
16+
}
17+
18+
// check that the default factory has 17 commands; it doesn't
19+
// include the commands registered through calls to init in
20+
// other packages
21+
cmds := Commands()
22+
if len(cmds) != 17 {
23+
t.Errorf("expected 17 commands, got %d", len(cmds))
24+
}
25+
26+
commandNames := slices.Collect(maps.Keys(cmds))
27+
slices.Sort(commandNames)
28+
29+
expectedCommandNames := []string{
30+
"adapt", "add-package", "build-info", "completion",
31+
"environ", "fmt", "list-modules", "manpage",
32+
"reload", "remove-package", "run", "start",
33+
"stop", "storage", "upgrade", "validate", "version",
34+
}
35+
36+
if !reflect.DeepEqual(expectedCommandNames, commandNames) {
37+
t.Errorf("expected %v, got %v", expectedCommandNames, commandNames)
38+
}
39+
}

0 commit comments

Comments
 (0)