cmd/tailscale: add CLI debug command to do raw LocalAPI requests
This adds a portable way to do a raw LocalAPI request without worrying about the Unix-vs-macOS-vs-Windows ways of hitting the LocalAPI server. (It was already possible but tedious with 'tailscale debug local-creds') Updates tailscale/corp#24690 Change-Id: I0828ca55edaedf0565c8db192c10f24bebb95f1b Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f34e08e186
commit
984cd1cab0
@ -136,6 +136,17 @@ func debugCmd() *ffcli.Command {
|
||||
Exec: runLocalCreds,
|
||||
ShortHelp: "Print how to access Tailscale LocalAPI",
|
||||
},
|
||||
{
|
||||
Name: "localapi",
|
||||
ShortUsage: "tailscale debug localapi [<method>] <path> [<body| \"-\">]",
|
||||
Exec: runLocalAPI,
|
||||
ShortHelp: "Call a LocalAPI method directly",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("localapi")
|
||||
fs.BoolVar(&localAPIFlags.verbose, "v", false, "verbose; dump HTTP headers")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "restun",
|
||||
ShortUsage: "tailscale debug restun",
|
||||
@ -451,6 +462,81 @@ func runLocalCreds(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func looksLikeHTTPMethod(s string) bool {
|
||||
if len(s) > len("OPTIONS") {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r < 'A' || r > 'Z' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var localAPIFlags struct {
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func runLocalAPI(ctx context.Context, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("expected at least one argument")
|
||||
}
|
||||
method := "GET"
|
||||
if looksLikeHTTPMethod(args[0]) {
|
||||
method = args[0]
|
||||
args = args[1:]
|
||||
if len(args) == 0 {
|
||||
return errors.New("expected at least one argument after method")
|
||||
}
|
||||
}
|
||||
path := args[0]
|
||||
if !strings.HasPrefix(path, "/localapi/") {
|
||||
if !strings.Contains(path, "/") {
|
||||
path = "/localapi/v0/" + path
|
||||
} else {
|
||||
path = "/localapi/" + path
|
||||
}
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if len(args) > 1 {
|
||||
if args[1] == "-" {
|
||||
fmt.Fprintf(Stderr, "# reading request body from stdin...\n")
|
||||
all, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading Stdin: %q", err)
|
||||
}
|
||||
body = bytes.NewReader(all)
|
||||
} else {
|
||||
body = strings.NewReader(args[1])
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequest(method, "http://local-tailscaled.sock"+path, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(Stderr, "# doing request %s %s\n", method, path)
|
||||
|
||||
res, err := localClient.DoLocalRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
is2xx := res.StatusCode >= 200 && res.StatusCode <= 299
|
||||
if localAPIFlags.verbose {
|
||||
res.Write(Stdout)
|
||||
} else {
|
||||
if !is2xx {
|
||||
fmt.Fprintf(Stderr, "# Response status %s\n", res.Status)
|
||||
}
|
||||
io.Copy(Stdout, res.Body)
|
||||
}
|
||||
if is2xx {
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
|
||||
type localClientRoundTripper struct{}
|
||||
|
||||
func (localClientRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user