Skip to content

Commit 6673a1d

Browse files
committed
tailscale: polish API and documentation in preparation for tagging release
Updates tailscale/corp#21867 Signed-off-by: Percy Wegmann <[email protected]>
1 parent 08f1287 commit 6673a1d

File tree

14 files changed

+147
-72
lines changed

14 files changed

+147
-72
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
[![Go Report Card](https://goreportcard.com/badge/github.com/tailscale/tailscale-client-go)](https://goreportcard.com/report/github.com/tailscale/tailscale-client-go)
55
![Github Actions](https://github.com/tailscale/tailscale-client-go/actions/workflows/ci.yml/badge.svg?branch=master)
66

7+
DEPRECATED - V1 is no longer being maintained. The [V2 SDK](v2) provides a more complete wrapper around the V2 [Tailscale API](https://tailscale.com/api).
8+
9+
---
710

811
A client implementation for the [Tailscale](https://tailscale.com) HTTP API.
912
For more details, please see [API documentation](https://github.com/tailscale/tailscale/blob/main/api.md).
1013

11-
A [V2](v2) implementation of the client is under active development, use at your own risk and expect breaking changes.
12-
1314
# Example
1415

1516
```go

tailscale/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -537,14 +537,14 @@ type setACLParams struct {
537537
type SetACLOption func(p *setACLParams)
538538

539539
// WithETag allows passing an ETag value with Set ACL API call that
540-
// will be used in the `If-Match` HTTP request header.
540+
// will be used in the "If-Match" HTTP request header.
541541
func WithETag(etag string) SetACLOption {
542542
return func(p *setACLParams) {
543543
p.headers["If-Match"] = fmt.Sprintf("%q", etag)
544544
}
545545
}
546546

547-
// SetACL sets the ACL for the given tailnet. `acl` can either be an [ACL],
547+
// SetACL sets the ACL for the given tailnet. "acl" can either be an [ACL],
548548
// or a HuJSON string.
549549
func (c *Client) SetACL(ctx context.Context, acl any, opts ...SetACLOption) error {
550550
const uriFmt = "/api/v2/tailnet/%s/acl"
@@ -574,7 +574,7 @@ func (c *Client) SetACL(ctx context.Context, acl any, opts ...SetACLOption) erro
574574
return c.performRequest(req, nil)
575575
}
576576

577-
// ValidateACL validates the provided ACL via the API. `acl` can either be an [ACL],
577+
// ValidateACL validates the provided ACL via the API. "acl" can either be an [ACL],
578578
// or a HuJSON string.
579579
func (c *Client) ValidateACL(ctx context.Context, acl any) error {
580580
const uriFmt = "/api/v2/tailnet/%s/acl/validate"

v2/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# tailscale-client-go/v2
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/tailscale/tailscale-client-go/v2.svg)](https://pkg.go.dev/github.com/tailscale/tailscale-client-go/v2)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/tailscale/tailscale-client-go/v2)](https://goreportcard.com/report/github.com/tailscale/tailscale-client-go/v2)
5+
![Github Actions](https://github.com/tailscale/tailscale-client-go/actions/workflows/ci.yml/badge.svg?branch=main)
6+
7+
A client implementation for the [Tailscale](https://tailscale.com) HTTP API.
8+
For more details, please see [API documentation](https://tailscale.com/api).
9+
10+
## Example (Using API Key)
11+
12+
```go
13+
package main
14+
15+
import (
16+
"context"
17+
"log"
18+
"os"
19+
20+
tsclient "github.com/tailscale/tailscale-client-go/v2"
21+
)
22+
23+
func main() {
24+
apiKey := os.Getenv("TAILSCALE_API_KEY")
25+
tailnet := os.Getenv("TAILSCALE_TAILNET")
26+
27+
&tsclient.Client{
28+
APIKey: apiKey,
29+
Tailnet: tailnet,
30+
}
31+
32+
devices, err := client.Devices().List(context.Background())
33+
}
34+
```
35+
36+
## Example (Using OAuth)
37+
38+
```go
39+
package main
40+
41+
import (
42+
"context"
43+
"log"
44+
"os"
45+
46+
tsclient "github.com/tailscale/tailscale-client-go/v2"
47+
)
48+
49+
func main() {
50+
oauthClientID := os.Getenv("TAILSCALE_OAUTH_CLIENT_ID")
51+
tailnet := os.Getenv("TAILSCALE_OAUTH_CLIENT_SECRET")
52+
oauthScopes := []string{"all:write"}
53+
54+
&tsclient.Client{
55+
APIKey: apiKey,
56+
Tailnet: tailnet,
57+
}
58+
clientV2.UseOAuth(oauthClientID, oauthClientSecret, oauthScopes)
59+
60+
devices, err := client.Devices().List(context.Background())
61+
}
62+
```

v2/client.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
// Package tsclient contains a basic implementation of a client for the Tailscale HTTP api. Documentation is here:
22
// https://tailscale.com/api
3-
//
4-
// WARNING - this v2 implementation is under active development, use at your own risk and expect breaking changes.
53
package tsclient
64

75
import (
@@ -21,11 +19,11 @@ import (
2119
)
2220

2321
type (
24-
// Client type is used to perform actions against the Tailscale API.
22+
// Client is used to perform actions against the Tailscale API.
2523
Client struct {
2624
// BaseURL is the base URL for accessing the Tailscale API server. Defaults to https://api.tailscale.com.
2725
BaseURL *url.URL
28-
// UserAgent configures the User-Agent HTTP header for requests, defaults to "tailscale-client-go"
26+
// UserAgent configures the User-Agent HTTP header for requests. Defaults to "tailscale-client-go".
2927
UserAgent string
3028
// APIKey allows specifying an APIKey to use for authentication.
3129
APIKey string
@@ -34,7 +32,7 @@ type (
3432

3533
// HTTP is the [http.Client] to use for requests to the API server.
3634
// If not specified, a new [http.Client] with a Timeout of 1 minute will be used.
37-
// This will be ignored if using [Client.UseOAuth].
35+
// This will be ignored if using [Client].UseOAuth.
3836
HTTP *http.Client
3937

4038
initOnce sync.Once
@@ -66,6 +64,10 @@ type (
6664
}
6765
)
6866

67+
const defaultContentType = "application/json"
68+
const defaultHttpClientTimeout = time.Minute
69+
const defaultUserAgent = "tailscale-client-go"
70+
6971
var defaultBaseURL *url.URL
7072
var oauthRelTokenURL *url.URL
7173

@@ -82,14 +84,10 @@ func init() {
8284
}
8385
}
8486

85-
const defaultContentType = "application/json"
86-
const defaultHttpClientTimeout = time.Minute
87-
const defaultUserAgent = "tailscale-client-go"
88-
8987
// init returns a new instance of the Client type that will perform operations against a chosen tailnet and will
90-
// provide the apiKey for authorization. Additional options can be provided, see ClientOption for more details.
88+
// provide the apiKey for authorization.
9189
//
92-
// To use OAuth Client credentials, call [UseOAuth].
90+
// To use OAuth Client credentials, call [Client].UseOAuth.
9391
func (c *Client) init() {
9492
c.initOnce.Do(func() {
9593
if c.BaseURL == nil {
@@ -115,7 +113,7 @@ func (c *Client) init() {
115113
}
116114

117115
// UseOAuth configures the client to use the specified OAuth credentials.
118-
// If [Client.HTTP] was previously specified, this replaces it.
116+
// If [Client].HTTP was previously specified, this replaces it.
119117
func (c *Client) UseOAuth(clientID, clientSecret string, scopes []string) {
120118
oauthConfig := clientcredentials.Config{
121119
ClientID: clientID,
@@ -129,51 +127,61 @@ func (c *Client) UseOAuth(clientID, clientSecret string, scopes []string) {
129127
c.HTTP.Timeout = defaultHttpClientTimeout
130128
}
131129

130+
// Contacts() provides access to https://tailscale.com/api#tag/contacts.
132131
func (c *Client) Contacts() *ContactsResource {
133132
c.init()
134133
return c.contacts
135134
}
136135

136+
// DevicePosture provides access to https://tailscale.com/api#tag/deviceposture.
137137
func (c *Client) DevicePosture() *DevicePostureResource {
138138
c.init()
139139
return c.devicePosture
140140
}
141141

142+
// Devices provides access to https://tailscale.com/api#tag/devices.
142143
func (c *Client) Devices() *DevicesResource {
143144
c.init()
144145
return c.devices
145146
}
146147

148+
// DNS provides access to https://tailscale.com/api#tag/dns.
147149
func (c *Client) DNS() *DNSResource {
148150
c.init()
149151
return c.dns
150152
}
151153

154+
// Keys provides access to https://tailscale.com/api#tag/keys.
152155
func (c *Client) Keys() *KeysResource {
153156
c.init()
154157
return c.keys
155158
}
156159

160+
// Logging provides access to https://tailscale.com/api#tag/logging.
157161
func (c *Client) Logging() *LoggingResource {
158162
c.init()
159163
return c.logging
160164
}
161165

166+
// PolicyFile provides access to https://tailscale.com/api#tag/policyfile.
162167
func (c *Client) PolicyFile() *PolicyFileResource {
163168
c.init()
164169
return c.policyFile
165170
}
166171

172+
// TailnetSettings provides access to https://tailscale.com/api#tag/tailnetsettings.
167173
func (c *Client) TailnetSettings() *TailnetSettingsResource {
168174
c.init()
169175
return c.tailnetSettings
170176
}
171177

178+
// Users provides access to https://tailscale.com/api#tag/users.
172179
func (c *Client) Users() *UsersResource {
173180
c.init()
174181
return c.users
175182
}
176183

184+
// Webhooks provides access to https://tailscale.com/api#tag/webhooks.
177185
func (c *Client) Webhooks() *WebhooksResource {
178186
c.init()
179187
return c.webhooks
@@ -341,8 +349,8 @@ func IsNotFound(err error) bool {
341349
return false
342350
}
343351

344-
// ErrorData returns the contents of the APIError.Data field from the provided error if it is of type APIError. Returns
345-
// a nil slice if the given error is not of type APIError.
352+
// ErrorData returns the contents of the [APIError].Data field from the provided error if it is of type [APIError].
353+
// Returns a nil slice if the given error is not of type [APIError].
346354
func ErrorData(err error) []APIErrorData {
347355
var apiErr APIError
348356
if errors.As(err, &apiErr) {
@@ -352,7 +360,7 @@ func ErrorData(err error) []APIErrorData {
352360
return nil
353361
}
354362

355-
// Duration type wraps a time.Duration, allowing it to be JSON marshalled as a string like "20h" rather than
363+
// Duration wraps a [time.Duration], allowing it to be JSON marshalled as a string like "20h" rather than
356364
// a numeric value.
357365
type Duration time.Duration
358366

v2/contacts.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
)
77

8+
// ContactsResource provides access to https://tailscale.com/api#tag/contacts.
89
type ContactsResource struct {
910
*Client
1011
}
@@ -41,7 +42,7 @@ type (
4142
}
4243
)
4344

44-
// Contacts retieves the contact information for a tailnet.
45+
// Get retieves the [Contacts] for the tailnet.
4546
func (cr *ContactsResource) Get(ctx context.Context) (*Contacts, error) {
4647
req, err := cr.buildRequest(ctx, http.MethodGet, cr.buildTailnetURL("contacts"))
4748
if err != nil {
@@ -52,7 +53,7 @@ func (cr *ContactsResource) Get(ctx context.Context) (*Contacts, error) {
5253
return &contacts, cr.do(req, &contacts)
5354
}
5455

55-
// UpdateContact updates the email for the specified ContactType within the tailnet.
56+
// Update updates the email for the specified [ContactType] within the tailnet.
5657
// If the email address changes, the system will send a verification email to confirm the change.
5758
func (cr *ContactsResource) Update(ctx context.Context, contactType ContactType, contact UpdateContactRequest) error {
5859
req, err := cr.buildRequest(ctx, http.MethodPatch, cr.buildTailnetURL("contacts", contactType), requestBody(contact))

v2/device_posture.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
)
77

8+
// DevicePostureResource provides access to https://tailscale.com/api#tag/deviceposture.
89
type DevicePostureResource struct {
910
*Client
1011
}
@@ -50,7 +51,7 @@ type (
5051
}
5152
)
5253

53-
// List lists all configured [PostureIntegration]s.
54+
// List lists every configured [PostureIntegration].
5455
func (pr *DevicePostureResource) ListIntegrations(ctx context.Context) ([]PostureIntegration, error) {
5556
req, err := pr.buildRequest(ctx, http.MethodGet, pr.buildTailnetURL("posture", "integrations"))
5657
if err != nil {
@@ -88,7 +89,7 @@ func (pr *DevicePostureResource) UpdateIntegration(ctx context.Context, id strin
8889
return &resp, pr.do(req, &resp)
8990
}
9091

91-
// DeleteIntegration deletes the PostureIntegration identified by id.
92+
// DeleteIntegration deletes the posture integration identified by id.
9293
func (pr *DevicePostureResource) DeleteIntegration(ctx context.Context, id string) error {
9394
req, err := pr.buildRequest(ctx, http.MethodDelete, pr.buildURL("posture", "integrations", id))
9495
if err != nil {
@@ -98,7 +99,7 @@ func (pr *DevicePostureResource) DeleteIntegration(ctx context.Context, id strin
9899
return pr.do(req, nil)
99100
}
100101

101-
// GetIntegration gets the PostureIntegration identified by id.
102+
// GetIntegration gets the posture integration identified by id.
102103
func (pr *DevicePostureResource) GetIntegration(ctx context.Context, id string) (*PostureIntegration, error) {
103104
req, err := pr.buildRequest(ctx, http.MethodGet, pr.buildURL("posture", "integrations", id))
104105
if err != nil {

v2/devices.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88
)
99

10+
// DevicesResource provides access to https://tailscale.com/api#tag/devices.
1011
type DevicesResource struct {
1112
*Client
1213
}
@@ -63,7 +64,7 @@ type Device struct {
6364
UpdateAvailable bool `json:"updateAvailable"`
6465
}
6566

66-
// Get gets a single device
67+
// Get gets the [Device] identified by deviceID.
6768
func (dr *DevicesResource) Get(ctx context.Context, deviceID string) (*Device, error) {
6869
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildURL("device", deviceID))
6970
if err != nil {
@@ -74,7 +75,7 @@ func (dr *DevicesResource) Get(ctx context.Context, deviceID string) (*Device, e
7475
return &result, dr.do(req, &result)
7576
}
7677

77-
// List lists the devices in a tailnet.
78+
// List lists every [Device] in the tailnet.
7879
func (dr *DevicesResource) List(ctx context.Context) ([]Device, error) {
7980
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildTailnetURL("devices"))
8081
if err != nil {
@@ -102,7 +103,7 @@ func (dr *DevicesResource) SetAuthorized(ctx context.Context, deviceID string, a
102103
return dr.do(req, nil)
103104
}
104105

105-
// Delete deletes the device given its deviceID.
106+
// Delete deletes the device identified by deviceID.
106107
func (dr *DevicesResource) Delete(ctx context.Context, deviceID string) error {
107108
req, err := dr.buildRequest(ctx, http.MethodDelete, dr.buildURL("device", deviceID))
108109
if err != nil {
@@ -112,7 +113,7 @@ func (dr *DevicesResource) Delete(ctx context.Context, deviceID string) error {
112113
return dr.do(req, nil)
113114
}
114115

115-
// SetTags updates the tags of a target device.
116+
// SetTags updates the tags of the device identified by deviceID.
116117
func (dr *DevicesResource) SetTags(ctx context.Context, deviceID string, tags []string) error {
117118
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "tags"), requestBody(map[string][]string{
118119
"tags": tags,

0 commit comments

Comments
 (0)