mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-29 12:32:24 +00:00
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>
This commit is contained in:
parent
57b039c51d
commit
945290cc3f
@ -24,39 +24,66 @@ 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
|
file string
|
||||||
localCreds bool
|
cpuSec int
|
||||||
goroutines bool
|
cpuFile string
|
||||||
ipn bool
|
memFile string
|
||||||
netMap bool
|
|
||||||
derpMap bool
|
|
||||||
file string
|
|
||||||
prefs bool
|
|
||||||
pretty bool
|
|
||||||
cpuSec int
|
|
||||||
cpuFile string
|
|
||||||
memFile string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeProfile(dst string, v []byte) error {
|
func writeProfile(dst string, v []byte) error {
|
||||||
@ -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,55 +133,8 @@ 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 {
|
|
||||||
prefs, err := tailscale.GetPrefs(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if debugArgs.pretty {
|
|
||||||
outln(prefs.Pretty())
|
|
||||||
} else {
|
|
||||||
j, _ := json.MarshalIndent(prefs, "", "\t")
|
|
||||||
outln(string(j))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if debugArgs.goroutines {
|
|
||||||
goroutines, err := tailscale.Goroutines(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
Stdout.Write(goroutines)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if debugArgs.derpMap {
|
|
||||||
dm, err := tailscale.CurrentDERPMap(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
enc := json.NewEncoder(Stdout)
|
|
||||||
enc.SetIndent("", "\t")
|
|
||||||
enc.Encode(dm)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if debugArgs.ipn {
|
|
||||||
c, bc, ctx, cancel := connect(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
bc.SetNotifyCallback(func(n ipn.Notify) {
|
|
||||||
if !debugArgs.netMap {
|
|
||||||
n.NetMap = nil
|
|
||||||
}
|
|
||||||
j, _ := json.MarshalIndent(n, "", "\t")
|
|
||||||
printf("%s\n", j)
|
|
||||||
})
|
|
||||||
bc.RequestEngineStatus()
|
|
||||||
pump(ctx, bc, c)
|
|
||||||
return errors.New("exit")
|
|
||||||
}
|
|
||||||
if debugArgs.file != "" {
|
if debugArgs.file != "" {
|
||||||
|
usedFlag = true // TODO(bradfitz): add "file" subcommand
|
||||||
if debugArgs.file == "get" {
|
if debugArgs.file == "get" {
|
||||||
wfs, err := tailscale.WaitingFiles(ctx)
|
wfs, err := tailscale.WaitingFiles(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -193,5 +157,91 @@ func runDebug(ctx context.Context, args []string) error {
|
|||||||
io.Copy(Stdout, rc)
|
io.Copy(Stdout, rc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if usedFlag {
|
||||||
|
// TODO(bradfitz): delete this path when all debug flags are migrated
|
||||||
|
// to subcommands.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("see 'tailscale debug --help")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
bc.SetNotifyCallback(func(n ipn.Notify) {
|
||||||
|
if !watchIPNArgs.netmap {
|
||||||
|
n.NetMap = nil
|
||||||
|
}
|
||||||
|
j, _ := json.MarshalIndent(n, "", "\t")
|
||||||
|
printf("%s\n", j)
|
||||||
|
})
|
||||||
|
bc.RequestEngineStatus()
|
||||||
|
pump(ctx, bc, c)
|
||||||
|
return errors.New("exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDERPMap(ctx context.Context, args []string) error {
|
||||||
|
dm, err := tailscale.CurrentDERPMap(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(Stdout)
|
||||||
|
enc.SetIndent("", "\t")
|
||||||
|
enc.Encode(dm)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runEnv(ctx context.Context, args []string) error {
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
outln(e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDaemonGoroutines(ctx context.Context, args []string) error {
|
||||||
|
goroutines, err := tailscale.Goroutines(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Stdout.Write(goroutines)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user