Skip to content

Commit

Permalink
feat: Use notification integration from sdk (Snowflake-Labs#2445)
Browse files Browse the repository at this point in the history
Use notification integration from sdk:
- use SDK
- change resource config to work correctly
- add to migration notes
  • Loading branch information
sfc-gh-asawicki authored Jan 30, 2024
1 parent c17effd commit e8915cc
Show file tree
Hide file tree
Showing 12 changed files with 566 additions and 635 deletions.
22 changes: 22 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ This document is meant to help you migrate your Terraform config to new newest v
describe deprecations or breaking changes and help you to change your configuration to keep the same (or similar) behaviour
across different versions.

## v0.84.0 ➞ v0.85.0

### snowflake_notification_integration resource changes
#### *(behavior change)* notification_provider
`notification_provider` becomes required and has three possible values `AZURE_STORAGE_QUEUE`, `AWS_SNS`, and `GCP_PUBSUB`.
It is still possible to set it to `AWS_SQS` but because there is no underlying SQL, so it will result in an error.
Attributes `aws_sqs_arn` and `aws_sqs_role_arn` will be ignored.
Computed attributes `aws_sqs_external_id` and `aws_sqs_iam_user_arn` won't be updated.

#### *(behavior change)* force new for multiple attributes
Force new was added for the following attributes (because no usable SQL alter statements for them):
- `azure_storage_queue_primary_uri`
- `azure_tenant_id`
- `gcp_pubsub_subscription_name`
- `gcp_pubsub_topic_name`

#### *(deprecation)* direction
`direction` parameter is deprecated because it is added automatically on the SDK level.

#### *(deprecation)* type
`type` parameter is deprecated because it is added automatically on the SDK level (and basically it's always `QUEUE`).

## v0.73.0 ➞ v0.74.0
### Provider configuration changes

Expand Down
22 changes: 11 additions & 11 deletions docs/resources/notification_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,29 @@ resource "snowflake_notification_integration" "integration" {
### Required

- `name` (String)
- `notification_provider` (String) The third-party cloud message queuing service (supported values: AZURE_STORAGE_QUEUE, AWS_SNS, GCP_PUBSUB; AWS_SQS is deprecated and will be removed in the future provider versions)

### Optional

- `aws_sns_role_arn` (String) AWS IAM role ARN for notification integration to assume
- `aws_sns_topic_arn` (String) AWS SNS Topic ARN for notification integration to connect to
- `aws_sqs_arn` (String) AWS SQS queue ARN for notification integration to connect to
- `aws_sqs_role_arn` (String) AWS IAM role ARN for notification integration to assume
- `azure_storage_queue_primary_uri` (String) The queue ID for the Azure Queue Storage queue created for Event Grid notifications
- `azure_tenant_id` (String) The ID of the Azure Active Directory tenant used for identity management
- `aws_sns_role_arn` (String) AWS IAM role ARN for notification integration to assume. Required for AWS_SNS provider
- `aws_sns_topic_arn` (String) AWS SNS Topic ARN for notification integration to connect to. Required for AWS_SNS provider.
- `aws_sqs_arn` (String, Deprecated) AWS SQS queue ARN for notification integration to connect to
- `aws_sqs_role_arn` (String, Deprecated) AWS IAM role ARN for notification integration to assume
- `azure_storage_queue_primary_uri` (String) The queue ID for the Azure Queue Storage queue created for Event Grid notifications. Required for AZURE_STORAGE_QUEUE provider
- `azure_tenant_id` (String) The ID of the Azure Active Directory tenant used for identity management. Required for AZURE_STORAGE_QUEUE provider
- `comment` (String) A comment for the integration
- `direction` (String) Direction of the cloud messaging with respect to Snowflake (required only for error notifications)
- `direction` (String, Deprecated) Direction of the cloud messaging with respect to Snowflake (required only for error notifications)
- `enabled` (Boolean)
- `gcp_pubsub_subscription_name` (String) The subscription id that Snowflake will listen to when using the GCP_PUBSUB provider.
- `gcp_pubsub_topic_name` (String) The topic id that Snowflake will use to push notifications.
- `notification_provider` (String) The third-party cloud message queuing service (e.g. AZURE_STORAGE_QUEUE, AWS_SQS, AWS_SNS)
- `type` (String) A type of integration
- `type` (String, Deprecated) A type of integration

### Read-Only

- `aws_sns_external_id` (String) The external ID that Snowflake will use when assuming the AWS role
- `aws_sns_iam_user_arn` (String) The Snowflake user that will attempt to assume the AWS role.
- `aws_sqs_external_id` (String) The external ID that Snowflake will use when assuming the AWS role
- `aws_sqs_iam_user_arn` (String) The Snowflake user that will attempt to assume the AWS role.
- `aws_sqs_external_id` (String, Deprecated) The external ID that Snowflake will use when assuming the AWS role
- `aws_sqs_iam_user_arn` (String, Deprecated) The Snowflake user that will attempt to assume the AWS role.
- `created_on` (String) Date and time when the notification integration was created.
- `gcp_pubsub_service_account` (String) The GCP service account identifier that Snowflake will use when assuming the GCP role
- `id` (String) The ID of this resource.
Expand Down
2 changes: 0 additions & 2 deletions pkg/resources/api_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,6 @@ func ReadAPIIntegration(d *schema.ResourceData, meta interface{}) error {
}

// Some properties come from the DESCRIBE INTEGRATION call
// We need to grab them in a loop

integrationProperties, err := client.ApiIntegrations.Describe(ctx, id)
if err != nil {
return fmt.Errorf("could not describe api integration: %w", err)
Expand Down
153 changes: 95 additions & 58 deletions pkg/resources/email_notification_integration.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package resources

import (
"context"
"database/sql"
"fmt"
"log"
"regexp"
"strings"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

Expand Down Expand Up @@ -50,90 +51,94 @@ func EmailNotificationIntegration() *schema.Resource {
}
}

func toAllowedRecipients(emails []string) []sdk.NotificationIntegrationAllowedRecipient {
allowedRecipients := make([]sdk.NotificationIntegrationAllowedRecipient, len(emails))
for i, prefix := range emails {
allowedRecipients[i] = sdk.NotificationIntegrationAllowedRecipient{Email: prefix}
}
return allowedRecipients
}

// CreateEmailNotificationIntegration implements schema.CreateFunc.
func CreateEmailNotificationIntegration(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
name := d.Get("name").(string)
ctx := context.Background()
client := sdk.NewClientFromDB(db)

stmt := snowflake.NewNotificationIntegrationBuilder(name).Create()
name := d.Get("name").(string)
id := sdk.NewAccountObjectIdentifier(name)
enabled := d.Get("enabled").(bool)

stmt.SetString("TYPE", "EMAIL")
stmt.SetBool(`ENABLED`, d.Get("enabled").(bool))
createRequest := sdk.NewCreateNotificationIntegrationRequest(id, enabled)

if v, ok := d.GetOk("allowed_recipients"); ok {
stmt.SetStringList(`ALLOWED_RECIPIENTS`, expandStringList(v.(*schema.Set).List()))
if v, ok := d.GetOk("comment"); ok {
createRequest.WithComment(sdk.String(v.(string)))
}

if v, ok := d.GetOk("comment"); ok {
stmt.SetString(`COMMENT`, v.(string))
emailParamsRequest := sdk.NewEmailParamsRequest()
if v, ok := d.GetOk("allowed_recipients"); ok {
emailParamsRequest.WithAllowedRecipients(toAllowedRecipients(expandStringList(v.(*schema.Set).List())))
}
createRequest.WithEmailParams(emailParamsRequest)

qry := stmt.Statement()
if err := snowflake.Exec(db, qry); err != nil {
err := client.NotificationIntegrations.Create(ctx, createRequest)
if err != nil {
return fmt.Errorf("error creating notification integration: %w", err)
}

d.SetId(name)
d.SetId(helpers.EncodeSnowflakeID(id))

return ReadEmailNotificationIntegration(d, meta)
}

// ReadEmailNotificationIntegration implements schema.ReadFunc.
func ReadEmailNotificationIntegration(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
ctx := context.Background()
client := sdk.NewClientFromDB(db)
id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)

stmt := snowflake.NewEmailNotificationIntegrationBuilder(d.Id()).Show()
row := snowflake.QueryRow(db, stmt)

// Some properties can come from the SHOW INTEGRATION call
s, err := snowflake.ScanEmailNotificationIntegration(row)
integration, err := client.NotificationIntegrations.ShowByID(ctx, id)
if err != nil {
return fmt.Errorf("could not show notification integration: %w", err)
log.Printf("[DEBUG] notification integration (%s) not found", d.Id())
d.SetId("")
return err
}

if err := d.Set("name", s.Name.String); err != nil {
if err := d.Set("name", integration.Name); err != nil {
return err
}

if err := d.Set("enabled", s.Enabled.Bool); err != nil {
if err := d.Set("enabled", integration.Enabled); err != nil {
return err
}

if err := d.Set("comment", s.Comment.String); err != nil {
if err := d.Set("comment", integration.Comment); err != nil {
return err
}

// Some properties come from the DESCRIBE INTEGRATION call
// We need to grab them in a loop
var k, pType string
var v, n interface{}
stmt = snowflake.NewNotificationIntegrationBuilder(d.Id()).Describe()
rows, err := db.Query(stmt)
integrationProperties, err := client.NotificationIntegrations.Describe(ctx, id)
if err != nil {
return fmt.Errorf("could not describe notification integration: %w", err)
}
defer rows.Close()
for rows.Next() {
if err := rows.Scan(&k, &pType, &v, &n); err != nil {
return err
}
switch k {
for _, property := range integrationProperties {
name := property.Name
value := property.Value

switch name {
case "ALLOWED_RECIPIENTS":
// Empty list returns strange string (it's empty on worksheet level).
// This is a quick workaround, should be fixed with moving the email integration to SDK.
r := regexp.MustCompile(`[[:print:]]`)
if r.MatchString(v.(string)) {
if err := d.Set("allowed_recipients", strings.Split(v.(string), ",")); err != nil {
if value == "" {
if err := d.Set("allowed_recipients", make([]string, 0)); err != nil {
return err
}
} else {
empty := make([]string, 0)
if err := d.Set("allowed_recipients", empty); err != nil {
if err := d.Set("allowed_recipients", strings.Split(value, ",")); err != nil {
return err
}
}
default:
log.Printf("[WARN] unexpected property %v returned from Snowflake", k)
log.Printf("[WARN] unexpected notification integration property %v returned from Snowflake", name)
}
}

Expand All @@ -143,39 +148,71 @@ func ReadEmailNotificationIntegration(d *schema.ResourceData, meta interface{})
// UpdateEmailNotificationIntegration implements schema.UpdateFunc.
func UpdateEmailNotificationIntegration(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
id := d.Id()

stmt := snowflake.NewEmailNotificationIntegrationBuilder(id).Alter()

ctx := context.Background()
client := sdk.NewClientFromDB(db)
id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)

var runSetStatement bool
var runUnsetStatement bool
setRequest := sdk.NewNotificationIntegrationSetRequest()
unsetRequest := sdk.NewNotificationIntegrationUnsetEmailParamsRequest()
if d.HasChange("comment") {
stmt.SetString("COMMENT", d.Get("comment").(string))
v := d.Get("comment").(string)
if v == "" {
runUnsetStatement = true
unsetRequest.WithComment(sdk.Bool(true))
} else {
runSetStatement = true
setRequest.WithComment(sdk.String(d.Get("comment").(string)))
}
}

if d.HasChange("enabled") {
stmt.SetBool(`ENABLED`, d.Get("enabled").(bool))
runSetStatement = true
setRequest.WithEnabled(sdk.Bool(d.Get("enabled").(bool)))
}

if d.HasChange("allowed_recipients") {
if v, ok := d.GetOk("allowed_recipients"); ok {
stmt.SetStringList(`ALLOWED_RECIPIENTS`, expandStringList(v.(*schema.Set).List()))
v := d.Get("allowed_recipients").(*schema.Set).List()
if len(v) == 0 {
runUnsetStatement = true
unsetRequest.WithAllowedRecipients(sdk.Bool(true))
} else {
// raw sql for now; will be updated with SDK rewrite
// https://docs.snowflake.com/en/sql-reference/sql/alter-notification-integration#syntax
unset := fmt.Sprintf(`ALTER NOTIFICATION INTEGRATION "%s" UNSET ALLOWED_RECIPIENTS`, id)
if err := snowflake.Exec(db, unset); err != nil {
return fmt.Errorf("error unsetting allowed recipients on email notification integration %v err = %w", id, err)
}
runSetStatement = true
setRequest.WithSetEmailParams(sdk.NewSetEmailParamsRequest(toAllowedRecipients(expandStringList(v))))
}
}

if err := snowflake.Exec(db, stmt.Statement()); err != nil {
return fmt.Errorf("error updating notification integration: %w", err)
if runSetStatement {
err := client.NotificationIntegrations.Alter(ctx, sdk.NewAlterNotificationIntegrationRequest(id).WithSet(setRequest))
if err != nil {
return fmt.Errorf("error updating notification integration: %w", err)
}
}

if runUnsetStatement {
err := client.NotificationIntegrations.Alter(ctx, sdk.NewAlterNotificationIntegrationRequest(id).WithUnsetEmailParams(unsetRequest))
if err != nil {
return fmt.Errorf("error updating notification integration: %w", err)
}
}

return ReadEmailNotificationIntegration(d, meta)
}

// DeleteEmailNotificationIntegration implements schema.DeleteFunc.
func DeleteEmailNotificationIntegration(d *schema.ResourceData, meta interface{}) error {
return DeleteResource("", snowflake.NewEmailNotificationIntegrationBuilder)(d, meta)
db := meta.(*sql.DB)
ctx := context.Background()
client := sdk.NewClientFromDB(db)
id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier)

err := client.NotificationIntegrations.Drop(ctx, sdk.NewDropNotificationIntegrationRequest(id))
if err != nil {
return err
}

d.SetId("")

return nil
}
Loading

0 comments on commit e8915cc

Please sign in to comment.