Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify alert_channel to use maps for headers, payload #81

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions newrelic/resource_newrelic_alert_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ var alertChannelTypes = map[string][]string{
"auth_type",
"auth_username",
"base_url",
"headers",
"payload_type",
"payload",
},
}

Expand Down Expand Up @@ -89,17 +87,51 @@ func resourceNewRelicAlertChannel() *schema.Resource {
//TODO: ValidateFunc: (use list of keys from map above)
Sensitive: true,
},
"headers": {
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
Sensitive: true,
},
"payload": {
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
Sensitive: true,
ValidateFunc: configBlockLengthGreaterThan(0),
},
},
}
}

// This function verifies that the number of keys within a configuration
// map is greater than the provided parameter.
func configBlockLengthGreaterThan(minLength int) schema.SchemaValidateFunc {
return func(i interface{}, st string) (s []string, errorSlice []error) {
length := len(i.(map[string]interface{}))
if length > minLength {
return
}
errorSlice = append(errorSlice, fmt.Errorf("expected %s not to be empty", st))
return
}
}

func buildAlertChannelStruct(d *schema.ResourceData) *newrelic.AlertChannel {
channel := newrelic.AlertChannel{
Name: d.Get("name").(string),
Type: d.Get("type").(string),
Configuration: d.Get("configuration").(map[string]interface{}),
}

if headerMap, ok := d.GetOk("headers"); ok {
channel.Configuration["headers"] = headerMap.(map[string]interface{})
}

if payload, ok := d.GetOk("payload"); ok {
channel.Configuration["payload"] = payload.(map[string]interface{})
}

return &channel
}

Expand Down Expand Up @@ -141,6 +173,19 @@ func resourceNewRelicAlertChannelRead(d *schema.ResourceData, meta interface{})

d.Set("name", channel.Name)
d.Set("type", channel.Type)

// extract headers from Configuration before we try and set it in the resource
if headers, ok := channel.Configuration["headers"]; ok {
d.Set("headers", headers)
delete(channel.Configuration, "headers")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little hesitant to do this delete as its a breaking change for anyone already using this behavior.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe that the delete could break anything because, as far as I can tell setting the headers or payload is broken currently

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think nathanael-ness is right, I don't see how either headers or payload could be working currently with the type mismatch.

We're having to use a null-resource to deploy a webhook alert channel, can't use the resource as it is.

}

// extract payload from Configuration before we try and set it in the resource
if payload, ok := channel.Configuration["payload"]; ok {
d.Set("payload", payload)
delete(channel.Configuration, "payload")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little hesitant to do this delete as its a breaking change for anyone already using this behavior.

}

if err := d.Set("configuration", channel.Configuration); err != nil {
return fmt.Errorf("[DEBUG] Error setting Alert Channel Configuration: %#v", err)
}
Expand Down
177 changes: 177 additions & 0 deletions newrelic/resource_newrelic_alert_channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package newrelic

import (
"fmt"
"regexp"
"strconv"
"testing"

Expand Down Expand Up @@ -29,6 +30,8 @@ func TestAccNewRelicAlertChannel_Basic(t *testing.T) {
"newrelic_alert_channel.foo", "configuration.recipients", "[email protected]"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.foo", "configuration.include_json_attachment", "1"),
resource.TestCheckNoResourceAttr(
"newrelic_alert_channel.foo", "headers"),
),
},
{
Expand All @@ -43,6 +46,8 @@ func TestAccNewRelicAlertChannel_Basic(t *testing.T) {
"newrelic_alert_channel.foo", "configuration.recipients", "[email protected]"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.foo", "configuration.include_json_attachment", "0"),
resource.TestCheckNoResourceAttr(
"newrelic_alert_channel.foo", "headers"),
),
},
},
Expand Down Expand Up @@ -70,6 +75,60 @@ func TestAccNewRelicAlertChannel_import(t *testing.T) {
})
}

func TestAccNewRelicAlertChannel_Webhook_withoutHeaders(t *testing.T) {
rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNewRelicAlertChannelDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckNewRelicAlertChannelConfigWebhook_withoutHeaders(rName),

ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckNewRelicAlertChannelExists("newrelic_alert_channel.channel_without_headers"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_without_headers", "name", fmt.Sprintf("tf-test-webhook-%s", rName)),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_without_headers", "type", "webhook"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_without_headers", "configuration.base_url", "http://test.com"),
resource.TestCheckNoResourceAttr(
"newrelic_alert_channel.channel_without_headers", "headers"),
),
},
},
})
}

func TestAccNewRelicAlertChannel_Webhook_withHeaders(t *testing.T) {
rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNewRelicAlertChannelDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckNewRelicAlertChannelConfigWebhook_withHeaders(rName),

ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckNewRelicAlertChannelExists("newrelic_alert_channel.channel_with_headers"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_with_headers", "name", fmt.Sprintf("tf-test-webhook-%s", rName)),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_with_headers", "type", "webhook"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_with_headers", "configuration.base_url", "http://test.com"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.channel_with_headers", "headers.header1", "test"),
),
},
},
})
}

func testAccCheckNewRelicAlertChannelDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).Client
for _, r := range s.RootModule().Resources {
Expand Down Expand Up @@ -149,3 +208,121 @@ resource "newrelic_alert_channel" "foo" {
}
`, rName)
}

func testAccCheckNewRelicAlertChannelConfigWebhook_withoutHeaders(rName string) string {
return fmt.Sprintf(`
resource "newrelic_alert_channel" "channel_without_headers" {
name = "tf-test-webhook-%s"
type = "webhook"

configuration = {
base_url = "http://test.com",
auth_username = "username",
auth_password = "password",
payload_type = "application/json",
}
}
`, rName)
}

func testAccCheckNewRelicAlertChannelConfigWebhook_withHeaders(rName string) string {
return fmt.Sprintf(`
resource "newrelic_alert_channel" "channel_with_headers" {
name = "tf-test-webhook-%s"
type = "webhook"

configuration = {
base_url = "http://test.com",
auth_username = "username",
auth_password = "password",
payload_type = "application/json",
}

headers {
header1 = "test"
header2 = "test2"
}
}
`, rName)
}

func testAccCheckNewRelicAlertChannelConfigWebhook_withEmptyPayload(rName string) string {
return fmt.Sprintf(`
resource "newrelic_alert_channel" "webhook_with_empty_payload" {
name = "tf-test-webhook-%s"
type = "webhook"

configuration = {
base_url = "http://test.com",
auth_username = "username",
auth_password = "password",
payload_type = "application/json",
}

payload = {
}
}
`, rName)
}

func testAccCheckNewRelicAlertChannelConfigWebhook_withPayload(rName string) string {
return fmt.Sprintf(`
resource "newrelic_alert_channel" "webhook_with_payload" {
name = "tf-test-webhook-%s"
type = "webhook"

configuration = {
base_url = "http://test.com",
auth_username = "username",
auth_password = "password",
payload_type = "application/json",
}

payload = {
account_id = "test"
}
}
`, rName)
}

func TestAccNewRelicAlertChannel_Webhook_withPayload(t *testing.T) {
rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNewRelicAlertChannelDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckNewRelicAlertChannelConfigWebhook_withPayload(rName),
ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckNewRelicAlertChannelExists("newrelic_alert_channel.webhook_with_payload"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.webhook_with_payload", "name", fmt.Sprintf("tf-test-webhook-%s", rName)),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.webhook_with_payload", "type", "webhook"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.webhook_with_payload", "configuration.base_url", "http://test.com"),
resource.TestCheckResourceAttr(
"newrelic_alert_channel.webhook_with_payload", "payload.account_id", "test"),
),
},
},
})
}

func TestAccNewRelicAlertChannel_Webhook_withEmptyPayloadReturnsError(t *testing.T) {
expectedErrorMsg, _ := regexp.Compile("expected payload not to be empty")
rName := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNewRelicAlertChannelDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckNewRelicAlertChannelConfigWebhook_withEmptyPayload(rName),
ExpectError: expectedErrorMsg,
},
},
})
}
42 changes: 42 additions & 0 deletions website/docs/r/alert_channel.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description: |-

## Example Usage

Example email channel:
```hcl
resource "newrelic_alert_channel" "foo" {
name = "foo"
Expand All @@ -22,6 +23,42 @@ resource "newrelic_alert_channel" "foo" {
}
```

Example webhook channel:

```hcl
resource "newrelic_alert_channel" "bar" {
name = "bar"
type = "webhook"

configuration = {
base_url = "http://test.com",
auth_username = "username",
auth_password = "password",
payload_type = "application/json",
}

headers {
api-key = "my-internal-api-key"
}

payload {
# using all the default fields
account_id = "$ACCOUNT_ID"
account_name = "$ACCOUNT_NAME"
closed_violations_count_critical = "$CLOSED_VIOLATIONS_COUNT_CRITICAL"
closed_violations_count_warning = "$CLOSED_VIOLATIONS_COUNT_WARNING"
condition_family_id = "$CONDITION_FAMILY_ID"
#... some defaults not shown ...
timestamp = "$TIMESTAMP"
violation_callback_url = "$VIOLATION_CALLBACK_URL"
violation_chart_url = "$VIOLATION_CHART_URL"

# add our own custom field
my_custom_field = "my custom value"
}
}
```

## Argument Reference

The following arguments are supported:
Expand All @@ -30,6 +67,11 @@ The following arguments are supported:
* `type` - (Required) The type of channel. One of: `campfire`, `email`, `hipchat`, `opsgenie`, `pagerduty`, `slack`, `victorops`, or `webhook`.
* `configuration` - (Required) A map of key / value pairs with channel type specific values.

Additionally, Webhook channels can have the following optional fields:

* `headers` - (Optional) A map of key / value pairs of HTTP headers to add to the webhook request.
* `payload` - (Optional) A map of key / value pairs to be sent as the webhook's payload. Fully replaces the [default payload](https://docs.newrelic.com/docs/alerts/rest-api-alerts/new-relic-alerts-rest-api/rest-api-calls-new-relic-alerts#webhook_json_channel). See [Webhook Documentation](https://docs.newrelic.com/docs/alerts/new-relic-alerts/managing-notification-channels/customize-your-webhook-payload#variables) for fields and values that New Relic provides.

## Attributes Reference

The following attributes are exported:
Expand Down