-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tailscale: add
contacts
resource (#387)
Add a `tailscale_contacts` resource to allow for managing tailscale contacts settings via Terraform. Updates tailscale/corp#21631 Signed-off-by: Mario Minardi <mario@tailscale.com>
- Loading branch information
Showing
8 changed files
with
497 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
--- | ||
# generated by /~https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "tailscale_contacts Resource - terraform-provider-tailscale" | ||
subcategory: "" | ||
description: |- | ||
The contacts resource allows you to configure contact details for your Tailscale network. See https://tailscale.com/kb/1224/contact-preferences for more information. | ||
Destroying this resource does not unset or modify values in the tailscale control plane, and simply removes the resource from Terraform state. | ||
--- | ||
|
||
# tailscale_contacts (Resource) | ||
|
||
The contacts resource allows you to configure contact details for your Tailscale network. See https://tailscale.com/kb/1224/contact-preferences for more information. | ||
|
||
Destroying this resource does not unset or modify values in the tailscale control plane, and simply removes the resource from Terraform state. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "tailscale_contacts" "sample_contacts" { | ||
account { | ||
email = "account@example.com" | ||
} | ||
support { | ||
email = "support@example.com" | ||
} | ||
security { | ||
email = "security@example.com" | ||
} | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `account` (Block Set, Min: 1, Max: 1) Configuration for communications about important changes to your tailnet (see [below for nested schema](#nestedblock--account)) | ||
- `security` (Block Set, Min: 1, Max: 1) Configuration for communications about security issues affecting your tailnet (see [below for nested schema](#nestedblock--security)) | ||
- `support` (Block Set, Min: 1, Max: 1) Configuration for communications about misconfigurations in your tailnet (see [below for nested schema](#nestedblock--support)) | ||
|
||
### Read-Only | ||
|
||
- `id` (String) The ID of this resource. | ||
|
||
<a id="nestedblock--account"></a> | ||
### Nested Schema for `account` | ||
|
||
Required: | ||
|
||
- `email` (String) Email address to send communications to | ||
|
||
|
||
<a id="nestedblock--security"></a> | ||
### Nested Schema for `security` | ||
|
||
Required: | ||
|
||
- `email` (String) Email address to send communications to | ||
|
||
|
||
<a id="nestedblock--support"></a> | ||
### Nested Schema for `support` | ||
|
||
Required: | ||
|
||
- `email` (String) Email address to send communications to | ||
|
||
## Import | ||
|
||
Import is supported using the following syntax: | ||
|
||
```shell | ||
# ID doesn't matter. | ||
terraform import tailscale_contacts.sample_contacts contacts | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# ID doesn't matter. | ||
terraform import tailscale_contacts.sample_contacts contacts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
resource "tailscale_contacts" "sample_contacts" { | ||
account { | ||
email = "account@example.com" | ||
} | ||
|
||
support { | ||
email = "support@example.com" | ||
} | ||
|
||
security { | ||
email = "security@example.com" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package tailscale | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
"github.com/tailscale/tailscale-client-go/tailscale" | ||
) | ||
|
||
const resourceContactsDescription = `The contacts resource allows you to configure contact details for your Tailscale network. See https://tailscale.com/kb/1224/contact-preferences for more information. | ||
Destroying this resource does not unset or modify values in the tailscale control plane, and simply removes the resource from Terraform state. | ||
` | ||
|
||
func resourceContacts() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: resourceContactsDescription, | ||
ReadContext: resourceContactsRead, | ||
CreateContext: resourceContactsCreate, | ||
UpdateContext: resourceContactsUpdate, | ||
DeleteContext: resourceContactsDelete, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
Schema: map[string]*schema.Schema{ | ||
"account": { | ||
Type: schema.TypeSet, | ||
Description: "Configuration for communications about important changes to your tailnet", | ||
Required: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"email": { | ||
Type: schema.TypeString, | ||
Description: "Email address to send communications to", | ||
Required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"support": { | ||
Type: schema.TypeSet, | ||
Description: "Configuration for communications about misconfigurations in your tailnet", | ||
Required: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"email": { | ||
Type: schema.TypeString, | ||
Description: "Email address to send communications to", | ||
Required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"security": { | ||
Type: schema.TypeSet, | ||
Description: "Configuration for communications about security issues affecting your tailnet", | ||
Required: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"email": { | ||
Type: schema.TypeString, | ||
Description: "Email address to send communications to", | ||
Required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceContactsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
client := m.(*tailscale.Client) | ||
|
||
if diagErr := updateContact(ctx, client, d, tailscale.ContactAccount); diagErr != nil { | ||
return diagErr | ||
} | ||
|
||
if diagErr := updateContact(ctx, client, d, tailscale.ContactSupport); diagErr != nil { | ||
return diagErr | ||
} | ||
|
||
if diagErr := updateContact(ctx, client, d, tailscale.ContactSecurity); diagErr != nil { | ||
return diagErr | ||
} | ||
|
||
d.SetId(createUUID()) | ||
return resourceContactsRead(ctx, d, m) | ||
} | ||
|
||
func resourceContactsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
client := m.(*tailscale.Client) | ||
|
||
contacts, err := client.Contacts(ctx) | ||
if err != nil { | ||
return diagnosticsError(err, "Failed to fetch contacts") | ||
} | ||
|
||
if err = d.Set("account", buildContactMap(contacts.Account)); err != nil { | ||
return diagnosticsError(err, "Failed to set account field") | ||
} | ||
|
||
if err = d.Set("support", buildContactMap(contacts.Support)); err != nil { | ||
return diagnosticsError(err, "Failed to set support field") | ||
} | ||
|
||
if err = d.Set("security", buildContactMap(contacts.Security)); err != nil { | ||
return diagnosticsError(err, "Failed to set security field") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceContactsUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
client := m.(*tailscale.Client) | ||
|
||
if d.HasChange("account") { | ||
if diagErr := updateContact(ctx, client, d, tailscale.ContactAccount); diagErr != nil { | ||
return diagErr | ||
} | ||
} | ||
|
||
if d.HasChange("support") { | ||
if diagErr := updateContact(ctx, client, d, tailscale.ContactSupport); diagErr != nil { | ||
return diagErr | ||
} | ||
} | ||
|
||
if d.HasChange("security") { | ||
if diagErr := updateContact(ctx, client, d, tailscale.ContactSecurity); diagErr != nil { | ||
return diagErr | ||
} | ||
} | ||
|
||
return resourceContactsRead(ctx, d, m) | ||
} | ||
|
||
func resourceContactsDelete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
// Deleting is a no-op since we cannot have unset contact information. | ||
// Deletion in this context is simply removing from terraform state. | ||
const diagDetail = `This resource has been successfully destroyed, but values in tailscale will remain set. | ||
See https://tailscale.com/kb/1224/contact-preferences to learn more.` | ||
|
||
return diag.Diagnostics{ | ||
diag.Diagnostic{ | ||
Severity: diag.Warning, | ||
Summary: "Destroying tailscale_contacts does not unset contact values on tailscale", | ||
Detail: diagDetail, | ||
}, | ||
} | ||
} | ||
|
||
// buildContactMap transforms a tailscale.Contact into an equivalnet single element | ||
// slice of map[string]interface{} so that it can be set on a schema.TypeSet property | ||
// in the resource. | ||
func buildContactMap(contact tailscale.Contact) []map[string]interface{} { | ||
return []map[string]interface{}{ | ||
{ | ||
"email": contact.Email, | ||
}, | ||
} | ||
} | ||
|
||
// updateContact updates the contact specified by the tailscale.ContactType by | ||
// reading the resource property with the correct name and using it to build a | ||
// request to the underlying client. | ||
func updateContact(ctx context.Context, client *tailscale.Client, d *schema.ResourceData, contactType tailscale.ContactType) diag.Diagnostics { | ||
contact := d.Get(string(contactType)).(*schema.Set).List() | ||
contactEmail := contact[0].(map[string]interface{})["email"].(string) | ||
|
||
if err := client.UpdateContact(ctx, contactType, tailscale.UpdateContactRequest{Email: &contactEmail}); err != nil { | ||
return diagnosticsError(err, "Failed to create contacts") | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.