diff --git a/client/tailscale/localclient.go b/client/tailscale/localclient.go index 1d65dee2d..e70f6b15a 100644 --- a/client/tailscale/localclient.go +++ b/client/tailscale/localclient.go @@ -231,20 +231,21 @@ func WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, erro return defaultLocalClient.WhoIs(ctx, remoteAddr) } +func decodeJSON[T any](b []byte) (ret T, err error) { + if err := json.Unmarshal(b, &ret); err != nil { + var zero T + return zero, fmt.Errorf("failed to unmarshal JSON into %T: %w", ret, err) + } + return ret, nil +} + // WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port. func (lc *LocalClient) WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) { body, err := lc.get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr)) if err != nil { return nil, err } - r := new(apitype.WhoIsResponse) - if err := json.Unmarshal(body, r); err != nil { - if max := 200; len(body) > max { - body = append(body[:max], "..."...) - } - return nil, fmt.Errorf("failed to parse JSON WhoIsResponse from %q", body) - } - return r, nil + return decodeJSON[*apitype.WhoIsResponse](body) } // Goroutines returns a dump of the Tailscale daemon's current goroutines. @@ -411,11 +412,7 @@ func (lc *LocalClient) status(ctx context.Context, queryString string) (*ipnstat if err != nil { return nil, err } - st := new(ipnstate.Status) - if err := json.Unmarshal(body, st); err != nil { - return nil, err - } - return st, nil + return decodeJSON[*ipnstate.Status](body) } // IDToken is a request to get an OIDC ID token for an audience. @@ -426,11 +423,7 @@ func (lc *LocalClient) IDToken(ctx context.Context, aud string) (*tailcfg.TokenR if err != nil { return nil, err } - tr := new(tailcfg.TokenResponse) - if err := json.Unmarshal(body, tr); err != nil { - return nil, err - } - return tr, nil + return decodeJSON[*tailcfg.TokenResponse](body) } func (lc *LocalClient) WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) { @@ -438,11 +431,7 @@ func (lc *LocalClient) WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, if err != nil { return nil, err } - var wfs []apitype.WaitingFile - if err := json.Unmarshal(body, &wfs); err != nil { - return nil, err - } - return wfs, nil + return decodeJSON[[]apitype.WaitingFile](body) } func (lc *LocalClient) DeleteWaitingFile(ctx context.Context, baseName string) error { @@ -476,11 +465,7 @@ func (lc *LocalClient) FileTargets(ctx context.Context) ([]apitype.FileTarget, e if err != nil { return nil, err } - var fts []apitype.FileTarget - if err := json.Unmarshal(body, &fts); err != nil { - return nil, fmt.Errorf("invalid JSON: %w", err) - } - return fts, nil + return decodeJSON[[]apitype.FileTarget](body) } // PushFile sends Taildrop file r to target. @@ -555,11 +540,7 @@ func (lc *LocalClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn if err != nil { return nil, err } - var p ipn.Prefs - if err := json.Unmarshal(body, &p); err != nil { - return nil, fmt.Errorf("invalid prefs JSON: %w", err) - } - return &p, nil + return decodeJSON[*ipn.Prefs](body) } func (lc *LocalClient) Logout(ctx context.Context) error { @@ -765,11 +746,7 @@ func (lc *LocalClient) Ping(ctx context.Context, ip netip.Addr, pingtype tailcfg if err != nil { return nil, fmt.Errorf("error %w: %s", err, body) } - pr := new(ipnstate.PingResult) - if err := json.Unmarshal(body, pr); err != nil { - return nil, err - } - return pr, nil + return decodeJSON[*ipnstate.PingResult](body) } // NetworkLockStatus fetches information about the tailnet key authority, if one is configured. @@ -778,11 +755,7 @@ func (lc *LocalClient) NetworkLockStatus(ctx context.Context) (*ipnstate.Network if err != nil { return nil, fmt.Errorf("error: %w", err) } - pr := new(ipnstate.NetworkLockStatus) - if err := json.Unmarshal(body, pr); err != nil { - return nil, err - } - return pr, nil + return decodeJSON[*ipnstate.NetworkLockStatus](body) } // NetworkLockInit initializes the tailnet key authority. @@ -803,12 +776,7 @@ func (lc *LocalClient) NetworkLockInit(ctx context.Context, keys []tka.Key, disa if err != nil { return nil, fmt.Errorf("error: %w", err) } - - pr := new(ipnstate.NetworkLockStatus) - if err := json.Unmarshal(body, pr); err != nil { - return nil, err - } - return pr, nil + return decodeJSON[*ipnstate.NetworkLockStatus](body) } // NetworkLockModify adds and/or removes key(s) to the tailnet key authority. @@ -827,12 +795,7 @@ func (lc *LocalClient) NetworkLockModify(ctx context.Context, addKeys, removeKey if err != nil { return nil, fmt.Errorf("error: %w", err) } - - pr := new(ipnstate.NetworkLockStatus) - if err := json.Unmarshal(body, pr); err != nil { - return nil, err - } - return pr, nil + return decodeJSON[*ipnstate.NetworkLockStatus](body) } // NetworkLockSign signs the specified node-key and transmits that signature to the control plane.