Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource/aws_sns_topic_subscription: Add configurable DeliveryPolicy #3289

Merged
merged 3 commits into from
Sep 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions aws/resource_aws_sns_topic_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var SNSSubscriptionAttributeMap = map[string]string{
"protocol": "Protocol",
"raw_message_delivery": "RawMessageDelivery",
"filter_policy": "FilterPolicy",
"delivery_policy": "DeliveryPolicy",
}

func resourceAwsSnsTopicSubscription() *schema.Resource {
Expand Down Expand Up @@ -75,8 +76,14 @@ func resourceAwsSnsTopicSubscription() *schema.Resource {
ForceNew: true,
},
"delivery_policy": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateJsonString,
DiffSuppressFunc: suppressEquivalentJsonDiffs,
StateFunc: func(v interface{}) string {
json, _ := structure.NormalizeJsonString(v)
return json
},
},
"raw_message_delivery": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -136,34 +143,27 @@ func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interfac
attrValue = "true"
}

req := &sns.SetSubscriptionAttributesInput{
SubscriptionArn: aws.String(d.Id()),
AttributeName: aws.String("RawMessageDelivery"),
AttributeValue: aws.String(attrValue),
}
_, err := snsconn.SetSubscriptionAttributes(req)

if err != nil {
return fmt.Errorf("Unable to set raw message delivery attribute on subscription")
if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "raw_message_delivery", attrValue); err != nil {
return err
}
}

if d.HasChange("filter_policy") {
_, n := d.GetChange("filter_policy")

attrValue := n.(string)

req := &sns.SetSubscriptionAttributesInput{
SubscriptionArn: aws.String(d.Id()),
AttributeName: aws.String("FilterPolicy"),
AttributeValue: aws.String(attrValue),
if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "filter_policy", n.(string)); err != nil {
return err
}
_, err := snsconn.SetSubscriptionAttributes(req)
}

if err != nil {
return fmt.Errorf("Unable to set filter policy attribute on subscription: %s", err)
if d.HasChange("delivery_policy") {
_, n := d.GetChange("delivery_policy")

if err := snsSubscriptionAttributeUpdate(snsconn, d.Id(), "delivery_policy", n.(string)); err != nil {
return err
}
}

return resourceAwsSnsTopicSubscriptionRead(d, meta)
}

Expand Down Expand Up @@ -337,3 +337,18 @@ func obfuscateEndpoint(endpoint string) string {
}
return obfuscatedEndpoint
}

func snsSubscriptionAttributeUpdate(snsconn *sns.SNS, subscriptionArn, attributeName, attributeValue string) error {
awsAttributeName := SNSSubscriptionAttributeMap[attributeName]
req := &sns.SetSubscriptionAttributesInput{
SubscriptionArn: aws.String(subscriptionArn),
AttributeName: aws.String(awsAttributeName),
AttributeValue: aws.String(attributeValue),
}
_, err := snsconn.SetSubscriptionAttributes(req)

if err != nil {
return fmt.Errorf("Unable to set %s attribute on subscription: %s", attributeName, err)
}
return nil
}
171 changes: 159 additions & 12 deletions aws/resource_aws_sns_topic_subscription_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ func TestAccAWSSNSTopicSubscription_filterPolicy(t *testing.T) {
},
})
}

func TestAccAWSSNSTopicSubscription_deliveryPolicy(t *testing.T) {
bflad marked this conversation as resolved.
Show resolved Hide resolved
ri := acctest.RandInt()
deliveryPolicy1 := `{"healthyRetryPolicy": {"minDelayTarget": 5, "maxDelayTarget": 20, "numRetries": 5}}`
deliveryPolicy2 := `{"healthyRetryPolicy": {"minDelayTarget": 3, "maxDelayTarget": 78, "numRetries": 11}}`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSNSTopicSubscriptionDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(ri, strconv.Quote(deliveryPolicy1)),
Check: resource.TestCheckResourceAttr("aws_sns_topic_subscription.test_subscription", "delivery_policy", deliveryPolicy1),
},
{
Config: testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(ri, strconv.Quote(deliveryPolicy2)),
Check: resource.TestCheckResourceAttr("aws_sns_topic_subscription.test_subscription", "delivery_policy", deliveryPolicy2),
},
},
})
}

func TestAccAWSSNSTopicSubscription_autoConfirmingEndpoint(t *testing.T) {
attributes := make(map[string]string)

Expand Down Expand Up @@ -170,40 +193,164 @@ func TestObfuscateEndpointPassword(t *testing.T) {
func testAccAWSSNSTopicSubscriptionConfig(i int) string {
return fmt.Sprintf(`
resource "aws_sns_topic" "test_topic" {
name = "terraform-test-topic-%d"
bflad marked this conversation as resolved.
Show resolved Hide resolved
name = "terraform-test-topic-%d"
}

resource "aws_sqs_queue" "test_queue" {
name = "terraform-subscription-test-queue-%d"
name = "terraform-subscription-test-queue-%d"
}

resource "aws_sns_topic_subscription" "test_subscription" {
topic_arn = "${aws_sns_topic.test_topic.arn}"
protocol = "sqs"
endpoint = "${aws_sqs_queue.test_queue.arn}"
topic_arn = "${aws_sns_topic.test_topic.arn}"
protocol = "sqs"
endpoint = "${aws_sqs_queue.test_queue.arn}"
}
`, i, i)
}

func testAccAWSSNSTopicSubscriptionConfig_filterPolicy(i int, policy string) string {
return fmt.Sprintf(`
resource "aws_sns_topic" "test_topic" {
name = "terraform-test-topic-%d"
name = "terraform-test-topic-%d"
}

resource "aws_sqs_queue" "test_queue" {
name = "terraform-subscription-test-queue-%d"
name = "terraform-subscription-test-queue-%d"
}

resource "aws_sns_topic_subscription" "test_subscription" {
topic_arn = "${aws_sns_topic.test_topic.arn}"
protocol = "sqs"
endpoint = "${aws_sqs_queue.test_queue.arn}"
filter_policy = %s
}
topic_arn = "${aws_sns_topic.test_topic.arn}"
protocol = "sqs"
endpoint = "${aws_sqs_queue.test_queue.arn}"
filter_policy = %s
}
`, i, i, policy)
}

func testAccAWSSNSTopicSubscriptionConfig_deliveryPolicy(i int, policy string) string {
Copy link
Contributor Author

@ddcprg ddcprg Feb 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to security restrictions in the accounts I have access to, I'm unable to run this acceptance test. Please let me know whether you prefer to have this deleted or run it in your end. Note this is a copy of one of the existing tests with the addition of delivery_policy in the SNS subscription

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally require acceptance testing for new enhancements and this one does look pretty good overall. It does need to be updated to a more valid delivery policy JSON though, as AWS is currently returning:

2018/02/08 11:56:37 [DEBUG] [aws-sdk-go] DEBUG: Validate Response sns/SetSubscriptionAttributes failed, not retrying, error InvalidParameter: Invalid parameter: DeliveryPolicy: healthyRetryPolicy.minDelayTarget must be specified
	status code: 400, request id: 4d4b2c18-4d55-5c2b-af81-7eeeced101fe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, I forgot the example on the doc is wrong. I'll update this with a valid policy

return fmt.Sprintf(`
resource "aws_sns_topic" "test_topic" {
name = "tf-acc-test-sns-%d"
}

resource "aws_api_gateway_rest_api" "test" {
name = "tf-acc-test-sns-%d"
description = "Terraform Acceptance test for SNS subscription"
}

resource "aws_api_gateway_method" "test" {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
resource_id = "${aws_api_gateway_rest_api.test.root_resource_id}"
http_method = "POST"
authorization = "NONE"
}

resource "aws_api_gateway_method_response" "test" {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
resource_id = "${aws_api_gateway_rest_api.test.root_resource_id}"
http_method = "${aws_api_gateway_method.test.http_method}"
status_code = "200"

response_parameters {
"method.response.header.Access-Control-Allow-Origin" = true
}
}

resource "aws_api_gateway_integration" "test" {
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
resource_id = "${aws_api_gateway_rest_api.test.root_resource_id}"
http_method = "${aws_api_gateway_method.test.http_method}"
integration_http_method = "POST"
type = "AWS"
uri = "${aws_lambda_function.lambda.invoke_arn}"
}

resource "aws_api_gateway_integration_response" "test" {
depends_on = ["aws_api_gateway_integration.test"]
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
resource_id = "${aws_api_gateway_rest_api.test.root_resource_id}"
http_method = "${aws_api_gateway_method.test.http_method}"
status_code = "${aws_api_gateway_method_response.test.status_code}"

response_parameters {
"method.response.header.Access-Control-Allow-Origin" = "'*'"
}
}

resource "aws_iam_role" "iam_for_lambda" {
name = "tf-acc-test-sns-%d"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}

resource "aws_iam_role_policy" "policy" {
name = "tf-acc-test-sns-%d"
role = "${aws_iam_role.iam_for_lambda.id}"

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}

resource "aws_lambda_permission" "apigw_lambda" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.lambda.arn}"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_deployment.test.execution_arn}/*"
}

resource "aws_lambda_function" "lambda" {
filename = "test-fixtures/lambda_confirm_sns.zip"
function_name = "tf-acc-test-sns-%d"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "main.confirm_subscription"
source_code_hash = "${base64sha256(file("test-fixtures/lambda_confirm_sns.zip"))}"
runtime = "python3.6"
}

resource "aws_api_gateway_deployment" "test" {
depends_on = ["aws_api_gateway_integration_response.test"]
rest_api_id = "${aws_api_gateway_rest_api.test.id}"
stage_name = "acctest"
}

resource "aws_sns_topic_subscription" "test_subscription" {
depends_on = ["aws_lambda_permission.apigw_lambda"]
topic_arn = "${aws_sns_topic.test_topic.arn}"
protocol = "https"
endpoint = "${aws_api_gateway_deployment.test.invoke_url}"
endpoint_auto_confirms = true
delivery_policy = %s
}
`, i, i, i, i, i, policy)
}

func testAccAWSSNSTopicSubscriptionConfig_autoConfirmingEndpoint(i int) string {
return fmt.Sprintf(`
resource "aws_sns_topic" "test_topic" {
Expand Down
3 changes: 2 additions & 1 deletion website/docs/r/sns_topic_subscription.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ The following arguments are supported:
* `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) The text of a filter policy to the topic subscription.
* `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.

### Protocols supported

Expand Down