Skip to content

Commit

Permalink
Merge pull request #87 from DopplerHQ/km/webhooks
Browse files Browse the repository at this point in the history
Add webhook resource
  • Loading branch information
kyle-mcguire authored Jun 4, 2024
2 parents fc55c96 + 97d07a9 commit 0e97576
Show file tree
Hide file tree
Showing 7 changed files with 529 additions and 0 deletions.
72 changes: 72 additions & 0 deletions docs/resources/webhook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
page_title: "doppler_webhook Resource - terraform-provider-doppler"
subcategory: ""
description: |-
Manage a Doppler Webhook.
---

# doppler_webhook (Resource)

Manage a Doppler Webhook.

## Example Usage

```terraform
resource "doppler_webhook" "ci" {
project = doppler_project.test_proj.name
url = "https://localhost/webhook"
secret = "my signing secret-2"
enabled = true
enabled_configs = [doppler_config.ci_github.name]
authentication {
type = "Bearer"
token = "my bearer token"
}
payload = jsonencode({
myKey = "my value"
})
}
```

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

### Required

- `project` (String) The name of the Doppler project where the webhook is located
- `url` (String) The URL of the webhook endpoint

### Optional

- `authentication` (Block List, Max: 1) Authentication method used by the webhook (see [below for nested schema](#nestedblock--authentication))
- `enabled` (Boolean) Whether the webhook is enabled or disabled. Default to true.
- `enabled_configs` (Set of String) Configs this webhook will trigger for
- `payload` (String, Sensitive) The webhook's payload as a JSON string. Leave empty to use the default webhook payload
- `secret` (String, Sensitive) Secret used for request signing

### Read-Only

- `id` (String) The ID of this resource.
- `slug` (String) The slug of the Webhook

<a id="nestedblock--authentication"></a>
### Nested Schema for `authentication`

Required:

- `type` (String)

Optional:

- `password` (String, Sensitive)
- `token` (String, Sensitive)
- `username` (String)

## State Management

For security reasons, the Doppler API does not return the fields listed below, which prevents the Terraform provider from checking this external state.
In other words, the Terraform provider can be used to update these webhook fields but it will be unaware of external changes.

- `secret`
- `authentication`
- `payload`
139 changes: 139 additions & 0 deletions doppler/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,145 @@ func (client APIClient) DeleteEnvironment(ctx context.Context, project string, s
return nil
}

// Webhooks

func (client APIClient) GetWebhook(ctx context.Context, project string, slug string) (*Webhook, error) {
params := []QueryParam{
{Key: "project", Value: project},
}
response, err := client.PerformRequestWithRetry(ctx, "GET", fmt.Sprintf("/v3/webhooks/webhook/%s", url.QueryEscape(slug)), params, nil)
if err != nil {
return nil, err
}
var result WebhookResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse webhook"}
}
return &result.Webhook, nil
}

type CreateWebhookOptionalParameters struct {
Secret string
Auth *WebhookAuth
WebhookPayload string
EnabledConfigs []string
}

func (client APIClient) CreateWebhook(ctx context.Context, project string, url string, enabled bool, options *CreateWebhookOptionalParameters) (*Webhook, error) {
params := []QueryParam{
{Key: "project", Value: project},
}

payload := map[string]interface{}{
"url": url,
"enabled": enabled,
}

if options != nil {
if options.Secret != "" {
payload["secret"] = options.Secret
}
if options.Auth != nil {
payload["authentication"] = *options.Auth
}
if options.WebhookPayload != "" {
payload["payload"] = options.WebhookPayload
}
if options.EnabledConfigs != nil {
payload["enableConfigs"] = options.EnabledConfigs
}
}

body, err := json.Marshal(payload)
if err != nil {
return nil, &APIError{Err: err, Message: "Unable to serialize webhook"}
}

response, err := client.PerformRequestWithRetry(ctx, "POST", "/v3/webhooks", params, body)
if err != nil {
return nil, err
}

var result WebhookResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse webhook"}
}
return &result.Webhook, nil
}

func (client APIClient) EnableWebhook(ctx context.Context, project string, slug string) (*Webhook, error) {
params := []QueryParam{
{Key: "project", Value: project},
}
response, err := client.PerformRequestWithRetry(ctx, "POST", fmt.Sprintf("/v3/webhooks/webhook/%s/enable", url.QueryEscape(slug)), params, nil)
if err != nil {
return nil, err
}

var result WebhookResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse webhook"}
}
return &result.Webhook, nil
}

func (client APIClient) DisableWebhook(ctx context.Context, project string, slug string) (*Webhook, error) {
params := []QueryParam{
{Key: "project", Value: project},
}
response, err := client.PerformRequestWithRetry(ctx, "POST", fmt.Sprintf("/v3/webhooks/webhook/%s/disable", url.QueryEscape(slug)), params, nil)
if err != nil {
return nil, err
}

var result WebhookResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse webhook"}
}
return &result.Webhook, nil
}

func (client APIClient) UpdateWebhook(ctx context.Context, project string, slug string, webhookUrl string, secret string, webhookPayload string, enabledConfigs []string, disabledConfigs []string, auth WebhookAuth) (*Webhook, error) {
params := []QueryParam{
{Key: "project", Value: project},
}

payload := map[string]interface{}{}
payload["url"] = webhookUrl
payload["secret"] = secret
payload["payload"] = webhookPayload
payload["enableConfigs"] = enabledConfigs
payload["disableConfigs"] = disabledConfigs
payload["authentication"] = auth

body, err := json.Marshal(payload)
if err != nil {
return nil, &APIError{Err: err, Message: "Unable to serialize webhook"}
}

response, err := client.PerformRequestWithRetry(ctx, "PATCH", fmt.Sprintf("/v3/webhooks/webhook/%s", url.QueryEscape(slug)), params, body)
if err != nil {
return nil, err
}

var result WebhookResponse
if err = json.Unmarshal(response.Body, &result); err != nil {
return nil, &APIError{Err: err, Message: "Unable to parse webhook"}
}
return &result.Webhook, nil
}

func (client APIClient) DeleteWebhook(ctx context.Context, project string, slug string) error {
params := []QueryParam{
{Key: "project", Value: project},
}
_, err := client.PerformRequestWithRetry(ctx, "DELETE", fmt.Sprintf("/v3/webhooks/webhook/%s", url.QueryEscape(slug)), params, nil)
if err != nil {
return err
}
return nil
}

// Configs

func (client APIClient) GetConfig(ctx context.Context, project string, name string) (*Config, error) {
Expand Down
18 changes: 18 additions & 0 deletions doppler/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,24 @@ func parseEnvironmentResourceId(id string) (project string, name string, err err
return tokens[0], tokens[1], nil
}

type WebhookAuth struct {
Type string `json:"type"`
Token string `json:"token"`
Username string `json:"username"`
Password string `json:"password"`
}

type Webhook struct {
Slug string `json:"id"`
Url string `json:"url"`
Enabled bool `json:"enabled"`
EnabledConfigs []string `json:"enabledConfigs"`
}

type WebhookResponse struct {
Webhook Webhook `json:"webhook"`
}

type Config struct {
Slug string `json:"slug"`
Name string `json:"name"`
Expand Down
2 changes: 2 additions & 0 deletions doppler/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func Provider() *schema.Provider {
"doppler_group": resourceGroup(),
"doppler_group_member": resourceGroupMemberWorkplaceUser(),

"doppler_webhook": resourceWebhook(),

"doppler_project_member_group": resourceProjectMemberGroup(),
"doppler_project_member_service_account": resourceProjectMemberServiceAccount(),

Expand Down
Loading

0 comments on commit 0e97576

Please sign in to comment.