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

feat: add in more functionality for UpdateResourceMonitor #1456

Merged
merged 4 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
102 changes: 95 additions & 7 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,30 @@ 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,
},
"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,
},
}

Expand All @@ -107,7 +101,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,13 +277,54 @@ func extractTriggerInts(s sql.NullString) ([]int, error) {
return out, nil
}

// inSlice returns true if n is in string slice h otherwise false.
func inSlice(n string, h []string) bool {
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
for _, v := range h {
if v == n {
return true
}
}
return false
}

// convertTerraformSetToStringSlice turns a terraform set into a string slice
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
func convertTerraformSetToStringSlice(i interface{}) []string {
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
var s []string
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
for _, w := range i.(*schema.Set).List() {
s = append(s, w.(string))
}
return s
}

// compareTerraformSets compares two terraform sets and returns a string slice of all values in the first set that is
// not in the second set.
func compareTerraformSets(firstSet interface{}, SecondSet interface{}) []string {
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
res := make([]string, 0)
sliceOne := convertTerraformSetToStringSlice(firstSet)

sliceTwo := convertTerraformSetToStringSlice(SecondSet)

for _, s := range sliceOne {
if !inSlice(s, sliceTwo) {
res = append(res, s)
}
}
return res
}

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

stmt := snowflake.NewResourceMonitorBuilder(id).Alter()
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
var runSetStatement bool

if d.HasChange("notify_users") {
runSetStatement = true
stmt.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))
Expand All @@ -310,12 +345,65 @@ func UpdateResourceMonitor(d *schema.ResourceData, meta interface{}) error {
stmt.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
stmt.SuspendAt(t)
}
siTrigs := expandIntList(d.Get("suspend_immediate_triggers").(*schema.Set).List())
for _, t := range siTrigs {
runSetStatement = true
stmt.SuspendImmediatelyAt(t)
}
nTrigs := expandIntList(d.Get("notify_triggers").(*schema.Set).List())
for _, t := range nTrigs {
runSetStatement = true
stmt.NotifyAt(t)
}

if runSetStatement {
if err := snowflake.Exec(db, stmt.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) == false {
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
if err := snowflake.Exec(db, stmt.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 := compareTerraformSets(oldV, v)
for _, w := range res {
if err := snowflake.Exec(db, stmt.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, stmt.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 := compareTerraformSets(v, oldV)
for _, w := range res {
if err := snowflake.Exec(db, stmt.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
93 changes: 93 additions & 0 deletions pkg/snowflake/resource_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ 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 trigger struct {
action string
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
threshold int
Expand Down Expand Up @@ -134,6 +142,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 fmt.Sprintf(`ALTER ACCOUNT SET RESOURCE_MONITOR = NULL`)
sfc-gh-jlove marked this conversation as resolved.
Show resolved Hide resolved
}

// 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