diff --git a/go.mod b/go.mod index 2c1a67810..072c4fb2d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/newrelic/go-agent/v3 v3.30.0 github.com/newrelic/go-insights v1.0.3 - github.com/newrelic/newrelic-client-go/v2 v2.27.0 + github.com/newrelic/newrelic-client-go/v2 v2.31.0 github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 ) diff --git a/go.sum b/go.sum index b440e0931..b5f36c736 100644 --- a/go.sum +++ b/go.sum @@ -46,7 +46,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -263,8 +263,8 @@ github.com/newrelic/go-agent/v3 v3.30.0 h1:ZXHCT/Cot4iIPwcegCZURuRQOsfmGA6wilW+S github.com/newrelic/go-agent/v3 v3.30.0/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg= github.com/newrelic/go-insights v1.0.3 h1:zSNp1CEZnXktzSIEsbHJk8v6ZihdPFP2WsO/fzau3OQ= github.com/newrelic/go-insights v1.0.3/go.mod h1:A20BoT8TNkqPGX2nS/Z2fYmKl3Cqa3iKZd4whzedCY4= -github.com/newrelic/newrelic-client-go/v2 v2.27.0 h1:pSE3LQKk1NatM72OZL2UzHS66tbNSFui5QO8FZ8+JVg= -github.com/newrelic/newrelic-client-go/v2 v2.27.0/go.mod h1:SO5KJuFJ/+l3lT8nOdNLTrcE9FoZ4m60kechCVb+1N4= +github.com/newrelic/newrelic-client-go/v2 v2.31.0 h1:Zj6VaWfEQ3shhgj1VM+XurnT3lVE/rNi9UIx5K09LzU= +github.com/newrelic/newrelic-client-go/v2 v2.31.0/go.mod h1:xP8n2q2daNbAM7GxER5gqh8lW5ZpBkL6BuG7MWGt2v0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= diff --git a/newrelic/data_source_newrelic_notifications_destination.go b/newrelic/data_source_newrelic_notifications_destination.go index 14b0f0eb3..9bbae667b 100644 --- a/newrelic/data_source_newrelic_notifications_destination.go +++ b/newrelic/data_source_newrelic_notifications_destination.go @@ -62,6 +62,20 @@ func dataSourceNewRelicNotificationDestination() *schema.Resource { Computed: true, Description: "The status of the destination.", }, + "secure_url": { + Type: schema.TypeSet, + Computed: true, + Optional: true, + Description: "URL in secure format", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, } } diff --git a/newrelic/data_source_newrelic_notifications_destination_test.go b/newrelic/data_source_newrelic_notifications_destination_test.go index f68f1aa02..6e02e5e0c 100644 --- a/newrelic/data_source_newrelic_notifications_destination_test.go +++ b/newrelic/data_source_newrelic_notifications_destination_test.go @@ -54,6 +54,27 @@ func TestAccNewRelicNotificationDestinationDataSource_ByName(t *testing.T) { }) } +func TestAccNewRelicNotificationDestinationDataSource_WithSecureURL(t *testing.T) { + resourceName := "newrelic_notification_destination.foo" + rand := acctest.RandString(5) + rName := fmt.Sprintf("tf-notifications-test-%s", rand) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckEnvVars(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccNewRelicNotificationsDestinationDataSourceConfigWithSecureURL(rName), + Check: resource.ComposeTestCheckFunc( + testAccNewRelicNotificationDestination("data.newrelic_notification_destination.foo"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "secure_url.0.prefix", "https://webhook.site/"), + ), + }, + }, + }) +} + func testAccNewRelicNotificationsDestinationDataSourceConfigById(name string) string { return fmt.Sprintf(` resource "newrelic_notification_destination" "foo" { @@ -92,6 +113,30 @@ func testAccNewRelicNotificationsDestinationDataSourceConfigByName(name string) `, name) } +func testAccNewRelicNotificationsDestinationDataSourceConfigWithSecureURL(name string) string { + return fmt.Sprintf(` + resource "newrelic_notification_destination" "foo" { + name = "%s" + type = "WEBHOOK" + active = true + + property { + key = "source" + value = "terraform" + } + + secure_url { + prefix = "https://webhook.site/" + secure_suffix = "aaaaa" + } + } + + data "newrelic_notification_destination" "foo" { + name = newrelic_notification_destination.foo.name + } +`, name) +} + func testAccNewRelicNotificationDestination(n string) resource.TestCheckFunc { return func(s *terraform.State) error { r := s.RootModule().Resources[n] diff --git a/newrelic/resource_newrelic_notifications_destination.go b/newrelic/resource_newrelic_notifications_destination.go index dcd0a432d..8c7edce18 100644 --- a/newrelic/resource_newrelic_notifications_destination.go +++ b/newrelic/resource_newrelic_notifications_destination.go @@ -54,7 +54,7 @@ func resourceNewRelicNotificationDestination() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - ConflictsWith: []string{"auth_token"}, + ConflictsWith: []string{"auth_token", "auth_custom_header"}, Description: "Basic username and password authentication credentials.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -74,7 +74,7 @@ func resourceNewRelicNotificationDestination() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - ConflictsWith: []string{"auth_basic"}, + ConflictsWith: []string{"auth_basic", "auth_custom_header"}, Description: "Token authentication credentials.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -90,6 +90,25 @@ func resourceNewRelicNotificationDestination() *schema.Resource { }, }, }, + "auth_custom_header": { + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"auth_basic", "auth_token"}, + Description: "Custom header based authentication", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + }, + }, + }, "active": { Type: schema.TypeBool, Optional: true, @@ -113,6 +132,25 @@ func resourceNewRelicNotificationDestination() *schema.Resource { Computed: true, Description: "Destination entity GUID", }, + "secure_url": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "URL in secure format", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Required: true, + }, + "secure_suffix": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + }, + }, + }, }, SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ diff --git a/newrelic/resource_newrelic_notifications_destination_test.go b/newrelic/resource_newrelic_notifications_destination_test.go index acaee17df..318080c4c 100644 --- a/newrelic/resource_newrelic_notifications_destination_test.go +++ b/newrelic/resource_newrelic_notifications_destination_test.go @@ -19,7 +19,6 @@ func TestNewRelicNotificationDestination_BasicAuth(t *testing.T) { resourceName := "newrelic_notification_destination.foo" rand := acctest.RandString(5) rName := fmt.Sprintf("tf-notifications-test-%s", rand) - authAttr := `auth_basic { user = "username" password = "abc123" @@ -32,7 +31,7 @@ func TestNewRelicNotificationDestination_BasicAuth(t *testing.T) { Steps: []resource.TestStep{ // Test: Create { - Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr), + Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr, ""), Check: resource.ComposeTestCheckFunc( testAccCheckNewRelicNotificationDestinationExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "guid"), @@ -40,7 +39,7 @@ func TestNewRelicNotificationDestination_BasicAuth(t *testing.T) { }, // Update { - Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr), + Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr, ""), Check: resource.ComposeTestCheckFunc( testAccCheckNewRelicNotificationDestinationExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "guid"), @@ -60,11 +59,60 @@ func TestNewRelicNotificationDestination_BasicAuth(t *testing.T) { }) } -func TestNewRelicNotificationDestination_TokenAuth(t *testing.T) { +func TestNewRelicNotificationDestination_CustomHeadersAuth(t *testing.T) { resourceName := "newrelic_notification_destination.foo" rand := acctest.RandString(5) rName := fmt.Sprintf("tf-notifications-test-%s", rand) + authAttr := ` +auth_custom_header { + key = "testKey1" + value = "testValue1" + } +auth_custom_header { + key = "testKey2" + value = "testValue2" + } +` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckEnvVars(t) }, + Providers: testAccProviders, + CheckDestroy: testAccNewRelicNotificationDestinationDestroy, + Steps: []resource.TestStep{ + // Test: Create + { + Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr, ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckNewRelicNotificationDestinationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "guid"), + ), + }, + // Update + { + Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr, ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckNewRelicNotificationDestinationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "guid"), + ), + }, + // Import + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "auth_custom_header.0.value", + "auth_custom_header.1.value", + }, + }, + }, + }) +} +func TestNewRelicNotificationDestination_TokenAuth(t *testing.T) { + resourceName := "newrelic_notification_destination.foo" + rand := acctest.RandString(5) + rName := fmt.Sprintf("tf-notifications-test-%s", rand) authAttr := `auth_token { prefix = "testprefix" token = "abc123" @@ -77,16 +125,114 @@ func TestNewRelicNotificationDestination_TokenAuth(t *testing.T) { Steps: []resource.TestStep{ // Test: Create { - Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr), + Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr, ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckNewRelicNotificationDestinationExists(resourceName), + ), + }, + // Update + { + Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr, ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckNewRelicNotificationDestinationExists(resourceName), + ), + }, + // Import + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "auth_token.0.token", + "auth_basic.0.password", + }, + }, + }, + }) +} + +func TestNewRelicNotificationDestination_secureURL(t *testing.T) { + resourceName := "newrelic_notification_destination.foo" + rand := acctest.RandString(5) + rName := fmt.Sprintf("tf-notifications-test-%s", rand) + urlAttr := `secure_url { + prefix = "https://webbhook.site/" + secure_suffix = "test" + }` + authAttr := `auth_basic { + user = "username" + password = "abc123" + }` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckEnvVars(t) }, + Providers: testAccProviders, + CheckDestroy: testAccNewRelicNotificationDestinationDestroy, + Steps: []resource.TestStep{ + // Test: Create + { + Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr, urlAttr), + Check: resource.ComposeTestCheckFunc( + testAccCheckNewRelicNotificationDestinationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "guid"), + ), + }, + // Update + { + Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr, urlAttr), + Check: resource.ComposeTestCheckFunc( + testAccCheckNewRelicNotificationDestinationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "guid"), + ), + }, + // Import + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "auth_custom_header.0.value", + "auth_token.0.token", + "auth_basic.0.password", + "secure_url.0.secure_suffix", + }, + }, + }, + }) +} + +func TestNewRelicNotificationDestination_secureURL_update(t *testing.T) { + resourceName := "newrelic_notification_destination.foo" + rand := acctest.RandString(5) + rName := fmt.Sprintf("tf-notifications-test-%s", rand) + urlAttr := `secure_url { + prefix = "https://webbhook.site/" + secure_suffix = "test" + }` + authAttr := `auth_basic { + user = "username" + password = "abc123" + }` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckEnvVars(t) }, + Providers: testAccProviders, + CheckDestroy: testAccNewRelicNotificationDestinationDestroy, + Steps: []resource.TestStep{ + // Test: Create + { + Config: testNewRelicNotificationDestinationConfig(testAccountID, rName, authAttr, urlAttr), Check: resource.ComposeTestCheckFunc( testAccCheckNewRelicNotificationDestinationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "guid"), ), }, // Update { - Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr), + Config: testNewRelicNotificationDestinationConfig(testAccountID, fmt.Sprintf("%s-updated", rName), authAttr, ""), Check: resource.ComposeTestCheckFunc( testAccCheckNewRelicNotificationDestinationExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "guid"), ), }, // Import @@ -95,26 +241,35 @@ func TestNewRelicNotificationDestination_TokenAuth(t *testing.T) { ImportState: true, ImportStateVerify: true, ImportStateVerifyIgnore: []string{ + "auth_custom_header.0.value", "auth_token.0.token", "auth_basic.0.password", + "secure_url.0.secure_suffix", }, }, }, }) } -func testNewRelicNotificationDestinationConfig(accountID int, name string, auth string) string { - return fmt.Sprintf(` +func testNewRelicNotificationDestinationConfig(accountID int, name string, auth string, url string) string { + var usedUrl string + if url == "" { + usedUrl = `property { + key = "url" + value = "https://webhook.site/" + }` + } else { + usedUrl = url + } + + sprintf := fmt.Sprintf(` resource "newrelic_notification_destination" "foo" { account_id = %[1]d name = "%[2]s" type = "WEBHOOK" active = true - property { - key = "url" - value = "https://webhook.site/" - } + %[4]s property { key = "source" @@ -124,7 +279,8 @@ resource "newrelic_notification_destination" "foo" { %[3]s } -`, accountID, name, auth) +`, accountID, name, auth, usedUrl) + return sprintf } func testAccNewRelicNotificationDestinationDestroy(s *terraform.State) error { diff --git a/newrelic/structures_newrelic_notifications_destination.go b/newrelic/structures_newrelic_notifications_destination.go index 57ca13a9b..271f28ebe 100644 --- a/newrelic/structures_newrelic_notifications_destination.go +++ b/newrelic/structures_newrelic_notifications_destination.go @@ -3,6 +3,7 @@ package newrelic import ( "context" "fmt" + "sort" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/newrelic/newrelic-client-go/v2/pkg/ai" @@ -31,13 +32,45 @@ func expandNotificationDestination(d *schema.ResourceData) (*notifications.AiNot destination.Auth = expandNotificationDestinationAuthToken(attr.([]interface{})) } + if attr, ok := d.GetOk("auth_custom_header"); ok { + destination.Auth = expandNotificationDestinationAuthCustomHeaders(attr.([]interface{})) + } + properties := d.Get("property") props := properties.(*schema.Set).List() destination.Properties = expandNotificationDestinationProperties(props) + if attr, ok := d.GetOk("secure_url"); ok { + destination.SecureURL = expandNotificationDestinationSecureURLInput(attr.([]interface{})) + } + return &destination, nil } +func expandNotificationDestinationSecureURLInput(url []interface{}) *notifications.AiNotificationsSecureURLInput { + secureURLInput := notifications.AiNotificationsSecureURLInput{} + + for _, u := range url { + uu := u.(map[string]interface{}) + secureURLInput.Prefix = uu["prefix"].(string) + secureURLInput.SecureSuffix = notifications.SecureValue(uu["secure_suffix"].(string)) + } + + return &secureURLInput +} + +func expandNotificationDestinationSecureURLUpdate(url []interface{}) *notifications.AiNotificationsSecureURLUpdate { + secureURLUpdate := notifications.AiNotificationsSecureURLUpdate{} + + for _, u := range url { + uu := u.(map[string]interface{}) + secureURLUpdate.Prefix = uu["prefix"].(string) + secureURLUpdate.SecureSuffix = notifications.SecureValue(uu["secure_suffix"].(string)) + } + + return &secureURLUpdate +} + func expandNotificationDestinationAuthBasic(authRaw []interface{}) *notifications.AiNotificationsCredentialsInput { authInput := notifications.AiNotificationsCredentialsInput{} authInput.Type = notifications.AiNotificationsAuthTypeTypes.BASIC @@ -64,6 +97,36 @@ func expandNotificationDestinationAuthToken(authRaw []interface{}) *notification return &authInput } +func expandNotificationDestinationAuthCustomHeaders(authRaw []interface{}) *notifications.AiNotificationsCredentialsInput { + authInput := notifications.AiNotificationsCredentialsInput{} + authInput.Type = notifications.AiNotificationsAuthTypeTypes.CUSTOM_HEADERS + + customHeadersList := []notifications.AiNotificationsCustomHeaderInput{} + + for _, h := range authRaw { + customHeadersList = append(customHeadersList, expandNotificationDestinationAuthCustomHeader(h.(map[string]interface{}))) + } + + customHeaders := notifications.AiNotificationsCustomHeadersAuthInput{CustomHeaders: customHeadersList} + authInput.CustomHeaders = &customHeaders + + return &authInput +} + +func expandNotificationDestinationAuthCustomHeader(authRaw map[string]interface{}) notifications.AiNotificationsCustomHeaderInput { + customHeader := notifications.AiNotificationsCustomHeaderInput{} + + if key, ok := authRaw["key"]; ok { + customHeader.Key = key.(string) + } + + if value, ok := authRaw["value"]; ok { + customHeader.Value = notifications.SecureValue(value.(string)) + } + + return customHeader +} + func expandNotificationDestinationUpdate(d *schema.ResourceData) (*notifications.AiNotificationsDestinationUpdate, error) { destination := notifications.AiNotificationsDestinationUpdate{ Name: d.Get("name").(string), @@ -78,6 +141,13 @@ func expandNotificationDestinationUpdate(d *schema.ResourceData) (*notifications destination.Auth = expandNotificationDestinationAuthToken(attr.([]interface{})) } + if attr, ok := d.GetOk("auth_custom_header"); ok { + destination.Auth = expandNotificationDestinationAuthCustomHeaders(attr.([]interface{})) + } + + secureURL := d.Get("secure_url") + destination.SecureURL = expandNotificationDestinationSecureURLUpdate(secureURL.([]interface{})) + properties := d.Get("property") props := properties.(*schema.Set).List() destination.Properties = expandNotificationDestinationProperties(props) @@ -146,6 +216,8 @@ func flattenNotificationDestination(destination *notifications.AiNotificationsDe authAttr = "auth_oauth2" case ai.AiNotificationsAuthType(notifications.AiNotificationsAuthTypeTypes.TOKEN): authAttr = "auth_token" + case ai.AiNotificationsAuthType(notifications.AiNotificationsAuthTypeTypes.CUSTOM_HEADERS): + authAttr = "auth_custom_header" } if authAttr != "" { @@ -174,6 +246,10 @@ func flattenNotificationDestination(destination *notifications.AiNotificationsDe return err } + if err := d.Set("secure_url", flattenNotificationDestinationSecureURL(&destination.SecureURL, d)); err != nil { + return err + } + return nil } @@ -191,6 +267,9 @@ func flattenNotificationDestinationAuth(a ai.AiNotificationsAuth, d *schema.Reso "prefix": a.Prefix, "token": d.Get("auth_token.0.token"), } + case ai.AiNotificationsAuthType(notifications.AiNotificationsAuthTypeTypes.CUSTOM_HEADERS): + customHeaders := flattenNotificationDestinationCustomHeaders(a.CustomHeaders, d) + authConfig = customHeaders case ai.AiNotificationsAuthType(notifications.AiNotificationsAuthTypeTypes.OAUTH2): // This auth type is not supported } @@ -198,6 +277,36 @@ func flattenNotificationDestinationAuth(a ai.AiNotificationsAuth, d *schema.Reso return authConfig } +func flattenNotificationDestinationCustomHeaders(a []ai.AiNotificationsCustomHeaders, d *schema.ResourceData) []map[string]interface{} { + customHeaders := []map[string]interface{}{} + + // Sorts the headers, so the created list will be consistent + sort.Slice(a, func(i, j int) bool { + return a[i].Key < a[j].Key + }) + + for _, customHeader := range a { + customHeaders = append(customHeaders, flattenNotificationDestinationCustomHeader(customHeader, d, len(a))) + } + + return customHeaders +} + +func flattenNotificationDestinationCustomHeader(header ai.AiNotificationsCustomHeaders, d *schema.ResourceData, len int) map[string]interface{} { + customHeaderResult := make(map[string]interface{}) + + customHeaderResult["key"] = header.Key + for i := 0; i <= len; i++ { + keyPath := fmt.Sprintf("auth_custom_header.%d.key", i) + if d.Get(keyPath) == header.Key { + valuePath := fmt.Sprintf("auth_custom_header.%d.value", i) + customHeaderResult["value"] = d.Get(valuePath) + } + } + + return customHeaderResult +} + func flattenNotificationDestinationProperties(p []notifications.AiNotificationsProperty) []map[string]interface{} { properties := []map[string]interface{}{} @@ -219,6 +328,35 @@ func flattenNotificationDestinationProperty(p notifications.AiNotificationsPrope return propertyResult } +func flattenNotificationDestinationSecureURL(url *notifications.AiNotificationsSecureURL, d *schema.ResourceData) []map[string]interface{} { + if url == nil || url.Prefix == "" { + return nil + } + + secureURLResult := make([]map[string]interface{}, 1) + + secureURLResult[0] = map[string]interface{}{ + "prefix": url.Prefix, + "secure_suffix": d.Get("secure_url.0.secure_suffix"), + } + + return secureURLResult +} + +func flattenNotificationDestinationSecureURLForDataSource(url *notifications.AiNotificationsSecureURL) []map[string]interface{} { + if url == nil || url.Prefix == "" { + return nil + } + + secureURLResult := make([]map[string]interface{}, 1) + + secureURLResult[0] = map[string]interface{}{ + "prefix": url.Prefix, + } + + return secureURLResult +} + func flattenNotificationDestinationDataSource(destination *notifications.AiNotificationsDestination, d *schema.ResourceData) error { if destination == nil { return nil @@ -240,6 +378,10 @@ func flattenNotificationDestinationDataSource(destination *notifications.AiNotif return err } + if err := d.Set("secure_url", flattenNotificationDestinationSecureURLForDataSource(&destination.SecureURL)); err != nil { + return err + } + if err := d.Set("active", destination.Active); err != nil { return err } diff --git a/newrelic/structures_newrelic_notifications_destination_test.go b/newrelic/structures_newrelic_notifications_destination_test.go index 72d9e26eb..9731e42c3 100644 --- a/newrelic/structures_newrelic_notifications_destination_test.go +++ b/newrelic/structures_newrelic_notifications_destination_test.go @@ -14,7 +14,7 @@ import ( func TestExpandNotificationDestination(t *testing.T) { user := "test-user" - auth := ai.AiNotificationsAuth{ + basicAuthType := ai.AiNotificationsAuth{ AuthType: "BASIC", User: user, } @@ -22,6 +22,18 @@ func TestExpandNotificationDestination(t *testing.T) { "user": user, "password": "123456", } + customHeadersAuthType := ai.AiNotificationsAuth{ + AuthType: "CUSTOM_HEADERS", + CustomHeaders: []ai.AiNotificationsCustomHeaders{ + { + Key: "testKey1", + }, + }, + } + customHeadersAuth := map[string]interface{}{ + "key": "testKey1", + "value": "testValue1", + } webhookProperty := map[string]interface{}{ "key": "url", "value": "https://webhook.com", @@ -63,7 +75,7 @@ func TestExpandNotificationDestination(t *testing.T) { Expanded: ¬ifications.AiNotificationsDestination{ Name: "pd-service-test", Type: notifications.AiNotificationsDestinationTypeTypes.PAGERDUTY_SERVICE_INTEGRATION, - Auth: auth, + Auth: basicAuthType, }, }, "valid email destination (no auth)": { @@ -83,6 +95,40 @@ func TestExpandNotificationDestination(t *testing.T) { }, }, }, + "valid secureURL webhook destination": { + Data: map[string]interface{}{ + "name": "webhook-test", + "type": "WEBHOOK", + "property": []interface{}{webhookProperty}, + }, + Expanded: ¬ifications.AiNotificationsDestination{ + Name: "webhook-test", + Type: notifications.AiNotificationsDestinationTypeTypes.WEBHOOK, + Properties: []notifications.AiNotificationsProperty{}, + SecureURL: notifications.AiNotificationsSecureURL{ + Prefix: "https://webhook.com", + }, + }, + }, + "valid webhook destination with custom headers auth": { + Data: map[string]interface{}{ + "name": "webhook-test", + "type": "WEBHOOK", + "property": []interface{}{webhookProperty}, + "auth_custom_header": []interface{}{customHeadersAuth}, + }, + Expanded: ¬ifications.AiNotificationsDestination{ + Name: "webhook-test", + Type: notifications.AiNotificationsDestinationTypeTypes.WEBHOOK, + Properties: []notifications.AiNotificationsProperty{ + { + Key: "email", + Value: "example@email.com", + }, + }, + Auth: customHeadersAuthType, + }, + }, } r := resourceNewRelicNotificationDestination() diff --git a/website/docs/d/notification_destination.html.markdown b/website/docs/d/notification_destination.html.markdown index 06a1f28bc..da88a713a 100644 --- a/website/docs/d/notification_destination.html.markdown +++ b/website/docs/d/notification_destination.html.markdown @@ -77,6 +77,7 @@ In addition to all arguments above, the following attributes are exported: * `active` - An indication whether the notification destination is active or not. * `status` - The status of the notification destination. * `guid` - The unique entity identifier of the destination in New Relic. +* `secure_url` - The URL in secure format, showing only the `prefix`, as the `secure_suffix` is a secret. ``` diff --git a/website/docs/r/notification_destination.html.markdown b/website/docs/r/notification_destination.html.markdown index 7136d3a94..732336b0b 100644 --- a/website/docs/r/notification_destination.html.markdown +++ b/website/docs/r/notification_destination.html.markdown @@ -19,14 +19,19 @@ resource "newrelic_notification_destination" "foo" { name = "foo" type = "WEBHOOK" + secure_url { + prefix = "https://webhook.mywebhook.com/" + secure_suffix = "service_id/123456" + } + property { - key = "url" - value = "https://webhook.mywebhook.com" + key = "source" + value = "terraform" } - auth_basic { - user = "username" - password = "password" + auth_custom_header { + key = "API_KEY" + value = "test-api-key" } } ``` @@ -41,6 +46,8 @@ The following arguments are supported: * `type` - (Required) The type of destination. One of: `EMAIL`, `SERVICE_NOW`, `WEBHOOK`, `JIRA`, `MOBILE_PUSH`, `EVENT_BRIDGE`, `PAGERDUTY_ACCOUNT_INTEGRATION` or `PAGERDUTY_SERVICE_INTEGRATION`. The types `SLACK` and `SLACK_COLLABORATION` can only be imported, updated and destroyed (cannot be created via terraform). * `auth_basic` - (Optional) A nested block that describes a basic username and password authentication credentials. Only one auth_basic block is permitted per notification destination definition. See [Nested auth_basic blocks](#nested-auth_basic-blocks) below for details. * `auth_token` - (Optional) A nested block that describes a token authentication credentials. Only one auth_token block is permitted per notification destination definition. See [Nested auth_token blocks](#nested-auth_token-blocks) below for details. +* `auth_custom_header` - (Optional) A nested block that describes a custom header authentication credentials. Multiple blocks are permitted per notification destination definition. [Nested auth_custom_header blocks](#nested-authcustomheader-blocks) below for details. +* `secure_url` - (Optional) A nested block that describes a URL that contains sensitive data at the path or parameters. Only one secure_url block is permitted per notification destination definition. See [Nested secure_url blocks](#nested-secureurl-blocks) below for details. * `property` - (Required) A nested block that describes a notification destination property. See [Nested property blocks](#nested-property-blocks) below for details. ### Nested `auth_basic` blocks @@ -53,8 +60,18 @@ The following arguments are supported: * `prefix` - (Required) The prefix of the token auth. * `token` - (Required) Specifies the token for integrating. +### Nested `auth_custom_header` blocks + +* `key` - (Required) The key of the header. +* `value` - (Required) The secret value of the header. + ~> **NOTE:** OAuth2 authentication type is not available via terraform for notifications destinations. +### Nested `secure_url` blocks + +* `prefix` - (Required) The prefix of the URL. +* `secure_suffix` - (Required) The suffix of the URL, which contains sensitive data. + ### Nested `property` blocks * `key` - (Required) The notification property key.