Skip to content

Commit 0b5de5f

Browse files
authored
tailscale: support a custom user-agent (#57)
This allows specifying a customer user-agent used for outgoing HTTP requests.
1 parent 773ff9c commit 0b5de5f

File tree

3 files changed

+47
-6
lines changed

3 files changed

+47
-6
lines changed

tailscale/client.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
type (
2222
// Client type is used to perform actions against the Tailscale API.
2323
Client struct {
24-
apiKey string
25-
http *http.Client
26-
baseURL *url.URL
27-
tailnet string
24+
apiKey string
25+
http *http.Client
26+
baseURL *url.URL
27+
tailnet string
28+
userAgent string // empty string means Go's default value.
2829
}
2930

3031
// APIError type describes an error as returned by the Tailscale API.
@@ -47,6 +48,7 @@ type (
4748
const baseURL = "https://api.tailscale.com"
4849
const contentType = "application/json"
4950
const defaultHttpClientTimeout = time.Minute
51+
const defaultUserAgent = "tailscale-client-go"
5052

5153
// NewClient returns a new instance of the Client type that will perform operations against a chosen tailnet and will
5254
// provide the apiKey for authorization. Additional options can be provided, see ClientOption for more details.
@@ -65,8 +67,9 @@ func NewClient(apiKey, tailnet string, options ...ClientOption) (*Client, error)
6567
}
6668

6769
c := &Client{
68-
baseURL: u,
69-
tailnet: tailnet,
70+
baseURL: u,
71+
tailnet: tailnet,
72+
userAgent: defaultUserAgent,
7073
}
7174

7275
if apiKey != "" {
@@ -122,6 +125,15 @@ func WithOAuthClientCredentials(clientID, clientSecret string, scopes []string)
122125
}
123126
}
124127

128+
// WithUserAgent sets a custom User-Agent header in HTTP requests.
129+
// Passing an empty string will make the client use Go's default value.
130+
func WithUserAgent(ua string) ClientOption {
131+
return func(c *Client) error {
132+
c.userAgent = ua
133+
return nil
134+
}
135+
}
136+
125137
// TODO: consider setting `headers` and `body` via opts to decrease the number of arguments.
126138
func (c *Client) buildRequest(ctx context.Context, method, uri string, headers map[string]string, body interface{}) (*http.Request, error) {
127139
u, err := c.baseURL.Parse(uri)
@@ -142,6 +154,10 @@ func (c *Client) buildRequest(ctx context.Context, method, uri string, headers m
142154
return nil, err
143155
}
144156

157+
if c.userAgent != "" {
158+
req.Header.Set("User-Agent", c.userAgent)
159+
}
160+
145161
for k, v := range headers {
146162
req.Header.Set(k, v)
147163
}

tailscale/client_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,3 +952,25 @@ func TestClient_ValidateACL(t *testing.T) {
952952
assert.EqualValues(t, http.MethodPost, server.Method)
953953
assert.EqualValues(t, "/api/v2/tailnet/example.com/acl/validate", server.Path)
954954
}
955+
956+
func TestClient_UserAgent(t *testing.T) {
957+
t.Parallel()
958+
client, server := NewTestHarness(t)
959+
server.ResponseCode = http.StatusOK
960+
961+
// Check the default user-agent.
962+
assert.NoError(t, client.SetDeviceAuthorized(context.Background(), "test", true))
963+
assert.Equal(t, "tailscale-client-go", server.Header.Get("User-Agent"))
964+
965+
// Check a custom user-agent.
966+
client, err := tailscale.NewClient("fake key", "", tailscale.WithBaseURL(server.BaseURL), tailscale.WithUserAgent("custom-user-agent"))
967+
assert.NoError(t, err)
968+
assert.NoError(t, client.SetDeviceAuthorized(context.Background(), "test", true))
969+
assert.Equal(t, "custom-user-agent", server.Header.Get("User-Agent"))
970+
971+
// Overriding with an empty string uses runtime's default value.
972+
client, err = tailscale.NewClient("fake key", "", tailscale.WithBaseURL(server.BaseURL), tailscale.WithUserAgent(""))
973+
assert.NoError(t, err)
974+
assert.NoError(t, client.SetDeviceAuthorized(context.Background(), "test", true))
975+
assert.Contains(t, server.Header.Get("User-Agent"), "Go-http-client")
976+
}

tailscale/tailscale_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
type TestServer struct {
1818
t *testing.T
1919

20+
BaseURL string
21+
2022
Method string
2123
Path string
2224
Body *bytes.Buffer
@@ -53,6 +55,7 @@ func NewTestHarness(t *testing.T) (*tailscale.Client, *TestServer) {
5355
})
5456

5557
baseURL := fmt.Sprintf("http://localhost:%v", listener.Addr().(*net.TCPAddr).Port)
58+
testServer.BaseURL = baseURL
5659
client, err := tailscale.NewClient("not a real key", "example.com", tailscale.WithBaseURL(baseURL))
5760
assert.NoError(t, err)
5861

0 commit comments

Comments
 (0)