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: Use notification integration from sdk #2445

Merged
merged 10 commits into from
Jan 30, 2024
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
Loading