From c95e898ad69aa7de2a84bb5a9f39d5b25f1b58d5 Mon Sep 17 00:00:00 2001 From: chammock Date: Wed, 27 Jan 2021 01:41:10 -0600 Subject: [PATCH 01/28] Update resource_aws_sns_topic_subscription.go --- aws/resource_aws_sns_topic_subscription.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index db3dd5ec76b..d4d583fc411 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -45,6 +45,7 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { "lambda", "sms", "sqs", + "firehose", }, true), }, "endpoint": { @@ -98,6 +99,11 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { return json }, }, + "subscription_role_arn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, }, } } @@ -190,6 +196,7 @@ func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{ d.Set("endpoint", attributeOutput.Attributes["Endpoint"]) d.Set("filter_policy", attributeOutput.Attributes["FilterPolicy"]) d.Set("protocol", attributeOutput.Attributes["Protocol"]) + d.Set("subscription_role_arn", attributeOutput.Attributes["SubscriptionRoleArn"]) d.Set("raw_message_delivery", false) if v, ok := attributeOutput.Attributes["RawMessageDelivery"]; ok && aws.StringValue(v) == "true" { @@ -219,6 +226,7 @@ func getResourceAttributes(d *schema.ResourceData) (output map[string]*string) { filter_policy := d.Get("filter_policy").(string) raw_message_delivery := d.Get("raw_message_delivery").(bool) redrive_policy := d.Get("redrive_policy").(string) + subscription_role_arn := d.Get("subscription_role_arn").(string) // Collect attributes if available attributes := map[string]*string{} @@ -235,6 +243,10 @@ func getResourceAttributes(d *schema.ResourceData) (output map[string]*string) { attributes["RawMessageDelivery"] = aws.String(fmt.Sprintf("%t", raw_message_delivery)) } + if subscription_role_arn != "" { + attributes["SubscriptionRoleArn"] = aws.String(subscription_role_arn) + } + if redrive_policy != "" { attributes["RedrivePolicy"] = aws.String(redrive_policy) } @@ -248,12 +260,17 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. topic_arn := d.Get("topic_arn").(string) endpoint_auto_confirms := d.Get("endpoint_auto_confirms").(bool) confirmation_timeout_in_minutes := d.Get("confirmation_timeout_in_minutes").(int) + subscription_role_arn := d.Get("subscription_role_arn").(string) attributes := getResourceAttributes(d) if strings.Contains(protocol, "http") && !endpoint_auto_confirms { return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!") } + if strings.Contains(protocol, "firehose") && !subscription_role_arn { + return nil, fmt.Errorf("Protocol firehose must contain subscription_role_arn!") + } + log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn) req := &sns.SubscribeInput{ From 88bf36b405c425c84c9dec5b5664fada651467ad Mon Sep 17 00:00:00 2001 From: chammock Date: Wed, 27 Jan 2021 02:26:05 -0600 Subject: [PATCH 02/28] Update resource_aws_sns_topic_subscription.go --- aws/resource_aws_sns_topic_subscription.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index d4d583fc411..e828caebd41 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -267,9 +267,12 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!") } - if strings.Contains(protocol, "firehose") && !subscription_role_arn { + if strings.Contains(protocol, "firehose") && subscription_role_arn == "" { return nil, fmt.Errorf("Protocol firehose must contain subscription_role_arn!") } + if !string.Contains(protocol, "firehose") && subscription_role_arn != "" { + return nil, fmt.Errorf("Only protocol firehose supports subscription_role_arn!") + } log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn) From aa7c76bbfcef31dce4297cb00c0e5119aeb0a07b Mon Sep 17 00:00:00 2001 From: chammock Date: Wed, 27 Jan 2021 02:30:50 -0600 Subject: [PATCH 03/28] Update resource_aws_sns_topic_subscription.go --- aws/resource_aws_sns_topic_subscription.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index e828caebd41..3dc6266b243 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -270,7 +270,8 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. if strings.Contains(protocol, "firehose") && subscription_role_arn == "" { return nil, fmt.Errorf("Protocol firehose must contain subscription_role_arn!") } - if !string.Contains(protocol, "firehose") && subscription_role_arn != "" { + + if !strings.Contains(protocol, "firehose") && subscription_role_arn != "" { return nil, fmt.Errorf("Only protocol firehose supports subscription_role_arn!") } From 4633e6bb47f2797e5664829b75add009ef8f07c5 Mon Sep 17 00:00:00 2001 From: curtis Date: Wed, 27 Jan 2021 03:08:58 -0600 Subject: [PATCH 04/28] docs update --- aws/resource_aws_sns_topic_subscription.go | 6 +++++- website/docs/r/sns_topic_subscription.html.markdown | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index 3dc6266b243..a33fb05db80 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -196,7 +196,6 @@ func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{ d.Set("endpoint", attributeOutput.Attributes["Endpoint"]) d.Set("filter_policy", attributeOutput.Attributes["FilterPolicy"]) d.Set("protocol", attributeOutput.Attributes["Protocol"]) - d.Set("subscription_role_arn", attributeOutput.Attributes["SubscriptionRoleArn"]) d.Set("raw_message_delivery", false) if v, ok := attributeOutput.Attributes["RawMessageDelivery"]; ok && aws.StringValue(v) == "true" { @@ -204,6 +203,11 @@ func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{ } d.Set("redrive_policy", attributeOutput.Attributes["RedrivePolicy"]) + d.Set("subscription_role_arn", "") + if v, ok := attributeOutput.Attributes["SubscriptionRoleArn"]; ok { + d.Set("subscription_role_arn", aws.StringValue(v)) + } + d.Set("topic_arn", attributeOutput.Attributes["TopicArn"]) return nil diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index 98cedf11775..b37f5391f8a 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -231,14 +231,18 @@ resource "aws_sns_topic_subscription" "sns-topic" { The following arguments are supported: * `topic_arn` - (Required) The ARN of the SNS topic to subscribe to -* `protocol` - (Required) The protocol to use. The possible values for this are: `sqs`, `sms`, `lambda`, `application`. (`http` or `https` are partially supported, see below) (`email` is an option but is unsupported, see below). +* `protocol` - (Required) The protocol to use. The possible values for this are: `sqs`, `sms`, `lambda`, `application`, `firehose`. (`http` or `https` are partially supported, see below) (`email` is an option but is unsupported, see below). * `endpoint` - (Required) The endpoint to send data to, the contents will vary with the protocol. (see below for more information) * `endpoint_auto_confirms` - (Optional) Boolean indicating whether the end point is capable of [auto confirming subscription](http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html#SendMessageToHttp.prepare) e.g., PagerDuty (default is false) * `confirmation_timeout_in_minutes` - (Optional) Integer indicating number of minutes to wait in retying mode for fetching subscription arn before marking it as failure. Only applicable for http and https protocols (default is 1 minute). * `raw_message_delivery` - (Optional) Boolean indicating whether or not to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property) (default is false). * `filter_policy` - (Optional) JSON String with the filter policy that will be used in the subscription to filter messages seen by the target resource. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html) for more details. * `delivery_policy` - (Optional) JSON String with the delivery policy (retries, backoff, etc.) that will be used in the subscription - this only applies to HTTP/S subscriptions. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html) for more details. +<<<<<<< HEAD * `redrive_policy` - (Optional) JSON String with the redrive policy that will be used in the subscription. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html#how-messages-moved-into-dead-letter-queue) for more details. +======= +* `subscription_role_arn` - (Optional) Required ARN of the IAM role to publish to Kinesis Data Firehose delivery stream. Require for `firehose` protocol. Refer to [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-firehose-as-subscriber.html) +>>>>>>> a95bbe357 (docs update) ### Protocols supported @@ -248,6 +252,7 @@ Supported SNS protocols include: * `sqs` -- delivery of JSON-encoded message to an Amazon SQS queue * `application` -- delivery of JSON-encoded message to an EndpointArn for a mobile app and device * `sms` -- delivery text message +* `firehose` -- delivery of JSON-encoded message to an Amazon Kinesis Data Firehose delivery stream Partially supported SNS protocols include: @@ -269,6 +274,8 @@ Endpoints have different format requirements according to the protocol that is c * SQS endpoints come in the form of the SQS queue's ARN (not the URL of the queue) e.g: `arn:aws:sqs:us-west-2:432981146916:terraform-queue-too` * Application endpoints are also the endpoint ARN for the mobile app and device. +* Firehose endpoints are in the form of Firehose ARN e.g: +`arn:aws:firehose:us-east-1:123456789012:deliverystream/ticketUploadStream` ## Attributes Reference From 1443fda81917f75fabf6582011e659dd90406a40 Mon Sep 17 00:00:00 2001 From: chammock Date: Wed, 27 Jan 2021 20:09:47 -0600 Subject: [PATCH 05/28] Create 17307.txt --- .changelog/17307.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/17307.txt diff --git a/.changelog/17307.txt b/.changelog/17307.txt new file mode 100644 index 00000000000..df6d5169409 --- /dev/null +++ b/.changelog/17307.txt @@ -0,0 +1,3 @@ +```release-notes:enhancement +resource/sns_topic_subscription: Add `firehose` protocol option with `subscription_role_arn`attribute +``` From 2842c5dcc44124bafc548809cc7800dd5ce001d6 Mon Sep 17 00:00:00 2001 From: curtis Date: Wed, 27 Jan 2021 21:09:55 -0600 Subject: [PATCH 06/28] acceptance tests --- .changelog/17307.txt | 2 +- ...esource_aws_sns_topic_subscription_test.go | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/.changelog/17307.txt b/.changelog/17307.txt index df6d5169409..e1bc562b8aa 100644 --- a/.changelog/17307.txt +++ b/.changelog/17307.txt @@ -1,3 +1,3 @@ ```release-notes:enhancement -resource/sns_topic_subscription: Add `firehose` protocol option with `subscription_role_arn`attribute +resource/sns_topic_subscription: Add `firehose` protocol option with `subscription_role_arn` attribute ``` diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index 3ddc936a27c..30fcfcf76d5 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -361,6 +361,43 @@ func TestAccAWSSNSTopicSubscription_autoConfirmingSecuredEndpoint(t *testing.T) }) } +func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { + attributes := make(map[string]string) + resourceName := "aws_sns_topic_subscription.test_subscription" + ri := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSNSTopicSubscriptionConfig_firehose(ri), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sns", regexp.MustCompile(fmt.Sprintf("terraform-test-topic-%d:.+", ri))), + resource.TestCheckResourceAttr(resourceName, "delivery_policy", ""), + resource.TestCheckResourceAttrPair(resourceName, "endpoint", "aws_kinesis_firehose_delivery_stream.test_stream", "arn"), + resource.TestCheckResourceAttr(resourceName, "filter_policy", ""), + resource.TestCheckResourceAttr(resourceName, "protocol", "firehose"), + resource.TestCheckResourceAttr(resourceName, "raw_message_delivery", "false"), + resource.TestCheckResourceAttrPair(resourceName, "topic_arn", "aws_sns_topic.test_topic", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "subscription_role_arn", "aws_iam_role.firehose_role", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "confirmation_timeout_in_minutes", + "endpoint_auto_confirms", + }, + }, + }, + }) +} + func testAccCheckAWSSNSTopicSubscriptionDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).snsconn @@ -903,3 +940,52 @@ resource "aws_sns_topic_subscription" "test_subscription" { } `, i, i, i, i, i, i, i, i, i, username, password, username, password) } + +func testAccAWSSNSTopicSubscriptionConfig_firehose(i int) string { + return fmt.Sprintf(` +resource "aws_sns_topic" "test_topic" { + name = "terraform-test-topic-%d" +} + +resource "aws_sns_topic_subscription" "test_subscription" { + endpoint = aws_kinesis_firehose_delivery_stream.test_stream.arn + protocol = "firehose" + topic_arn = aws_sns_topic.test_topic.arn + subscription_role_arn = aws_iam_role.firehose_role.arn +} +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "private" +} + +resource "aws_iam_role" "firehose_role" { + name = "tf-test-firehose-role-%d" + + assume_role_policy = < Date: Thu, 28 Jan 2021 01:35:00 -0600 Subject: [PATCH 07/28] allow changes to subscription_role_arn --- aws/resource_aws_sns_topic_subscription.go | 16 +++++++++++++++- aws/resource_aws_sns_topic_subscription_test.go | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index a33fb05db80..c61c48db48f 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -102,7 +102,6 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { "subscription_role_arn": { Type: schema.TypeString, Optional: true, - ForceNew: true, }, }, } @@ -165,6 +164,21 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac } } + if d.HasChange("subscription_role_arn") { + protocol := d.Get("protocol").(string) + subscription_role_arn := d.Get("subscription_role_arn").(string) + if strings.Contains(protocol, "firehose") && subscription_role_arn == "" { + return fmt.Errorf("Protocol firehose must contain subscription_role_arn!") + } + if !strings.Contains(protocol, "firehose") && subscription_role_arn != "" { + return fmt.Errorf("Only protocol firehose supports subscription_role_arn!") + } + + if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "SubscriptionRoleArn", subscription_role_arn); err != nil { + return err + } + } + return resourceAwsSnsTopicSubscriptionRead(d, meta) } diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index 30fcfcf76d5..ce1a3349261 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -372,7 +372,7 @@ func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_firehose(ri), + Config: testAccAWSSNSTopicSubscriptionConfig_firehose(ri, 1), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sns", regexp.MustCompile(fmt.Sprintf("terraform-test-topic-%d:.+", ri))), @@ -394,6 +394,14 @@ func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { "endpoint_auto_confirms", }, }, + // Test attribute update + { + Config: testAccAWSSNSTopicSubscriptionConfig_firehose(ri, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), + resource.TestCheckResourceAttrPair(resourceName, "subscription_role_arn", "aws_iam_role.firehose_role", "arn"), + ), + }, }, }) } @@ -941,7 +949,7 @@ resource "aws_sns_topic_subscription" "test_subscription" { `, i, i, i, i, i, i, i, i, i, username, password, username, password) } -func testAccAWSSNSTopicSubscriptionConfig_firehose(i int) string { +func testAccAWSSNSTopicSubscriptionConfig_firehose(i, n int) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { name = "terraform-test-topic-%d" @@ -959,7 +967,7 @@ resource "aws_s3_bucket" "bucket" { } resource "aws_iam_role" "firehose_role" { - name = "tf-test-firehose-role-%d" + name = "tf-test-firehose-role-%d-%d" assume_role_policy = < Date: Tue, 9 Feb 2021 22:06:58 -0600 Subject: [PATCH 08/28] merge conflict fix --- aws/resource_aws_sns_topic_subscription.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index c61c48db48f..3df3d4a4fcf 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -158,12 +158,6 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac } } - if d.HasChange("redrive_policy") { - if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "RedrivePolicy", d.Get("redrive_policy").(string)); err != nil { - return err - } - } - if d.HasChange("subscription_role_arn") { protocol := d.Get("protocol").(string) subscription_role_arn := d.Get("subscription_role_arn").(string) @@ -179,6 +173,12 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac } } + if d.HasChange("redrive_policy") { + if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "RedrivePolicy", d.Get("redrive_policy").(string)); err != nil { + return err + } + } + return resourceAwsSnsTopicSubscriptionRead(d, meta) } From 7ae70a400ea4f17e025749ebe0a6152d134593ce Mon Sep 17 00:00:00 2001 From: nikhil-goenka <70277861+nikhil-goenka@users.noreply.github.com> Date: Mon, 31 Aug 2020 20:24:15 +0530 Subject: [PATCH 09/28] Email support for sns subscription --- aws/resource_aws_sns_topic_subscription.go | 6 ++++++ website/docs/r/sns_topic_subscription.html.markdown | 9 +++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index db3dd5ec76b..73322ed2b73 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -45,6 +45,8 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { "lambda", "sms", "sqs", + "email", + "email-json", }, true), }, "endpoint": { @@ -263,6 +265,10 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. Attributes: attributes, } + if strings.Contains(protocol, "email") { + req.ReturnSubscriptionArn = aws.Bool(true) + } + output, err = snsconn.Subscribe(req) if err != nil { return nil, fmt.Errorf("Error creating SNS topic subscription: %s", err) diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index 98cedf11775..74d65ae9dfa 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -231,7 +231,7 @@ resource "aws_sns_topic_subscription" "sns-topic" { The following arguments are supported: * `topic_arn` - (Required) The ARN of the SNS topic to subscribe to -* `protocol` - (Required) The protocol to use. The possible values for this are: `sqs`, `sms`, `lambda`, `application`. (`http` or `https` are partially supported, see below) (`email` is an option but is unsupported, see below). +* `protocol` - (Required) The protocol to use. The possible values for this are: `sqs`, `sms`, `lambda`, `application`, `email`, `email-json`. (`http` or `https` are partially supported, see below). * `endpoint` - (Required) The endpoint to send data to, the contents will vary with the protocol. (see below for more information) * `endpoint_auto_confirms` - (Optional) Boolean indicating whether the end point is capable of [auto confirming subscription](http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html#SendMessageToHttp.prepare) e.g., PagerDuty (default is false) * `confirmation_timeout_in_minutes` - (Optional) Integer indicating number of minutes to wait in retying mode for fetching subscription arn before marking it as failure. Only applicable for http and https protocols (default is 1 minute). @@ -248,17 +248,14 @@ Supported SNS protocols include: * `sqs` -- delivery of JSON-encoded message to an Amazon SQS queue * `application` -- delivery of JSON-encoded message to an EndpointArn for a mobile app and device * `sms` -- delivery text message +* `email` -- delivery of message via SMTP (Confirmation tokens are valid for three days). +* `email-json` -- delivery of JSON-encoded message via SMTP (Confirmation tokens are valid for three days). Partially supported SNS protocols include: * `http` -- delivery of JSON-encoded messages via HTTP. Supported only for the end points that auto confirms the subscription. * `https` -- delivery of JSON-encoded messages via HTTPS. Supported only for the end points that auto confirms the subscription. -Unsupported protocols include the following: - -* `email` -- delivery of message via SMTP -* `email-json` -- delivery of JSON-encoded message via SMTP - These are unsupported because the endpoint needs to be authorized and does not generate an ARN until the target email address has been validated. This breaks the Terraform model and as a result are not currently supported. From 3a860c0d9559d7cca572abcda7becd6e9d2777db Mon Sep 17 00:00:00 2001 From: nikhil-goenka <70277861+nikhil-goenka@users.noreply.github.com> Date: Mon, 31 Aug 2020 21:20:49 +0530 Subject: [PATCH 10/28] Email support for sns subscription --- aws/resource_aws_sns_topic_subscription.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index 73322ed2b73..0215d3c81ae 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -266,8 +266,8 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. } if strings.Contains(protocol, "email") { - req.ReturnSubscriptionArn = aws.Bool(true) - } + req.ReturnSubscriptionArn = aws.Bool(true) + } output, err = snsconn.Subscribe(req) if err != nil { From a349c845bbf735891dd5fb1547354166d8cf43d1 Mon Sep 17 00:00:00 2001 From: nikhil-goenka <70277861+nikhil-goenka@users.noreply.github.com> Date: Tue, 1 Sep 2020 00:19:29 +0530 Subject: [PATCH 11/28] Update resource_aws_sns_topic_subscription.go --- aws/resource_aws_sns_topic_subscription.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index 0215d3c81ae..ed602efccf7 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -266,7 +266,7 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. } if strings.Contains(protocol, "email") { - req.ReturnSubscriptionArn = aws.Bool(true) + req.ReturnSubscriptionArn = aws.Bool(true) } output, err = snsconn.Subscribe(req) From 2c5af0de658063afc37b8af32e9f4595f992b858 Mon Sep 17 00:00:00 2001 From: nikhil-goenka <70277861+nikhil-goenka@users.noreply.github.com> Date: Tue, 1 Sep 2020 19:19:35 +0530 Subject: [PATCH 12/28] Update resource_aws_sns_topic_subscription.go --- aws/resource_aws_sns_topic_subscription.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index ed602efccf7..cc4cf669ca3 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -38,7 +38,6 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ - // email and email-json not supported "application", "http", "https", @@ -265,6 +264,7 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. Attributes: attributes, } + //https://docs.aws.amazon.com/cli/latest/reference/sns/subscribe.html#options if strings.Contains(protocol, "email") { req.ReturnSubscriptionArn = aws.Bool(true) } From 84189020963ab0709d930e013b4505f5891e7fcc Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 19 Feb 2021 17:47:04 -0500 Subject: [PATCH 13/28] resource/sns_topic_subscription: Rework with waiter, add attributes --- aws/internal/service/sns/waiter/status.go | 30 ++ aws/internal/service/sns/waiter/waiter.go | 28 ++ aws/resource_aws_sns_topic_subscription.go | 370 +++++++----------- ...esource_aws_sns_topic_subscription_test.go | 137 +++---- 4 files changed, 273 insertions(+), 292 deletions(-) create mode 100644 aws/internal/service/sns/waiter/status.go create mode 100644 aws/internal/service/sns/waiter/waiter.go diff --git a/aws/internal/service/sns/waiter/status.go b/aws/internal/service/sns/waiter/status.go new file mode 100644 index 00000000000..31013219404 --- /dev/null +++ b/aws/internal/service/sns/waiter/status.go @@ -0,0 +1,30 @@ +package waiter + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sns" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func SubscriptionPendingConfirmation(conn *sns.SNS, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := conn.GetSubscriptionAttributes(&sns.GetSubscriptionAttributesInput{ + SubscriptionArn: aws.String(id), + }) + + if tfawserr.ErrCodeEquals(err, sns.ErrCodeResourceNotFoundException) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if output == nil { + return nil, "", nil + } + + return output, aws.StringValue(output.Attributes["PendingConfirmation"]), nil + } +} diff --git a/aws/internal/service/sns/waiter/waiter.go b/aws/internal/service/sns/waiter/waiter.go new file mode 100644 index 00000000000..fb7c5a85008 --- /dev/null +++ b/aws/internal/service/sns/waiter/waiter.go @@ -0,0 +1,28 @@ +package waiter + +import ( + "time" + + "github.com/aws/aws-sdk-go/service/sns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + SubscriptionPendingConfirmationTimeout = 2 * time.Minute +) + +func SubscriptionConfirmed(conn *sns.SNS, id, expectedValue string, timeout time.Duration) (*sns.GetSubscriptionAttributesOutput, error) { + stateConf := &resource.StateChangeConf{ + Target: []string{expectedValue}, + Refresh: SubscriptionPendingConfirmation(conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*sns.GetSubscriptionAttributesOutput); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index cc4cf669ca3..733185af56f 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -12,10 +12,11 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/service/sns" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sns/waiter" ) const awsSNSPendingConfirmationMessage = "pending confirmation" @@ -33,40 +34,18 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "protocol": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - "application", - "http", - "https", - "lambda", - "sms", - "sqs", - "email", - "email-json", - }, true), - }, - "endpoint": { + "arn": { Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "endpoint_auto_confirms": { - Type: schema.TypeBool, - Optional: true, - Default: false, + Computed: true, }, "confirmation_timeout_in_minutes": { Type: schema.TypeInt, Optional: true, Default: 1, }, - "topic_arn": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "confirmation_was_authenticated": { + Type: schema.TypeBool, + Computed: true, }, "delivery_policy": { Type: schema.TypeString, @@ -74,21 +53,16 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { ValidateFunc: validation.StringIsJSON, DiffSuppressFunc: suppressEquivalentSnsTopicSubscriptionDeliveryPolicy, }, - "redrive_policy": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: suppressEquivalentJsonDiffs, + "endpoint": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, - "raw_message_delivery": { + "endpoint_auto_confirms": { Type: schema.TypeBool, Optional: true, Default: false, }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, "filter_policy": { Type: schema.TypeString, Optional: true, @@ -99,64 +73,90 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { return json }, }, + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, + "pending_confirmation": { + Type: schema.TypeBool, + Computed: true, + }, + "protocol": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "application", + "email-json", + "email", + "http", + "https", + "lambda", + "sms", + "sqs", + }, true), + }, + "raw_message_delivery": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "redrive_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: suppressEquivalentJsonDiffs, + }, + "topic_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, }, } } func resourceAwsSnsTopicSubscriptionCreate(d *schema.ResourceData, meta interface{}) error { - snsconn := meta.(*AWSClient).snsconn + conn := meta.(*AWSClient).snsconn + + input := &sns.SubscribeInput{ + Attributes: expandSNSSubscriptionAttributes(d), + Endpoint: aws.String(d.Get("endpoint").(string)), + Protocol: aws.String(d.Get("protocol").(string)), + ReturnSubscriptionArn: aws.Bool(true), // even if not confirmed, will get ARN + TopicArn: aws.String(d.Get("topic_arn").(string)), + } - output, err := subscribeToSNSTopic(d, snsconn) + output, err := conn.Subscribe(input) if err != nil { - return err + return fmt.Errorf("error creating SNS topic subscription: %w", err) } - if subscriptionHasPendingConfirmation(output.SubscriptionArn) { - log.Printf("[WARN] Invalid SNS Subscription, received a \"%s\" ARN", awsSNSPendingConfirmationMessage) - return nil + if output == nil || output.SubscriptionArn == nil || aws.StringValue(output.SubscriptionArn) == "" { + return fmt.Errorf("error creating SNS topic subscription: empty response") } - log.Printf("New subscription ARN: %s", *output.SubscriptionArn) d.SetId(aws.StringValue(output.SubscriptionArn)) - // Write the ARN to the 'arn' field for export - d.Set("arn", output.SubscriptionArn) - - return resourceAwsSnsTopicSubscriptionRead(d, meta) -} - -func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interface{}) error { - snsconn := meta.(*AWSClient).snsconn + waitForConfirmation := true - if d.HasChange("raw_message_delivery") { - if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "RawMessageDelivery", fmt.Sprintf("%t", d.Get("raw_message_delivery").(bool))); err != nil { - return err - } + if !d.Get("endpoint_auto_confirms").(bool) && strings.Contains(d.Get("protocol").(string), "http") { + waitForConfirmation = false } - if d.HasChange("filter_policy") { - filterPolicy := d.Get("filter_policy").(string) - - // https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html#message-filtering-policy-remove - if filterPolicy == "" { - filterPolicy = "{}" - } - - if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "FilterPolicy", filterPolicy); err != nil { - return err - } + if strings.Contains(d.Get("protocol").(string), "email") { + waitForConfirmation = false } - if d.HasChange("delivery_policy") { - if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "DeliveryPolicy", d.Get("delivery_policy").(string)); err != nil { - return err - } + timeout := waiter.SubscriptionPendingConfirmationTimeout + if strings.Contains(d.Get("protocol").(string), "http") { + timeout = time.Duration(d.Get("confirmation_timeout_in_minutes").(int)) * time.Minute } - if d.HasChange("redrive_policy") { - if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "RedrivePolicy", d.Get("redrive_policy").(string)); err != nil { - return err + if waitForConfirmation { + if _, err := waiter.SubscriptionConfirmed(conn, d.Id(), "false", timeout); err != nil { + return fmt.Errorf("waiting for SNS topic subscription (%s) confirmation: %w", d.Id(), err) } } @@ -164,50 +164,99 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac } func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{}) error { - snsconn := meta.(*AWSClient).snsconn + conn := meta.(*AWSClient).snsconn log.Printf("[DEBUG] Loading subscription %s", d.Id()) - attributeOutput, err := snsconn.GetSubscriptionAttributes(&sns.GetSubscriptionAttributesInput{ + output, err := conn.GetSubscriptionAttributes(&sns.GetSubscriptionAttributesInput{ SubscriptionArn: aws.String(d.Id()), }) - if isAWSErr(err, sns.ErrCodeNotFoundException, "") { - log.Printf("[WARN] SNS Topic Subscription (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && (tfawserr.ErrCodeEquals(err, sns.ErrCodeResourceNotFoundException) || tfawserr.ErrCodeEquals(err, sns.ErrCodeNotFoundException)) { + log.Printf("[WARN] SNS subscription attributes (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error reading SNS Topic Subscription (%s) attributes: %s", d.Id(), err) + return fmt.Errorf("getting SNS subscription attributes (%s): %w", d.Id(), err) } - if attributeOutput == nil || len(attributeOutput.Attributes) == 0 { - return fmt.Errorf("error reading SNS Topic Subscription (%s) attributes: no attributes found", d.Id()) + if output == nil || output.Attributes == nil || len(output.Attributes) == 0 { + return fmt.Errorf("getting SNS subscription attributes (%s): empty response", d.Id()) } - d.Set("arn", attributeOutput.Attributes["SubscriptionArn"]) - d.Set("delivery_policy", attributeOutput.Attributes["DeliveryPolicy"]) - d.Set("endpoint", attributeOutput.Attributes["Endpoint"]) - d.Set("filter_policy", attributeOutput.Attributes["FilterPolicy"]) - d.Set("protocol", attributeOutput.Attributes["Protocol"]) + attributes := output.Attributes + + d.Set("arn", attributes["SubscriptionArn"]) + d.Set("delivery_policy", attributes["DeliveryPolicy"]) + d.Set("endpoint", attributes["Endpoint"]) + d.Set("filter_policy", attributes["FilterPolicy"]) + d.Set("owner_id", attributes["Owner"]) + d.Set("protocol", attributes["Protocol"]) + d.Set("redrive_policy", attributes["RedrivePolicy"]) + d.Set("topic_arn", attributes["TopicArn"]) + + d.Set("confirmation_was_authenticated", false) + if v, ok := attributes["ConfirmationWasAuthenticated"]; ok && aws.StringValue(v) == "true" { + d.Set("confirmation_was_authenticated", true) + } + + d.Set("pending_confirmation", false) + if v, ok := attributes["PendingConfirmation"]; ok && aws.StringValue(v) == "true" { + d.Set("pending_confirmation", true) + } d.Set("raw_message_delivery", false) - if v, ok := attributeOutput.Attributes["RawMessageDelivery"]; ok && aws.StringValue(v) == "true" { + if v, ok := attributes["RawMessageDelivery"]; ok && aws.StringValue(v) == "true" { d.Set("raw_message_delivery", true) } - d.Set("redrive_policy", attributeOutput.Attributes["RedrivePolicy"]) - d.Set("topic_arn", attributeOutput.Attributes["TopicArn"]) - return nil } +func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).snsconn + + if d.HasChange("raw_message_delivery") { + if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "RawMessageDelivery", fmt.Sprintf("%t", d.Get("raw_message_delivery").(bool))); err != nil { + return err + } + } + + if d.HasChange("filter_policy") { + filterPolicy := d.Get("filter_policy").(string) + + // https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html#message-filtering-policy-remove + if filterPolicy == "" { + filterPolicy = "{}" + } + + if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "FilterPolicy", filterPolicy); err != nil { + return err + } + } + + if d.HasChange("delivery_policy") { + if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "DeliveryPolicy", d.Get("delivery_policy").(string)); err != nil { + return err + } + } + + if d.HasChange("redrive_policy") { + if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "RedrivePolicy", d.Get("redrive_policy").(string)); err != nil { + return err + } + } + + return resourceAwsSnsTopicSubscriptionRead(d, meta) +} + func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { - snsconn := meta.(*AWSClient).snsconn + conn := meta.(*AWSClient).snsconn log.Printf("[DEBUG] SNS delete topic subscription: %s", d.Id()) - _, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{ + _, err := conn.Unsubscribe(&sns.UnsubscribeInput{ SubscriptionArn: aws.String(d.Id()), }) @@ -215,7 +264,7 @@ func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interfac } // Assembles supplied attributes into a single map - empty/default values are excluded from the map -func getResourceAttributes(d *schema.ResourceData) (output map[string]*string) { +func expandSNSSubscriptionAttributes(d *schema.ResourceData) (output map[string]*string) { delivery_policy := d.Get("delivery_policy").(string) filter_policy := d.Get("filter_policy").(string) raw_message_delivery := d.Get("raw_message_delivery").(bool) @@ -243,135 +292,6 @@ func getResourceAttributes(d *schema.ResourceData) (output map[string]*string) { return attributes } -func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns.SubscribeOutput, err error) { - protocol := d.Get("protocol").(string) - endpoint := d.Get("endpoint").(string) - topic_arn := d.Get("topic_arn").(string) - endpoint_auto_confirms := d.Get("endpoint_auto_confirms").(bool) - confirmation_timeout_in_minutes := d.Get("confirmation_timeout_in_minutes").(int) - attributes := getResourceAttributes(d) - - if strings.Contains(protocol, "http") && !endpoint_auto_confirms { - return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!") - } - - log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn) - - req := &sns.SubscribeInput{ - Protocol: aws.String(protocol), - Endpoint: aws.String(endpoint), - TopicArn: aws.String(topic_arn), - Attributes: attributes, - } - - //https://docs.aws.amazon.com/cli/latest/reference/sns/subscribe.html#options - if strings.Contains(protocol, "email") { - req.ReturnSubscriptionArn = aws.Bool(true) - } - - output, err = snsconn.Subscribe(req) - if err != nil { - return nil, fmt.Errorf("Error creating SNS topic subscription: %s", err) - } - - log.Printf("[DEBUG] Finished subscribing to topic %s with subscription arn %s", topic_arn, *output.SubscriptionArn) - - if strings.Contains(protocol, "http") && subscriptionHasPendingConfirmation(output.SubscriptionArn) { - - log.Printf("[DEBUG] SNS create topic subscription is pending so fetching the subscription list for topic : %s (%s) @ '%s'", endpoint, protocol, topic_arn) - - err = resource.Retry(time.Duration(confirmation_timeout_in_minutes)*time.Minute, func() *resource.RetryError { - - subscription, err := findSubscriptionByNonID(d, snsconn) - - if err != nil { - return resource.NonRetryableError(err) - } - - if subscription == nil { - return resource.RetryableError(fmt.Errorf("Endpoint (%s) did not autoconfirm the subscription for topic %s", endpoint, topic_arn)) - } - - output.SubscriptionArn = subscription.SubscriptionArn - return nil - }) - - if isResourceTimeoutError(err) { - var subscription *sns.Subscription - subscription, err = findSubscriptionByNonID(d, snsconn) - - if subscription != nil { - output.SubscriptionArn = subscription.SubscriptionArn - } - } - - if err != nil { - return nil, err - } - } - - log.Printf("[DEBUG] Created new subscription! %s", *output.SubscriptionArn) - return output, nil -} - -// finds a subscription using protocol, endpoint and topic_arn (which is a key in sns subscription) -func findSubscriptionByNonID(d *schema.ResourceData, conn *sns.SNS) (*sns.Subscription, error) { - protocol := d.Get("protocol").(string) - endpoint := d.Get("endpoint").(string) - topicARN := d.Get("topic_arn").(string) - obfuscatedEndpoint := obfuscateEndpoint(endpoint) - - input := &sns.ListSubscriptionsByTopicInput{ - TopicArn: aws.String(topicARN), - } - var result *sns.Subscription - - err := conn.ListSubscriptionsByTopicPages(input, func(page *sns.ListSubscriptionsByTopicOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, subscription := range page.Subscriptions { - if subscription == nil { - continue - } - - if aws.StringValue(subscription.Endpoint) != obfuscatedEndpoint { - continue - } - - if aws.StringValue(subscription.Protocol) != protocol { - continue - } - - if aws.StringValue(subscription.TopicArn) != topicARN { - continue - } - - if subscriptionHasPendingConfirmation(subscription.SubscriptionArn) { - continue - } - - result = subscription - - return false - } - - return !lastPage - }) - - return result, err -} - -// returns true if arn is nil or has both pending and confirmation words in the arn -func subscriptionHasPendingConfirmation(arn *string) bool { - if arn != nil && !strings.Contains(strings.Replace(strings.ToLower(*arn), " ", "", -1), awsSNSPendingConfirmationMessageWithoutSpaces) { - return false - } - - return true -} - // returns the endpoint with obfuscated password, if any func obfuscateEndpoint(endpoint string) string { res, err := url.Parse(endpoint) @@ -391,7 +311,7 @@ func obfuscateEndpoint(endpoint string) string { return obfuscatedEndpoint } -func snsSubscriptionAttributeUpdate(snsconn *sns.SNS, subscriptionArn, attributeName, attributeValue string) error { +func snsSubscriptionAttributeUpdate(conn *sns.SNS, subscriptionArn, attributeName, attributeValue string) error { req := &sns.SetSubscriptionAttributesInput{ SubscriptionArn: aws.String(subscriptionArn), AttributeName: aws.String(attributeName), @@ -404,7 +324,7 @@ func snsSubscriptionAttributeUpdate(snsconn *sns.SNS, subscriptionArn, attribute req.AttributeValue = nil } - _, err := snsconn.SetSubscriptionAttributes(req) + _, err := conn.SetSubscriptionAttributes(req) if err != nil { return fmt.Errorf("error setting subscription (%s) attribute (%s): %s", subscriptionArn, attributeName, err) diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index 3ddc936a27c..0d9f24f3188 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -61,7 +61,7 @@ func TestSuppressEquivalentSnsTopicSubscriptionDeliveryPolicy(t *testing.T) { func TestAccAWSSNSTopicSubscription_basic(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -69,13 +69,15 @@ func TestAccAWSSNSTopicSubscription_basic(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig(ri), + Config: testAccAWSSNSTopicSubscriptionConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sns", regexp.MustCompile(fmt.Sprintf("terraform-test-topic-%d:.+", ri))), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", sns.ServiceName, regexp.MustCompile(fmt.Sprintf("%s:.+", rName))), + resource.TestCheckResourceAttr(resourceName, "confirmation_was_authenticated", "true"), resource.TestCheckResourceAttr(resourceName, "delivery_policy", ""), resource.TestCheckResourceAttrPair(resourceName, "endpoint", "aws_sqs_queue.test_queue", "arn"), resource.TestCheckResourceAttr(resourceName, "filter_policy", ""), + resource.TestCheckResourceAttr(resourceName, "pending_confirmation", "false"), resource.TestCheckResourceAttr(resourceName, "protocol", "sqs"), resource.TestCheckResourceAttr(resourceName, "raw_message_delivery", "false"), resource.TestCheckResourceAttrPair(resourceName, "topic_arn", "aws_sns_topic.test_topic", "arn"), @@ -97,9 +99,9 @@ func TestAccAWSSNSTopicSubscription_basic(t *testing.T) { func TestAccAWSSNSTopicSubscription_filterPolicy(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() filterPolicy1 := `{"key1": ["val1"], "key2": ["val2"]}` filterPolicy2 := `{"key3": ["val3"], "key4": ["val4"]}` + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -107,7 +109,7 @@ func TestAccAWSSNSTopicSubscription_filterPolicy(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_filterPolicy(ri, strconv.Quote(filterPolicy1)), + Config: testAccAWSSNSTopicSubscriptionConfig_filterPolicy(rName, strconv.Quote(filterPolicy1)), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "filter_policy", filterPolicy1), @@ -124,7 +126,7 @@ func TestAccAWSSNSTopicSubscription_filterPolicy(t *testing.T) { }, // Test attribute update { - Config: testAccAWSSNSTopicSubscriptionConfig_filterPolicy(ri, strconv.Quote(filterPolicy2)), + Config: testAccAWSSNSTopicSubscriptionConfig_filterPolicy(rName, strconv.Quote(filterPolicy2)), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "filter_policy", filterPolicy2), @@ -132,7 +134,7 @@ func TestAccAWSSNSTopicSubscription_filterPolicy(t *testing.T) { }, // Test attribute removal { - Config: testAccAWSSNSTopicSubscriptionConfig(ri), + Config: testAccAWSSNSTopicSubscriptionConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "filter_policy", ""), @@ -145,7 +147,7 @@ func TestAccAWSSNSTopicSubscription_filterPolicy(t *testing.T) { func TestAccAWSSNSTopicSubscription_deliveryPolicy(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -153,7 +155,7 @@ func TestAccAWSSNSTopicSubscription_deliveryPolicy(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(ri, strconv.Quote(`{"healthyRetryPolicy":{"minDelayTarget":5,"maxDelayTarget":20,"numRetries": 5}}`)), + Config: testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(rName, strconv.Quote(`{"healthyRetryPolicy":{"minDelayTarget":5,"maxDelayTarget":20,"numRetries": 5}}`)), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), testAccCheckAWSSNSTopicSubscriptionDeliveryPolicyAttribute(attributes, &snsTopicSubscriptionDeliveryPolicy{ @@ -176,7 +178,7 @@ func TestAccAWSSNSTopicSubscription_deliveryPolicy(t *testing.T) { }, // Test attribute update { - Config: testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(ri, strconv.Quote(`{"healthyRetryPolicy":{"minDelayTarget":3,"maxDelayTarget":78,"numRetries": 11}}`)), + Config: testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(rName, strconv.Quote(`{"healthyRetryPolicy":{"minDelayTarget":3,"maxDelayTarget":78,"numRetries": 11}}`)), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), testAccCheckAWSSNSTopicSubscriptionDeliveryPolicyAttribute(attributes, &snsTopicSubscriptionDeliveryPolicy{ @@ -190,7 +192,7 @@ func TestAccAWSSNSTopicSubscription_deliveryPolicy(t *testing.T) { }, // Test attribute removal { - Config: testAccAWSSNSTopicSubscriptionConfig(ri), + Config: testAccAWSSNSTopicSubscriptionConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "delivery_policy", ""), @@ -203,9 +205,9 @@ func TestAccAWSSNSTopicSubscription_deliveryPolicy(t *testing.T) { func TestAccAWSSNSTopicSubscription_redrivePolicy(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() - dlqName := fmt.Sprintf("tf-acc-test-queue-dlq-%d", ri) - updatedDlqName := fmt.Sprintf("tf-acc-test-queue-dlq-update-%d", ri) + dlqName := acctest.RandomWithPrefix("tf-acc-test") + updatedDlqName := acctest.RandomWithPrefix("tf-acc-test") + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -213,7 +215,7 @@ func TestAccAWSSNSTopicSubscription_redrivePolicy(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_redrivePolicy(ri, dlqName), + Config: testAccAWSSNSTopicSubscriptionConfig_redrivePolicy(rName, dlqName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), testAccCheckAWSSNSTopicSubscriptionRedrivePolicyAttribute(attributes, dlqName), @@ -230,7 +232,7 @@ func TestAccAWSSNSTopicSubscription_redrivePolicy(t *testing.T) { }, // Test attribute update { - Config: testAccAWSSNSTopicSubscriptionConfig_redrivePolicy(ri, updatedDlqName), + Config: testAccAWSSNSTopicSubscriptionConfig_redrivePolicy(rName, updatedDlqName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), testAccCheckAWSSNSTopicSubscriptionRedrivePolicyAttribute(attributes, updatedDlqName), @@ -247,7 +249,7 @@ func TestAccAWSSNSTopicSubscription_redrivePolicy(t *testing.T) { }, // Test attribute removal { - Config: testAccAWSSNSTopicSubscriptionConfig(ri), + Config: testAccAWSSNSTopicSubscriptionConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "redrive_policy", ""), @@ -260,7 +262,7 @@ func TestAccAWSSNSTopicSubscription_redrivePolicy(t *testing.T) { func TestAccAWSSNSTopicSubscription_rawMessageDelivery(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -268,7 +270,7 @@ func TestAccAWSSNSTopicSubscription_rawMessageDelivery(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_rawMessageDelivery(ri, true), + Config: testAccAWSSNSTopicSubscriptionConfig_rawMessageDelivery(rName, true), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "raw_message_delivery", "true"), @@ -285,7 +287,7 @@ func TestAccAWSSNSTopicSubscription_rawMessageDelivery(t *testing.T) { }, // Test attribute update { - Config: testAccAWSSNSTopicSubscriptionConfig_rawMessageDelivery(ri, false), + Config: testAccAWSSNSTopicSubscriptionConfig_rawMessageDelivery(rName, false), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "raw_message_delivery", "false"), @@ -293,7 +295,7 @@ func TestAccAWSSNSTopicSubscription_rawMessageDelivery(t *testing.T) { }, // Test attribute removal { - Config: testAccAWSSNSTopicSubscriptionConfig(ri), + Config: testAccAWSSNSTopicSubscriptionConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), resource.TestCheckResourceAttr(resourceName, "raw_message_delivery", "false"), @@ -306,7 +308,7 @@ func TestAccAWSSNSTopicSubscription_rawMessageDelivery(t *testing.T) { func TestAccAWSSNSTopicSubscription_autoConfirmingEndpoint(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAPIGatewayTypeEDGEPreCheck(t) }, @@ -314,7 +316,7 @@ func TestAccAWSSNSTopicSubscription_autoConfirmingEndpoint(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_autoConfirmingEndpoint(ri), + Config: testAccAWSSNSTopicSubscriptionConfig_autoConfirmingEndpoint(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), ), @@ -335,7 +337,7 @@ func TestAccAWSSNSTopicSubscription_autoConfirmingEndpoint(t *testing.T) { func TestAccAWSSNSTopicSubscription_autoConfirmingSecuredEndpoint(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccAPIGatewayTypeEDGEPreCheck(t) }, @@ -343,7 +345,7 @@ func TestAccAWSSNSTopicSubscription_autoConfirmingSecuredEndpoint(t *testing.T) CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_autoConfirmingSecuredEndpoint(ri, "john", "doe"), + Config: testAccAWSSNSTopicSubscriptionConfig_autoConfirmingSecuredEndpoint(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), ), @@ -483,14 +485,14 @@ func TestObfuscateEndpointPassword(t *testing.T) { } } -func testAccAWSSNSTopicSubscriptionConfig(i int) string { +func testAccAWSSNSTopicSubscriptionConfig(rName string) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "terraform-test-topic-%d" + name = %[1]q } resource "aws_sqs_queue" "test_queue" { - name = "terraform-subscription-test-queue-%d" + name = %[1]q } resource "aws_sns_topic_subscription" "test_subscription" { @@ -498,17 +500,17 @@ resource "aws_sns_topic_subscription" "test_subscription" { protocol = "sqs" endpoint = aws_sqs_queue.test_queue.arn } -`, i, i) +`, rName) } -func testAccAWSSNSTopicSubscriptionConfig_filterPolicy(i int, policy string) string { +func testAccAWSSNSTopicSubscriptionConfig_filterPolicy(rName, policy string) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "terraform-test-topic-%d" + name = %[1]q } resource "aws_sqs_queue" "test_queue" { - name = "terraform-subscription-test-queue-%d" + name = %[1]q } resource "aws_sns_topic_subscription" "test_subscription" { @@ -517,17 +519,17 @@ resource "aws_sns_topic_subscription" "test_subscription" { endpoint = aws_sqs_queue.test_queue.arn filter_policy = %s } -`, i, i, policy) +`, rName, policy) } -func testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(i int, policy string) string { +func testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(rName, policy string) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "terraform-test-topic-%d" + name = %[1]q } resource "aws_sqs_queue" "test_queue" { - name = "terraform-subscription-test-queue-%d" + name = %[1]q } resource "aws_sns_topic_subscription" "test_subscription" { @@ -536,21 +538,21 @@ resource "aws_sns_topic_subscription" "test_subscription" { protocol = "sqs" topic_arn = aws_sns_topic.test_topic.arn } -`, i, i, policy) +`, rName, policy) } -func testAccAWSSNSTopicSubscriptionConfig_redrivePolicy(i int, dlqName string) string { +func testAccAWSSNSTopicSubscriptionConfig_redrivePolicy(rName, dlqName string) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "terraform-test-topic-%[1]d" + name = %[1]q } resource "aws_sqs_queue" "test_queue" { - name = "terraform-subscription-test-queue-%[1]d" + name = %[1]q } resource "aws_sqs_queue" "test_queue_dlq" { - name = "%s" + name = %[2]q } resource "aws_sns_topic_subscription" "test_subscription" { @@ -559,17 +561,17 @@ resource "aws_sns_topic_subscription" "test_subscription" { protocol = "sqs" topic_arn = aws_sns_topic.test_topic.arn } -`, i, dlqName) +`, rName, dlqName) } -func testAccAWSSNSTopicSubscriptionConfig_rawMessageDelivery(i int, rawMessageDelivery bool) string { +func testAccAWSSNSTopicSubscriptionConfig_rawMessageDelivery(rName string, rawMessageDelivery bool) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "terraform-test-topic-%d" + name = %[1]q } resource "aws_sqs_queue" "test_queue" { - name = "terraform-subscription-test-queue-%d" + name = %[1]q } resource "aws_sns_topic_subscription" "test_subscription" { @@ -578,17 +580,17 @@ resource "aws_sns_topic_subscription" "test_subscription" { raw_message_delivery = %t topic_arn = aws_sns_topic.test_topic.arn } -`, i, i, rawMessageDelivery) +`, rName, rawMessageDelivery) } -func testAccAWSSNSTopicSubscriptionConfig_autoConfirmingEndpoint(i int) string { +func testAccAWSSNSTopicSubscriptionConfig_autoConfirmingEndpoint(rName string) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "tf-acc-test-sns-%d" + name = %[1]q } resource "aws_api_gateway_rest_api" "test" { - name = "tf-acc-test-sns-%d" + name = %[1]q description = "Terraform Acceptance test for SNS subscription" } @@ -632,7 +634,7 @@ resource "aws_api_gateway_integration_response" "test" { } resource "aws_iam_role" "iam_for_lambda" { - name = "tf-acc-test-sns-%d" + name = %[1]q assume_role_policy = < Date: Fri, 19 Feb 2021 17:49:53 -0500 Subject: [PATCH 14/28] resource/sns_topic_subscription: Move obfuscation to test --- aws/resource_aws_sns_topic_subscription.go | 24 ------------------- ...esource_aws_sns_topic_subscription_test.go | 23 ++++++++++++++++++ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index 733185af56f..39842a35fa3 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "log" - "net/url" "strings" "time" @@ -19,10 +18,6 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sns/waiter" ) -const awsSNSPendingConfirmationMessage = "pending confirmation" -const awsSNSPendingConfirmationMessageWithoutSpaces = "pendingconfirmation" -const awsSNSPasswordObfuscationPattern = "****" - func resourceAwsSnsTopicSubscription() *schema.Resource { return &schema.Resource{ Create: resourceAwsSnsTopicSubscriptionCreate, @@ -292,25 +287,6 @@ func expandSNSSubscriptionAttributes(d *schema.ResourceData) (output map[string] return attributes } -// returns the endpoint with obfuscated password, if any -func obfuscateEndpoint(endpoint string) string { - res, err := url.Parse(endpoint) - if err != nil { - fmt.Println(err) - } - - var obfuscatedEndpoint = res.String() - - // If the user is defined, we try to get the username and password, if defined. - // Then, we update the user with the obfuscated version. - if res.User != nil { - if password, ok := res.User.Password(); ok { - obfuscatedEndpoint = strings.Replace(obfuscatedEndpoint, password, awsSNSPasswordObfuscationPattern, 1) - } - } - return obfuscatedEndpoint -} - func snsSubscriptionAttributeUpdate(conn *sns.SNS, subscriptionArn, attributeName, attributeValue string) error { req := &sns.SetSubscriptionAttributesInput{ SubscriptionArn: aws.String(subscriptionArn), diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index 0d9f24f3188..d73027ca4dd 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -3,9 +3,11 @@ package aws import ( "encoding/json" "fmt" + "net/url" "reflect" "regexp" "strconv" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -470,6 +472,27 @@ func testAccCheckAWSSNSTopicSubscriptionRedrivePolicyAttribute(attributes map[st } } +const awsSNSPasswordObfuscationPattern = "****" + +// returns the endpoint with obfuscated password, if any +func obfuscateEndpoint(endpoint string) string { + res, err := url.Parse(endpoint) + if err != nil { + fmt.Println(err) + } + + var obfuscatedEndpoint = res.String() + + // If the user is defined, we try to get the username and password, if defined. + // Then, we update the user with the obfuscated version. + if res.User != nil { + if password, ok := res.User.Password(); ok { + obfuscatedEndpoint = strings.Replace(obfuscatedEndpoint, password, awsSNSPasswordObfuscationPattern, 1) + } + } + return obfuscatedEndpoint +} + func TestObfuscateEndpointPassword(t *testing.T) { checks := map[string]string{ "https://example.com/myroute": "https://example.com/myroute", From e451a519482936d794ec05557c2c5d219b3ec360 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 19 Feb 2021 17:51:59 -0500 Subject: [PATCH 15/28] resource/sns_topic_subscription: Lint --- aws/resource_aws_sns_topic_subscription_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index d73027ca4dd..ed7f88a07c2 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -925,7 +925,6 @@ resource "aws_sns_topic_subscription" "test_subscription" { protocol = "https" endpoint = replace(aws_api_gateway_deployment.test.invoke_url, "https://", "https://davematthews:granny@") endpoint_auto_confirms = true - confirmation_timeout_in_minutes = 3 } `, rName) } From dd51171f7ff1ae0f42ea55511bf5217b2a9e9b97 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 19 Feb 2021 18:51:29 -0500 Subject: [PATCH 16/28] docs/r/sns_topic_subscription: Update documentation --- .../r/sns_topic_subscription.html.markdown | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index 74d65ae9dfa..df611beb359 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -8,18 +8,17 @@ description: |- # Resource: aws_sns_topic_subscription - Provides a resource for subscribing to SNS topics. Requires that an SNS topic exist for the subscription to attach to. -This resource allows you to automatically place messages sent to SNS topics in SQS queues, send them as HTTP(S) POST requests -to a given endpoint, send SMS messages, or notify devices / applications. The most likely use case for Terraform users will -probably be SQS queues. +Provides a resource for subscribing to SNS topics. Requires that an SNS topic exist for the subscription to attach to. This resource allows you to automatically place messages sent to SNS topics in SQS queues, send them as HTTP(S) POST requests to a given endpoint, send SMS messages, or notify devices / applications. The most likely use case for Terraform users will probably be SQS queues. -~> **NOTE:** If the SNS topic and SQS queue are in different AWS regions, it is important for the "aws_sns_topic_subscription" to use an AWS provider that is in the same region of the SNS topic. If the "aws_sns_topic_subscription" is using a provider with a different region than the SNS topic, terraform will fail to create the subscription. +~> **NOTE:** If the SNS topic and SQS queue are in different AWS regions, it is important for the `aws_sns_topic_subscription` to use an AWS provider that is in the same region as the SNS topic. If the `aws_sns_topic_subscription` is using a provider with a different region than the SNS topic, Terraform will fail to create the subscription. ~> **NOTE:** Setup of cross-account subscriptions from SNS topics to SQS queues requires Terraform to have access to BOTH accounts. -~> **NOTE:** If SNS topic and SQS queue are in different AWS accounts but the same region it is important for the "aws_sns_topic_subscription" to use the AWS provider of the account with the SQS queue. If "aws_sns_topic_subscription" is using a Provider with a different account than the SQS queue, terraform creates the subscriptions but does not keep state and tries to re-create the subscription at every apply. +~> **NOTE:** If SNS topic and SQS queue are in different AWS accounts but the same region, the "aws_sns_topic_subscription" must use the AWS provider for the account with the SQS queue. If `aws_sns_topic_subscription` is using a Provider with a different account than the SQS queue, Terraform creates the subscription but does not keep state and tries to re-create the subscription at every apply. -~> **NOTE:** If SNS topic and SQS queue are in different AWS accounts and different AWS regions it is important to recognize that the subscription needs to be initiated from the account with the SQS queue but in the region of the SNS topic. +~> **NOTE:** If SNS topic and SQS queue are in different AWS accounts and different AWS regions, the subscription needs to be initiated from the account with the SQS queue but in the region of the SNS topic. + +~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed outside of Terraform, Terraform cannot delete / unsubscribe the subscription. If try to `destroy`, you will receive an `InvalidParameter` error. ## Example Usage @@ -228,54 +227,49 @@ resource "aws_sns_topic_subscription" "sns-topic" { ## Argument Reference -The following arguments are supported: - -* `topic_arn` - (Required) The ARN of the SNS topic to subscribe to -* `protocol` - (Required) The protocol to use. The possible values for this are: `sqs`, `sms`, `lambda`, `application`, `email`, `email-json`. (`http` or `https` are partially supported, see below). -* `endpoint` - (Required) The endpoint to send data to, the contents will vary with the protocol. (see below for more information) -* `endpoint_auto_confirms` - (Optional) Boolean indicating whether the end point is capable of [auto confirming subscription](http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html#SendMessageToHttp.prepare) e.g., PagerDuty (default is false) -* `confirmation_timeout_in_minutes` - (Optional) Integer indicating number of minutes to wait in retying mode for fetching subscription arn before marking it as failure. Only applicable for http and https protocols (default is 1 minute). -* `raw_message_delivery` - (Optional) Boolean indicating whether or not to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property) (default is false). -* `filter_policy` - (Optional) JSON String with the filter policy that will be used in the subscription to filter messages seen by the target resource. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html) for more details. -* `delivery_policy` - (Optional) JSON String with the delivery policy (retries, backoff, etc.) that will be used in the subscription - this only applies to HTTP/S subscriptions. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html) for more details. -* `redrive_policy` - (Optional) JSON String with the redrive policy that will be used in the subscription. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html#how-messages-moved-into-dead-letter-queue) for more details. +The following arguments are required: -### Protocols supported +* `endpoint` - (Required) Endpoint to send data to, the contents will vary with the protocol. See details below. +* `protocol` - (Required) Protocol to use. Valid values are: `sqs`, `sms`, `lambda`, and `application`. Protocols `email`, `email-json`, `http` and `https` are also valid but partially supported. See details below. +* `topic_arn` - (Required) ARN of the SNS topic to subscribe to. -Supported SNS protocols include: +The following arguments are optional: -* `lambda` -- delivery of JSON-encoded message to a lambda function -* `sqs` -- delivery of JSON-encoded message to an Amazon SQS queue -* `application` -- delivery of JSON-encoded message to an EndpointArn for a mobile app and device -* `sms` -- delivery text message -* `email` -- delivery of message via SMTP (Confirmation tokens are valid for three days). -* `email-json` -- delivery of JSON-encoded message via SMTP (Confirmation tokens are valid for three days). +* `confirmation_timeout_in_minutes` - (Optional) Integer indicating number of minutes to wait in retying mode for fetching subscription arn before marking it as failure. Only applicable for http and https protocols. Default is `1`. +* `delivery_policy` - (Optional) JSON String with the delivery policy (retries, backoff, etc.) that will be used in the subscription - this only applies to HTTP/S subscriptions. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html) for more details. +* `endpoint_auto_confirms` - (Optional) Whether the endpoint is capable of [auto confirming subscription](http://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html#SendMessageToHttp.prepare) (e.g., PagerDuty). Default is `false`. +* `filter_policy` - (Optional) JSON String with the filter policy that will be used in the subscription to filter messages seen by the target resource. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html) for more details. +* `raw_message_delivery` - (Optional) Whether to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property). Default is `false`. +* `redrive_policy` - (Optional) JSON String with the redrive policy that will be used in the subscription. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html#how-messages-moved-into-dead-letter-queue) for more details. -Partially supported SNS protocols include: +### Protocol support -* `http` -- delivery of JSON-encoded messages via HTTP. Supported only for the end points that auto confirms the subscription. -* `https` -- delivery of JSON-encoded messages via HTTPS. Supported only for the end points that auto confirms the subscription. +Supported values for `protocol` include: -These are unsupported because the endpoint needs to be authorized and does not -generate an ARN until the target email address has been validated. This breaks -the Terraform model and as a result are not currently supported. +* `application` - Delivers JSON-encoded messages. `endpoint` is the endpoint ARN of a mobile app and device. +* `firehose` - Delivers JSON-encoded messages. `endpoint` is the ARN of an Amazon Kinesis Data Firehose delivery stream. +* `lambda` - Delivers JSON-encoded messages. `endpoint` is the ARN of an AWS Lambda function. +* `sms` - Delivers text messages via SMS. `endpoint` is the phone number of an SMS-enabled device. +* `sqs` - Delivers JSON-encoded messages. `endpoint` is the ARN of an Amazon SQS queue (e.g., `arn:aws:sqs:us-west-2:432981146916:terraform-queue-too`). -### Specifying endpoints +Partially supported values for `protocol` include: -Endpoints have different format requirements according to the protocol that is chosen. +~> **NOTE:** If a subscription uses a partially-supported protocol and is not confirmed, either through automatic confirmation or means outside of Terraform (e.g., clicking on a "Confirm Subscription" link in an email), Terraform cannot delete / unsubscribe the subscription. Attempting a `destroy` of an unconfirmed subscription will cause an error. The `pending_confirmation` attribute provides confirmation status. -* SQS endpoints come in the form of the SQS queue's ARN (not the URL of the queue) e.g: `arn:aws:sqs:us-west-2:432981146916:terraform-queue-too` -* Application endpoints are also the endpoint ARN for the mobile app and device. +* `email` - Delivers messages via SMTP. `endpoint` is an email address. +* `email-json` - Delivers JSON-encoded messages via SMTP. `endpoint` is an email address. +* `http` -- Delivers JSON-encoded messages via HTTP POST. `endpoint` is a URL beginning with `http://`. +* `https` -- Delivers JSON-encoded messages via HTTPS POST. `endpoint` is a URL beginning with `https://`. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `id` - The ARN of the subscription -* `topic_arn` - The ARN of the topic the subscription belongs to -* `protocol` - The protocol being used -* `endpoint` - The full endpoint to send data to (SQS ARN, HTTP(S) URL, Application ARN, SMS number, etc.) -* `arn` - The ARN of the subscription stored as a more user-friendly property +* `arn` - ARN of the subscription. +* `confirmation_was_authenticated` - Whether the subscription confirmation request was authenticated. +* `id` - ARN of the subscription. +* `owner_id` - AWS account ID of the subscription's owner. +* `pending_confirmation` - Whether the subscription has not been confirmed. ## Import From 7355025b1f6db5fd072f3384b15fc36449f3f960 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 19 Feb 2021 19:10:06 -0500 Subject: [PATCH 17/28] r/sns_topic_subscription: Add changelog, fix docs --- .changelog/14923.txt | 3 +++ aws/resource_aws_sns_topic_subscription.go | 1 + .../docs/r/sns_topic_subscription.html.markdown | 14 +++++++------- 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 .changelog/14923.txt diff --git a/.changelog/14923.txt b/.changelog/14923.txt new file mode 100644 index 00000000000..d10097c71c3 --- /dev/null +++ b/.changelog/14923.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_sns_topic_subscription: Add `confirmation_was_authenticated`, `owner_id`, and `pending_confirmation` attributes. Add `email`, `email-json`, and `firehose` to values for `protocol`. +``` \ No newline at end of file diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index 39842a35fa3..4f450d98273 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -84,6 +84,7 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { "application", "email-json", "email", + "firehose", "http", "https", "lambda", diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index df611beb359..8a7e3c6145a 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -10,15 +10,15 @@ description: |- Provides a resource for subscribing to SNS topics. Requires that an SNS topic exist for the subscription to attach to. This resource allows you to automatically place messages sent to SNS topics in SQS queues, send them as HTTP(S) POST requests to a given endpoint, send SMS messages, or notify devices / applications. The most likely use case for Terraform users will probably be SQS queues. -~> **NOTE:** If the SNS topic and SQS queue are in different AWS regions, it is important for the `aws_sns_topic_subscription` to use an AWS provider that is in the same region as the SNS topic. If the `aws_sns_topic_subscription` is using a provider with a different region than the SNS topic, Terraform will fail to create the subscription. +~> **NOTE:** If the SNS topic and SQS queue are in different AWS regions, the `aws_sns_topic_subscription` must use an AWS provider that is in the same region as the SNS topic. If the `aws_sns_topic_subscription` uses a provider with a different region than the SNS topic, Terraform will fail to create the subscription. ~> **NOTE:** Setup of cross-account subscriptions from SNS topics to SQS queues requires Terraform to have access to BOTH accounts. -~> **NOTE:** If SNS topic and SQS queue are in different AWS accounts but the same region, the "aws_sns_topic_subscription" must use the AWS provider for the account with the SQS queue. If `aws_sns_topic_subscription` is using a Provider with a different account than the SQS queue, Terraform creates the subscription but does not keep state and tries to re-create the subscription at every apply. +~> **NOTE:** If an SNS topic and SQS queue are in different AWS accounts but the same region, the `aws_sns_topic_subscription` must use the AWS provider for the account with the SQS queue. If `aws_sns_topic_subscription` uses a Provider with a different account than the SQS queue, Terraform creates the subscription but does not keep state and tries to re-create the subscription at every `apply`. -~> **NOTE:** If SNS topic and SQS queue are in different AWS accounts and different AWS regions, the subscription needs to be initiated from the account with the SQS queue but in the region of the SNS topic. +~> **NOTE:** If an SNS topic and SQS queue are in different AWS accounts and different AWS regions, the subscription needs to be initiated from the account with the SQS queue but in the region of the SNS topic. -~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed outside of Terraform, Terraform cannot delete / unsubscribe the subscription. If try to `destroy`, you will receive an `InvalidParameter` error. +~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed (e.g., outside of Terraform), AWS does not allow Terraform to delete / unsubscribe the subscription. If you `destroy` an unconfirmed subscription, Terraform will remove the subscription from its state but the subscription still exist in AWS. ## Example Usage @@ -229,8 +229,8 @@ resource "aws_sns_topic_subscription" "sns-topic" { The following arguments are required: -* `endpoint` - (Required) Endpoint to send data to, the contents will vary with the protocol. See details below. -* `protocol` - (Required) Protocol to use. Valid values are: `sqs`, `sms`, `lambda`, and `application`. Protocols `email`, `email-json`, `http` and `https` are also valid but partially supported. See details below. +* `endpoint` - (Required) Endpoint to send data to. The contents vary with the protocol. See details below. +* `protocol` - (Required) Protocol to use. Valid values are: `sqs`, `sms`, `lambda`, `firehose`, and `application`. Protocols `email`, `email-json`, `http` and `https` are also valid but partially supported. See details below. * `topic_arn` - (Required) ARN of the SNS topic to subscribe to. The following arguments are optional: @@ -254,7 +254,7 @@ Supported values for `protocol` include: Partially supported values for `protocol` include: -~> **NOTE:** If a subscription uses a partially-supported protocol and is not confirmed, either through automatic confirmation or means outside of Terraform (e.g., clicking on a "Confirm Subscription" link in an email), Terraform cannot delete / unsubscribe the subscription. Attempting a `destroy` of an unconfirmed subscription will cause an error. The `pending_confirmation` attribute provides confirmation status. +~> **NOTE:** If an `aws_sns_topic_subscription` uses a partially-supported protocol and the subscription is not confirmed, either through automatic confirmation or means outside of Terraform (e.g., clicking on a "Confirm Subscription" link in an email), Terraform cannot delete / unsubscribe the subscription. Attempting to `destroy` an unconfirmed subscription will remove the `aws_sns_topic_subscription` from Terraform's state but **_will not_** remove the subscription from AWS. The `pending_confirmation` attribute provides confirmation status. * `email` - Delivers messages via SMTP. `endpoint` is an email address. * `email-json` - Delivers JSON-encoded messages via SMTP. `endpoint` is an email address. From 99f40ee602ea4799be6c7bf7e9291ff10aaca2f6 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 19 Feb 2021 19:23:54 -0500 Subject: [PATCH 18/28] r/sns_topic_subscription: Remove unconfirmed subs from state --- aws/resource_aws_sns_topic_subscription.go | 6 +++ ...esource_aws_sns_topic_subscription_test.go | 43 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index 4f450d98273..bfb27690af3 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -256,6 +256,12 @@ func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interfac SubscriptionArn: aws.String(d.Id()), }) + if tfawserr.ErrMessageContains(err, sns.ErrCodeInvalidParameterException, "Cannot unsubscribe a subscription that is pending confirmation") { + log.Printf("[WARN] Removing unconfirmed SNS topic subscription (%s) from Terraform state but failed to remove it from AWS!", d.Id()) + d.SetId("") + return nil + } + return err } diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index ed7f88a07c2..4c40eaebde7 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -365,6 +365,35 @@ func TestAccAWSSNSTopicSubscription_autoConfirmingSecuredEndpoint(t *testing.T) }) } +func TestAccAWSSNSTopicSubscription_email(t *testing.T) { + attributes := make(map[string]string) + resourceName := "aws_sns_topic_subscription.test_subscription" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSNSTopicSubscriptionEmailConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", sns.ServiceName, regexp.MustCompile(fmt.Sprintf("%s:.+", rName))), + resource.TestCheckResourceAttr(resourceName, "confirmation_was_authenticated", "false"), + resource.TestCheckResourceAttr(resourceName, "delivery_policy", ""), + resource.TestCheckResourceAttr(resourceName, "endpoint", "invalid_email@example.com"), + resource.TestCheckResourceAttr(resourceName, "filter_policy", ""), + resource.TestCheckResourceAttr(resourceName, "pending_confirmation", "true"), + resource.TestCheckResourceAttr(resourceName, "protocol", "email"), + resource.TestCheckResourceAttr(resourceName, "raw_message_delivery", "false"), + resource.TestCheckResourceAttrPair(resourceName, "topic_arn", "aws_sns_topic.test_topic", "arn"), + ), + }, + }, + }) +} + func testAccCheckAWSSNSTopicSubscriptionDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).snsconn @@ -928,3 +957,17 @@ resource "aws_sns_topic_subscription" "test_subscription" { } `, rName) } + +func testAccAWSSNSTopicSubscriptionEmailConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_sns_topic" "test_topic" { + name = %[1]q +} + +resource "aws_sns_topic_subscription" "test_subscription" { + topic_arn = aws_sns_topic.test_topic.arn + protocol = "email" + endpoint = "invalid_email@example.com" +} +`, rName) +} From 01711791a55d946a4f1f0e701e0a96f631473375 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 19 Feb 2021 19:44:39 -0500 Subject: [PATCH 19/28] docs/r/sns_topic_subscription: Add note to docs --- website/docs/r/sns_topic_subscription.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index 8a7e3c6145a..a30ccc872ba 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -18,7 +18,7 @@ Provides a resource for subscribing to SNS topics. Requires that an SNS topic ex ~> **NOTE:** If an SNS topic and SQS queue are in different AWS accounts and different AWS regions, the subscription needs to be initiated from the account with the SQS queue but in the region of the SNS topic. -~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed (e.g., outside of Terraform), AWS does not allow Terraform to delete / unsubscribe the subscription. If you `destroy` an unconfirmed subscription, Terraform will remove the subscription from its state but the subscription still exist in AWS. +~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed (e.g., outside of Terraform), AWS does not allow Terraform to delete / unsubscribe the subscription. If you `destroy` an unconfirmed subscription, Terraform will remove the subscription from its state but the subscription still exist in AWS. However, if you delete an SNS topic, SNS [deletes all the subscriptions](https://docs.aws.amazon.com/sns/latest/dg/sns-delete-subscription-topic.html) associated with the topic. ## Example Usage From 55cba222e9a6ffd29d28321ff481af761d19afd4 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 10:10:36 -0500 Subject: [PATCH 20/28] r/sns_topic_subscription: Lint --- website/docs/r/sns_topic_subscription.html.markdown | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index b37f5391f8a..18ef6993b7f 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -238,11 +238,8 @@ The following arguments are supported: * `raw_message_delivery` - (Optional) Boolean indicating whether or not to enable raw message delivery (the original message is directly passed, not wrapped in JSON with the original message in the message property) (default is false). * `filter_policy` - (Optional) JSON String with the filter policy that will be used in the subscription to filter messages seen by the target resource. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html) for more details. * `delivery_policy` - (Optional) JSON String with the delivery policy (retries, backoff, etc.) that will be used in the subscription - this only applies to HTTP/S subscriptions. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html) for more details. -<<<<<<< HEAD * `redrive_policy` - (Optional) JSON String with the redrive policy that will be used in the subscription. Refer to the [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html#how-messages-moved-into-dead-letter-queue) for more details. -======= -* `subscription_role_arn` - (Optional) Required ARN of the IAM role to publish to Kinesis Data Firehose delivery stream. Require for `firehose` protocol. Refer to [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-firehose-as-subscriber.html) ->>>>>>> a95bbe357 (docs update) +* `subscription_role_arn` - (Optional) Required ARN of the IAM role to publish to Kinesis Data Firehose delivery stream. Require for `firehose` protocol. Refer to [SNS docs](https://docs.aws.amazon.com/sns/latest/dg/sns-firehose-as-subscriber.html). ### Protocols supported From 149b10da1c29d2c6ac1299b548a3febe44fcb26a Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 10:47:12 -0500 Subject: [PATCH 21/28] r/sns_topic_subscription: Add new arg to read --- aws/resource_aws_sns_topic_subscription.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index fef8e99c2ca..c81e5145285 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -195,6 +195,7 @@ func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{ d.Set("owner_id", attributes["Owner"]) d.Set("protocol", attributes["Protocol"]) d.Set("redrive_policy", attributes["RedrivePolicy"]) + d.Set("subscription_role_arn", attributes["SubscriptionRoleArn"]) d.Set("topic_arn", attributes["TopicArn"]) d.Set("confirmation_was_authenticated", false) @@ -247,10 +248,10 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac protocol := d.Get("protocol").(string) subscription_role_arn := d.Get("subscription_role_arn").(string) if strings.Contains(protocol, "firehose") && subscription_role_arn == "" { - return fmt.Errorf("Protocol firehose must contain subscription_role_arn!") + return fmt.Errorf("protocol firehose must contain subscription_role_arn!") } if !strings.Contains(protocol, "firehose") && subscription_role_arn != "" { - return fmt.Errorf("Only protocol firehose supports subscription_role_arn!") + return fmt.Errorf("only protocol firehose supports subscription_role_arn!") } if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "SubscriptionRoleArn", subscription_role_arn); err != nil { From 474bcd3cc08086b1eedf99dea5014bc5673b3efe Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 10:50:42 -0500 Subject: [PATCH 22/28] r/sns_topic_subscription: Update changelog --- .changelog/14923.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/14923.txt b/.changelog/14923.txt index d10097c71c3..ce57b47ce2b 100644 --- a/.changelog/14923.txt +++ b/.changelog/14923.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/aws_sns_topic_subscription: Add `confirmation_was_authenticated`, `owner_id`, and `pending_confirmation` attributes. Add `email`, `email-json`, and `firehose` to values for `protocol`. +resource/aws_sns_topic_subscription: Add `email`, `email-json`, and `firehose` to protocol values. Add `subscription_role_arn` argument for Firehose support. Add `confirmation_was_authenticated`, `owner_id`, and `pending_confirmation` attributes. ``` \ No newline at end of file From a3da892ea38635375537e8339ae7e2269ec1aab7 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 10:59:58 -0500 Subject: [PATCH 23/28] r/sns_topic_subscription: Simplify firehose test --- ...esource_aws_sns_topic_subscription_test.go | 35 +++++-------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index dbf02c874a2..f1e0fe7ad49 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -397,7 +397,7 @@ func TestAccAWSSNSTopicSubscription_email(t *testing.T) { func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { attributes := make(map[string]string) resourceName := "aws_sns_topic_subscription.test_subscription" - ri := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -405,10 +405,10 @@ func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSNSTopicSubscriptionConfig_firehose(ri, 1), + Config: testAccAWSSNSTopicSubscriptionConfig_firehose(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sns", regexp.MustCompile(fmt.Sprintf("terraform-test-topic-%d:.+", ri))), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", sns.ServiceName, regexp.MustCompile(fmt.Sprintf("%s:.+", rName))), resource.TestCheckResourceAttr(resourceName, "delivery_policy", ""), resource.TestCheckResourceAttrPair(resourceName, "endpoint", "aws_kinesis_firehose_delivery_stream.test_stream", "arn"), resource.TestCheckResourceAttr(resourceName, "filter_policy", ""), @@ -418,23 +418,6 @@ func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "subscription_role_arn", "aws_iam_role.firehose_role", "arn"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "confirmation_timeout_in_minutes", - "endpoint_auto_confirms", - }, - }, - // Test attribute update - { - Config: testAccAWSSNSTopicSubscriptionConfig_firehose(ri, 2), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSNSTopicSubscriptionExists(resourceName, attributes), - resource.TestCheckResourceAttrPair(resourceName, "subscription_role_arn", "aws_iam_role.firehose_role", "arn"), - ), - }, }, }) } @@ -1017,10 +1000,10 @@ resource "aws_sns_topic_subscription" "test_subscription" { `, rName) } -func testAccAWSSNSTopicSubscriptionConfig_firehose(i, n int) string { +func testAccAWSSNSTopicSubscriptionConfig_firehose(rName string) string { return fmt.Sprintf(` resource "aws_sns_topic" "test_topic" { - name = "terraform-test-topic-%d" + name = %[1]q } resource "aws_sns_topic_subscription" "test_subscription" { @@ -1030,12 +1013,12 @@ resource "aws_sns_topic_subscription" "test_subscription" { subscription_role_arn = aws_iam_role.firehose_role.arn } resource "aws_s3_bucket" "bucket" { - bucket = "tf-test-bucket-%d" + bucket = %[1]q acl = "private" } resource "aws_iam_role" "firehose_role" { - name = "tf-test-firehose-role-%d-%d" + name = %[1]q assume_role_policy = < Date: Thu, 25 Feb 2021 11:31:43 -0500 Subject: [PATCH 24/28] r/sns_topic_subscription: Add error check for firehose (govcloud) --- aws/resource_aws_sns_topic_subscription_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index f1e0fe7ad49..665b7ce632c 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -401,6 +401,7 @@ func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheckSkipSNS(t), Providers: testAccProviders, CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy, Steps: []resource.TestStep{ @@ -422,6 +423,13 @@ func TestAccAWSSNSTopicSubscription_firehose(t *testing.T) { }) } +// testAccErrorCheckSkipSNS skips SNS tests that have error messages indicating unsupported features +func testAccErrorCheckSkipSNS(t *testing.T) resource.ErrorCheckFunc { + return testAccErrorCheckSkipMessagesContaining(t, + "Invalid protocol type: firehose", + ) +} + func testAccCheckAWSSNSTopicSubscriptionDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).snsconn From ab67aa80442b0eb101d2a6b88edb932552460915 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 11:35:44 -0500 Subject: [PATCH 25/28] r/sns_topic_subscription: Remove unneeded changelog --- .changelog/17307.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .changelog/17307.txt diff --git a/.changelog/17307.txt b/.changelog/17307.txt deleted file mode 100644 index e1bc562b8aa..00000000000 --- a/.changelog/17307.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-notes:enhancement -resource/sns_topic_subscription: Add `firehose` protocol option with `subscription_role_arn` attribute -``` From 5cc6fb4944fd3d903267cc44e2871399360dccec Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 11:41:12 -0500 Subject: [PATCH 26/28] docs/r/sns_topic_subscription: Fix docs --- website/docs/r/sns_topic_subscription.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/sns_topic_subscription.html.markdown b/website/docs/r/sns_topic_subscription.html.markdown index 84230845b77..46d17ced8b5 100644 --- a/website/docs/r/sns_topic_subscription.html.markdown +++ b/website/docs/r/sns_topic_subscription.html.markdown @@ -18,7 +18,7 @@ Provides a resource for subscribing to SNS topics. Requires that an SNS topic ex ~> **NOTE:** If an SNS topic and SQS queue are in different AWS accounts and different AWS regions, the subscription needs to be initiated from the account with the SQS queue but in the region of the SNS topic. -~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed (e.g., outside of Terraform), AWS does not allow Terraform to delete / unsubscribe the subscription. If you `destroy` an unconfirmed subscription, Terraform will remove the subscription from its state but the subscription still exist in AWS. However, if you delete an SNS topic, SNS [deletes all the subscriptions](https://docs.aws.amazon.com/sns/latest/dg/sns-delete-subscription-topic.html) associated with the topic. +~> **NOTE:** You cannot unsubscribe to a subscription that is pending confirmation. If you use `email`, `email-json`, or `http`/`https` (without auto-confirmation enabled), until the subscription is confirmed (e.g., outside of Terraform), AWS does not allow Terraform to delete / unsubscribe the subscription. If you `destroy` an unconfirmed subscription, Terraform will remove the subscription from its state but the subscription will still exist in AWS. However, if you delete an SNS topic, SNS [deletes all the subscriptions](https://docs.aws.amazon.com/sns/latest/dg/sns-delete-subscription-topic.html) associated with the topic. Also, you can import a subscription after confirmation and then have the capability to delete it. ## Example Usage From 14ce978272f226a38e185fffa75dc4e87fd8ed31 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 11:56:04 -0500 Subject: [PATCH 27/28] tests/r/sns_topic_subscription: Fix hardcoded suffixes --- ...resource_aws_sns_topic_subscription_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription_test.go b/aws/resource_aws_sns_topic_subscription_test.go index 665b7ce632c..6e54f345ff8 100644 --- a/aws/resource_aws_sns_topic_subscription_test.go +++ b/aws/resource_aws_sns_topic_subscription_test.go @@ -721,6 +721,8 @@ resource "aws_api_gateway_integration_response" "test" { } } +data "aws_partition" "current" {} + resource "aws_iam_role" "iam_for_lambda" { name = %[1]q @@ -731,7 +733,7 @@ resource "aws_iam_role" "iam_for_lambda" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "lambda.amazonaws.com" + "Service": "lambda.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -765,7 +767,7 @@ resource "aws_lambda_permission" "apigw_lambda" { statement_id = "AllowExecutionFromAPIGateway" action = "lambda:InvokeFunction" function_name = aws_lambda_function.lambda.arn - principal = "apigateway.amazonaws.com" + principal = "apigateway.${data.aws_partition.current.dns_suffix}" source_arn = "${aws_api_gateway_deployment.test.execution_arn}/*" } @@ -845,6 +847,8 @@ resource "aws_api_gateway_integration_response" "test" { } } +data "aws_partition" "current" {} + resource "aws_iam_role" "iam_for_lambda" { name = %[1]q @@ -855,7 +859,7 @@ resource "aws_iam_role" "iam_for_lambda" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "lambda.amazonaws.com" + "Service": "lambda.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -889,7 +893,7 @@ resource "aws_lambda_permission" "apigw_lambda" { statement_id = "AllowExecutionFromAPIGateway" action = "lambda:InvokeFunction" function_name = aws_lambda_function.lambda.arn - principal = "apigateway.amazonaws.com" + principal = "apigateway.${data.aws_partition.current.dns_suffix}" source_arn = "${aws_api_gateway_deployment.test.execution_arn}/*" } @@ -919,7 +923,7 @@ resource "aws_iam_role" "invocation_role" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "apigateway.amazonaws.com" + "Service": "apigateway.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -1025,6 +1029,8 @@ resource "aws_s3_bucket" "bucket" { acl = "private" } +data "aws_partition" "current" {} + resource "aws_iam_role" "firehose_role" { name = %[1]q @@ -1035,7 +1041,7 @@ resource "aws_iam_role" "firehose_role" { { "Action": "sts:AssumeRole", "Principal": { - "Service": ["sns.amazonaws.com","firehose.amazonaws.com"] + "Service": ["sns.${data.aws_partition.current.dns_suffix}","firehose.${data.aws_partition.current.dns_suffix}"] }, "Effect": "Allow", "Sid": "" From 0344547e3d9c1bccf947eed1a9a2049a3c1917ed Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 25 Feb 2021 12:12:11 -0500 Subject: [PATCH 28/28] r/sns_topic_subscription: Go-ize variable names --- aws/resource_aws_sns_topic_subscription.go | 38 +++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/aws/resource_aws_sns_topic_subscription.go b/aws/resource_aws_sns_topic_subscription.go index c81e5145285..1174bca2841 100644 --- a/aws/resource_aws_sns_topic_subscription.go +++ b/aws/resource_aws_sns_topic_subscription.go @@ -246,15 +246,15 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac if d.HasChange("subscription_role_arn") { protocol := d.Get("protocol").(string) - subscription_role_arn := d.Get("subscription_role_arn").(string) - if strings.Contains(protocol, "firehose") && subscription_role_arn == "" { + subscriptionRoleARN := d.Get("subscription_role_arn").(string) + if strings.Contains(protocol, "firehose") && subscriptionRoleARN == "" { return fmt.Errorf("protocol firehose must contain subscription_role_arn!") } - if !strings.Contains(protocol, "firehose") && subscription_role_arn != "" { + if !strings.Contains(protocol, "firehose") && subscriptionRoleARN != "" { return fmt.Errorf("only protocol firehose supports subscription_role_arn!") } - if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "SubscriptionRoleArn", subscription_role_arn); err != nil { + if err := snsSubscriptionAttributeUpdate(conn, d.Id(), "SubscriptionRoleArn", subscriptionRoleARN); err != nil { return err } } @@ -287,33 +287,33 @@ func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interfac // Assembles supplied attributes into a single map - empty/default values are excluded from the map func expandSNSSubscriptionAttributes(d *schema.ResourceData) (output map[string]*string) { - delivery_policy := d.Get("delivery_policy").(string) - filter_policy := d.Get("filter_policy").(string) - raw_message_delivery := d.Get("raw_message_delivery").(bool) - redrive_policy := d.Get("redrive_policy").(string) - subscription_role_arn := d.Get("subscription_role_arn").(string) + deliveryPolicy := d.Get("delivery_policy").(string) + filterPolicy := d.Get("filter_policy").(string) + rawMessageDelivery := d.Get("raw_message_delivery").(bool) + redrivePolicy := d.Get("redrive_policy").(string) + subscriptionRoleARN := d.Get("subscription_role_arn").(string) // Collect attributes if available attributes := map[string]*string{} - if delivery_policy != "" { - attributes["DeliveryPolicy"] = aws.String(delivery_policy) + if deliveryPolicy != "" { + attributes["DeliveryPolicy"] = aws.String(deliveryPolicy) } - if filter_policy != "" { - attributes["FilterPolicy"] = aws.String(filter_policy) + if filterPolicy != "" { + attributes["FilterPolicy"] = aws.String(filterPolicy) } - if raw_message_delivery { - attributes["RawMessageDelivery"] = aws.String(fmt.Sprintf("%t", raw_message_delivery)) + if rawMessageDelivery { + attributes["RawMessageDelivery"] = aws.String(fmt.Sprintf("%t", rawMessageDelivery)) } - if subscription_role_arn != "" { - attributes["SubscriptionRoleArn"] = aws.String(subscription_role_arn) + if subscriptionRoleARN != "" { + attributes["SubscriptionRoleArn"] = aws.String(subscriptionRoleARN) } - if redrive_policy != "" { - attributes["RedrivePolicy"] = aws.String(redrive_policy) + if redrivePolicy != "" { + attributes["RedrivePolicy"] = aws.String(redrivePolicy) } return attributes