Skip to content

Commit 1ce94fb

Browse files
committed
devices: support querying all device fields
Add 'AdvertisedRoutes', 'EnabledRoutes' and 'ClientConnectivity' to the 'Device' type and add the method 'ListWithAllFields' to obtain Devices with those fields populated. Updates tailscale/corp#22748 Signed-off-by: Percy Wegmann <[email protected]>
1 parent 6aa69f5 commit 1ce94fb

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

devices.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,29 @@ func (t *Time) UnmarshalJSON(data []byte) error {
4444
return nil
4545
}
4646

47+
type DERPRegion struct {
48+
Preferred bool `json:"preferred,omitempty"`
49+
LatencyMilliseconds float64 `json:"latencyMs"`
50+
}
51+
52+
type ClientSupports struct {
53+
HairPinning bool `json:"hairPinning"`
54+
IPV6 bool `json:"ipv6"`
55+
PCP bool `json:"pcp"`
56+
PMP bool `json:"pmp"`
57+
UDP bool `json:"udp"`
58+
UPNP bool `json:"upnp"`
59+
}
60+
61+
type ClientConnectivity struct {
62+
Endpoints []string `json:"endpoints"`
63+
DERP string `json:"derp"`
64+
MappingVariesByDestIP bool `json:"mappingVariesByDestIP"`
65+
// DERPLatency is mapped by region name (e.g. "New York City", "Seattle").
66+
DERPLatency map[string]DERPRegion `json:"latency"`
67+
ClientSupports ClientSupports `json:"clientSupports"`
68+
}
69+
4770
type Device struct {
4871
Addresses []string `json:"addresses"`
4972
Name string `json:"name"`
@@ -66,6 +89,11 @@ type Device struct {
6689
TailnetLockError string `json:"tailnetLockError"`
6790
TailnetLockKey string `json:"tailnetLockKey"`
6891
UpdateAvailable bool `json:"updateAvailable"`
92+
93+
// The below are only included in listings when querying `all` fields.
94+
AdvertisedRoutes []string `json:"AdvertisedRoutes"`
95+
EnabledRoutes []string `json:"enabledRoutes"`
96+
ClientConnectivity *ClientConnectivity `json:"clientConnectivity"`
6997
}
7098

7199
type DevicePostureAttributes struct {
@@ -115,13 +143,31 @@ func (dr *DevicesResource) SetPostureAttribute(ctx context.Context, deviceID, at
115143
return dr.do(req, nil)
116144
}
117145

118-
// List lists every [Device] in the tailnet.
146+
// ListWithAllFields lists every [Device] in the tailnet. Each [Device] in
147+
// the response will have all fields populated.
148+
func (dr *DevicesResource) ListWithAllFields(ctx context.Context) ([]Device, error) {
149+
return dr.list(ctx, true)
150+
}
151+
152+
// List lists every [Device] in the tailnet. The fields `EnabledRoutes`,
153+
// `AdvertisedRoutes` and `ClientConnectivity` will be omitted from the resulting
154+
// [Devices]. To get these fields, use `ListWithAllFields`.
119155
func (dr *DevicesResource) List(ctx context.Context) ([]Device, error) {
156+
return dr.list(ctx, false)
157+
}
158+
159+
func (dr *DevicesResource) list(ctx context.Context, all bool) ([]Device, error) {
120160
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildTailnetURL("devices"))
121161
if err != nil {
122162
return nil, err
123163
}
124164

165+
if all {
166+
q := req.URL.Query()
167+
q.Set("fields", "all")
168+
req.URL.RawQuery = q.Encode()
169+
}
170+
125171
m := make(map[string][]Device)
126172
err = dr.do(req, &m)
127173
if err != nil {

devices_test.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,30 @@ func TestClient_Devices_Get(t *testing.T) {
6464
TailnetLockError: "test error",
6565
TailnetLockKey: "tlpub:test",
6666
UpdateAvailable: true,
67+
AdvertisedRoutes: []string{"127.0.0.1", "127.0.0.2"},
68+
EnabledRoutes: []string{"127.0.0.1"},
69+
ClientConnectivity: &ClientConnectivity{
70+
Endpoints: []string{"199.9.14.201:59128", "192.68.0.21:59128"},
71+
DERP: "New York City",
72+
DERPLatency: map[string]DERPRegion{
73+
"Dallas": {
74+
LatencyMilliseconds: 60.463043,
75+
},
76+
"New York City": {
77+
Preferred: true,
78+
LatencyMilliseconds: 31.323811,
79+
},
80+
},
81+
MappingVariesByDestIP: true,
82+
ClientSupports: ClientSupports{
83+
HairPinning: false,
84+
IPV6: false,
85+
PCP: false,
86+
PMP: false,
87+
UDP: false,
88+
UPNP: false,
89+
},
90+
},
6791
}
6892

6993
client, server := NewTestHarness(t)
@@ -142,6 +166,30 @@ func TestClient_Devices_List(t *testing.T) {
142166
NodeKey: "nodekey:test",
143167
OS: "windows",
144168
UpdateAvailable: true,
169+
AdvertisedRoutes: []string{"127.0.0.1", "127.0.0.2"},
170+
EnabledRoutes: []string{"127.0.0.1"},
171+
ClientConnectivity: &ClientConnectivity{
172+
Endpoints: []string{"199.9.14.201:59128", "192.68.0.21:59128"},
173+
DERP: "New York City",
174+
DERPLatency: map[string]DERPRegion{
175+
"Dallas": {
176+
LatencyMilliseconds: 60.463043,
177+
},
178+
"New York City": {
179+
Preferred: true,
180+
LatencyMilliseconds: 31.323811,
181+
},
182+
},
183+
MappingVariesByDestIP: true,
184+
ClientSupports: ClientSupports{
185+
HairPinning: false,
186+
IPV6: false,
187+
PCP: false,
188+
PMP: false,
189+
UDP: false,
190+
UPNP: false,
191+
},
192+
},
145193
},
146194
},
147195
}
@@ -150,10 +198,11 @@ func TestClient_Devices_List(t *testing.T) {
150198
server.ResponseCode = http.StatusOK
151199
server.ResponseBody = expectedDevices
152200

153-
actualDevices, err := client.Devices().List(context.Background())
201+
actualDevices, err := client.Devices().ListWithAllFields(context.Background())
154202
assert.NoError(t, err)
155203
assert.Equal(t, http.MethodGet, server.Method)
156204
assert.Equal(t, "/api/v2/tailnet/example.com/devices", server.Path)
205+
assert.Equal(t, "all", server.Query.Get("fields"))
157206
assert.EqualValues(t, expectedDevices["devices"], actualDevices)
158207
}
159208

0 commit comments

Comments
 (0)