Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/data-sources/device.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,21 @@ data "tailscale_device" "sample_device2" {
### Read-Only

- `addresses` (List of String) The list of device's IPs
- `authorized` (Boolean) Whether the device is authorized to access the tailnet
- `blocks_incoming_connections` (Boolean) Whether the device blocks incoming connections
- `client_version` (String) The Tailscale client version running on the device
- `created` (String) The creation time of the device
- `expires` (String) The expiry time of the device's key
- `id` (String) The ID of this resource.
- `is_external` (Boolean) Whether the device is marked as external
- `key_expiry_disabled` (Boolean) Whether the device's key expiry is disabled
- `last_seen` (String) The last seen time of the device
- `machine_key` (String) The machine key of the device
- `node_id` (String) The preferred indentifier for a device.
- `node_key` (String) The node key of the device
- `os` (String) The operating system of the device
- `tags` (Set of String) The tags applied to the device
- `tailnet_lock_error` (String) The tailnet lock error for the device, if any
- `tailnet_lock_key` (String) The tailnet lock key for the device, if any
- `update_available` (Boolean) Whether an update is available for the device
- `user` (String) The user associated with the device
14 changes: 14 additions & 0 deletions docs/data-sources/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,23 @@ data "tailscale_devices" "sample_devices" {
Read-Only:

- `addresses` (List of String)
- `authorized` (Boolean)
- `blocks_incoming_connections` (Boolean)
- `client_version` (String)
- `created` (String)
- `expires` (String)
- `hostname` (String)
- `id` (String)
- `is_external` (Boolean)
- `key_expiry_disabled` (Boolean)
- `last_seen` (String)
- `machine_key` (String)
- `name` (String)
- `node_id` (String)
- `node_key` (String)
- `os` (String)
- `tags` (Set of String)
- `tailnet_lock_error` (String)
- `tailnet_lock_key` (String)
- `update_available` (Boolean)
- `user` (String)
103 changes: 97 additions & 6 deletions tailscale/data_source_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,76 @@ func dataSourceDevice() *schema.Resource {
Type: schema.TypeString,
},
},
"authorized": {
Type: schema.TypeBool,
Description: "Whether the device is authorized to access the tailnet",
Computed: true,
},
"key_expiry_disabled": {
Type: schema.TypeBool,
Description: "Whether the device's key expiry is disabled",
Computed: true,
},
"blocks_incoming_connections": {
Type: schema.TypeBool,
Description: "Whether the device blocks incoming connections",
Computed: true,
},
"client_version": {
Type: schema.TypeString,
Description: "The Tailscale client version running on the device",
Computed: true,
},
"created": {
Type: schema.TypeString,
Description: "The creation time of the device",
Computed: true,
},
"expires": {
Type: schema.TypeString,
Description: "The expiry time of the device's key",
Computed: true,
},
"is_external": {
Type: schema.TypeBool,
Description: "Whether the device is marked as external",
Computed: true,
},
"last_seen": {
Type: schema.TypeString,
Description: "The last seen time of the device",
Computed: true,
},
"machine_key": {
Type: schema.TypeString,
Description: "The machine key of the device",
Computed: true,
},
"node_key": {
Type: schema.TypeString,
Description: "The node key of the device",
Computed: true,
},
"os": {
Type: schema.TypeString,
Description: "The operating system of the device",
Computed: true,
},
"update_available": {
Type: schema.TypeBool,
Description: "Whether an update is available for the device",
Computed: true,
},
"tailnet_lock_error": {
Type: schema.TypeString,
Description: "The tailnet lock error for the device, if any",
Computed: true,
},
"tailnet_lock_key": {
Type: schema.TypeString,
Description: "The tailnet lock key for the device, if any",
Computed: true,
},
"wait_for": {
Type: schema.TypeString,
Description: "If specified, the provider will make multiple attempts to obtain the data source until the wait_for duration is reached. Retries are made every second so this value should be greater than 1s",
Expand Down Expand Up @@ -123,12 +193,33 @@ func dataSourceDeviceRead(ctx context.Context, d *schema.ResourceData, m interfa
// resource in Terraform. This omits the "id" which is expected to be set
// using [schema.ResourceData.SetId].
func deviceToMap(device *tailscale.Device) map[string]any {
var lastSeen string
if device.LastSeen == nil {
lastSeen = ""
} else {
lastSeen = device.LastSeen.Format(time.RFC3339)
}

return map[string]any{
"name": device.Name,
"hostname": device.Hostname,
"user": device.User,
"node_id": device.NodeID,
"addresses": device.Addresses,
"tags": device.Tags,
"name": device.Name,
"hostname": device.Hostname,
"user": device.User,
"node_id": device.NodeID,
"addresses": device.Addresses,
"tags": device.Tags,
"authorized": device.Authorized,
"key_expiry_disabled": device.KeyExpiryDisabled,
"blocks_incoming_connections": device.BlocksIncomingConnections,
"client_version": device.ClientVersion,
"created": device.Created.Format(time.RFC3339),
"expires": device.Expires.Format(time.RFC3339),
"is_external": device.IsExternal,
"last_seen": lastSeen,
"machine_key": device.MachineKey,
"node_key": device.NodeKey,
"os": device.OS,
"update_available": device.UpdateAvailable,
"tailnet_lock_error": device.TailnetLockError,
"tailnet_lock_key": device.TailnetLockKey,
}
}
119 changes: 119 additions & 0 deletions tailscale/data_source_device_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) David Bond, Tailscale Inc, & Contributors
// SPDX-License-Identifier: MIT

package tailscale

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
tsclient "tailscale.com/client/tailscale/v2"
"tailscale.com/tstest"
)

func TestDeviceToMap(t *testing.T) {
t.Parallel()
cl := tstest.NewClock(tstest.ClockOpts{})
created := tsclient.Time{Time: cl.Now().Truncate(time.Second)}
expires := tsclient.Time{Time: cl.Now().Truncate(time.Second).Add(24 * time.Hour)}
lastSeen := tsclient.Time{Time: cl.Now().Truncate(time.Second).Add(-5 * time.Minute)}

dev := &tsclient.Device{
Name: "host.example.ts.net",
Hostname: "host",
User: "[email protected]",
NodeID: "node-123",
Addresses: []string{"100.100.100.101", "fd7a:115c:a1e0::1"},
Tags: []string{"tag:test1", "tag:test2"},
Authorized: true,
KeyExpiryDisabled: true,
BlocksIncomingConnections: true,
ClientVersion: "1.88.4",
Created: created,
Expires: expires,
IsExternal: false,
LastSeen: &lastSeen,
MachineKey: "machine-key",
NodeKey: "node-key",
OS: "linux",
UpdateAvailable: true,
TailnetLockError: "lock-error",
TailnetLockKey: "lock-key",
}

m := deviceToMap(dev)

assert.Equal(t, dev.Name, m["name"].(string))
assert.Equal(t, dev.Hostname, m["hostname"].(string))
assert.Equal(t, dev.User, m["user"].(string))
assert.Equal(t, dev.NodeID, m["node_id"].(string))
assert.Equal(t, dev.Addresses, m["addresses"].([]string))
assert.Equal(t, dev.Tags, m["tags"].([]string))
assert.Equal(t, dev.Authorized, m["authorized"].(bool))
assert.Equal(t, dev.KeyExpiryDisabled, m["key_expiry_disabled"].(bool))
assert.Equal(t, dev.BlocksIncomingConnections, m["blocks_incoming_connections"].(bool))
assert.Equal(t, dev.ClientVersion, m["client_version"].(string))
assert.Equal(t, created.Format(time.RFC3339), m["created"].(string))
assert.Equal(t, expires.Format(time.RFC3339), m["expires"].(string))
assert.Equal(t, dev.IsExternal, m["is_external"].(bool))
assert.Equal(t, lastSeen.Format(time.RFC3339), m["last_seen"].(string))
assert.Equal(t, dev.MachineKey, m["machine_key"].(string))
assert.Equal(t, dev.NodeKey, m["node_key"].(string))
assert.Equal(t, dev.OS, m["os"].(string))
assert.Equal(t, dev.UpdateAvailable, m["update_available"].(bool))
assert.Equal(t, dev.TailnetLockError, m["tailnet_lock_error"].(string))
assert.Equal(t, dev.TailnetLockKey, m["tailnet_lock_key"].(string))
}
func TestDeviceToMap_LastSeenNil(t *testing.T) {
t.Parallel()
cl := tstest.NewClock(tstest.ClockOpts{})
created := tsclient.Time{Time: cl.Now().Truncate(time.Second)}
expires := tsclient.Time{Time: cl.Now().Truncate(time.Second).Add(24 * time.Hour)}

dev := &tsclient.Device{
Name: "host.example.ts.net",
Hostname: "host",
User: "[email protected]",
NodeID: "node-123",
Addresses: []string{"100.100.100.101", "fd7a:115c:a1e0::1"},
Tags: []string{"tag:test1", "tag:test2"},
Authorized: true,
KeyExpiryDisabled: true,
BlocksIncomingConnections: true,
ClientVersion: "1.88.4",
Created: created,
Expires: expires,
IsExternal: false,
LastSeen: nil,
MachineKey: "machine-key",
NodeKey: "node-key",
OS: "linux",
UpdateAvailable: true,
TailnetLockError: "lock-error",
TailnetLockKey: "lock-key",
}

m := deviceToMap(dev)

assert.Equal(t, dev.Name, m["name"].(string))
assert.Equal(t, dev.Hostname, m["hostname"].(string))
assert.Equal(t, dev.User, m["user"].(string))
assert.Equal(t, dev.NodeID, m["node_id"].(string))
assert.Equal(t, dev.Addresses, m["addresses"].([]string))
assert.Equal(t, dev.Tags, m["tags"].([]string))
assert.Equal(t, dev.Authorized, m["authorized"].(bool))
assert.Equal(t, dev.KeyExpiryDisabled, m["key_expiry_disabled"].(bool))
assert.Equal(t, dev.BlocksIncomingConnections, m["blocks_incoming_connections"].(bool))
assert.Equal(t, dev.ClientVersion, m["client_version"].(string))
assert.Equal(t, created.Format(time.RFC3339), m["created"].(string))
assert.Equal(t, expires.Format(time.RFC3339), m["expires"].(string))
assert.Equal(t, dev.IsExternal, m["is_external"].(bool))
assert.Equal(t, "", m["last_seen"]) // Expect empty string for nil LastSeen
assert.Equal(t, dev.MachineKey, m["machine_key"].(string))
assert.Equal(t, dev.NodeKey, m["node_key"].(string))
assert.Equal(t, dev.OS, m["os"].(string))
assert.Equal(t, dev.UpdateAvailable, m["update_available"].(bool))
assert.Equal(t, dev.TailnetLockError, m["tailnet_lock_error"].(string))
assert.Equal(t, dev.TailnetLockKey, m["tailnet_lock_key"].(string))
}
70 changes: 70 additions & 0 deletions tailscale/data_source_devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,76 @@ func dataSourceDevices() *schema.Resource {
Type: schema.TypeString,
},
},
"authorized": {
Type: schema.TypeBool,
Description: "Whether the device is authorized to access the tailnet",
Computed: true,
},
"key_expiry_disabled": {
Type: schema.TypeBool,
Description: "Whether the device's key expiry is disabled",
Computed: true,
},
"blocks_incoming_connections": {
Type: schema.TypeBool,
Description: "Whether the device blocks incoming connections",
Computed: true,
},
"client_version": {
Type: schema.TypeString,
Description: "The Tailscale client version running on the device",
Computed: true,
},
"created": {
Type: schema.TypeString,
Description: "The creation time of the device",
Computed: true,
},
"expires": {
Type: schema.TypeString,
Description: "The expiry time of the device's key",
Computed: true,
},
"is_external": {
Type: schema.TypeBool,
Description: "Whether the device is marked as external",
Computed: true,
},
"last_seen": {
Type: schema.TypeString,
Description: "The last seen time of the device",
Computed: true,
},
"machine_key": {
Type: schema.TypeString,
Description: "The machine key of the device",
Computed: true,
},
"node_key": {
Type: schema.TypeString,
Description: "The node key of the device",
Computed: true,
},
"os": {
Type: schema.TypeString,
Description: "The operating system of the device",
Computed: true,
},
"update_available": {
Type: schema.TypeBool,
Description: "Whether an update is available for the device",
Computed: true,
},
"tailnet_lock_error": {
Type: schema.TypeString,
Description: "The tailnet lock error for the device, if any",
Computed: true,
},
"tailnet_lock_key": {
Type: schema.TypeString,
Description: "The tailnet lock key for the device, if any",
Computed: true,
},
},
},
},
Expand Down
Loading