-
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.
Add a `tailscale_webhook` resource to allow for managing tailscale webhooks via terraform. Updates tailscale/corp#21625 Signed-off-by: Mario Minardi <mario@tailscale.com>
- Loading branch information
Showing
8 changed files
with
266 additions
and
6 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,47 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "tailscale_webhook Resource - terraform-provider-tailscale" | ||
subcategory: "" | ||
description: |- | ||
The webhook resource allows you to configure webhook endpoints for your Tailscale network. See https://tailscale.com/kb/1213/webhooks for more information. | ||
--- | ||
|
||
# tailscale_webhook (Resource) | ||
|
||
The webhook resource allows you to configure webhook endpoints for your Tailscale network. See https://tailscale.com/kb/1213/webhooks for more information. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "tailscale_webhook" "sample_webhook" { | ||
endpoint_url = "https://example.com/webhook/endpoint" | ||
provider_type = "slack" | ||
subscriptions = ["nodeCreated", "userDeleted"] | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `endpoint_url` (String) The endpoint to send webhook events to. | ||
- `subscriptions` (Set of String) The Tailscale events to subscribe this webhook to. See https://tailscale.com/kb/1213/webhooks#events for the list of valid events. | ||
|
||
### Optional | ||
|
||
- `provider_type` (String) The provider type of the endpoint URL. Also referred to as the 'destination' for the webhook in the admin panel. Webhook event payloads are formatted according to the provider type if it is set to a known value. Must be one of `slack`, `mattermost`, `googlechat`, or `discord` if set. | ||
|
||
### Read-Only | ||
|
||
- `id` (String) The ID of this resource. | ||
- `secret` (String, Sensitive) The secret used for signing webhook payloads. Only set on resource creation. See https://tailscale.com/kb/1213/webhooks#webhook-secret for more information. | ||
|
||
## Import | ||
|
||
Import is supported using the following syntax: | ||
|
||
```shell | ||
# Webhooks can be imported using the endpoint id, e.g., | ||
terraform import tailscale_webhook.sample_webhook 123456789 | ||
``` |
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 @@ | ||
# Webhooks can be imported using the endpoint id, e.g., | ||
terraform import tailscale_webhook.sample_webhook 123456789 |
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,5 @@ | ||
resource "tailscale_webhook" "sample_webhook" { | ||
endpoint_url = "https://example.com/webhook/endpoint" | ||
provider_type = "slack" | ||
subscriptions = ["nodeCreated", "userDeleted"] | ||
} |
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,171 @@ | ||
package tailscale | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" | ||
"github.com/tailscale/tailscale-client-go/tailscale" | ||
) | ||
|
||
func resourceWebhook() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: "The webhook resource allows you to configure webhook endpoints for your Tailscale network. See https://tailscale.com/kb/1213/webhooks for more information.", | ||
ReadContext: resourceWebhookRead, | ||
CreateContext: resourceWebhookCreate, | ||
UpdateContext: resourceWebhookUpdate, | ||
DeleteContext: resourceWebhookDelete, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
Schema: map[string]*schema.Schema{ | ||
"endpoint_url": { | ||
Type: schema.TypeString, | ||
Description: "The endpoint to send webhook events to.", | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"provider_type": { | ||
Type: schema.TypeString, | ||
Description: "The provider type of the endpoint URL. Also referred to as the 'destination' for the webhook in the admin panel. Webhook event payloads are formatted according to the provider type if it is set to a known value. Must be one of `slack`, `mattermost`, `googlechat`, or `discord` if set.", | ||
Optional: true, | ||
ForceNew: true, | ||
ValidateFunc: validation.StringInSlice( | ||
[]string{ | ||
string(tailscale.EmptyProviderType), | ||
string(tailscale.SlackProviderType), | ||
string(tailscale.MattermostProviderType), | ||
string(tailscale.GoogleChatProviderType), | ||
string(tailscale.DiscordProviderType), | ||
}, | ||
false, | ||
), | ||
}, | ||
"subscriptions": { | ||
Type: schema.TypeSet, | ||
Description: "The Tailscale events to subscribe this webhook to. See https://tailscale.com/kb/1213/webhooks#events for the list of valid events.", | ||
Required: true, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
ValidateFunc: validation.StringInSlice( | ||
[]string{ | ||
string(tailscale.NodeCreated), | ||
string(tailscale.NodeNeedsApproval), | ||
string(tailscale.NodeApproved), | ||
string(tailscale.NodeKeyExpiringInOneDay), | ||
string(tailscale.NodeKeyExpired), | ||
string(tailscale.NodeDeleted), | ||
string(tailscale.PolicyUpdate), | ||
string(tailscale.UserCreated), | ||
string(tailscale.UserNeedsApproval), | ||
string(tailscale.UserSuspended), | ||
string(tailscale.UserRestored), | ||
string(tailscale.UserDeleted), | ||
string(tailscale.UserApproved), | ||
string(tailscale.UserRoleUpdated), | ||
string(tailscale.SubnetIPForwardingNotEnabled), | ||
string(tailscale.ExitNodeIPForwardingNotEnabled), | ||
}, | ||
false, | ||
), | ||
}, | ||
}, | ||
"secret": { | ||
Type: schema.TypeString, | ||
Description: "The secret used for signing webhook payloads. Only set on resource creation. See https://tailscale.com/kb/1213/webhooks#webhook-secret for more information.", | ||
Sensitive: true, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceWebhookCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
client := m.(*tailscale.Client) | ||
|
||
endpointURL := d.Get("endpoint_url").(string) | ||
providerType := tailscale.ProviderType(d.Get("provider_type").(string)) | ||
subscriptions := d.Get("subscriptions").(*schema.Set).List() | ||
|
||
var requestSubscriptions []tailscale.SubscriptionType | ||
for _, subscription := range subscriptions { | ||
requestSubscriptions = append(requestSubscriptions, tailscale.SubscriptionType(subscription.(string))) | ||
} | ||
|
||
request := tailscale.CreateWebhookRequest{ | ||
EndpointURL: endpointURL, | ||
ProviderType: providerType, | ||
Subscriptions: requestSubscriptions, | ||
} | ||
|
||
webhook, err := client.CreateWebhook(ctx, request) | ||
if err != nil { | ||
return diagnosticsError(err, "Failed to create webhook") | ||
} | ||
|
||
d.SetId(webhook.EndpointID) | ||
// Secret is only returned on create. | ||
d.Set("secret", webhook.Secret) | ||
|
||
return resourceWebhookRead(ctx, d, m) | ||
} | ||
|
||
func resourceWebhookRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
client := m.(*tailscale.Client) | ||
|
||
webhook, err := client.Webhook(ctx, d.Id()) | ||
if err != nil { | ||
return diagnosticsError(err, "Failed to fetch webhook") | ||
} | ||
|
||
if err = d.Set("endpoint_url", webhook.EndpointURL); err != nil { | ||
return diagnosticsError(err, "Failed to set endpoint_url field") | ||
} | ||
|
||
if err = d.Set("provider_type", webhook.ProviderType); err != nil { | ||
return diagnosticsError(err, "Failed to set provider_type field") | ||
} | ||
|
||
if err = d.Set("subscriptions", webhook.Subscriptions); err != nil { | ||
return diagnosticsError(err, "Failed to set subscriptions field") | ||
} | ||
|
||
if err = d.Set("secret", d.Get("secret").(string)); err != nil { | ||
return diagnosticsError(err, "Failed to set secret field") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceWebhookUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
if !d.HasChange("subscriptions") { | ||
return resourceWebhookRead(ctx, d, m) | ||
} | ||
|
||
client := m.(*tailscale.Client) | ||
subscriptions := d.Get("subscriptions").(*schema.Set).List() | ||
|
||
var requestSubscriptions []tailscale.SubscriptionType | ||
for _, subscription := range subscriptions { | ||
requestSubscriptions = append(requestSubscriptions, tailscale.SubscriptionType(subscription.(string))) | ||
} | ||
|
||
_, err := client.UpdateWebhook(ctx, d.Id(), requestSubscriptions) | ||
if err != nil { | ||
return diagnosticsError(err, "Failed to update webhook") | ||
} | ||
|
||
return resourceWebhookRead(ctx, d, m) | ||
} | ||
|
||
func resourceWebhookDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
client := m.(*tailscale.Client) | ||
|
||
err := client.DeleteWebhook(ctx, d.Id()) | ||
if err != nil { | ||
return diagnosticsError(err, "Failed to delete webhook") | ||
} | ||
|
||
return nil | ||
} |
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,34 @@ | ||
package tailscale_test | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
|
||
"github.com/tailscale/tailscale-client-go/tailscale" | ||
) | ||
|
||
const testWebhook = ` | ||
resource "tailscale_webhook" "test_webhook" { | ||
endpoint_url = "https://example.com/endpoint" | ||
provider_type = "slack" | ||
subscriptions = ["userNeedsApproval", "nodeCreated"] | ||
}` | ||
|
||
func TestProvider_TailscaleWebhook(t *testing.T) { | ||
resource.Test(t, resource.TestCase{ | ||
IsUnitTest: true, | ||
PreCheck: func() { | ||
testServer.ResponseCode = http.StatusOK | ||
testServer.ResponseBody = tailscale.Webhook{ | ||
EndpointID: "12345", | ||
} | ||
}, | ||
ProviderFactories: testProviderFactories(t), | ||
Steps: []resource.TestStep{ | ||
testResourceCreated("tailscale_webhook.test_webhook", testWebhook), | ||
testResourceDestroyed("tailscale_webhook.test_webhook", testWebhook), | ||
}, | ||
}) | ||
} |