Skip to content

Commit 74a18f6

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 df6d4a0 commit 74a18f6

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

devices.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ 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 ClientConnectivity struct {
53+
Endpoints []string `json:"endpoints"`
54+
DERP string `json:"derp"`
55+
MappingVariesByDestIP bool `json:"mappingVariesByDestIP"`
56+
// DERPLatency is mapped by region name (e.g. "New York City", "Seattle").
57+
DERPLatency map[string]DERPRegion `json:"latency"`
58+
ClientSupports map[string]bool `json:"clientSupports"`
59+
}
60+
4761
type Device struct {
4862
Addresses []string `json:"addresses"`
4963
Name string `json:"name"`
@@ -65,6 +79,11 @@ type Device struct {
6579
TailnetLockError string `json:"tailnetLockError"`
6680
TailnetLockKey string `json:"tailnetLockKey"`
6781
UpdateAvailable bool `json:"updateAvailable"`
82+
83+
// The below are only included in listings when querying `all` fields.
84+
AdvertisedRoutes []string `json:"AdvertisedRoutes"`
85+
EnabledRoutes []string `json:"enabledRoutes"`
86+
ClientConnectivity *ClientConnectivity `json:"clientConnectivity"`
6887
}
6988

7089
type DevicePostureAttributes struct {
@@ -108,13 +127,31 @@ func (dr *DevicesResource) SetPostureAttribute(ctx context.Context, deviceID, at
108127
return dr.do(req, nil)
109128
}
110129

111-
// List lists every [Device] in the tailnet.
130+
// ListWithAllFields lists every [Device] in the tailnet. Each [Device] in
131+
// the response will have all fields populated.
132+
func (dr *DevicesResource) ListWithAllFields(ctx context.Context) ([]Device, error) {
133+
return dr.list(ctx, true)
134+
}
135+
136+
// List lists every [Device] in the tailnet. The fields `EnabledRoutes`,
137+
// `AdvertisedRoutes` and `ClientConnectivity` will be omitted from the resulting
138+
// [Devices]. To get these fields, use `ListWithAllFields`.
112139
func (dr *DevicesResource) List(ctx context.Context) ([]Device, error) {
140+
return dr.list(ctx, false)
141+
}
142+
143+
func (dr *DevicesResource) list(ctx context.Context, all bool) ([]Device, error) {
113144
req, err := dr.buildRequest(ctx, http.MethodGet, dr.buildTailnetURL("devices"))
114145
if err != nil {
115146
return nil, err
116147
}
117148

149+
if all {
150+
q := req.URL.Query()
151+
q.Set("fields", "all")
152+
req.URL.RawQuery = q.Encode()
153+
}
154+
118155
m := make(map[string][]Device)
119156
err = dr.do(req, &m)
120157
if err != nil {

devices_test.go

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

6892
client, server := NewTestHarness(t)
@@ -131,6 +155,30 @@ func TestClient_Devices_List(t *testing.T) {
131155
NodeKey: "nodekey:test",
132156
OS: "windows",
133157
UpdateAvailable: true,
158+
AdvertisedRoutes: []string{"127.0.0.1", "127.0.0.2"},
159+
EnabledRoutes: []string{"127.0.0.1"},
160+
ClientConnectivity: &ClientConnectivity{
161+
Endpoints: []string{"199.9.14.201:59128", "192.68.0.21:59128"},
162+
DERP: "New York City",
163+
DERPLatency: map[string]DERPRegion{
164+
"Dallas": {
165+
LatencyMilliseconds: 60.463043,
166+
},
167+
"New York City": {
168+
Preferred: true,
169+
LatencyMilliseconds: 31.323811,
170+
},
171+
},
172+
MappingVariesByDestIP: true,
173+
ClientSupports: map[string]bool{
174+
"hairPinning": false,
175+
"ipv6": false,
176+
"pcp": false,
177+
"pmp": false,
178+
"udp": false,
179+
"upnp": false,
180+
},
181+
},
134182
},
135183
},
136184
}
@@ -139,10 +187,11 @@ func TestClient_Devices_List(t *testing.T) {
139187
server.ResponseCode = http.StatusOK
140188
server.ResponseBody = expectedDevices
141189

142-
actualDevices, err := client.Devices().List(context.Background())
190+
actualDevices, err := client.Devices().ListWithAllFields(context.Background())
143191
assert.NoError(t, err)
144192
assert.Equal(t, http.MethodGet, server.Method)
145193
assert.Equal(t, "/api/v2/tailnet/example.com/devices", server.Path)
194+
assert.Equal(t, "all", server.Query.Get("fields"))
146195
assert.EqualValues(t, expectedDevices["devices"], actualDevices)
147196
}
148197

0 commit comments

Comments
 (0)