cmd/tailscale/cli: migrate hidden debug subcommand to use subcomands

It was a mess of flags. Use subcommands under "debug" instead.

And document loudly that it's not a stable interface.

Change-Id: Idcc58f6a6cff51f72cb5565aa977ac0cc30c3a03
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/3312/head
Brad Fitzpatrick 4 years ago committed by Brad Fitzpatrick
parent 57b039c51d
commit 945290cc3f

@ -26,34 +26,61 @@ import (
var debugCmd = &ffcli.Command{ var debugCmd = &ffcli.Command{
Name: "debug", Name: "debug",
Exec: runDebug, Exec: runDebug,
LongHelp: `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("debug") fs := newFlagSet("debug")
fs.BoolVar(&debugArgs.goroutines, "daemon-goroutines", false, "If true, dump the tailscaled daemon's goroutines")
fs.BoolVar(&debugArgs.ipn, "ipn", false, "If true, subscribe to IPN notifications")
fs.BoolVar(&debugArgs.prefs, "prefs", false, "If true, dump active prefs")
fs.BoolVar(&debugArgs.derpMap, "derp", false, "If true, dump DERP map")
fs.BoolVar(&debugArgs.pretty, "pretty", false, "If true, pretty-print output (for --prefs)")
fs.BoolVar(&debugArgs.netMap, "netmap", true, "whether to include netmap in --ipn mode")
fs.BoolVar(&debugArgs.env, "env", false, "dump environment")
fs.BoolVar(&debugArgs.localCreds, "local-creds", false, "print how to connect to local tailscaled")
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME") fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-sec seconds and write it to this file; - for stdout") fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-sec seconds and write it to this file; - for stdout")
fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout") fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout")
fs.IntVar(&debugArgs.cpuSec, "profile-seconds", 15, "number of seconds to run a CPU profile for, when --cpu-profile is non-empty") fs.IntVar(&debugArgs.cpuSec, "profile-seconds", 15, "number of seconds to run a CPU profile for, when --cpu-profile is non-empty")
return fs return fs
})(), })(),
Subcommands: []*ffcli.Command{
{
Name: "derp-map",
Exec: runDERPMap,
ShortHelp: "print DERP map",
},
{
Name: "daemon-goroutines",
Exec: runDaemonGoroutines,
ShortHelp: "print tailscaled's goroutines",
},
&ffcli.Command{
Name: "env",
Exec: runEnv,
ShortHelp: "print cmd/tailscale environment",
},
&ffcli.Command{
Name: "local-creds",
Exec: runLocalCreds,
ShortHelp: "print how to access Tailscale local API",
},
&ffcli.Command{
Name: "prefs",
Exec: runPrefs,
ShortHelp: "print prefs",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("prefs")
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
return fs
})(),
},
&ffcli.Command{
Name: "watch-ipn",
Exec: runWatchIPN,
ShortHelp: "subscribe to IPN message bus",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("watch-ipn")
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
return fs
})(),
},
},
} }
var debugArgs struct { var debugArgs struct {
env bool
localCreds bool
goroutines bool
ipn bool
netMap bool
derpMap bool
file string file string
prefs bool
pretty bool
cpuSec int cpuSec int
cpuFile string cpuFile string
memFile string memFile string
@ -81,26 +108,9 @@ func runDebug(ctx context.Context, args []string) error {
if len(args) > 0 { if len(args) > 0 {
return errors.New("unknown arguments") return errors.New("unknown arguments")
} }
if debugArgs.env { var usedFlag bool
for _, e := range os.Environ() {
outln(e)
}
return nil
}
if debugArgs.localCreds {
port, token, err := safesocket.LocalTCPPortAndToken()
if err == nil {
printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
return nil
}
if runtime.GOOS == "windows" {
printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
return nil
}
printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
return nil
}
if out := debugArgs.cpuFile; out != "" { if out := debugArgs.cpuFile; out != "" {
usedFlag = true // TODO(bradfitz): add "profile" subcommand
log.Printf("Capturing CPU profile for %v seconds ...", debugArgs.cpuSec) log.Printf("Capturing CPU profile for %v seconds ...", debugArgs.cpuSec)
if v, err := tailscale.Profile(ctx, "profile", debugArgs.cpuSec); err != nil { if v, err := tailscale.Profile(ctx, "profile", debugArgs.cpuSec); err != nil {
return err return err
@ -112,6 +122,7 @@ func runDebug(ctx context.Context, args []string) error {
} }
} }
if out := debugArgs.memFile; out != "" { if out := debugArgs.memFile; out != "" {
usedFlag = true // TODO(bradfitz): add "profile" subcommand
log.Printf("Capturing memory profile ...") log.Printf("Capturing memory profile ...")
if v, err := tailscale.Profile(ctx, "heap", 0); err != nil { if v, err := tailscale.Profile(ctx, "heap", 0); err != nil {
return err return err
@ -122,45 +133,80 @@ func runDebug(ctx context.Context, args []string) error {
log.Printf("Memory profile written to %s", outName(out)) log.Printf("Memory profile written to %s", outName(out))
} }
} }
if debugArgs.prefs { if debugArgs.file != "" {
prefs, err := tailscale.GetPrefs(ctx) usedFlag = true // TODO(bradfitz): add "file" subcommand
if debugArgs.file == "get" {
wfs, err := tailscale.WaitingFiles(ctx)
if err != nil { if err != nil {
return err fatalf("%v\n", err)
}
if debugArgs.pretty {
outln(prefs.Pretty())
} else {
j, _ := json.MarshalIndent(prefs, "", "\t")
outln(string(j))
} }
e := json.NewEncoder(Stdout)
e.SetIndent("", "\t")
e.Encode(wfs)
return nil return nil
} }
if debugArgs.goroutines { delete := strings.HasPrefix(debugArgs.file, "delete:")
goroutines, err := tailscale.Goroutines(ctx) if delete {
return tailscale.DeleteWaitingFile(ctx, strings.TrimPrefix(debugArgs.file, "delete:"))
}
rc, size, err := tailscale.GetWaitingFile(ctx, debugArgs.file)
if err != nil { if err != nil {
return err return err
} }
Stdout.Write(goroutines) log.Printf("Size: %v\n", size)
io.Copy(Stdout, rc)
return nil return nil
} }
if debugArgs.derpMap { if usedFlag {
dm, err := tailscale.CurrentDERPMap(ctx) // TODO(bradfitz): delete this path when all debug flags are migrated
if err != nil { // to subcommands.
return fmt.Errorf( return nil
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
)
} }
enc := json.NewEncoder(Stdout) return errors.New("see 'tailscale debug --help")
enc.SetIndent("", "\t") }
enc.Encode(dm)
func runLocalCreds(ctx context.Context, args []string) error {
port, token, err := safesocket.LocalTCPPortAndToken()
if err == nil {
printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
return nil return nil
} }
if debugArgs.ipn { if runtime.GOOS == "windows" {
printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
return nil
}
printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
return nil
}
var prefsArgs struct {
pretty bool
}
func runPrefs(ctx context.Context, args []string) error {
prefs, err := tailscale.GetPrefs(ctx)
if err != nil {
return err
}
if prefsArgs.pretty {
outln(prefs.Pretty())
} else {
j, _ := json.MarshalIndent(prefs, "", "\t")
outln(string(j))
}
return nil
}
var watchIPNArgs struct {
netmap bool
}
func runWatchIPN(ctx context.Context, args []string) error {
c, bc, ctx, cancel := connect(ctx) c, bc, ctx, cancel := connect(ctx)
defer cancel() defer cancel()
bc.SetNotifyCallback(func(n ipn.Notify) { bc.SetNotifyCallback(func(n ipn.Notify) {
if !debugArgs.netMap { if !watchIPNArgs.netmap {
n.NetMap = nil n.NetMap = nil
} }
j, _ := json.MarshalIndent(n, "", "\t") j, _ := json.MarshalIndent(n, "", "\t")
@ -169,29 +215,33 @@ func runDebug(ctx context.Context, args []string) error {
bc.RequestEngineStatus() bc.RequestEngineStatus()
pump(ctx, bc, c) pump(ctx, bc, c)
return errors.New("exit") return errors.New("exit")
} }
if debugArgs.file != "" {
if debugArgs.file == "get" { func runDERPMap(ctx context.Context, args []string) error {
wfs, err := tailscale.WaitingFiles(ctx) dm, err := tailscale.CurrentDERPMap(ctx)
if err != nil { if err != nil {
fatalf("%v\n", err) return fmt.Errorf(
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
)
} }
e := json.NewEncoder(Stdout) enc := json.NewEncoder(Stdout)
e.SetIndent("", "\t") enc.SetIndent("", "\t")
e.Encode(wfs) enc.Encode(dm)
return nil return nil
}
func runEnv(ctx context.Context, args []string) error {
for _, e := range os.Environ() {
outln(e)
} }
delete := strings.HasPrefix(debugArgs.file, "delete:") return nil
if delete { }
return tailscale.DeleteWaitingFile(ctx, strings.TrimPrefix(debugArgs.file, "delete:"))
} func runDaemonGoroutines(ctx context.Context, args []string) error {
rc, size, err := tailscale.GetWaitingFile(ctx, debugArgs.file) goroutines, err := tailscale.Goroutines(ctx)
if err != nil { if err != nil {
return err return err
} }
log.Printf("Size: %v\n", size) Stdout.Write(goroutines)
io.Copy(Stdout, rc)
return nil
}
return nil return nil
} }

Loading…
Cancel
Save