From 7094f15133cd768bd4aa4431adc66802a7f955c0 Mon Sep 17 00:00:00 2001 From: Nicholas Farley Date: Mon, 7 Nov 2022 16:17:44 -0500 Subject: [PATCH] feat: add support for `notify_users` to `snowflake_resource_monitor` resource (#1340) * Add NotifyUsers field to resourceMonitor struct * Add notify_users property to resources/resource_monitor schema * Set notify_users field in CreateResourceMonitor * Add test for altering pkg/snowflake/resource_monitor * Update snowflake/resource_monitor creation test for notify_users * Update resource_monitor's CreateBuilder to allow string lists * Add notify_users to TestResourceMonitorCreate * Switch resource_monitor.notify_users to schema.TypeSet * Allow reading notify_users from a ResourceMonitor resource * Change TestResourceMonitorCreate to use conventional name styles * Add notify_users information to expectReadResourceMonitor * Add notify_users testing to TestAcc_ResourceMonitor * Fix method used to test for Set element existence * Update TestResourceMonitor to use more conventional user names * Remove notify_users setup/check from existing test * Add fn to generate test config for resource monitors with notify_users * Add (unconventional) TestAcc_ResourceMonitorNotifyUsers test * Add notify_users to snowflake_resource_monitor example * Add notify_users to resource_monitor docs * Run `go fmt` on changed files * Fix indentation on inline terraform declaration * Run `make docs` --- docs/resources/resource_monitor.md | 3 ++ .../snowflake_resource_monitor/resource.tf | 2 + pkg/resources/resource_monitor.go | 19 +++++++ .../resource_monitor_acceptance_test.go | 52 +++++++++++++++++++ pkg/resources/resource_monitor_test.go | 7 +-- pkg/snowflake/resource_monitor.go | 18 ++++--- pkg/snowflake/resource_monitor_test.go | 8 ++- 7 files changed, 99 insertions(+), 10 deletions(-) diff --git a/docs/resources/resource_monitor.md b/docs/resources/resource_monitor.md index f06d3c181f..7773637f5c 100644 --- a/docs/resources/resource_monitor.md +++ b/docs/resources/resource_monitor.md @@ -24,6 +24,8 @@ resource "snowflake_resource_monitor" "monitor" { notify_triggers = [40] suspend_triggers = [50] suspend_immediate_triggers = [90] + + notify_users = ["USERONE", "USERTWO"] } ``` @@ -40,6 +42,7 @@ resource "snowflake_resource_monitor" "monitor" { - `end_timestamp` (String) The date and time when the resource monitor suspends the assigned warehouses. - `frequency` (String) The frequency interval at which the credit usage resets to 0. If you set a frequency for a resource monitor, you must also set START_TIMESTAMP. - `notify_triggers` (Set of Number) A list of percentage thresholds at which to send an alert to subscribed users. +- `notify_users` (Set of String) Specifies the list of users to receive email notifications on resource monitors. - `set_for_account` (Boolean) Specifies whether the resource monitor should be applied globally to your Snowflake account. - `start_timestamp` (String) The date and time when the resource monitor starts monitoring credit usage for the assigned warehouses. - `suspend_immediate_triggers` (Set of Number) A list of percentage thresholds at which to immediately suspend all warehouses. diff --git a/examples/resources/snowflake_resource_monitor/resource.tf b/examples/resources/snowflake_resource_monitor/resource.tf index 1142b0578a..0feba7a9f2 100644 --- a/examples/resources/snowflake_resource_monitor/resource.tf +++ b/examples/resources/snowflake_resource_monitor/resource.tf @@ -9,4 +9,6 @@ resource "snowflake_resource_monitor" "monitor" { notify_triggers = [40] suspend_triggers = [50] suspend_immediate_triggers = [90] + + notify_users = ["USERONE", "USERTWO"] } diff --git a/pkg/resources/resource_monitor.go b/pkg/resources/resource_monitor.go index ed6059ce2a..bc260f9b9c 100644 --- a/pkg/resources/resource_monitor.go +++ b/pkg/resources/resource_monitor.go @@ -21,6 +21,15 @@ var resourceMonitorSchema = map[string]*schema.Schema{ Description: "Identifier for the resource monitor; must be unique for your account.", ForceNew: true, }, + "notify_users": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Description: "Specifies the list of users to receive email notifications on resource monitors.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, "credit_quota": { Type: schema.TypeInt, Optional: true, @@ -108,6 +117,9 @@ func CreateResourceMonitor(d *schema.ResourceData, meta interface{}) error { cb := snowflake.ResourceMonitor(name).Create() // Set optionals + if v, ok := d.GetOk("notify_users"); ok { + cb.SetStringList("notify_users", expandStringList(v.(*schema.Set).List())) + } if v, ok := d.GetOk("credit_quota"); ok { cb.SetInt("credit_quota", v.(int)) } @@ -194,6 +206,13 @@ func ReadResourceMonitor(d *schema.ResourceData, meta interface{}) error { return err } + if len(rm.NotifyUsers.String) > 0 { + err = d.Set("notify_users", strings.Split(rm.NotifyUsers.String, ", ")) + if err != nil { + return err + } + } + // Snowflake returns credit_quota as a float, but only accepts input as an int if rm.CreditQuota.Valid { cqf, err := strconv.ParseFloat(rm.CreditQuota.String, 64) diff --git a/pkg/resources/resource_monitor_acceptance_test.go b/pkg/resources/resource_monitor_acceptance_test.go index 95837b6d1a..0a4cf35d59 100644 --- a/pkg/resources/resource_monitor_acceptance_test.go +++ b/pkg/resources/resource_monitor_acceptance_test.go @@ -1,7 +1,9 @@ package resources_test import ( + "encoding/json" "fmt" + "os" "strings" "testing" @@ -44,3 +46,53 @@ resource "snowflake_resource_monitor" "test" { } `, accName) } + +func TestAcc_ResourceMonitorNotifyUsers(t *testing.T) { + userEnv := os.Getenv("RESOURCE_MONITOR_NOTIFY_USERS_TEST") + if userEnv == "" { + t.Skip("Skipping TestAcc_ResourceMonitorNotifyUsers") + } + users := strings.Split(userEnv, ",") + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + config, err := resourceMonitorNotifyUsersConfig(name, users) + if err != nil { + t.Error(err) + } + checks := []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "name", name), + resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "set_for_account", "false"), + } + for _, s := range users { + checks = append(checks, resource.TestCheckTypeSetElemAttr("snowflake_resource_monitor.test", "notify_users.*", s)) + } + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + ResourceName: "snowflake_resource_monitor.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func resourceMonitorNotifyUsersConfig(accName string, accNotifyUsers []string) (string, error) { + notifyUsers, err := json.Marshal(accNotifyUsers) + if err != nil { + return "", err + } + config := fmt.Sprintf(` +resource "snowflake_resource_monitor" "test" { + name = "%v" + set_for_account = false + notify_users = %v +} +`, accName, string(notifyUsers)) + return config, nil +} diff --git a/pkg/resources/resource_monitor_test.go b/pkg/resources/resource_monitor_test.go index 263d384534..4766f7468d 100644 --- a/pkg/resources/resource_monitor_test.go +++ b/pkg/resources/resource_monitor_test.go @@ -25,6 +25,7 @@ func TestResourceMonitorCreate(t *testing.T) { in := map[string]interface{}{ "name": "good_name", + "notify_users": []interface{}{"USERONE", "USERTWO"}, "credit_quota": 100, "notify_triggers": []interface{}{75, 88}, "suspend_triggers": []interface{}{99}, @@ -37,7 +38,7 @@ func TestResourceMonitorCreate(t *testing.T) { WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { mock.ExpectExec( - `^CREATE RESOURCE MONITOR "good_name" CREDIT_QUOTA=100 TRIGGERS ON 99 PERCENT DO SUSPEND ON 105 PERCENT DO SUSPEND_IMMEDIATE ON 88 PERCENT DO NOTIFY ON 75 PERCENT DO NOTIFY$`, + `^CREATE RESOURCE MONITOR "good_name" CREDIT_QUOTA=100 NOTIFY_USERS=\('USERTWO', 'USERONE'\) TRIGGERS ON 99 PERCENT DO SUSPEND ON 105 PERCENT DO SUSPEND_IMMEDIATE ON 88 PERCENT DO NOTIFY ON 75 PERCENT DO NOTIFY$`, ).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`^ALTER ACCOUNT SET RESOURCE_MONITOR = "good_name"$`).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -51,10 +52,10 @@ func expectReadResourceMonitor(mock sqlmock.Sqlmock) { rows := sqlmock.NewRows([]string{ "name", "credit_quota", "used_credits", "remaining_credits", "level", "frequency", "start_time", "end_time", "notify_at", "suspend_at", - "suspend_immediately_at", "created_on", "owner", "comment", + "suspend_immediately_at", "created_on", "owner", "comment", "notify_users", }).AddRow( "good_name", 100.00, 0.00, 100.00, "ACCOUNT", "MONTHLY", "2001-01-01 00:00:00.000 -0700", - "", "75%,88%", "99%", "105%", "2001-01-01 00:00:00.000 -0700", "ACCOUNTADMIN", "") + "", "75%,88%", "99%", "105%", "2001-01-01 00:00:00.000 -0700", "ACCOUNTADMIN", "", "USERONE, USERTWO") mock.ExpectQuery(`^SHOW RESOURCE MONITORS LIKE 'good_name'$`).WillReturnRows(rows) } diff --git a/pkg/snowflake/resource_monitor.go b/pkg/snowflake/resource_monitor.go index f2f4010861..a9ea60d841 100644 --- a/pkg/snowflake/resource_monitor.go +++ b/pkg/snowflake/resource_monitor.go @@ -62,12 +62,13 @@ const ( func (rb *ResourceMonitorBuilder) Create() *ResourceMonitorCreateBuilder { return &ResourceMonitorCreateBuilder{ CreateBuilder{ - name: rb.name, - entityType: rb.entityType, - stringProperties: make(map[string]string), - boolProperties: make(map[string]bool), - intProperties: make(map[string]int), - floatProperties: make(map[string]float64), + name: rb.name, + entityType: rb.entityType, + stringProperties: make(map[string]string), + boolProperties: make(map[string]bool), + intProperties: make(map[string]int), + floatProperties: make(map[string]float64), + stringListProperties: make(map[string][]string), }, make([]trigger, 0), } @@ -108,6 +109,10 @@ func (rcb *ResourceMonitorCreateBuilder) Statement() string { sb.WriteString(fmt.Sprintf(` %v=%.2f`, strings.ToUpper(k), v)) } + for k, v := range rcb.stringListProperties { + sb.WriteString(fmt.Sprintf(" %s=%s", strings.ToUpper(k), formatStringList(v))) + } + if len(rcb.triggers) > 0 { sb.WriteString(" TRIGGERS") } @@ -144,6 +149,7 @@ type resourceMonitor struct { CreatedOn sql.NullString `db:"created_on"` Owner sql.NullString `db:"owner"` Comment sql.NullString `db:"comment"` + NotifyUsers sql.NullString `db:"notify_users"` } func ScanResourceMonitor(row *sqlx.Row) (*resourceMonitor, error) { diff --git a/pkg/snowflake/resource_monitor_test.go b/pkg/snowflake/resource_monitor_test.go index c36ebe89b6..3f34c92760 100644 --- a/pkg/snowflake/resource_monitor_test.go +++ b/pkg/snowflake/resource_monitor_test.go @@ -26,13 +26,19 @@ func TestResourceMonitor(t *testing.T) { q = ab.Statement() r.Equal(`ALTER RESOURCE MONITOR "resource_monitor" SET CREDIT_QUOTA=66`, q) + ab = rm.Alter() + ab.SetStringList("notify_users", []string{"USERONE", "USERTWO"}) + q = ab.Statement() + r.Equal(`ALTER RESOURCE MONITOR "resource_monitor" SET NOTIFY_USERS=('USERONE', 'USERTWO')`, q) + cb := snowflake.ResourceMonitor("resource_monitor").Create() cb.NotifyAt(80).NotifyAt(90).SuspendAt(95).SuspendImmediatelyAt(100) cb.SetString("frequency", "YEARLY") cb.SetInt("credit_quota", 666) + cb.SetStringList("notify_users", []string{"USERONE", "USERTWO"}) q = cb.Statement() - r.Equal(`CREATE RESOURCE MONITOR "resource_monitor" FREQUENCY='YEARLY' CREDIT_QUOTA=666 TRIGGERS ON 80 PERCENT DO NOTIFY ON 90 PERCENT DO NOTIFY ON 95 PERCENT DO SUSPEND ON 100 PERCENT DO SUSPEND_IMMEDIATE`, q) + r.Equal(`CREATE RESOURCE MONITOR "resource_monitor" FREQUENCY='YEARLY' CREDIT_QUOTA=666 NOTIFY_USERS=('USERONE', 'USERTWO') TRIGGERS ON 80 PERCENT DO NOTIFY ON 90 PERCENT DO NOTIFY ON 95 PERCENT DO SUSPEND ON 100 PERCENT DO SUSPEND_IMMEDIATE`, q) } func TestResourceMonitorSetOnAccount(t *testing.T) {