client/tailscale,cmd/k8s-operator,internal/client/tailscale: move VIP service client methods into internal control client

Updates tailscale/corp#22748

Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
Percy Wegmann
2025-02-12 10:34:28 -06:00
committed by Percy Wegmann
parent 9c731b848b
commit 1f1a26776b
13 changed files with 222 additions and 188 deletions

View File

@@ -83,7 +83,7 @@ func (c *Client) ACL(ctx context.Context) (acl *ACL, err error) {
}
}()
path := fmt.Sprintf("%s/api/v2/tailnet/%s/acl", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("acl")
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if err != nil {
return nil, err
@@ -97,7 +97,7 @@ func (c *Client) ACL(ctx context.Context) (acl *ACL, err error) {
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
// Otherwise, try to decode the response.
@@ -126,7 +126,7 @@ func (c *Client) ACLHuJSON(ctx context.Context) (acl *ACLHuJSON, err error) {
}
}()
path := fmt.Sprintf("%s/api/v2/tailnet/%s/acl?details=1", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("acl?details=1")
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if err != nil {
return nil, err
@@ -138,7 +138,7 @@ func (c *Client) ACLHuJSON(ctx context.Context) (acl *ACLHuJSON, err error) {
}
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
data := struct {
@@ -184,7 +184,7 @@ func (e ACLTestError) Error() string {
}
func (c *Client) aclPOSTRequest(ctx context.Context, body []byte, avoidCollisions bool, etag, acceptHeader string) ([]byte, string, error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/acl", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("acl")
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(body))
if err != nil {
return nil, "", err
@@ -328,7 +328,7 @@ type ACLPreview struct {
}
func (c *Client) previewACLPostRequest(ctx context.Context, body []byte, previewType string, previewFor string) (res *ACLPreviewResponse, err error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/acl/preview", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("acl/preview")
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(body))
if err != nil {
return nil, err
@@ -350,7 +350,7 @@ func (c *Client) previewACLPostRequest(ctx context.Context, body []byte, preview
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
if err = json.Unmarshal(b, &res); err != nil {
return nil, err
@@ -488,7 +488,7 @@ func (c *Client) ValidateACLJSON(ctx context.Context, source, dest string) (test
return nil, err
}
path := fmt.Sprintf("%s/api/v2/tailnet/%s/acl/validate", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("acl/validate")
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewBuffer(postData))
if err != nil {
return nil, err

View File

@@ -131,7 +131,7 @@ func (c *Client) Devices(ctx context.Context, fields *DeviceFieldsOpts) (deviceL
}
}()
path := fmt.Sprintf("%s/api/v2/tailnet/%s/devices", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("devices")
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if err != nil {
return nil, err
@@ -149,7 +149,7 @@ func (c *Client) Devices(ctx context.Context, fields *DeviceFieldsOpts) (deviceL
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
var devices GetDevicesResponse
@@ -188,7 +188,7 @@ func (c *Client) Device(ctx context.Context, deviceID string, fields *DeviceFiel
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
err = json.Unmarshal(b, &device)
@@ -221,7 +221,7 @@ func (c *Client) DeleteDevice(ctx context.Context, deviceID string) (err error)
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return handleErrorResponse(b, resp)
return HandleErrorResponse(b, resp)
}
return nil
}
@@ -253,7 +253,7 @@ func (c *Client) SetAuthorized(ctx context.Context, deviceID string, authorized
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return handleErrorResponse(b, resp)
return HandleErrorResponse(b, resp)
}
return nil
@@ -281,7 +281,7 @@ func (c *Client) SetTags(ctx context.Context, deviceID string, tags []string) er
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return handleErrorResponse(b, resp)
return HandleErrorResponse(b, resp)
}
return nil

View File

@@ -44,7 +44,7 @@ type DNSPreferences struct {
}
func (c *Client) dnsGETRequest(ctx context.Context, endpoint string) ([]byte, error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
path := c.BuildTailnetURL("dns", endpoint)
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if err != nil {
return nil, err
@@ -57,14 +57,14 @@ func (c *Client) dnsGETRequest(ctx context.Context, endpoint string) ([]byte, er
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
return b, nil
}
func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData any) ([]byte, error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/dns/%s", c.baseURL(), c.tailnet, endpoint)
path := c.BuildTailnetURL("dns", endpoint)
data, err := json.Marshal(&postData)
if err != nil {
return nil, err
@@ -84,7 +84,7 @@ func (c *Client) dnsPOSTRequest(ctx context.Context, endpoint string, postData a
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
return b, nil

View File

@@ -40,7 +40,7 @@ type KeyDeviceCreateCapabilities struct {
// Keys returns the list of keys for the current user.
func (c *Client) Keys(ctx context.Context) ([]string, error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("keys")
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if err != nil {
return nil, err
@@ -51,7 +51,7 @@ func (c *Client) Keys(ctx context.Context) ([]string, error) {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
var keys struct {
@@ -99,7 +99,7 @@ func (c *Client) CreateKeyWithExpiry(ctx context.Context, caps KeyCapabilities,
return "", nil, err
}
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys", c.baseURL(), c.tailnet)
path := c.BuildTailnetURL("keys")
req, err := http.NewRequestWithContext(ctx, "POST", path, bytes.NewReader(bs))
if err != nil {
return "", nil, err
@@ -110,7 +110,7 @@ func (c *Client) CreateKeyWithExpiry(ctx context.Context, caps KeyCapabilities,
return "", nil, err
}
if resp.StatusCode != http.StatusOK {
return "", nil, handleErrorResponse(b, resp)
return "", nil, HandleErrorResponse(b, resp)
}
var key struct {
@@ -126,7 +126,7 @@ func (c *Client) CreateKeyWithExpiry(ctx context.Context, caps KeyCapabilities,
// Key returns the metadata for the given key ID. Currently, capabilities are
// only returned for auth keys, API keys only return general metadata.
func (c *Client) Key(ctx context.Context, id string) (*Key, error) {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys/%s", c.baseURL(), c.tailnet, id)
path := c.BuildTailnetURL("keys", id)
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
if err != nil {
return nil, err
@@ -137,7 +137,7 @@ func (c *Client) Key(ctx context.Context, id string) (*Key, error) {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
var key Key
@@ -149,7 +149,7 @@ func (c *Client) Key(ctx context.Context, id string) (*Key, error) {
// DeleteKey deletes the key with the given ID.
func (c *Client) DeleteKey(ctx context.Context, id string) error {
path := fmt.Sprintf("%s/api/v2/tailnet/%s/keys/%s", c.baseURL(), c.tailnet, id)
path := c.BuildTailnetURL("keys", id)
req, err := http.NewRequestWithContext(ctx, "DELETE", path, nil)
if err != nil {
return err
@@ -160,7 +160,7 @@ func (c *Client) DeleteKey(ctx context.Context, id string) error {
return err
}
if resp.StatusCode != http.StatusOK {
return handleErrorResponse(b, resp)
return HandleErrorResponse(b, resp)
}
return nil
}

View File

@@ -44,7 +44,7 @@ func (c *Client) Routes(ctx context.Context, deviceID string) (routes *Routes, e
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
var sr Routes
@@ -84,7 +84,7 @@ func (c *Client) SetRoutes(ctx context.Context, deviceID string, subnets []netip
// If status code was not successful, return the error.
// TODO: Change the check for the StatusCode to include other 2XX success codes.
if resp.StatusCode != http.StatusOK {
return nil, handleErrorResponse(b, resp)
return nil, HandleErrorResponse(b, resp)
}
var srr *Routes

View File

@@ -9,7 +9,6 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"tailscale.com/util/httpm"
)
@@ -22,7 +21,7 @@ func (c *Client) TailnetDeleteRequest(ctx context.Context, tailnetID string) (er
}
}()
path := fmt.Sprintf("%s/api/v2/tailnet/%s", c.baseURL(), url.PathEscape(string(tailnetID)))
path := c.BuildTailnetURL("tailnet")
req, err := http.NewRequestWithContext(ctx, httpm.DELETE, path, nil)
if err != nil {
return err
@@ -35,7 +34,7 @@ func (c *Client) TailnetDeleteRequest(ctx context.Context, tailnetID string) (er
}
if resp.StatusCode != http.StatusOK {
return handleErrorResponse(b, resp)
return HandleErrorResponse(b, resp)
}
return nil

View File

@@ -17,6 +17,8 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"path"
)
// I_Acknowledge_This_API_Is_Unstable must be set true to use this package
@@ -63,6 +65,36 @@ func (c *Client) httpClient() *http.Client {
return http.DefaultClient
}
// BuildURL builds a url to http(s)://<apiserver>/api/v2/<slash-separated-pathElements>
// using the given pathElements. It url escapes each path element, so the caller
// doesn't need to worry about that.
//
// For example, BuildURL(devices, 5) with the default server URL would result in
// https://api.tailscale.com/api/v2/devices/5.
func (c *Client) BuildURL(pathElements ...any) string {
elem := make([]string, 2, len(pathElements)+1)
elem[0] = c.baseURL()
elem[1] = "/api/v2"
for _, pathElement := range pathElements {
elem = append(elem, url.PathEscape(fmt.Sprint(pathElement)))
}
return path.Join(elem...)
}
// BuildTailnetURL builds a url to http(s)://<apiserver>/api/v2/tailnet/<tailnet>/<slash-separated-pathElements>
// using the given pathElements. It url escapes each path element, so the
// caller doesn't need to worry about that.
//
// For example, BuildTailnetURL(policy, validate) with the default server URL and a tailnet of "example.com"
// would result in https://api.tailscale.com/api/v2/tailnet/example.com/policy/validate.
func (c *Client) BuildTailnetURL(pathElements ...any) string {
allElements := make([]any, 3, len(pathElements)+2)
allElements[0] = "tailnet"
allElements[1] = c.Tailnet
allElements = append(allElements, pathElements...)
return c.BuildURL(allElements...)
}
func (c *Client) baseURL() string {
if c.BaseURL != "" {
return c.BaseURL
@@ -150,9 +182,11 @@ func (e ErrResponse) Error() string {
return fmt.Sprintf("Status: %d, Message: %q", e.Status, e.Message)
}
// handleErrorResponse decodes the error message from the server and returns
// HandleErrorResponse decodes the error message from the server and returns
// an ErrResponse from it.
func handleErrorResponse(b []byte, resp *http.Response) error {
//
// Deprecated: use tailscale.com/client/tailscale/v2 instead.
func HandleErrorResponse(b []byte, resp *http.Response) error {
var errResp ErrResponse
if err := json.Unmarshal(b, &errResp); err != nil {
return err