Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 23:30 UTC
cmd: fix `Commands` function not returning all registered commands (#7059)
Herman Slatman committed
Jun 13, 2025 at 01:17 UTC
e633d013f64d057f462786ccf4a430cd27817d4d
2 files changed
+85
-21
cmd/commands.go
+46
-21
@@ -20,6 +20,7 @@ import (
20
"os"
21
"regexp"
22
"strings"
23
+ "sync"
24
25
"github.com/spf13/cobra"
26
"github.com/spf13/cobra/doc"
@@ -80,10 +81,16 @@ type CommandFunc func(Flags) (int, error)
81
// Commands returns a list of commands initialised by
82
// RegisterCommand
83
func Commands() map[string]Command {
84
+ commandsMu.RLock()
85
+ defer commandsMu.RUnlock()
86
+
87
return commands
88
}
89
86
-var commands = make(map[string]Command)
90
+var (
91
+ commandsMu sync.RWMutex
92
+ commands = make(map[string]Command)
93
+)
94
95
func init() {
96
RegisterCommand(Command{
@@ -441,7 +448,7 @@ EXPERIMENTAL: May be changed or removed.
448
})
449
450
defaultFactory.Use(func(rootCmd *cobra.Command) {
444
- rootCmd.AddCommand(caddyCmdToCobra(Command{
451
+ manpageCommand := Command{
452
Name: "manpage",
453
Usage: "--directory <path>",
454
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.
478
return caddy.ExitCodeSuccess, nil
479
})
480
},
474
- }))
481
+ }
482
483
// 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]",
487
Short: "Generate completion script",
488
Long: fmt.Sprintf(`To load completions:
489
@@ -516,24 +524,37 @@ argument of --directory. If the directory does not exist, it will be created.
524
PS> %[1]s completion powershell > %[1]s.ps1
525
# and source this file from your PowerShell profile.
526
`, 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
+ }
544
}
545
},
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()
558
})
559
}
560
@@ -552,6 +573,9 @@ argument of --directory. If the directory does not exist, it will be created.
573
//
574
// This function should be used in init().
575
func RegisterCommand(cmd Command) {
576
+ commandsMu.Lock()
577
+ defer commandsMu.Unlock()
578
+
579
if cmd.Name == "" {
580
panic("command name is required")
581
}
@@ -570,6 +594,7 @@ func RegisterCommand(cmd Command) {
594
defaultFactory.Use(func(rootCmd *cobra.Command) {
595
rootCmd.AddCommand(caddyCmdToCobra(cmd))
596
})
597
+ commands[cmd.Name] = cmd
598
}
599
600
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
cmd/commands_test.go
new
+39
@@ -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
+}