From a087eb283f06b95c5bca7412717b446feedf61b8 Mon Sep 17 00:00:00 2001 From: mayank0202 Date: Fri, 29 Mar 2024 15:31:01 +0530 Subject: [PATCH 01/17] Enhancement: snowflake destination addition in firehose kinesis stream --- internal/service/firehose/delivery_stream.go | 119 ++++++++++++++++++ ...sis_firehose_delivery_stream.html.markdown | 102 ++++++++++++++- 2 files changed, 220 insertions(+), 1 deletion(-) diff --git a/internal/service/firehose/delivery_stream.go b/internal/service/firehose/delivery_stream.go index 71b95012532..6db84ed98e7 100644 --- a/internal/service/firehose/delivery_stream.go +++ b/internal/service/firehose/delivery_stream.go @@ -42,6 +42,7 @@ const ( destinationTypeOpenSearchServerless destinationType = "opensearchserverless" destinationTypeRedshift destinationType = "redshift" destinationTypeSplunk destinationType = "splunk" + destinationTypeSnowflake destinationType = "snowflake" ) func (destinationType) Values() []destinationType { @@ -53,6 +54,7 @@ func (destinationType) Values() []destinationType { destinationTypeOpenSearchServerless, destinationTypeRedshift, destinationTypeSplunk, + destinationTypeSnowflake, } } @@ -977,6 +979,113 @@ func resourceDeliveryStream() *schema.Resource { }, }, }, + "snowflake_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "account_url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateSnowflakeAccountURL, + }, + "private_key": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "key_passphrase": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "user": { + Type: schema.TypeString, + Required: true, + }, + "database": { + Type: schema.TypeString, + Required: true, + }, + "schema": { + Type: schema.TypeString, + Required: true, + }, + "table": { + Type: schema.TypeString, + Required: true, + }, + "snowflake_role_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "snowflake_role": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "data_loading_option": { + Type: schema.TypeString, + Optional: true, + }, + "meta_data_column_name": { + Type: schema.TypeString, + Optional: true, + }, + "content_column_name": { + Type: schema.TypeString, + Optional: true, + }, + "snowflake_vpc_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "private_link_vpce_id": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "cloud_watch_logging_options": cloudWatchLoggingOptionsSchema(), + "processing_configuration": processingConfigurationSchema(), + "role_arn": { + Type: schema.TypeString, + Required: true, + }, + "retry_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "duration_in_seconds": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + "s3_backup_mode": { + Type: schema.TypeString, + Optional: true, + }, + "s3_configuration": s3ConfigurationSchema(), + }, + }, + }, "opensearchserverless_configuration": { Type: schema.TypeList, Optional: true, @@ -3592,3 +3701,13 @@ func defaultProcessorParameters(destinationType destinationType, processorType t return make(map[types.ProcessorParameterName]string) } } + +func validateSnowflakeAccountURL(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if !strings.HasPrefix(value, "https://") { + errors = append(errors, fmt.Errorf("%q must start with 'https://'", k)) + } + // Add more validation logic if needed, such as checking the format of the account URL + // You can use regular expressions or other methods for this validation. + return +} diff --git a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown index 8ce4c323076..90d2050ffac 100644 --- a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown @@ -8,7 +8,7 @@ description: |- # Resource: aws_kinesis_firehose_delivery_stream -Provides a Kinesis Firehose Delivery Stream resource. Amazon Kinesis Firehose is a fully managed, elastic service to easily deliver real-time data streams to destinations such as Amazon S3 and Amazon Redshift. +Provides a Kinesis Firehose Delivery Stream resource. Amazon Kinesis Firehose is a fully managed, elastic service to easily deliver real-time data streams to destinations such as Amazon S3 , Amazon Redshift and Snowflake. For more details, see the [Amazon Kinesis Firehose Documentation][1]. @@ -592,6 +592,83 @@ resource "aws_kinesis_firehose_delivery_stream" "test_stream" { } ``` +### Snowflake Destination + +```terraform +resource "aws_kinesis_firehose_delivery_stream" "example_snowflake_destination" { + name = "example-snowflake-destination" + destination = "snowflake" + + snowflake_destination_configuration { + account_url = "string" + private_key = "string" + key_passphrase = "string" + user = "string" + database = "string" + schema = "string" + table = "string" + snowflake_role_configuration { + enabled = true + snowflake_role = "string" + } + data_loading_option = "JSON_MAPPING" + meta_data_column_name = "string" + content_column_name = "string" + snowflake_vpc_configuration { + private_link_vpce_id = "string" + } + cloud_watch_logging_options { + enabled = true + log_group_name = "string" + log_stream_name = "string" + } + processing_configuration { + enabled = true + processors = [ + { + type = "RecordDeAggregation" + parameters = [ + { + parameter_name = "LambdaArn" + parameter_value = "string" + } + # Add more parameters as needed + ] + } + # Add more processors as needed + ] + } + role_arn = "string" + retry_options { + duration_in_seconds = 123 + } + s3_backup_mode = "FailedDataOnly" + s3_configuration { + role_arn = "string" + bucket_arn = "string" + prefix = "string" + error_output_prefix = "string" + buffering_hints { + size_in_mbs = 123 + interval_in_seconds = 456 + } + compression_format = "UNCOMPRESSED" + encryption_configuration { + no_encryption_config = "NoEncryption" + kms_encryption_config { + aws_kms_key_arn = "string" + } + } + cloud_watch_logging_options { + enabled = true + log_group_name = "string" + log_stream_name = "string" + } + } + } +} +``` + ## Argument Reference This resource supports the following arguments: @@ -760,6 +837,29 @@ The `http_endpoint_configuration` configuration block supports the following arg * `request_configuration` - (Optional) The request configuration. See [`request_configuration` block](#request_configuration-block) below for details. * `retry_duration` - (Optional) Total amount of seconds Firehose spends on retries. This duration starts after the initial attempt fails, It does not include the time periods during which Firehose waits for acknowledgment from the specified destination after each attempt. Valid values between `0` and `7200`. Default is `300`. +### `snowflake_configuration` block + +The `snowflake_configuration` configuration block supports the following arguments: + +* `account_url` - (Required) The URL of the Snowflake account. Format: https://[account_identifier].snowflakecomputing.com. +* `private_key` - (Required) The private key for authentication. +* `key_passphrase` - (Required) The passphrase for the private key. +* `user` - (Required) The user for authentication. +* `database` - (Required) The Snowflake database name. +* `schema` - (Required) The Snowflake schema name. +* `table` - (Required) The Snowflake table name. +* `snowflake_role_configuration` - (Optional) The configuration for Snowflake role. See [`snowflake_role_configuration` block](#snowflake_role_configuration-block) below for details. +* `data_loading_option` - (Optional) The data loading option. +* `meta_data_column_name` - (Optional) The name of the metadata column. +* `content_column_name` - (Optional) The name of the content column. +* `snowflake_vpc_configuration` - (Optional) The VPC configuration for Snowflake. See [`snowflake_vpc_configuration` block](#snowflake_vpc_configuration-block) below for details. +* `cloud_watch_logging_options` - (Optional) The CloudWatch logging options. See [`cloud_watch_logging_options` block](#cloud_watch_logging_options-block) below for details. +* `processing_configuration` - (Optional) The processing configuration. See [`processing_configuration` block](#processing_configuration-block) below for details. +* `role_arn` - (Required) The ARN of the IAM role. +* `retry_options` - (Optional) The retry options. See [`retry_options` block](#retry_options-block) below for details. +* `s3_backup_mode` - (Optional) The S3 backup mode. +* `s3_configuration` - (Required) The S3 configuration. See [`s3_configuration` block](#s3_configuration-block) below for details. + ### `cloudwatch_logging_options` block The `cloudwatch_logging_options` configuration block supports the following arguments: From 76309f7dc70074a5346b664cb4aad84f7fe299eb Mon Sep 17 00:00:00 2001 From: mayank0202 Date: Fri, 29 Mar 2024 15:49:01 +0530 Subject: [PATCH 02/17] added changelog --- .changelog/36646.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/36646.txt diff --git a/.changelog/36646.txt b/.changelog/36646.txt new file mode 100644 index 00000000000..98aa0783b85 --- /dev/null +++ b/.changelog/36646.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_kinesis_firehose_delivery_stream: Add `snowflake` as a destination +``` \ No newline at end of file From 9941f22bac94f2efa200558f91bb1c76a181e751 Mon Sep 17 00:00:00 2001 From: mayank0202 Date: Fri, 29 Mar 2024 15:52:25 +0530 Subject: [PATCH 03/17] ran make-fmt --- internal/service/firehose/delivery_stream.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/service/firehose/delivery_stream.go b/internal/service/firehose/delivery_stream.go index 6db84ed98e7..67423791720 100644 --- a/internal/service/firehose/delivery_stream.go +++ b/internal/service/firehose/delivery_stream.go @@ -1059,8 +1059,8 @@ func resourceDeliveryStream() *schema.Resource { }, }, }, - "cloud_watch_logging_options": cloudWatchLoggingOptionsSchema(), - "processing_configuration": processingConfigurationSchema(), + "cloud_watch_logging_options": cloudWatchLoggingOptionsSchema(), + "processing_configuration": processingConfigurationSchema(), "role_arn": { Type: schema.TypeString, Required: true, @@ -1082,7 +1082,7 @@ func resourceDeliveryStream() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "s3_configuration": s3ConfigurationSchema(), + "s3_configuration": s3ConfigurationSchema(), }, }, }, @@ -3703,11 +3703,11 @@ func defaultProcessorParameters(destinationType destinationType, processorType t } func validateSnowflakeAccountURL(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if !strings.HasPrefix(value, "https://") { - errors = append(errors, fmt.Errorf("%q must start with 'https://'", k)) - } - // Add more validation logic if needed, such as checking the format of the account URL - // You can use regular expressions or other methods for this validation. - return + value := v.(string) + if !strings.HasPrefix(value, "https://") { + errors = append(errors, fmt.Errorf("%q must start with 'https://'", k)) + } + // Add more validation logic if needed, such as checking the format of the account URL + // You can use regular expressions or other methods for this validation. + return } From 9d6d4a011bba54e25d3fe36e7c37e62ad5b77972 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 15:08:22 -0400 Subject: [PATCH 04/17] r/aws_kinesis_firehose_delivery_stream: Tidy up 'snowflake_configuration' schema. --- internal/service/firehose/delivery_stream.go | 119 +++++++++--------- ...sis_firehose_delivery_stream.html.markdown | 15 ++- 2 files changed, 65 insertions(+), 69 deletions(-) diff --git a/internal/service/firehose/delivery_stream.go b/internal/service/firehose/delivery_stream.go index 67423791720..a565f3a69b4 100644 --- a/internal/service/firehose/delivery_stream.go +++ b/internal/service/firehose/delivery_stream.go @@ -986,35 +986,63 @@ func resourceDeliveryStream() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "account_url": { + Type: schema.TypeString, + Required: true, + }, + "cloudwatch_logging_options": cloudWatchLoggingOptionsSchema(), + "content_column_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "data_loading_option": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.SnowflakeDataLoadingOption](), + }, + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "key_passphrase": { Type: schema.TypeString, Required: true, - ValidateFunc: validateSnowflakeAccountURL, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(7, 255), + }, + "metadata_column_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, "private_key": { Type: schema.TypeString, Required: true, Sensitive: true, }, - "key_passphrase": { - Type: schema.TypeString, - Required: true, - Sensitive: true, + "processing_configuration": processingConfigurationSchema(), + "retry_duration": { + Type: schema.TypeInt, + Optional: true, + Default: 60, + ValidateFunc: validation.IntBetween(0, 7200), }, - "user": { - Type: schema.TypeString, - Required: true, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, }, - "database": { - Type: schema.TypeString, - Required: true, + "s3_backup_mode": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.SnowflakeS3BackupMode](), }, + "s3_configuration": s3ConfigurationSchema(), "schema": { - Type: schema.TypeString, - Required: true, - }, - "table": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, "snowflake_role_configuration": { Type: schema.TypeList, @@ -1028,24 +1056,13 @@ func resourceDeliveryStream() *schema.Resource { Default: false, }, "snowflake_role": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, }, }, }, - "data_loading_option": { - Type: schema.TypeString, - Optional: true, - }, - "meta_data_column_name": { - Type: schema.TypeString, - Optional: true, - }, - "content_column_name": { - Type: schema.TypeString, - Optional: true, - }, "snowflake_vpc_configuration": { Type: schema.TypeList, Optional: true, @@ -1059,30 +1076,16 @@ func resourceDeliveryStream() *schema.Resource { }, }, }, - "cloud_watch_logging_options": cloudWatchLoggingOptionsSchema(), - "processing_configuration": processingConfigurationSchema(), - "role_arn": { - Type: schema.TypeString, - Required: true, - }, - "retry_options": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "duration_in_seconds": { - Type: schema.TypeInt, - Required: true, - }, - }, - }, + "table": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, - "s3_backup_mode": { - Type: schema.TypeString, - Optional: true, + "user": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), }, - "s3_configuration": s3ConfigurationSchema(), }, }, }, @@ -3701,13 +3704,3 @@ func defaultProcessorParameters(destinationType destinationType, processorType t return make(map[types.ProcessorParameterName]string) } } - -func validateSnowflakeAccountURL(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if !strings.HasPrefix(value, "https://") { - errors = append(errors, fmt.Errorf("%q must start with 'https://'", k)) - } - // Add more validation logic if needed, such as checking the format of the account URL - // You can use regular expressions or other methods for this validation. - return -} diff --git a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown index 90d2050ffac..d5a55e8ec5e 100644 --- a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown @@ -612,7 +612,7 @@ resource "aws_kinesis_firehose_delivery_stream" "example_snowflake_destination" snowflake_role = "string" } data_loading_option = "JSON_MAPPING" - meta_data_column_name = "string" + metadata_column_name = "string" content_column_name = "string" snowflake_vpc_configuration { private_link_vpce_id = "string" @@ -848,15 +848,18 @@ The `snowflake_configuration` configuration block supports the following argumen * `database` - (Required) The Snowflake database name. * `schema` - (Required) The Snowflake schema name. * `table` - (Required) The Snowflake table name. -* `snowflake_role_configuration` - (Optional) The configuration for Snowflake role. See [`snowflake_role_configuration` block](#snowflake_role_configuration-block) below for details. +* `snowflake_role_configuration` - (Optional) The configuration for Snowflake role. + * `enabled` - (Optional) Whether the Snowflake role is enabled. + * `snowflake_role` - (Optional) The Snowflake role. * `data_loading_option` - (Optional) The data loading option. -* `meta_data_column_name` - (Optional) The name of the metadata column. +* `metadata_column_name` - (Optional) The name of the metadata column. * `content_column_name` - (Optional) The name of the content column. -* `snowflake_vpc_configuration` - (Optional) The VPC configuration for Snowflake. See [`snowflake_vpc_configuration` block](#snowflake_vpc_configuration-block) below for details. -* `cloud_watch_logging_options` - (Optional) The CloudWatch logging options. See [`cloud_watch_logging_options` block](#cloud_watch_logging_options-block) below for details. +* `snowflake_vpc_configuration` - (Optional) The VPC configuration for Snowflake. + * `private_link_vpce_id` - (Required) The VPCE ID for Firehose to privately connect with Snowflake. +* `cloudwatch_logging_options` - (Optional) The CloudWatch logging options. See [`cloud_watch_logging_options` block](#cloud_watch_logging_options-block) below for details. * `processing_configuration` - (Optional) The processing configuration. See [`processing_configuration` block](#processing_configuration-block) below for details. * `role_arn` - (Required) The ARN of the IAM role. -* `retry_options` - (Optional) The retry options. See [`retry_options` block](#retry_options-block) below for details. +* `retry_duration` - (Optional) After an initial failure to deliver to Snowflake, the total amount of time, in seconds between 0 to 7200, during which Firehose re-attempts delivery (including the first attempt). After this time has elapsed, the failed documents are written to Amazon S3. The default value is 60s. There will be no retry if the value is 0. * `s3_backup_mode` - (Optional) The S3 backup mode. * `s3_configuration` - (Required) The S3 configuration. See [`s3_configuration` block](#s3_configuration-block) below for details. From 1a89309927727efb6ad592a53f8435d2d73540e7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:15:41 -0400 Subject: [PATCH 05/17] r/aws_kinesis_firehose_delivery_stream: Add 'snowflake_configuration' flex. --- internal/service/firehose/delivery_stream.go | 223 +++++++++++++++++- ...sis_firehose_delivery_stream.html.markdown | 3 +- 2 files changed, 222 insertions(+), 4 deletions(-) diff --git a/internal/service/firehose/delivery_stream.go b/internal/service/firehose/delivery_stream.go index a565f3a69b4..055f3cd0c78 100644 --- a/internal/service/firehose/delivery_stream.go +++ b/internal/service/firehose/delivery_stream.go @@ -41,8 +41,8 @@ const ( destinationTypeOpenSearch destinationType = "opensearch" destinationTypeOpenSearchServerless destinationType = "opensearchserverless" destinationTypeRedshift destinationType = "redshift" - destinationTypeSplunk destinationType = "splunk" destinationTypeSnowflake destinationType = "snowflake" + destinationTypeSplunk destinationType = "splunk" ) func (destinationType) Values() []destinationType { @@ -53,8 +53,8 @@ func (destinationType) Values() []destinationType { destinationTypeOpenSearch, destinationTypeOpenSearchServerless, destinationTypeRedshift, - destinationTypeSplunk, destinationTypeSnowflake, + destinationTypeSplunk, } } @@ -1007,7 +1007,7 @@ func resourceDeliveryStream() *schema.Resource { }, "key_passphrase": { Type: schema.TypeString, - Required: true, + Optional: true, Sensitive: true, ValidateFunc: validation.StringLenBetween(7, 255), }, @@ -1332,6 +1332,7 @@ func resourceDeliveryStream() *schema.Resource { destinationTypeOpenSearch: "opensearch_configuration", destinationTypeOpenSearchServerless: "opensearchserverless_configuration", destinationTypeRedshift: "redshift_configuration", + destinationTypeSnowflake: "snowflake_configuration", destinationTypeSplunk: "splunk_configuration", }[destination] @@ -1389,6 +1390,10 @@ func resourceDeliveryStreamCreate(ctx context.Context, d *schema.ResourceData, m if v, ok := d.GetOk("redshift_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.RedshiftDestinationConfiguration = expandRedshiftDestinationConfiguration(v.([]interface{})[0].(map[string]interface{})) } + case destinationTypeSnowflake: + if v, ok := d.GetOk("snowflake_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.SnowflakeDestinationConfiguration = expandSnowflakeDestinationConfiguration(v.([]interface{})[0].(map[string]interface{})) + } case destinationTypeSplunk: if v, ok := d.GetOk("splunk_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.SplunkDestinationConfiguration = expandSplunkDestinationConfiguration(v.([]interface{})[0].(map[string]interface{})) @@ -1510,6 +1515,13 @@ func resourceDeliveryStreamRead(ctx context.Context, d *schema.ResourceData, met if err := d.Set("redshift_configuration", flattenRedshiftDestinationDescription(destination.RedshiftDestinationDescription, configuredPassword)); err != nil { return sdkdiag.AppendErrorf(diags, "setting redshift_configuration: %s", err) } + case destination.SnowflakeDestinationDescription != nil: + d.Set("destination", destinationTypeSnowflake) + configuredKeyPassphrase := d.Get("snowflake_configuration.0.key_passphrase").(string) + configuredPrivateKey := d.Get("snowflake_configuration.0.private_key").(string) + if err := d.Set("snowflake_configuration", flattenSnowflakeDestinationDescription(destination.SnowflakeDestinationDescription, configuredKeyPassphrase, configuredPrivateKey)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting snowflake_configuration: %s", err) + } case destination.SplunkDestinationDescription != nil: d.Set("destination", destinationTypeSplunk) if err := d.Set("splunk_configuration", flattenSplunkDestinationDescription(destination.SplunkDestinationDescription)); err != nil { @@ -1565,6 +1577,10 @@ func resourceDeliveryStreamUpdate(ctx context.Context, d *schema.ResourceData, m if v, ok := d.GetOk("redshift_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.RedshiftDestinationUpdate = expandRedshiftDestinationUpdate(v.([]interface{})[0].(map[string]interface{})) } + case destinationTypeSnowflake: + if v, ok := d.GetOk("snowflake_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.SnowflakeDestinationUpdate = expandSnowflakeDestinationUpdate(v.([]interface{})[0].(map[string]interface{})) + } case destinationTypeSplunk: if v, ok := d.GetOk("splunk_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input.SplunkDestinationUpdate = expandSplunkDestinationUpdate(v.([]interface{})[0].(map[string]interface{})) @@ -2627,6 +2643,108 @@ func expandAmazonOpenSearchServerlessDestinationUpdate(oss map[string]interface{ return update } +func expandSnowflakeDestinationConfiguration(tfMap map[string]interface{}) *types.SnowflakeDestinationConfiguration { + roleARN := tfMap["role_arn"].(string) + apiObject := &types.SnowflakeDestinationConfiguration{ + AccountUrl: aws.String(tfMap["account_url"].(string)), + Database: aws.String(tfMap["database"].(string)), + PrivateKey: aws.String(tfMap["private_key"].(string)), + RetryOptions: expandSnowflakeRetryOptions(tfMap), + RoleARN: aws.String(roleARN), + S3Configuration: expandS3DestinationConfiguration(tfMap["s3_configuration"].([]interface{})), + Schema: aws.String(tfMap["schema"].(string)), + Table: aws.String(tfMap["table"].(string)), + User: aws.String(tfMap["user"].(string)), + } + + if _, ok := tfMap["cloudwatch_logging_options"]; ok { + apiObject.CloudWatchLoggingOptions = expandCloudWatchLoggingOptions(tfMap) + } + + if v, ok := tfMap["content_column_name"]; ok && v.(string) != "" { + apiObject.ContentColumnName = aws.String(v.(string)) + } + + if v, ok := tfMap["data_loading_option"]; ok && v.(string) != "" { + apiObject.DataLoadingOption = types.SnowflakeDataLoadingOption(v.(string)) + } + + if v, ok := tfMap["key_passphrase"]; ok && v.(string) != "" { + apiObject.KeyPassphrase = aws.String(v.(string)) + } + + if v, ok := tfMap["metadata_column_name"]; ok && v.(string) != "" { + apiObject.MetaDataColumnName = aws.String(v.(string)) + } + + if _, ok := tfMap["processing_configuration"]; ok { + apiObject.ProcessingConfiguration = expandProcessingConfiguration(tfMap, destinationTypeSnowflake, roleARN) + } + + if v, ok := tfMap["s3_backup_mode"]; ok { + apiObject.S3BackupMode = types.SnowflakeS3BackupMode(v.(string)) + } + + if _, ok := tfMap["snowflake_role_configuration"]; ok { + apiObject.SnowflakeRoleConfiguration = expandSnowflakeRoleConfiguration(tfMap) + } + + if _, ok := tfMap["snowflake_vpc_configuration"]; ok { + apiObject.SnowflakeVpcConfiguration = expandSnowflakeVPCConfiguration(tfMap) + } + + return apiObject +} + +func expandSnowflakeDestinationUpdate(tfMap map[string]interface{}) *types.SnowflakeDestinationUpdate { + roleARN := tfMap["role_arn"].(string) + apiObject := &types.SnowflakeDestinationUpdate{ + AccountUrl: aws.String(tfMap["account_url"].(string)), + Database: aws.String(tfMap["database"].(string)), + PrivateKey: aws.String(tfMap["private_key"].(string)), + RetryOptions: expandSnowflakeRetryOptions(tfMap), + RoleARN: aws.String(roleARN), + S3Update: expandS3DestinationUpdate(tfMap["s3_configuration"].([]interface{})), + Schema: aws.String(tfMap["schema"].(string)), + Table: aws.String(tfMap["table"].(string)), + User: aws.String(tfMap["user"].(string)), + } + + if _, ok := tfMap["cloudwatch_logging_options"]; ok { + apiObject.CloudWatchLoggingOptions = expandCloudWatchLoggingOptions(tfMap) + } + + if v, ok := tfMap["content_column_name"]; ok && v.(string) != "" { + apiObject.ContentColumnName = aws.String(v.(string)) + } + + if v, ok := tfMap["data_loading_option"]; ok && v.(string) != "" { + apiObject.DataLoadingOption = types.SnowflakeDataLoadingOption(v.(string)) + } + + if v, ok := tfMap["key_passphrase"]; ok && v.(string) != "" { + apiObject.KeyPassphrase = aws.String(v.(string)) + } + + if v, ok := tfMap["metadata_column_name"]; ok && v.(string) != "" { + apiObject.MetaDataColumnName = aws.String(v.(string)) + } + + if _, ok := tfMap["processing_configuration"]; ok { + apiObject.ProcessingConfiguration = expandProcessingConfiguration(tfMap, destinationTypeSnowflake, roleARN) + } + + if v, ok := tfMap["s3_backup_mode"]; ok { + apiObject.S3BackupMode = types.SnowflakeS3BackupMode(v.(string)) + } + + if _, ok := tfMap["snowflake_role_configuration"]; ok { + apiObject.SnowflakeRoleConfiguration = expandSnowflakeRoleConfiguration(tfMap) + } + + return apiObject +} + func expandSplunkDestinationConfiguration(splunk map[string]interface{}) *types.SplunkDestinationConfiguration { configuration := &types.SplunkDestinationConfiguration{ HECToken: aws.String(splunk["hec_token"].(string)), @@ -2913,6 +3031,47 @@ func expandRedshiftRetryOptions(redshift map[string]interface{}) *types.Redshift return retryOptions } +func expandSnowflakeRetryOptions(tfMap map[string]interface{}) *types.SnowflakeRetryOptions { + apiObject := &types.SnowflakeRetryOptions{} + + if v, ok := tfMap["retry_duration"].(int); ok { + apiObject.DurationInSeconds = aws.Int32(int32(v)) + } + + return apiObject +} + +func expandSnowflakeRoleConfiguration(tfMap map[string]interface{}) *types.SnowflakeRoleConfiguration { + tfList := tfMap["snowflake_role_configuration"].([]interface{}) + if len(tfList) == 0 { + return nil + } + + tfMap = tfList[0].(map[string]interface{}) + + apiObject := &types.SnowflakeRoleConfiguration{ + Enabled: aws.Bool(tfMap["enabled"].(bool)), + SnowflakeRole: aws.String(tfMap["snowflake_role"].(string)), + } + + return apiObject +} + +func expandSnowflakeVPCConfiguration(tfMap map[string]interface{}) *types.SnowflakeVpcConfiguration { + tfList := tfMap["snowflake_vpc_configuration"].([]interface{}) + if len(tfList) == 0 { + return nil + } + + tfMap = tfList[0].(map[string]interface{}) + + apiObject := &types.SnowflakeVpcConfiguration{ + PrivateLinkVpceId: aws.String(tfMap["private_link_vpce_id"].(string)), + } + + return apiObject +} + func expandSplunkRetryOptions(splunk map[string]interface{}) *types.SplunkRetryOptions { retryOptions := &types.SplunkRetryOptions{} @@ -3241,6 +3400,39 @@ func flattenRedshiftDestinationDescription(description *types.RedshiftDestinatio return []map[string]interface{}{m} } +func flattenSnowflakeDestinationDescription(apiObject *types.SnowflakeDestinationDescription, configuredKeyPassphrase, configuredPrivateKey string) []map[string]interface{} { + if apiObject == nil { + return []map[string]interface{}{} + } + + roleARN := aws.ToString(apiObject.RoleARN) + tfMap := map[string]interface{}{ + "account_url": aws.ToString(apiObject.AccountUrl), + "cloudwatch_logging_options": flattenCloudWatchLoggingOptions(apiObject.CloudWatchLoggingOptions), + "content_column_name": aws.ToString(apiObject.ContentColumnName), + "data_loading_option": apiObject.DataLoadingOption, + "database": aws.ToString(apiObject.Database), + "key_passphrase": configuredKeyPassphrase, + "metadata_column_name": aws.ToString(apiObject.MetaDataColumnName), + "private_key": configuredPrivateKey, + "processing_configuration": flattenProcessingConfiguration(apiObject.ProcessingConfiguration, destinationTypeSnowflake, roleARN), + "role_arn": roleARN, + "s3_backup_mode": apiObject.S3BackupMode, + "s3_configuration": flattenS3DestinationDescription(apiObject.S3DestinationDescription), + "schema": aws.ToString(apiObject.Schema), + "snowflake_role_configuration": flattenSnowflakeRoleConfiguration(apiObject.SnowflakeRoleConfiguration), + "snowflake_vpc_configuration": flattenSnowflakeVPCConfiguration(apiObject.SnowflakeVpcConfiguration), + "table": aws.ToString(apiObject.Table), + "user": aws.ToString(apiObject.User), + } + + if apiObject.RetryOptions != nil { + tfMap["retry_duration"] = int(aws.ToInt32(apiObject.RetryOptions.DurationInSeconds)) + } + + return []map[string]interface{}{tfMap} +} + func flattenSplunkDestinationDescription(description *types.SplunkDestinationDescription) []map[string]interface{} { if description == nil { return []map[string]interface{}{} @@ -3666,6 +3858,31 @@ func flattenDocumentIDOptions(apiObject *types.DocumentIdOptions) map[string]int return tfMap } +func flattenSnowflakeRoleConfiguration(apiObject *types.SnowflakeRoleConfiguration) []map[string]interface{} { + if apiObject == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "enabled": aws.ToBool(apiObject.Enabled), + "snowflake_role": aws.ToString(apiObject.SnowflakeRole), + } + + return []map[string]interface{}{m} +} + +func flattenSnowflakeVPCConfiguration(apiObject *types.SnowflakeVpcConfiguration) []map[string]interface{} { + if apiObject == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "private_link_vpce_id": aws.ToString(apiObject.PrivateLinkVpceId), + } + + return []map[string]interface{}{m} +} + func isDeliveryStreamOptionDisabled(v interface{}) bool { tfList := v.([]interface{}) if len(tfList) == 0 || tfList[0] == nil { diff --git a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown index d5a55e8ec5e..045a31cfec0 100644 --- a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown @@ -680,13 +680,14 @@ This resource supports the following arguments: * `server_side_encryption` - (Optional) Encrypt at rest options. See [`server_side_encryption` block](#server_side_encryption-block) below for details. **NOTE:** Server-side encryption should not be enabled when a kinesis stream is configured as the source of the firehose delivery stream. -* `destination` – (Required) This is the destination to where the data is delivered. The only options are `s3` (Deprecated, use `extended_s3` instead), `extended_s3`, `redshift`, `elasticsearch`, `splunk`, `http_endpoint`, `opensearch` and `opensearchserverless`. +* `destination` – (Required) This is the destination to where the data is delivered. The only options are `s3` (Deprecated, use `extended_s3` instead), `extended_s3`, `redshift`, `elasticsearch`, `splunk`, `http_endpoint`, `opensearch`, `opensearchserverless` and `snowflake`. * `elasticsearch_configuration` - (Optional) Configuration options when `destination` is `elasticsearch`. See [`elasticsearch_configuration` block](#elasticsearch_configuration-block) below for details. * `extended_s3_configuration` - (Optional, only Required when `destination` is `extended_s3`) Enhanced configuration options for the s3 destination. See [`extended_s3_configuration` block](#extended_s3_configuration-block) below for details. * `http_endpoint_configuration` - (Optional) Configuration options when `destination` is `http_endpoint`. Requires the user to also specify an `s3_configuration` block. See [`http_endpoint_configuration` block](#http_endpoint_configuration-block) below for details. * `opensearch_configuration` - (Optional) Configuration options when `destination` is `opensearch`. See [`opensearch_configuration` block](#opensearch_configuration-block) below for details. * `opensearchserverless_configuration` - (Optional) Configuration options when `destination` is `opensearchserverless`. See [`opensearchserverless_configuration` block](#opensearchserverless_configuration-block) below for details. * `redshift_configuration` - (Optional) Configuration options when `destination` is `redshift`. Requires the user to also specify an `s3_configuration` block. See [`redshift_configuration` block](#redshift_configuration-block) below for details. +* `snowflake_configuration` - (Optional) Configuration options when `destination` is `snowflake`. See [`snowflake_configuration` block](#snowflake_configuration-block) below for details. * `splunk_configuration` - (Optional) Configuration options when `destination` is `splunk`. See [`splunk_configuration` block](#splunk_configuration-block) below for details. ### `kinesis_source_configuration` block From 3ec2c92355c5fdb4eeb6a83bbaf3623e06ad209e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:21:10 -0400 Subject: [PATCH 06/17] Tweak 'TestAccFirehoseDeliveryStream_basic'. --- internal/service/firehose/delivery_stream_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/firehose/delivery_stream_test.go b/internal/service/firehose/delivery_stream_test.go index c4f173e19b4..6006d17c845 100644 --- a/internal/service/firehose/delivery_stream_test.go +++ b/internal/service/firehose/delivery_stream_test.go @@ -90,6 +90,7 @@ func TestAccFirehoseDeliveryStream_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "false"), resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_arn", ""), resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_type", "AWS_OWNED_CMK"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "splunk_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrSet(resourceName, "version_id"), From b7f9cbe78cd68c115d8d0b60164d3552a5d97134 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:21:47 -0400 Subject: [PATCH 07/17] Tweak CHANGELOG entry. --- .changelog/36646.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/36646.txt b/.changelog/36646.txt index 98aa0783b85..9539e1a81d5 100644 --- a/.changelog/36646.txt +++ b/.changelog/36646.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/aws_kinesis_firehose_delivery_stream: Add `snowflake` as a destination +resource/aws_kinesis_firehose_delivery_stream: Add `snowflake_configuration` argument ``` \ No newline at end of file From b069759e045e0a6bc7e3fa8318eea5516513f04b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:24:49 -0400 Subject: [PATCH 08/17] Temporarily remove 'snowflake_destination_configuration' example. --- ...sis_firehose_delivery_stream.html.markdown | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown index 045a31cfec0..8eeda86fa8f 100644 --- a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown @@ -600,71 +600,6 @@ resource "aws_kinesis_firehose_delivery_stream" "example_snowflake_destination" destination = "snowflake" snowflake_destination_configuration { - account_url = "string" - private_key = "string" - key_passphrase = "string" - user = "string" - database = "string" - schema = "string" - table = "string" - snowflake_role_configuration { - enabled = true - snowflake_role = "string" - } - data_loading_option = "JSON_MAPPING" - metadata_column_name = "string" - content_column_name = "string" - snowflake_vpc_configuration { - private_link_vpce_id = "string" - } - cloud_watch_logging_options { - enabled = true - log_group_name = "string" - log_stream_name = "string" - } - processing_configuration { - enabled = true - processors = [ - { - type = "RecordDeAggregation" - parameters = [ - { - parameter_name = "LambdaArn" - parameter_value = "string" - } - # Add more parameters as needed - ] - } - # Add more processors as needed - ] - } - role_arn = "string" - retry_options { - duration_in_seconds = 123 - } - s3_backup_mode = "FailedDataOnly" - s3_configuration { - role_arn = "string" - bucket_arn = "string" - prefix = "string" - error_output_prefix = "string" - buffering_hints { - size_in_mbs = 123 - interval_in_seconds = 456 - } - compression_format = "UNCOMPRESSED" - encryption_configuration { - no_encryption_config = "NoEncryption" - kms_encryption_config { - aws_kms_key_arn = "string" - } - } - cloud_watch_logging_options { - enabled = true - log_group_name = "string" - log_stream_name = "string" - } - } } } ``` From e327ee1184cab27693a8abf7ee60c868f029ef09 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:56:46 -0400 Subject: [PATCH 09/17] Fix markdown-link-check error. --- website/docs/r/kinesis_firehose_delivery_stream.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown index 8eeda86fa8f..4574e108478 100644 --- a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown @@ -599,7 +599,7 @@ resource "aws_kinesis_firehose_delivery_stream" "example_snowflake_destination" name = "example-snowflake-destination" destination = "snowflake" - snowflake_destination_configuration { + snowflake_configuration { } } ``` @@ -792,7 +792,7 @@ The `snowflake_configuration` configuration block supports the following argumen * `content_column_name` - (Optional) The name of the content column. * `snowflake_vpc_configuration` - (Optional) The VPC configuration for Snowflake. * `private_link_vpce_id` - (Required) The VPCE ID for Firehose to privately connect with Snowflake. -* `cloudwatch_logging_options` - (Optional) The CloudWatch logging options. See [`cloud_watch_logging_options` block](#cloud_watch_logging_options-block) below for details. +* `cloudwatch_logging_options` - (Optional) The CloudWatch Logging Options for the delivery stream. See [`cloudwatch_logging_options` block](#cloudwatch_logging_options-block) below for details. * `processing_configuration` - (Optional) The processing configuration. See [`processing_configuration` block](#processing_configuration-block) below for details. * `role_arn` - (Required) The ARN of the IAM role. * `retry_duration` - (Optional) After an initial failure to deliver to Snowflake, the total amount of time, in seconds between 0 to 7200, during which Firehose re-attempts delivery (including the first attempt). After this time has elapsed, the failed documents are written to Amazon S3. The default value is 60s. There will be no retry if the value is 0. From 78384d77b83a62fce10ac8a9abbbaf49225b7b70 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:57:31 -0400 Subject: [PATCH 10/17] r/aws_kinesis_firehose_delivery_stream: 'snowflake_configuration' default values. --- internal/service/firehose/delivery_stream.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/service/firehose/delivery_stream.go b/internal/service/firehose/delivery_stream.go index 055f3cd0c78..4262ef50cf1 100644 --- a/internal/service/firehose/delivery_stream.go +++ b/internal/service/firehose/delivery_stream.go @@ -998,6 +998,7 @@ func resourceDeliveryStream() *schema.Resource { "data_loading_option": { Type: schema.TypeString, Optional: true, + Default: types.SnowflakeDataLoadingOptionJsonMapping, ValidateDiagFunc: enum.Validate[types.SnowflakeDataLoadingOption](), }, "database": { @@ -1036,6 +1037,7 @@ func resourceDeliveryStream() *schema.Resource { "s3_backup_mode": { Type: schema.TypeString, Optional: true, + Default: types.SnowflakeS3BackupModeFailedDataOnly, ValidateDiagFunc: enum.Validate[types.SnowflakeS3BackupMode](), }, "s3_configuration": s3ConfigurationSchema(), @@ -1062,6 +1064,7 @@ func resourceDeliveryStream() *schema.Resource { }, }, }, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, }, "snowflake_vpc_configuration": { Type: schema.TypeList, From dcfb7e5658b7833c20160e40d3dbf58e238898b5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 16:59:40 -0400 Subject: [PATCH 11/17] Restore 'snowflake_configuration' example. --- ...kinesis_firehose_delivery_stream.html.markdown | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown index 4574e108478..7f242b2d883 100644 --- a/website/docs/r/kinesis_firehose_delivery_stream.html.markdown +++ b/website/docs/r/kinesis_firehose_delivery_stream.html.markdown @@ -600,6 +600,21 @@ resource "aws_kinesis_firehose_delivery_stream" "example_snowflake_destination" destination = "snowflake" snowflake_configuration { + account_url = "https://example.snowflakecomputing.com" + database = "example-db" + private_key = "..." + role_arn = aws_iam_role.firehose.arn + schema = "example-schema" + table = "example-table" + user = "example-usr" + + s3_configuration { + role_arn = aws_iam_role.firehose.arn + bucket_arn = aws_s3_bucket.bucket.arn + buffering_size = 10 + buffering_interval = 400 + compression_format = "GZIP" + } } } ``` From e3463787b663d2a5e2c47a0f456cad096b018ab5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 17:19:15 -0400 Subject: [PATCH 12/17] Add 'TestAccFirehoseDeliveryStream_snowflakeUpdates'. --- .../service/firehose/delivery_stream_test.go | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/internal/service/firehose/delivery_stream_test.go b/internal/service/firehose/delivery_stream_test.go index 6006d17c845..95d939346c5 100644 --- a/internal/service/firehose/delivery_stream_test.go +++ b/internal/service/firehose/delivery_stream_test.go @@ -1062,6 +1062,89 @@ func TestAccFirehoseDeliveryStream_redshiftUpdates(t *testing.T) { }) } +func TestAccFirehoseDeliveryStream_snowflakeUpdates(t *testing.T) { + ctx := acctest.Context(t) + var stream types.DeliveryStreamDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + key := acctest.TLSRSAPrivateKeyPEM(t, 4096) + resourceName := "aws_kinesis_firehose_delivery_stream.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.FirehoseServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDeliveryStreamDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDeliveryStreamConfig_snowflakeBasic(rName, key), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeliveryStreamExists(ctx, resourceName, &stream), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "destination", "opensearchserverless"), + resource.TestCheckResourceAttrSet(resourceName, "destination_id"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "extended_s3_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http_endpoint_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis_source_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "opensearch_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "opensearchserverless_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "redshift_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_arn", ""), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_type", "AWS_OWNED_CMK"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.account_url", fmt.Sprintf("https://%s.snowflakecomputing.com", rName)), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.0.log_group_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.0.log_stream_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.content_column_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.data_loading_option", "JSON_MAPPING"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.database", "test-db"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.key_passphrase", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.metadata_column_name", ""), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.private_key"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.#", "0"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.retry_duration", "60"), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.role_arn"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_backup_mode", "FailedDataOnly"), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.s3_configuration.0.bucket_arn"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.buffering_interval", "400"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.buffering_size", "10"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.0.log_group_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.0.log_stream_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.compression_format", "GZIP"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.error_output_prefix", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.kms_key_arn", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.prefix", ""), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.s3_configuration.0.role_arn"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.schema", "test-schema"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.0.snowflake_role", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_vpc_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.table", "test-table"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.user", "test-usr"), + resource.TestCheckResourceAttr(resourceName, "splunk_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "version_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccFirehoseDeliveryStream_splunkUpdates(t *testing.T) { ctx := acctest.Context(t) var stream types.DeliveryStreamDescription @@ -1837,6 +1920,7 @@ func TestAccFirehoseDeliveryStream_openSearchServerlessUpdates(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "false"), resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_arn", ""), resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_type", "AWS_OWNED_CMK"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "splunk_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrSet(resourceName, "version_id"), @@ -1908,6 +1992,7 @@ func TestAccFirehoseDeliveryStream_openSearchServerlessUpdates(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "false"), resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_arn", ""), resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_type", "AWS_OWNED_CMK"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "splunk_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrSet(resourceName, "version_id"), @@ -3548,6 +3633,34 @@ resource "aws_kinesis_firehose_delivery_stream" "test" { `, rName)) } +func testAccDeliveryStreamConfig_snowflakeBasic(rName, privateKey string) string { + return acctest.ConfigCompose(testAccDeliveryStreamConfig_base(rName), fmt.Sprintf(` +resource "aws_kinesis_firehose_delivery_stream" "test" { + depends_on = [aws_iam_role_policy.firehose] + name = %[1]q + destination = "snowflake" + + snowflake_configuration { + account_url = "https://%[1]s.snowflakecomputing.com" + database = "test-db" + private_key = "%[2]s" + role_arn = aws_iam_role.firehose.arn + schema = "test-schema" + table = "test-table" + user = "test-usr" + + s3_configuration { + role_arn = aws_iam_role.firehose.arn + bucket_arn = aws_s3_bucket.bucket.arn + buffering_size = 10 + buffering_interval = 400 + compression_format = "GZIP" + } + } +} +`, rName, acctest.TLSPEMEscapeNewlines(privateKey))) +} + func testAccDeliveryStreamConfig_splunkBasic(rName string) string { return acctest.ConfigCompose(testAccDeliveryStreamConfig_base(rName), fmt.Sprintf(` resource "aws_kinesis_firehose_delivery_stream" "test" { From dc50aeb9d3802914c058e1493c4103469f69f838 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 17:32:28 -0400 Subject: [PATCH 13/17] Add 'acctest.TLSPEMRemoveRSAPrivateKeyEncapsulationBoundaries'. --- internal/acctest/crypto.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/acctest/crypto.go b/internal/acctest/crypto.go index d1745a5835d..d606e4351d0 100644 --- a/internal/acctest/crypto.go +++ b/internal/acctest/crypto.go @@ -32,6 +32,12 @@ var ( tlsX509CertificateSerialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) //nolint:gomnd ) +// TLSPEMRemoveRSAPrivateKeyEncapsulationBoundaries removes RSA private key +// pre and post encapsulation boundaries from a PEM string. +func TLSPEMRemoveRSAPrivateKeyEncapsulationBoundaries(pem string) string { + return removePEMEncapsulationBoundaries(pem, PEMBlockTypeRSAPrivateKey) +} + // TLSPEMRemovePublicKeyEncapsulationBoundaries removes public key // pre and post encapsulation boundaries from a PEM string. func TLSPEMRemovePublicKeyEncapsulationBoundaries(pem string) string { From c5457303948b6261363fafa6d3b8a38c0c0f5fc3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 17:32:56 -0400 Subject: [PATCH 14/17] Fixup 'TestAccFirehoseDeliveryStream_snowflakeUpdates'. --- internal/service/firehose/delivery_stream_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/firehose/delivery_stream_test.go b/internal/service/firehose/delivery_stream_test.go index 95d939346c5..ba3e35d2a45 100644 --- a/internal/service/firehose/delivery_stream_test.go +++ b/internal/service/firehose/delivery_stream_test.go @@ -1128,7 +1128,7 @@ func TestAccFirehoseDeliveryStream_snowflakeUpdates(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.0.enabled", "false"), resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.0.snowflake_role", ""), - resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_vpc_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_vpc_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.table", "test-table"), resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.user", "test-usr"), resource.TestCheckResourceAttr(resourceName, "splunk_configuration.#", "0"), @@ -3658,7 +3658,7 @@ resource "aws_kinesis_firehose_delivery_stream" "test" { } } } -`, rName, acctest.TLSPEMEscapeNewlines(privateKey))) +`, rName, acctest.TLSPEMRemoveRSAPrivateKeyEncapsulationBoundaries(acctest.TLSPEMRemoveNewlines(privateKey)))) } func testAccDeliveryStreamConfig_splunkBasic(rName string) string { From 75dbf6bf14a8ccdac9eb7d2cc07323511fe14224 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 19 Apr 2024 17:36:44 -0400 Subject: [PATCH 15/17] Fixup 'TestAccFirehoseDeliveryStream_snowflakeUpdates'. --- internal/service/firehose/delivery_stream_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/firehose/delivery_stream_test.go b/internal/service/firehose/delivery_stream_test.go index ba3e35d2a45..06de27ba499 100644 --- a/internal/service/firehose/delivery_stream_test.go +++ b/internal/service/firehose/delivery_stream_test.go @@ -1080,7 +1080,7 @@ func TestAccFirehoseDeliveryStream_snowflakeUpdates(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckDeliveryStreamExists(ctx, resourceName, &stream), resource.TestCheckResourceAttrSet(resourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "destination", "opensearchserverless"), + resource.TestCheckResourceAttr(resourceName, "destination", "snowflake"), resource.TestCheckResourceAttrSet(resourceName, "destination_id"), resource.TestCheckResourceAttr(resourceName, "elasticsearch_configuration.#", "0"), resource.TestCheckResourceAttr(resourceName, "extended_s3_configuration.#", "0"), From 1169ab93c8e72346a277f8d44e6f979480fca703 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 20 Apr 2024 16:28:41 -0400 Subject: [PATCH 16/17] Add 'testAccDeliveryStreamConfig_snowflakeUpdate'. --- .../service/firehose/delivery_stream_test.go | 138 +++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/internal/service/firehose/delivery_stream_test.go b/internal/service/firehose/delivery_stream_test.go index 06de27ba499..92810de04ee 100644 --- a/internal/service/firehose/delivery_stream_test.go +++ b/internal/service/firehose/delivery_stream_test.go @@ -1137,9 +1137,84 @@ func TestAccFirehoseDeliveryStream_snowflakeUpdates(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"snowflake_configuration.0.private_key"}, + }, + { + Config: testAccDeliveryStreamConfig_snowflakeUpdate(rName, key), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeliveryStreamExists(ctx, resourceName, &stream), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "destination", "snowflake"), + resource.TestCheckResourceAttrSet(resourceName, "destination_id"), + resource.TestCheckResourceAttr(resourceName, "elasticsearch_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "extended_s3_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "http_endpoint_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "kinesis_source_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "opensearch_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "opensearchserverless_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "redshift_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_arn", ""), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.key_type", "AWS_OWNED_CMK"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.account_url", fmt.Sprintf("https://%s.snowflakecomputing.com", rName)), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.0.log_group_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.cloudwatch_logging_options.0.log_stream_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.content_column_name", "test-content"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.data_loading_option", "VARIANT_CONTENT_MAPPING"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.database", "test-db"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.key_passphrase", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.metadata_column_name", ""), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.private_key"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.0.type", "Lambda"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.0.parameters.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.0.parameters.*", map[string]string{ + "parameter_name": "LambdaArn", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.0.parameters.*", map[string]string{ + "parameter_name": "BufferSizeInMBs", + "parameter_value": "1.1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "snowflake_configuration.0.processing_configuration.0.processors.0.parameters.*", map[string]string{ + "parameter_name": "BufferIntervalInSeconds", + "parameter_value": "70", + }), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.retry_duration", "60"), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.role_arn"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_backup_mode", "FailedDataOnly"), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.s3_configuration.0.bucket_arn"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.buffering_interval", "400"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.buffering_size", "10"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.0.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.0.log_group_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.cloudwatch_logging_options.0.log_stream_name", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.compression_format", "GZIP"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.error_output_prefix", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.kms_key_arn", ""), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.s3_configuration.0.prefix", ""), + resource.TestCheckResourceAttrSet(resourceName, "snowflake_configuration.0.s3_configuration.0.role_arn"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.schema", "test-schema"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.0.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_role_configuration.0.snowflake_role", "test-role"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.snowflake_vpc_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.table", "test-table"), + resource.TestCheckResourceAttr(resourceName, "snowflake_configuration.0.user", "test-usr"), + resource.TestCheckResourceAttr(resourceName, "splunk_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "version_id"), + ), }, }, }) @@ -3661,6 +3736,63 @@ resource "aws_kinesis_firehose_delivery_stream" "test" { `, rName, acctest.TLSPEMRemoveRSAPrivateKeyEncapsulationBoundaries(acctest.TLSPEMRemoveNewlines(privateKey)))) } +func testAccDeliveryStreamConfig_snowflakeUpdate(rName, privateKey string) string { + return acctest.ConfigCompose(testAccDeliveryStreamConfig_base(rName), testAccDeliveryStreamConfig_baseLambda(rName), fmt.Sprintf(` +resource "aws_kinesis_firehose_delivery_stream" "test" { + depends_on = [aws_iam_role_policy.firehose] + name = %[1]q + destination = "snowflake" + + snowflake_configuration { + account_url = "https://%[1]s.snowflakecomputing.com" + database = "test-db" + private_key = "%[2]s" + role_arn = aws_iam_role.firehose.arn + schema = "test-schema" + table = "test-table" + user = "test-usr" + + s3_configuration { + role_arn = aws_iam_role.firehose.arn + bucket_arn = aws_s3_bucket.bucket.arn + buffering_size = 10 + buffering_interval = 400 + compression_format = "GZIP" + } + + data_loading_option = "VARIANT_CONTENT_MAPPING" + content_column_name = "test-content" + + snowflake_role_configuration { + enabled = true + snowflake_role = "test-role" + } + + processing_configuration { + enabled = false + + processors { + type = "Lambda" + + parameters { + parameter_name = "LambdaArn" + parameter_value = "${aws_lambda_function.lambda_function_test.arn}:$LATEST" + } + parameters { + parameter_name = "BufferSizeInMBs" + parameter_value = "1.1" + } + parameters { + parameter_name = "BufferIntervalInSeconds" + parameter_value = "70" + } + } + } + } +} +`, rName, acctest.TLSPEMRemoveRSAPrivateKeyEncapsulationBoundaries(acctest.TLSPEMRemoveNewlines(privateKey)))) +} + func testAccDeliveryStreamConfig_splunkBasic(rName string) string { return acctest.ConfigCompose(testAccDeliveryStreamConfig_base(rName), fmt.Sprintf(` resource "aws_kinesis_firehose_delivery_stream" "test" { From 2fef0a45129584e68b7991ee41087dfc7204db47 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 20 Apr 2024 16:37:51 -0400 Subject: [PATCH 17/17] Acceptance test output: % make testacc TESTARGS='-run=TestAccFirehoseDeliveryStream_snowflakeUpdates' PKG=firehose ==> Checking that code complies with gofmt requirements... TF_ACC=1 go1.21.8 test ./internal/service/firehose/... -v -count 1 -parallel 20 -run=TestAccFirehoseDeliveryStream_snowflakeUpdates -timeout 360m === RUN TestAccFirehoseDeliveryStream_snowflakeUpdates === PAUSE TestAccFirehoseDeliveryStream_snowflakeUpdates === CONT TestAccFirehoseDeliveryStream_snowflakeUpdates --- PASS: TestAccFirehoseDeliveryStream_snowflakeUpdates (102.16s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/firehose 114.313s