Skip to content

Commit

Permalink
tailscale: expose both device name and hostname
Browse files Browse the repository at this point in the history
Add support for looking up devices by short name (hostname) as an
alternative to the full name (name). Expose both fields in the list of
devices.

Updates #327

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
  • Loading branch information
knyar committed Feb 2, 2024
1 parent d7723f4 commit d1f9ed9
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 17 deletions.
13 changes: 8 additions & 5 deletions docs/data-sources/device.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@ The device data source describes a single device in a tailnet

```terraform
data "tailscale_device" "sample_device" {
name = "user1-device.example.com"
name = "device1.example.ts.net"
wait_for = "60s"
}
data "tailscale_device" "sample_device2" {
hostname = "device2"
wait_for = "60s"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) The name of the device

### Optional

- `hostname` (String) The short hostname of the device
- `name` (String) The full name of the device (e.g. `hostname.domain.ts.net`)
- `wait_for` (String) 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

### Read-Only
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ data "tailscale_devices" "sample_devices" {
Read-Only:

- `addresses` (List of String)
- `hostname` (String)
- `id` (String)
- `name` (String)
- `tags` (Set of String)
Expand Down
7 changes: 6 additions & 1 deletion examples/data-sources/tailscale_device/data-source.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
data "tailscale_device" "sample_device" {
name = "user1-device.example.com"
name = "device1.example.ts.net"
wait_for = "60s"
}

data "tailscale_device" "sample_device2" {
hostname = "device2"
wait_for = "60s"
}
50 changes: 40 additions & 10 deletions tailscale/data_source_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tailscale

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-cty/cty"
Expand All @@ -17,9 +18,16 @@ func dataSourceDevice() *schema.Resource {
ReadContext: readWithWaitFor(dataSourceDeviceRead),
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the device",
Type: schema.TypeString,
Description: "The full name of the device (e.g. `hostname.domain.ts.net`)",
Optional: true,
ExactlyOneOf: []string{"name", "hostname"},
},
"hostname": {
Type: schema.TypeString,
Description: "The short hostname of the device",
Optional: true,
ExactlyOneOf: []string{"name", "hostname"},
},
"user": {
Type: schema.TypeString,
Expand Down Expand Up @@ -64,7 +72,23 @@ func dataSourceDevice() *schema.Resource {

func dataSourceDeviceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*tailscale.Client)
name := d.Get("name").(string)

var filter func(d tailscale.Device) bool
var filterDesc string

if name, ok := d.GetOk("name"); ok {
filter = func(d tailscale.Device) bool {
return d.Name == name.(string)
}
filterDesc = fmt.Sprintf("name=%q", name.(string))
}

if hostname, ok := d.GetOk("hostname"); ok {
filter = func(d tailscale.Device) bool {
return d.Hostname == hostname.(string)
}
filterDesc = fmt.Sprintf("hostname=%q", hostname.(string))
}

devices, err := client.Devices(ctx)
if err != nil {
Expand All @@ -73,20 +97,26 @@ func dataSourceDeviceRead(ctx context.Context, d *schema.ResourceData, m interfa

var selected *tailscale.Device
for _, device := range devices {
if device.Name != name {
continue
if filter(device) {
selected = &device
break
}

selected = &device
break
}

if selected == nil {
return diag.Errorf("Could not find device with name %s", name)
return diag.Errorf("Could not find device with %s", filterDesc)
}

d.SetId(selected.ID)

if err = d.Set("name", selected.Name); err != nil {
return diagnosticsError(err, "Failed to set name")
}

if err = d.Set("hostname", selected.Hostname); err != nil {
return diagnosticsError(err, "Failed to set hostname")
}

if err = d.Set("user", selected.User); err != nil {
return diagnosticsError(err, "Failed to set user")
}
Expand Down
8 changes: 7 additions & 1 deletion tailscale/data_source_devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ func dataSourceDevices() *schema.Resource {
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name of the device",
Description: "The full name of the device (e.g. `hostname.domain.ts.net`)",
Computed: true,
},
"hostname": {
Type: schema.TypeString,
Description: "The short hostname of the device",
Computed: true,
},
"user": {
Expand Down Expand Up @@ -82,6 +87,7 @@ func dataSourceDevicesRead(ctx context.Context, d *schema.ResourceData, m interf
deviceMaps = append(deviceMaps, map[string]interface{}{
"addresses": device.Addresses,
"name": device.Name,
"hostname": device.Hostname,
"user": device.User,
"id": device.ID,
"tags": device.Tags,
Expand Down

0 comments on commit d1f9ed9

Please sign in to comment.