Skip to content

Commit

Permalink
feat: add in more functionality for UpdateResourceMonitor (#1456)
Browse files Browse the repository at this point in the history
* SNOW-710421: Adding more functionality to resource_monitor update

* SNOW-710421: reviewdog 🐶 fixes

* SNOW-710421: Cleaning up code

* SNOW-710421: Adds ConflictsWith for warehouses and set_for_account
  • Loading branch information
sfc-gh-jlove authored Jan 10, 2023
1 parent f8cecd7 commit 2df570f
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 27 deletions.
15 changes: 15 additions & 0 deletions pkg/resources/helper_expansion.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package resources

import "golang.org/x/exp/slices"

// borrowed from https://github.com/terraform-providers/terraform-provider-aws/blob/master/aws/structure.go#L924:6

func expandIntList(configured []interface{}) []int {
Expand Down Expand Up @@ -36,3 +38,16 @@ func expandObjectIdentifier(objectIdentifier interface{}) (string, string, strin
}
return objectDatabase, objectSchema, objectName
}

// intersectionAAndNotB takes the intersection of set A and the intersection of not set B. A∩B′ in set notation.
func intersectionAAndNotB(setA []interface{}, setB []interface{}) []string {
res := make([]string, 0)
sliceA := expandStringList(setA)
sliceB := expandStringList(setB)
for _, s := range sliceA {
if !slices.Contains(sliceB, s) {
res = append(res, s)
}
}
return res
}
97 changes: 76 additions & 21 deletions pkg/resources/resource_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ var resourceMonitorSchema = map[string]*schema.Schema{
"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,
Expand Down Expand Up @@ -60,35 +59,32 @@ var resourceMonitorSchema = map[string]*schema.Schema{
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
Description: "A list of percentage thresholds at which to suspend all warehouses.",
ForceNew: true,
},
"suspend_immediate_triggers": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
Description: "A list of percentage thresholds at which to immediately suspend all warehouses.",
ForceNew: true,
},
"notify_triggers": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
Description: "A list of percentage thresholds at which to send an alert to subscribed users.",
ForceNew: true,
},
"set_for_account": {
Type: schema.TypeBool,
Optional: true,
Description: "Specifies whether the resource monitor should be applied globally to your Snowflake account.",
Default: false,
ForceNew: true,
Type: schema.TypeBool,
Optional: true,
ConflictsWith: []string{"warehouses"},
Description: "Specifies whether the resource monitor should be applied globally to your Snowflake account.",
Default: false,
},
"warehouses": {
Type: schema.TypeSet,
Optional: true,
Description: "A list of warehouses to apply the resource monitor to.",
Elem: &schema.Schema{Type: schema.TypeString},
ForceNew: true,
Type: schema.TypeSet,
Optional: true,
ConflictsWith: []string{"set_for_account"},
Description: "A list of warehouses to apply the resource monitor to.",
Elem: &schema.Schema{Type: schema.TypeString},
},
}

Expand All @@ -107,7 +103,7 @@ func ResourceMonitor() *schema.Resource {
}
}

// CreateResourceMonitor implents schema.CreateFunc.
// CreateResourceMonitor implements schema.CreateFunc.
func CreateResourceMonitor(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
name := d.Get("name").(string)
Expand Down Expand Up @@ -283,39 +279,98 @@ func extractTriggerInts(s sql.NullString) ([]int, error) {
return out, nil
}

// UpdateResourceMonitor implements schema.UpdateFunc.
func UpdateResourceMonitor(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
id := d.Id()

stmt := snowflake.NewResourceMonitorBuilder(id).Alter()
ub := snowflake.NewResourceMonitorBuilder(id).Alter()
var runSetStatement bool

if d.HasChange("notify_users") {
runSetStatement = true
ub.SetStringList(`NOTIFY_USERS`, expandStringList(d.Get("notify_users").(*schema.Set).List()))
}

if d.HasChange("credit_quota") {
runSetStatement = true
stmt.SetInt(`CREDIT_QUOTA`, d.Get("credit_quota").(int))
ub.SetInt(`CREDIT_QUOTA`, d.Get("credit_quota").(int))
}

if d.HasChange("frequency") {
runSetStatement = true
stmt.SetString(`FREQUENCY`, d.Get("frequency").(string))
ub.SetString(`FREQUENCY`, d.Get("frequency").(string))
}

if d.HasChange("start_timestamp") {
runSetStatement = true
stmt.SetString(`START_TIMESTAMP`, d.Get("start_timestamp").(string))
ub.SetString(`START_TIMESTAMP`, d.Get("start_timestamp").(string))
}

if d.HasChange("end_timestamp") {
runSetStatement = true
stmt.SetString(`END_TIMESTAMP`, d.Get("end_timestamp").(string))
ub.SetString(`END_TIMESTAMP`, d.Get("end_timestamp").(string))
}

// Set triggers
sTrigs := expandIntList(d.Get("suspend_triggers").(*schema.Set).List())
for _, t := range sTrigs {
runSetStatement = true
ub.SuspendAt(t)
}
siTrigs := expandIntList(d.Get("suspend_immediate_triggers").(*schema.Set).List())
for _, t := range siTrigs {
runSetStatement = true
ub.SuspendImmediatelyAt(t)
}
nTrigs := expandIntList(d.Get("notify_triggers").(*schema.Set).List())
for _, t := range nTrigs {
runSetStatement = true
ub.NotifyAt(t)
}

if runSetStatement {
if err := snowflake.Exec(db, stmt.Statement()); err != nil {
if err := snowflake.Exec(db, ub.Statement()); err != nil {
return fmt.Errorf("error updating resource monitor %v\n%w", id, err)
}
}

// Remove from account
if d.HasChange("set_for_account") && !d.Get("set_for_account").(bool) {
if err := snowflake.Exec(db, ub.UnsetOnAccount()); err != nil {
return fmt.Errorf("error unsetting resource monitor %v on account err = %w", id, err)
}
}

// Remove from all old warehouses
if d.HasChange("warehouses") {
oldV, v := d.GetChange("warehouses")
res := intersectionAAndNotB(oldV.(*schema.Set).List(), v.(*schema.Set).List())
for _, w := range res {
if err := snowflake.Exec(db, ub.UnsetOnWarehouse(w)); err != nil {
return fmt.Errorf("error setting resource monitor %v on warehouse %v err = %w", id, w, err)
}
}
}

// Add to account
if d.HasChange("set_for_account") && d.Get("set_for_account").(bool) {
if err := snowflake.Exec(db, ub.SetOnAccount()); err != nil {
return fmt.Errorf("error setting resource monitor %v on account err = %w", id, err)
}
}

// Add to all new warehouses
if d.HasChange("warehouses") {
oldV, v := d.GetChange("warehouses")
res := intersectionAAndNotB(v.(*schema.Set).List(), oldV.(*schema.Set).List())
for _, w := range res {
if err := snowflake.Exec(db, ub.SetOnWarehouse(w)); err != nil {
return fmt.Errorf("error setting resource monitor %v on warehouse %v err = %w", id, w, err)
}
}
}

return ReadResourceMonitor(d, meta)
}

Expand Down
8 changes: 6 additions & 2 deletions pkg/resources/resource_monitor_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestAcc_ResourceMonitor(t *testing.T) {
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "name", name),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "credit_quota", "100"),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "set_for_account", "false"),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "notify_triggers.0", "40"),
),
},
// CHANGE PROPERTIES
Expand All @@ -33,7 +34,8 @@ func TestAcc_ResourceMonitor(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "name", name),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "credit_quota", "150"),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "set_for_account", "false"),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "set_for_account", "true"),
resource.TestCheckResourceAttr("snowflake_resource_monitor.test", "notify_triggers.0", "50"),
),
},
// IMPORT
Expand All @@ -52,6 +54,7 @@ resource "snowflake_resource_monitor" "test" {
name = "%v"
credit_quota = 100
set_for_account = false
notify_triggers = [40]
}
`, accName)
}
Expand All @@ -61,7 +64,8 @@ func resourceMonitorConfig2(accName string) string {
resource "snowflake_resource_monitor" "test" {
name = "%v"
credit_quota = 150
set_for_account = false
set_for_account = true
notify_triggers = [50]
}
`, accName)
}
Expand Down
103 changes: 99 additions & 4 deletions pkg/snowflake/resource_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,28 @@ type ResourceMonitorCreateBuilder struct {
triggers []trigger
}

type ResourceMonitorAlterBuilder struct {
AlterPropertiesBuilder

// triggers consist of the type (DO SUSPEND | SUSPEND_IMMEDIATE | NOTIFY) and
// the threshold (a percentage value)
triggers []trigger
}

type action string

type trigger struct {
action string
action action
threshold int
}

const (
// SuspendTrigger suspends all assigned warehouses while allowing currently running queries to complete.
SuspendTrigger = "SUSPEND"
SuspendTrigger action = "SUSPEND"
// SuspendImmediatelyTrigger suspends all assigned warehouses immediately and cancel any currently running queries or statements using the warehouses.
SuspendImmediatelyTrigger = "SUSPEND_IMMEDIATE"
SuspendImmediatelyTrigger action = "SUSPEND_IMMEDIATE"
// NotifyTrigger sends an alert (to all users who have enabled notifications for themselves), but do not take any other action.
NotifyTrigger = "NOTIFY"
NotifyTrigger action = "NOTIFY"
)

// Create returns a pointer to a ResourceMonitorCreateBuilder.
Expand Down Expand Up @@ -134,6 +144,91 @@ func (rcb *ResourceMonitorCreateBuilder) SetOnWarehouse(warehouse string) string
return fmt.Sprintf(`ALTER WAREHOUSE "%v" SET RESOURCE_MONITOR = "%v"`, warehouse, rcb.name)
}

// Alter returns a pointer to a ResourceMonitorAlterBuilder.
func (rb *ResourceMonitorBuilder) Alter() *ResourceMonitorAlterBuilder {
return &ResourceMonitorAlterBuilder{
AlterPropertiesBuilder{
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),
}
}

// NotifyAt adds a notify trigger at the specified percentage threshold.
func (rcb *ResourceMonitorAlterBuilder) NotifyAt(pct int) *ResourceMonitorAlterBuilder {
rcb.triggers = append(rcb.triggers, trigger{NotifyTrigger, pct})
return rcb
}

// SuspendAt adds a suspend trigger at the specified percentage threshold.
func (rcb *ResourceMonitorAlterBuilder) SuspendAt(pct int) *ResourceMonitorAlterBuilder {
rcb.triggers = append(rcb.triggers, trigger{SuspendTrigger, pct})
return rcb
}

// SuspendImmediatelyAt adds a suspend immediately trigger at the specified percentage threshold.
func (rcb *ResourceMonitorAlterBuilder) SuspendImmediatelyAt(pct int) *ResourceMonitorAlterBuilder {
rcb.triggers = append(rcb.triggers, trigger{SuspendImmediatelyTrigger, pct})
return rcb
}

// Statement returns the SQL statement needed to actually alter the resource.
func (rcb *ResourceMonitorAlterBuilder) Statement() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf(`ALTER %v "%v" SET`, rcb.entityType, rcb.name))

for k, v := range rcb.stringProperties {
sb.WriteString(fmt.Sprintf(` %v='%v'`, strings.ToUpper(k), EscapeString(v)))
}

for k, v := range rcb.intProperties {
sb.WriteString(fmt.Sprintf(` %v=%d`, strings.ToUpper(k), v))
}

for k, v := range rcb.floatProperties {
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")
}

for _, trig := range rcb.triggers {
sb.WriteString(fmt.Sprintf(` ON %d PERCENT DO %v`, trig.threshold, trig.action))
}

return sb.String()
}

// SetOnAccount returns the SQL query that will set the resource monitor globally on your Snowflake account.
func (rcb *ResourceMonitorAlterBuilder) SetOnAccount() string {
return fmt.Sprintf(`ALTER ACCOUNT SET RESOURCE_MONITOR = "%v"`, rcb.name)
}

func (rcb *ResourceMonitorAlterBuilder) UnsetOnAccount() string {
return `ALTER ACCOUNT SET RESOURCE_MONITOR = NULL`
}

// SetOnWarehouse returns the SQL query that will set the resource monitor on the specified warehouse.
func (rcb *ResourceMonitorAlterBuilder) SetOnWarehouse(warehouse string) string {
return fmt.Sprintf(`ALTER WAREHOUSE "%v" SET RESOURCE_MONITOR = "%v"`, warehouse, rcb.name)
}

// UnsetOnWarehouse returns the SQL query that will unset the resource monitor on the specified warehouse.
func (rcb *ResourceMonitorAlterBuilder) UnsetOnWarehouse(warehouse string) string {
return fmt.Sprintf(`ALTER WAREHOUSE "%v" SET RESOURCE_MONITOR = NULL`, warehouse)
}

type ResourceMonitor struct {
Name sql.NullString `db:"name"`
CreditQuota sql.NullString `db:"credit_quota"`
Expand Down

0 comments on commit 2df570f

Please sign in to comment.