diff --git a/newrelic/resource_newrelic_alert_channel.go b/newrelic/resource_newrelic_alert_channel.go index 0faf728ed..04ebc4e8a 100644 --- a/newrelic/resource_newrelic_alert_channel.go +++ b/newrelic/resource_newrelic_alert_channel.go @@ -50,9 +50,7 @@ var alertChannelTypes = map[string][]string{ "auth_type", "auth_username", "base_url", - "headers", "payload_type", - "payload", }, } @@ -89,10 +87,36 @@ 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), @@ -100,6 +124,14 @@ func buildAlertChannelStruct(d *schema.ResourceData) *newrelic.AlertChannel { 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 } @@ -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") + } + + // 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") + } + if err := d.Set("configuration", channel.Configuration); err != nil { return fmt.Errorf("[DEBUG] Error setting Alert Channel Configuration: %#v", err) } diff --git a/newrelic/resource_newrelic_alert_channel_test.go b/newrelic/resource_newrelic_alert_channel_test.go index 00d08c02f..eb7f2731b 100644 --- a/newrelic/resource_newrelic_alert_channel_test.go +++ b/newrelic/resource_newrelic_alert_channel_test.go @@ -2,6 +2,7 @@ package newrelic import ( "fmt" + "regexp" "strconv" "testing" @@ -29,6 +30,8 @@ func TestAccNewRelicAlertChannel_Basic(t *testing.T) { "newrelic_alert_channel.foo", "configuration.recipients", "terraform-acctest+foo@hashicorp.com"), resource.TestCheckResourceAttr( "newrelic_alert_channel.foo", "configuration.include_json_attachment", "1"), + resource.TestCheckNoResourceAttr( + "newrelic_alert_channel.foo", "headers"), ), }, { @@ -43,6 +46,8 @@ func TestAccNewRelicAlertChannel_Basic(t *testing.T) { "newrelic_alert_channel.foo", "configuration.recipients", "terraform-acctest+bar@hashicorp.com"), resource.TestCheckResourceAttr( "newrelic_alert_channel.foo", "configuration.include_json_attachment", "0"), + resource.TestCheckNoResourceAttr( + "newrelic_alert_channel.foo", "headers"), ), }, }, @@ -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 { @@ -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, + }, + }, + }) +} diff --git a/website/docs/r/alert_channel.html.markdown b/website/docs/r/alert_channel.html.markdown index eb533a778..6d9908bce 100644 --- a/website/docs/r/alert_channel.html.markdown +++ b/website/docs/r/alert_channel.html.markdown @@ -10,6 +10,7 @@ description: |- ## Example Usage +Example email channel: ```hcl resource "newrelic_alert_channel" "foo" { name = "foo" @@ -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: @@ -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: