client/tailscale: factor out some helpers to reduce boilerplate

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1670/head
Brad Fitzpatrick 4 years ago
parent d4b609e138
commit 03be116997

@ -64,9 +64,8 @@ func DoLocalRequest(req *http.Request) (*http.Response, error) {
return tsClient.Do(req) return tsClient.Do(req)
} }
// WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port. func send(ctx context.Context, method, path string, wantStatus int, body io.Reader) ([]byte, error) {
func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, error) { req, err := http.NewRequestWithContext(ctx, method, "http://local-tailscaled.sock"+path, body)
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -75,60 +74,47 @@ func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, erro
return nil, err return nil, err
} }
defer res.Body.Close() defer res.Body.Close()
slurp, _ := ioutil.ReadAll(res.Body) slurp, err := ioutil.ReadAll(res.Body)
if res.StatusCode != 200 { if err != nil {
return nil, fmt.Errorf("HTTP %s: %s", res.Status, slurp) return nil, err
} }
r := new(tailcfg.WhoIsResponse) if res.StatusCode != wantStatus {
if err := json.Unmarshal(slurp, r); err != nil { return nil, fmt.Errorf("HTTP %s: %s (expected %v)", res.Status, slurp, wantStatus)
if max := 200; len(slurp) > max {
slurp = slurp[:max]
} }
return nil, fmt.Errorf("failed to parse JSON WhoIsResponse from %q", slurp) return slurp, nil
} }
return r, nil
func get200(ctx context.Context, path string) ([]byte, error) {
return send(ctx, "GET", path, 200, nil)
} }
// Goroutines returns a dump of the Tailscale daemon's current goroutines. // WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port.
func Goroutines(ctx context.Context) ([]byte, error) { func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/goroutines", nil) body, err := get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr))
if err != nil { if err != nil {
return nil, err return nil, err
} }
res, err := DoLocalRequest(req) r := new(tailcfg.WhoIsResponse)
if err != nil { if err := json.Unmarshal(body, r); err != nil {
return nil, err if max := 200; len(body) > max {
body = append(body[:max], "..."...)
} }
defer res.Body.Close() return nil, fmt.Errorf("failed to parse JSON WhoIsResponse from %q", body)
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
} }
if res.StatusCode != 200 { return r, nil
return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
} }
return body, nil
// Goroutines returns a dump of the Tailscale daemon's current goroutines.
func Goroutines(ctx context.Context) ([]byte, error) {
return get200(ctx, "/localapi/v0/goroutines")
} }
// BugReport logs and returns a log marker that can be shared by the user with support. // BugReport logs and returns a log marker that can be shared by the user with support.
func BugReport(ctx context.Context, note string) (string, error) { func BugReport(ctx context.Context, note string) (string, error) {
u := fmt.Sprintf("http://local-tailscaled.sock/localapi/v0/bugreport?note=%s", url.QueryEscape(note)) body, err := send(ctx, "POST", "/localapi/v0/bugreport?note="+url.QueryEscape(note), 200, nil)
req, err := http.NewRequestWithContext(ctx, "POST", u, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
res, err := DoLocalRequest(req)
if err != nil {
return "", err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
if res.StatusCode != 200 {
return "", fmt.Errorf("HTTP %s: %s", res.Status, body)
}
return strings.TrimSpace(string(body)), nil return strings.TrimSpace(string(body)), nil
} }
@ -143,21 +129,12 @@ func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
} }
func status(ctx context.Context, queryString string) (*ipnstate.Status, error) { func status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status"+queryString, nil) body, err := get200(ctx, "/localapi/v0/status"+queryString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
res, err := DoLocalRequest(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
}
st := new(ipnstate.Status) st := new(ipnstate.Status)
if err := json.NewDecoder(res.Body).Decode(st); err != nil { if err := json.Unmarshal(body, st); err != nil {
return nil, err return nil, err
} }
return st, nil return st, nil
@ -169,42 +146,21 @@ type WaitingFile struct {
} }
func WaitingFiles(ctx context.Context) ([]WaitingFile, error) { func WaitingFiles(ctx context.Context) ([]WaitingFile, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/files/", nil) body, err := get200(ctx, "/localapi/v0/files/")
if err != nil { if err != nil {
return nil, err return nil, err
} }
res, err := DoLocalRequest(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
}
var wfs []WaitingFile var wfs []WaitingFile
if err := json.NewDecoder(res.Body).Decode(&wfs); err != nil { if err := json.Unmarshal(body, &wfs); err != nil {
return nil, err return nil, err
} }
return wfs, nil return wfs, nil
} }
func DeleteWaitingFile(ctx context.Context, baseName string) error { func DeleteWaitingFile(ctx context.Context, baseName string) error {
req, err := http.NewRequestWithContext(ctx, "DELETE", "http://local-tailscaled.sock/localapi/v0/files/"+url.PathEscape(baseName), nil) _, err := send(ctx, "DELETE", "/localapi/v0/files/"+url.PathEscape(baseName), http.StatusNoContent, nil)
if err != nil {
return err
}
res, err := DoLocalRequest(req)
if err != nil {
return err return err
} }
defer res.Body.Close()
if res.StatusCode != http.StatusNoContent {
body, _ := ioutil.ReadAll(res.Body)
return fmt.Errorf("expected 204 No Content; got HTTP %s: %s", res.Status, body)
}
return nil
}
func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, size int64, err error) { func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, size int64, err error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/files/"+url.PathEscape(baseName), nil) req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/files/"+url.PathEscape(baseName), nil)
@ -228,24 +184,14 @@ func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, siz
} }
func CheckIPForwarding(ctx context.Context) error { func CheckIPForwarding(ctx context.Context) error {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/check-ip-forwarding", nil) body, err := get200(ctx, "/localapi/v0/check-ip-forwarding")
if err != nil { if err != nil {
return err return err
} }
res, err := DoLocalRequest(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
res.Body.Close()
return fmt.Errorf("HTTP %s: %s", res.Status, body)
}
var jres struct { var jres struct {
Warning string Warning string
} }
if err := json.NewDecoder(res.Body).Decode(&jres); err != nil { if err := json.Unmarshal(body, &jres); err != nil {
return fmt.Errorf("invalid JSON from check-ip-forwarding: %w", err) return fmt.Errorf("invalid JSON from check-ip-forwarding: %w", err)
} }
if jres.Warning != "" { if jres.Warning != "" {

Loading…
Cancel
Save