Skip to content

Commit 3a9fb56

Browse files
committed
v2: add TailnetSettingsResource
Supports getting and updating Tailnet settings. Updates tailscale/corp#21628 Signed-off-by: Percy Wegmann <[email protected]>
1 parent 2a9680f commit 3a9fb56

File tree

3 files changed

+155
-9
lines changed

3 files changed

+155
-9
lines changed

v2/client.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ type (
3838
initOnce sync.Once
3939

4040
// Specific resources
41-
contacts *ContactsResource
42-
devicePosture *DevicePostureResource
43-
devices *DevicesResource
44-
dns *DNSResource
45-
keys *KeysResource
46-
logging *LoggingResource
47-
policyFile *PolicyFileResource
48-
users *UsersResource
49-
webhooks *WebhooksResource
41+
contacts *ContactsResource
42+
devicePosture *DevicePostureResource
43+
devices *DevicesResource
44+
dns *DNSResource
45+
keys *KeysResource
46+
logging *LoggingResource
47+
policyFile *PolicyFileResource
48+
tailnetSettings *TailnetSettingsResource
49+
users *UsersResource
50+
webhooks *WebhooksResource
5051
}
5152

5253
// APIError type describes an error as returned by the Tailscale API.
@@ -105,6 +106,7 @@ func (c *Client) init() {
105106
c.keys = &KeysResource{c}
106107
c.logging = &LoggingResource{c}
107108
c.policyFile = &PolicyFileResource{c}
109+
c.tailnetSettings = &TailnetSettingsResource{c}
108110
c.users = &UsersResource{c}
109111
c.webhooks = &WebhooksResource{c}
110112
})
@@ -159,6 +161,11 @@ func (c *Client) PolicyFile() *PolicyFileResource {
159161
return c.policyFile
160162
}
161163

164+
func (c *Client) TailnetSettings() *TailnetSettingsResource {
165+
c.init()
166+
return c.tailnetSettings
167+
}
168+
162169
func (c *Client) Users() *UsersResource {
163170
c.init()
164171
return c.users

v2/tailnet_settings.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package tsclient
2+
3+
import (
4+
"context"
5+
"net/http"
6+
)
7+
8+
// TailnetSettingsResource provides an API to view/control settings for a tailnet.
9+
type TailnetSettingsResource struct {
10+
*Client
11+
}
12+
13+
type (
14+
// TailnetSettings represents the current settings of a tailnet.
15+
// See https://tailscale.com/api#model/tailnetsettings.
16+
TailnetSettings struct {
17+
DevicesApprovalOn bool `json:"devicesApprovalOn"`
18+
DevicesAutoUpdatesOn bool `json:"devicesAutoUpdatesOn"`
19+
DevicesKeyDurationDays int `json:"devicesKeyDurationDays"` // days before device key expiry
20+
21+
UsersApprovalOn bool `json:"usersApprovalOn"`
22+
UsersRoleAllowedToJoinExternalTailnets RoleAllowedToJoinExternalTailnets `json:"usersRoleAllowedToJoinExternalTailnets"`
23+
24+
NetworkFlowLoggingOn bool `json:"networkFlowLoggingOn"`
25+
RegionalRoutingOn bool `json:"regionalRoutingOn"`
26+
PostureIdentityCollectionOn bool `json:"postureIdentityCollectionOn"`
27+
}
28+
29+
// UpdateTailnetSettingsRequest is a request to update the settings of a tailnet.
30+
// Nil values indicate that the existing setting should be left unchanged.
31+
UpdateTailnetSettingsRequest struct {
32+
DevicesApprovalOn *bool `json:"devicesApprovalOn,omitempty"`
33+
DevicesAutoUpdatesOn *bool `json:"devicesAutoUpdatesOn,omitempty"`
34+
DevicesKeyDurationDays *int `json:"devicesKeyDurationDays,omitempty"` // days before device key expiry
35+
36+
UsersApprovalOn *bool `json:"usersApprovalOn,omitempty"`
37+
UsersRoleAllowedToJoinExternalTailnets *RoleAllowedToJoinExternalTailnets `json:"usersRoleAllowedToJoinExternalTailnets,omitempty"`
38+
39+
NetworkFlowLoggingOn *bool `json:"networkFlowLoggingOn,omitempty"`
40+
RegionalRoutingOn *bool `json:"regionalRoutingOn,omitempty"`
41+
PostureIdentityCollectionOn *bool `json:"postureIdentityCollectionOn,omitempty"`
42+
}
43+
44+
// RoleAllowedToJoinExternalTailnets constrains which users are allowed to join external tailnets
45+
// based on their role.
46+
RoleAllowedToJoinExternalTailnets string
47+
)
48+
49+
const (
50+
RoleAllowedToJoinExternalTailnetsNone RoleAllowedToJoinExternalTailnets = "none"
51+
RoleAllowedToJoinExternalTailnetsAdmin RoleAllowedToJoinExternalTailnets = "admin"
52+
RoleAllowedToJoinExternalTailnetsMember RoleAllowedToJoinExternalTailnets = "member"
53+
)
54+
55+
// Get retrieves the current TailnetSettings.
56+
// See https://tailscale.com/api#tag/tailnetsettings/GET/tailnet/{tailnet}/settings.
57+
func (tsr *TailnetSettingsResource) Get(ctx context.Context) (*TailnetSettings, error) {
58+
req, err := tsr.buildRequest(ctx, http.MethodGet, tsr.buildTailnetURL("settings"))
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
var resp TailnetSettings
64+
return &resp, tsr.do(req, &resp)
65+
}
66+
67+
// Update updates the tailnet settings.
68+
// See https://tailscale.com/api#tag/tailnetsettings/PATCH/tailnet/{tailnet}/settings.
69+
func (tsr *TailnetSettingsResource) Update(ctx context.Context, request UpdateTailnetSettingsRequest) error {
70+
req, err := tsr.buildRequest(ctx, http.MethodPatch, tsr.buildTailnetURL("settings"), requestBody(request))
71+
if err != nil {
72+
return err
73+
}
74+
75+
return tsr.do(req, nil)
76+
}

v2/tailnet_settings_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package tsclient_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
tsclient "github.com/tailscale/tailscale-client-go/v2"
11+
)
12+
13+
func TestClient_TailnetSettings_Get(t *testing.T) {
14+
t.Parallel()
15+
16+
client, server := NewTestHarness(t)
17+
server.ResponseCode = http.StatusOK
18+
19+
expected := tsclient.TailnetSettings{
20+
DevicesApprovalOn: true,
21+
DevicesAutoUpdatesOn: true,
22+
DevicesKeyDurationDays: 5,
23+
UsersApprovalOn: true,
24+
UsersRoleAllowedToJoinExternalTailnets: tsclient.RoleAllowedToJoinExternalTailnetsMember,
25+
NetworkFlowLoggingOn: true,
26+
RegionalRoutingOn: true,
27+
PostureIdentityCollectionOn: true,
28+
}
29+
server.ResponseBody = expected
30+
31+
actual, err := client.TailnetSettings().Get(context.Background())
32+
assert.NoError(t, err)
33+
assert.Equal(t, http.MethodGet, server.Method)
34+
assert.Equal(t, "/api/v2/tailnet/example.com/settings", server.Path)
35+
assert.Equal(t, &expected, actual)
36+
}
37+
38+
func TestClient_TailnetSettings_Update(t *testing.T) {
39+
t.Parallel()
40+
41+
client, server := NewTestHarness(t)
42+
server.ResponseCode = http.StatusOK
43+
server.ResponseBody = nil
44+
45+
updateRequest := tsclient.UpdateTailnetSettingsRequest{
46+
DevicesApprovalOn: tsclient.PointerTo(true),
47+
DevicesAutoUpdatesOn: tsclient.PointerTo(true),
48+
DevicesKeyDurationDays: tsclient.PointerTo(5),
49+
UsersApprovalOn: tsclient.PointerTo(true),
50+
UsersRoleAllowedToJoinExternalTailnets: tsclient.PointerTo(tsclient.RoleAllowedToJoinExternalTailnetsMember),
51+
NetworkFlowLoggingOn: tsclient.PointerTo(true),
52+
RegionalRoutingOn: tsclient.PointerTo(true),
53+
PostureIdentityCollectionOn: tsclient.PointerTo(true),
54+
}
55+
err := client.TailnetSettings().Update(context.Background(), updateRequest)
56+
assert.NoError(t, err)
57+
assert.Equal(t, http.MethodPatch, server.Method)
58+
assert.Equal(t, "/api/v2/tailnet/example.com/settings", server.Path)
59+
var receivedRequest tsclient.UpdateTailnetSettingsRequest
60+
err = json.Unmarshal(server.Body.Bytes(), &receivedRequest)
61+
assert.NoError(t, err)
62+
assert.EqualValues(t, updateRequest, receivedRequest)
63+
}

0 commit comments

Comments
 (0)