From d1f9ed9ce8e2ad2597c09f3b6fb1324c033afb9f Mon Sep 17 00:00:00 2001 From: Anton Tolchanov Date: Thu, 1 Feb 2024 19:11:48 +0000 Subject: [PATCH] tailscale: expose both device name and hostname 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 --- docs/data-sources/device.md | 13 +++-- docs/data-sources/devices.md | 1 + .../tailscale_device/data-source.tf | 7 ++- tailscale/data_source_device.go | 50 +++++++++++++++---- tailscale/data_source_devices.go | 8 ++- 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/docs/data-sources/device.md b/docs/data-sources/device.md index 93773cf5..98244f5a 100644 --- a/docs/data-sources/device.md +++ b/docs/data-sources/device.md @@ -14,7 +14,12 @@ 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" } ``` @@ -22,12 +27,10 @@ data "tailscale_device" "sample_device" { ## 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 diff --git a/docs/data-sources/devices.md b/docs/data-sources/devices.md index 8410457a..a46a00e5 100644 --- a/docs/data-sources/devices.md +++ b/docs/data-sources/devices.md @@ -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) diff --git a/examples/data-sources/tailscale_device/data-source.tf b/examples/data-sources/tailscale_device/data-source.tf index 460d274c..c61b3994 100644 --- a/examples/data-sources/tailscale_device/data-source.tf +++ b/examples/data-sources/tailscale_device/data-source.tf @@ -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" } diff --git a/tailscale/data_source_device.go b/tailscale/data_source_device.go index 813341f8..aa9ada30 100644 --- a/tailscale/data_source_device.go +++ b/tailscale/data_source_device.go @@ -2,6 +2,7 @@ package tailscale import ( "context" + "fmt" "time" "github.com/hashicorp/go-cty/cty" @@ -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, @@ -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 { @@ -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") } diff --git a/tailscale/data_source_devices.go b/tailscale/data_source_devices.go index 6e24576c..279d7593 100644 --- a/tailscale/data_source_devices.go +++ b/tailscale/data_source_devices.go @@ -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": { @@ -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,