Skip to content

Commit 6aa69f5

Browse files
afiuneoxtoacart
authored andcommitted
⭐️ Add NodeID to Device struct
Replaces tailscale/tailscale-client-go#134 The Tailscale API specification mentions that the `id` of a device is a legacy identifier for a device, instead users should use `nodeId`. https://tailscale.com/api#tag/devices/GET/tailnet/{tailnet}/devices This change adds the preferred identifier for a device; `nodeId`. Signed-off-by: Salim Afiune Maya <[email protected]>
1 parent df6d4a0 commit 6aa69f5

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

devices.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ func (t *Time) UnmarshalJSON(data []byte) error {
4747
type Device struct {
4848
Addresses []string `json:"addresses"`
4949
Name string `json:"name"`
50-
ID string `json:"id"`
50+
ID string `json:"id"` // The legacy identifier for a device. Use NodeId instead.
51+
NodeID string `json:"nodeId"` // The preferred identifier for a device.
5152
Authorized bool `json:"authorized"`
5253
User string `json:"user"`
5354
Tags []string `json:"tags"`
@@ -79,6 +80,8 @@ type DevicePostureAttributeRequest struct {
7980
}
8081

8182
// Get gets the [Device] identified by deviceID.
83+
//
84+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
8285
func (dr *DevicesResource) Get(ctx context.Context, deviceID string) (*Device, error) {
8386
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildURL("device", deviceID))
8487
if err != nil {
@@ -89,6 +92,8 @@ func (dr *DevicesResource) Get(ctx context.Context, deviceID string) (*Device, e
8992
}
9093

9194
// GetPostureAttributes retrieves the posture attributes of the device identified by deviceID.
95+
//
96+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
9297
func (dr *DevicesResource) GetPostureAttributes(ctx context.Context, deviceID string) (*DevicePostureAttributes, error) {
9398
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildURL("device", deviceID, "attributes"))
9499
if err != nil {
@@ -99,6 +104,8 @@ func (dr *DevicesResource) GetPostureAttributes(ctx context.Context, deviceID st
99104
}
100105

101106
// SetPostureAttribute sets the posture attribute of the device identified by deviceID.
107+
//
108+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
102109
func (dr *DevicesResource) SetPostureAttribute(ctx context.Context, deviceID, attributeKey string, request DevicePostureAttributeRequest) error {
103110
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "attributes", attributeKey), requestBody(request))
104111
if err != nil {
@@ -125,6 +132,8 @@ func (dr *DevicesResource) List(ctx context.Context) ([]Device, error) {
125132
}
126133

127134
// SetAuthorized marks the specified device as authorized or not.
135+
//
136+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
128137
func (dr *DevicesResource) SetAuthorized(ctx context.Context, deviceID string, authorized bool) error {
129138
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "authorized"), requestBody(map[string]bool{
130139
"authorized": authorized,
@@ -137,6 +146,8 @@ func (dr *DevicesResource) SetAuthorized(ctx context.Context, deviceID string, a
137146
}
138147

139148
// Delete deletes the device identified by deviceID.
149+
//
150+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
140151
func (dr *DevicesResource) Delete(ctx context.Context, deviceID string) error {
141152
req, err := dr.buildRequest(ctx, http.MethodDelete, dr.buildURL("device", deviceID))
142153
if err != nil {
@@ -147,6 +158,8 @@ func (dr *DevicesResource) Delete(ctx context.Context, deviceID string) error {
147158
}
148159

149160
// SetName updates the name of the device identified by deviceID.
161+
//
162+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
150163
func (dr *DevicesResource) SetName(ctx context.Context, deviceID, name string) error {
151164
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "name"), requestBody(map[string]string{
152165
"name": name,
@@ -159,6 +172,8 @@ func (dr *DevicesResource) SetName(ctx context.Context, deviceID, name string) e
159172
}
160173

161174
// SetTags updates the tags of the device identified by deviceID.
175+
//
176+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
162177
func (dr *DevicesResource) SetTags(ctx context.Context, deviceID string, tags []string) error {
163178
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "tags"), requestBody(map[string][]string{
164179
"tags": tags,
@@ -177,6 +192,8 @@ type DeviceKey struct {
177192
}
178193

179194
// SetKey updates the properties of a device's key.
195+
//
196+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
180197
func (dr *DevicesResource) SetKey(ctx context.Context, deviceID string, key DeviceKey) error {
181198
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "key"), requestBody(key))
182199
if err != nil {
@@ -187,6 +204,8 @@ func (dr *DevicesResource) SetKey(ctx context.Context, deviceID string, key Devi
187204
}
188205

189206
// SetDeviceIPv4Address sets the Tailscale IPv4 address of the device.
207+
//
208+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
190209
func (dr *DevicesResource) SetIPv4Address(ctx context.Context, deviceID string, ipv4Address string) error {
191210
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "ip"), requestBody(map[string]string{
192211
"ipv4": ipv4Address,
@@ -200,6 +219,8 @@ func (dr *DevicesResource) SetIPv4Address(ctx context.Context, deviceID string,
200219

201220
// SetSubnetRoutes sets which subnet routes are enabled to be routed by a device by replacing the existing list
202221
// of subnet routes with the supplied routes. Routes can be enabled without a device advertising them (e.g. for preauth).
222+
//
223+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
203224
func (dr *DevicesResource) SetSubnetRoutes(ctx context.Context, deviceID string, routes []string) error {
204225
req, err := dr.buildRequest(ctx, http.MethodPost, dr.buildURL("device", deviceID, "routes"), requestBody(map[string][]string{
205226
"routes": routes,
@@ -214,6 +235,8 @@ func (dr *DevicesResource) SetSubnetRoutes(ctx context.Context, deviceID string,
214235
// SubnetRoutes Retrieves the list of subnet routes that a device is advertising, as well as those that are
215236
// enabled for it. Enabled routes are not necessarily advertised (e.g. for pre-enabling), and likewise, advertised
216237
// routes are not necessarily enabled.
238+
//
239+
// Using the device `NodeID` is preferred, but its numeric `ID` value can also be used.
217240
func (dr *DevicesResource) SubnetRoutes(ctx context.Context, deviceID string) (*DeviceRoutes, error) {
218241
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildURL("device", deviceID, "routes"))
219242
if err != nil {

devices_test.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ func TestClient_Devices_Get(t *testing.T) {
4343
expectedDevice := &Device{
4444
Addresses: []string{"127.0.0.1"},
4545
Name: "test",
46-
ID: "testid",
46+
ID: "12345",
47+
NodeID: "nTESTJ31",
4748
Authorized: true,
4849
KeyExpiryDisabled: true,
4950
@@ -69,11 +70,21 @@ func TestClient_Devices_Get(t *testing.T) {
6970
server.ResponseCode = http.StatusOK
7071
server.ResponseBody = expectedDevice
7172

72-
actualDevice, err := client.Devices().Get(context.Background(), "testid")
73-
assert.NoError(t, err)
74-
assert.Equal(t, http.MethodGet, server.Method)
75-
assert.Equal(t, "/api/v2/device/testid", server.Path)
76-
assert.EqualValues(t, expectedDevice, actualDevice)
73+
t.Run("using legacy id", func(t *testing.T) {
74+
actualDevice, err := client.Devices().Get(context.Background(), "12345")
75+
assert.NoError(t, err)
76+
assert.Equal(t, http.MethodGet, server.Method)
77+
assert.Equal(t, "/api/v2/device/12345", server.Path)
78+
assert.EqualValues(t, expectedDevice, actualDevice)
79+
})
80+
81+
t.Run("using preferred nodeId", func(t *testing.T) {
82+
actualDevice, err := client.Devices().Get(context.Background(), "nTESTJ31")
83+
assert.NoError(t, err)
84+
assert.Equal(t, http.MethodGet, server.Method)
85+
assert.Equal(t, "/api/v2/device/nTESTJ31", server.Path)
86+
assert.EqualValues(t, expectedDevice, actualDevice)
87+
})
7788
}
7889

7990
func TestClient_Devices_GetPostureAttributes(t *testing.T) {
@@ -97,10 +108,10 @@ func TestClient_Devices_GetPostureAttributes(t *testing.T) {
97108
server.ResponseCode = http.StatusOK
98109
server.ResponseBody = expectedAttributes
99110

100-
actualAttributes, err := client.Devices().GetPostureAttributes(context.Background(), "testid")
111+
actualAttributes, err := client.Devices().GetPostureAttributes(context.Background(), "12345")
101112
assert.NoError(t, err)
102113
assert.Equal(t, http.MethodGet, server.Method)
103-
assert.Equal(t, "/api/v2/device/testid/attributes", server.Path)
114+
assert.Equal(t, "/api/v2/device/12345/attributes", server.Path)
104115

105116
assert.EqualValues(t, expectedAttributes, actualAttributes)
106117
}
@@ -171,6 +182,7 @@ func TestDevices_Unmarshal(t *testing.T) {
171182
},
172183
Hostname: "hello",
173184
ID: "50052",
185+
NodeID: "nTESTJ30",
174186
IsExternal: true,
175187
KeyExpiryDisabled: true,
176188
LastSeen: Time{
@@ -196,6 +208,7 @@ func TestDevices_Unmarshal(t *testing.T) {
196208
},
197209
Hostname: "foo",
198210
ID: "50053",
211+
NodeID: "nTESTJ31",
199212
IsExternal: false,
200213
KeyExpiryDisabled: true,
201214
LastSeen: Time{

testdata/devices.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"expires": "0001-01-01T00:00:00Z",
1313
"hostname": "hello",
1414
"id": "50052",
15+
"nodeId": "nTESTJ30",
1516
"isExternal": true,
1617
"keyExpiryDisabled": true,
1718
"lastSeen": "2022-04-15T13:24:40Z",
@@ -34,6 +35,7 @@
3435
"expires": "2022-09-01T17:10:27Z",
3536
"hostname": "foo",
3637
"id": "50053",
38+
"nodeId": "nTESTJ31",
3739
"isExternal": false,
3840
"keyExpiryDisabled": true,
3941
"lastSeen": "2022-04-15T13:25:21Z",

0 commit comments

Comments
 (0)