mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-22 12:58:37 +00:00
cmd/tailscale/cli: fix "subcommand required" errors when typod
Fixes #11672 Signed-off-by: Paul Scott <paul@tailscale.com>
This commit is contained in:
parent
3ff3445e9d
commit
d07ede461a
@ -99,15 +99,32 @@ func Run(args []string) (err error) {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
if noexec := (ffcli.NoExecError{}); errors.As(err, &noexec) {
|
||||
// When the user enters an unknown subcommand, ffcli tries to run
|
||||
// the closest valid parent subcommand with everything else as args,
|
||||
// returning NoExecError if it doesn't have an Exec function.
|
||||
cmd := noexec.Command
|
||||
args := cmd.FlagSet.Args()
|
||||
if len(cmd.Subcommands) > 0 {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("%s: unknown subcommand: %s", fullCmd(rootCmd, cmd), args[0])
|
||||
}
|
||||
subs := make([]string, 0, len(cmd.Subcommands))
|
||||
for _, sub := range cmd.Subcommands {
|
||||
subs = append(subs, sub.Name)
|
||||
}
|
||||
return fmt.Errorf("%s: missing subcommand: %s", fullCmd(rootCmd, cmd), strings.Join(subs, ", "))
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if envknob.Bool("TS_DUMP_HELP") {
|
||||
walkCommands(rootCmd, func(w cmdWalk) bool {
|
||||
fmt.Println("===")
|
||||
c := w.cmd
|
||||
// UsageFuncs are typically called during Command.Run which ensures
|
||||
// FlagSet is not nil.
|
||||
c := w.Command
|
||||
if c.FlagSet == nil {
|
||||
c.FlagSet = flag.NewFlagSet(c.Name, flag.ContinueOnError)
|
||||
}
|
||||
@ -182,7 +199,12 @@ change in the future.
|
||||
driveCmd,
|
||||
},
|
||||
FlagSet: rootfs,
|
||||
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("tailscale: unknown subcommand: %s", args[0])
|
||||
}
|
||||
return flag.ErrHelp
|
||||
},
|
||||
}
|
||||
if envknob.UseWIPCode() {
|
||||
rootCmd.Subcommands = append(rootCmd.Subcommands,
|
||||
@ -195,8 +217,8 @@ change in the future.
|
||||
}
|
||||
|
||||
walkCommands(rootCmd, func(w cmdWalk) bool {
|
||||
if w.cmd.UsageFunc == nil {
|
||||
w.cmd.UsageFunc = usageFunc
|
||||
if w.UsageFunc == nil {
|
||||
w.UsageFunc = usageFunc
|
||||
}
|
||||
return true
|
||||
})
|
||||
@ -220,10 +242,24 @@ var rootArgs struct {
|
||||
}
|
||||
|
||||
type cmdWalk struct {
|
||||
cmd *ffcli.Command
|
||||
*ffcli.Command
|
||||
parents []*ffcli.Command
|
||||
}
|
||||
|
||||
func (w cmdWalk) Path() string {
|
||||
if len(w.parents) == 0 {
|
||||
return w.Name
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, p := range w.parents {
|
||||
sb.WriteString(p.Name)
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
sb.WriteString(w.Name)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// walkCommands calls f for root and all of its nested subcommands until f
|
||||
// returns false or all have been visited.
|
||||
func walkCommands(root *ffcli.Command, f func(w cmdWalk) (more bool)) {
|
||||
@ -243,6 +279,21 @@ func walkCommands(root *ffcli.Command, f func(w cmdWalk) (more bool)) {
|
||||
walk(root, nil, f)
|
||||
}
|
||||
|
||||
// fullCmd returns the full "tailscale ... cmd" invocation for a subcommand.
|
||||
func fullCmd(root, cmd *ffcli.Command) (full string) {
|
||||
walkCommands(root, func(w cmdWalk) bool {
|
||||
if w.Command == cmd {
|
||||
full = w.Path()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if full == "" {
|
||||
return cmd.Name
|
||||
}
|
||||
return full
|
||||
}
|
||||
|
||||
// usageFuncNoDefaultValues is like usageFunc but doesn't print default values.
|
||||
func usageFuncNoDefaultValues(c *ffcli.Command) string {
|
||||
return usageFuncOpt(c, false)
|
||||
|
@ -40,7 +40,7 @@ func TestShortUsage(t *testing.T) {
|
||||
}
|
||||
|
||||
walkCommands(newRootCmd(), func(w cmdWalk) bool {
|
||||
c, parents := w.cmd, w.parents
|
||||
c, parents := w.Command, w.parents
|
||||
|
||||
// Words that we expect to be in the usage.
|
||||
words := make([]string, len(parents)+1)
|
||||
|
@ -4,7 +4,6 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -26,9 +25,6 @@ services on the host to use Tailscale in more ways.
|
||||
return fs
|
||||
})(),
|
||||
Subcommands: configureSubcommands(),
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
return flag.ErrHelp
|
||||
},
|
||||
}
|
||||
|
||||
func configureSubcommands() (out []*ffcli.Command) {
|
||||
|
@ -346,7 +346,7 @@ func outName(dst string) string {
|
||||
|
||||
func runDebug(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return errors.New("unknown arguments")
|
||||
return fmt.Errorf("tailscale debug: unknown subcommand: %s", args[0])
|
||||
}
|
||||
var usedFlag bool
|
||||
if out := debugArgs.cpuFile; out != "" {
|
||||
@ -401,7 +401,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||
// to subcommands.
|
||||
return nil
|
||||
}
|
||||
return errors.New("see 'tailscale debug --help")
|
||||
return errors.New("tailscale debug: subcommand or flag required")
|
||||
}
|
||||
|
||||
func runLocalCreds(ctx context.Context, args []string) error {
|
||||
|
@ -5,7 +5,6 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -57,9 +56,6 @@ var driveCmd = &ffcli.Command{
|
||||
Exec: runDriveList,
|
||||
},
|
||||
},
|
||||
Exec: func(context.Context, []string) error {
|
||||
return errors.New("drive subcommand required; run 'tailscale drive -h' for details")
|
||||
},
|
||||
}
|
||||
|
||||
// runDriveShare is the entry point for the "tailscale drive share" command.
|
||||
|
@ -25,10 +25,6 @@ func exitNodeCmd() *ffcli.Command {
|
||||
Name: "exit-node",
|
||||
ShortUsage: "tailscale exit-node [flags]",
|
||||
ShortHelp: "Show machines on your tailnet configured as exit nodes",
|
||||
LongHelp: "Show machines on your tailnet configured as exit nodes",
|
||||
Exec: func(context.Context, []string) error {
|
||||
return errors.New("exit-node subcommand required; run 'tailscale exit-node -h' for details")
|
||||
},
|
||||
Subcommands: append([]*ffcli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
|
@ -44,12 +44,6 @@ var fileCmd = &ffcli.Command{
|
||||
fileCpCmd,
|
||||
fileGetCmd,
|
||||
},
|
||||
Exec: func(context.Context, []string) error {
|
||||
// TODO(bradfitz): is there a better ffcli way to
|
||||
// annotate subcommand-required commands that don't
|
||||
// have an exec body of their own?
|
||||
return errors.New("file subcommand required; run 'tailscale file -h' for details")
|
||||
},
|
||||
}
|
||||
|
||||
type countingReader struct {
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
|
||||
var netlockCmd = &ffcli.Command{
|
||||
Name: "lock",
|
||||
ShortUsage: "tailscale lock <sub-command> <arguments>",
|
||||
ShortUsage: "tailscale lock <subcommand> [arguments...]",
|
||||
ShortHelp: "Manage tailnet lock",
|
||||
LongHelp: "Manage tailnet lock",
|
||||
Subcommands: []*ffcli.Command{
|
||||
@ -49,6 +49,9 @@ func runNetworkLockNoSubcommand(ctx context.Context, args []string) error {
|
||||
if len(args) >= 2 && args[0] == "tskey-wrap" {
|
||||
return runTskeyWrapCmd(ctx, args[1:])
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("tailscale lock: unknown subcommand: %s", args[0])
|
||||
}
|
||||
|
||||
return runNetworkLockStatus(ctx, args)
|
||||
}
|
||||
@ -195,6 +198,10 @@ var nlStatusCmd = &ffcli.Command{
|
||||
}
|
||||
|
||||
func runNetworkLockStatus(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("tailscale lock status: unexpected argument")
|
||||
}
|
||||
|
||||
st, err := localClient.NetworkLockStatus(ctx)
|
||||
if err != nil {
|
||||
return fixTailscaledConnectError(err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user