diff --git a/builtin/providers/aws/resource_aws_sns_topic_subscription.go b/builtin/providers/aws/resource_aws_sns_topic_subscription.go index 72e9c3307a02..cece62b8a40a 100644 --- a/builtin/providers/aws/resource_aws_sns_topic_subscription.go +++ b/builtin/providers/aws/resource_aws_sns_topic_subscription.go @@ -5,13 +5,16 @@ import ( "log" "strings" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/sns" + "time" ) const awsSNSPendingConfirmationMessage = "pending confirmation" +const awsSNSPendingConfirmationMessageWithoutSpaces = "pendingconfirmation" func resourceAwsSnsTopicSubscription() *schema.Resource { return &schema.Resource{ @@ -27,7 +30,7 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { ForceNew: false, ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - forbidden := []string{"email", "sms", "http"} + forbidden := []string{"email", "sms"} for _, f := range forbidden { if strings.Contains(value, f) { errors = append( @@ -42,22 +45,28 @@ func resourceAwsSnsTopicSubscription() *schema.Resource { "endpoint": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: false, + }, + "endpoint_auto_confirms": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "confirmation_timeout_in_minutes": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1, }, "topic_arn": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: false, }, "delivery_policy": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: false, }, "raw_message_delivery": &schema.Schema{ Type: schema.TypeBool, Optional: true, - ForceNew: false, Default: false, }, "arn": &schema.Schema{ @@ -77,7 +86,7 @@ func resourceAwsSnsTopicSubscriptionCreate(d *schema.ResourceData, meta interfac return err } - if output.SubscriptionArn != nil && *output.SubscriptionArn == awsSNSPendingConfirmationMessage { + if subscriptionHasPendingConfirmation(output.SubscriptionArn) { log.Printf("[WARN] Invalid SNS Subscription, received a \"%s\" ARN", awsSNSPendingConfirmationMessage) return nil } @@ -178,6 +187,12 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. 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) + + 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) @@ -192,6 +207,76 @@ func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns. return nil, fmt.Errorf("Error creating SNS topic: %s", err) } - log.Printf("[DEBUG] Created new subscription!") + 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(int(time.Minute)*confirmation_timeout_in_minutes), func() error { + + subscription, err := findSubscriptionByNonID(d, snsconn) + + if subscription != nil { + output.SubscriptionArn = subscription.SubscriptionArn + return nil + } + + if err != nil { + return fmt.Errorf("Error fetching subscriptions for SNS topic %s: %s", topic_arn, err) + } + + return fmt.Errorf("Endpoint (%s) did not autoconfirm the subscription for topic %s", endpoint, topic_arn) + }) + + 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, snsconn *sns.SNS) (*sns.Subscription, error) { + protocol := d.Get("protocol").(string) + endpoint := d.Get("endpoint").(string) + topic_arn := d.Get("topic_arn").(string) + + req := &sns.ListSubscriptionsByTopicInput{ + TopicArn: aws.String(topic_arn), + } + + for { + + res, err := snsconn.ListSubscriptionsByTopic(req) + + if err != nil { + return nil, fmt.Errorf("Error fetching subscripitions for topic %s : %s", topic_arn, err) + } + + for _, subscription := range res.Subscriptions { + log.Printf("[DEBUG] check subscription with EndPoint %s, Protocol %s, topicARN %s and SubscriptionARN %s", *subscription.Endpoint, *subscription.Protocol, *subscription.TopicArn, *subscription.SubscriptionArn) + if *subscription.Endpoint == endpoint && *subscription.Protocol == protocol && *subscription.TopicArn == topic_arn && !subscriptionHasPendingConfirmation(subscription.SubscriptionArn) { + return subscription, nil + } + } + + // if there are more than 100 subscriptions then go to the next 100 otherwise return nil + if res.NextToken != nil { + req.NextToken = res.NextToken + } else { + return nil, nil + } + } +} + +// 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 +} diff --git a/website/source/docs/providers/aws/r/sns_topic_subscription.html.markdown b/website/source/docs/providers/aws/r/sns_topic_subscription.html.markdown index 6e13bea7182f..ab4ffd594ebf 100644 --- a/website/source/docs/providers/aws/r/sns_topic_subscription.html.markdown +++ b/website/source/docs/providers/aws/r/sns_topic_subscription.html.markdown @@ -49,8 +49,10 @@ resource "aws_sns_topic_subscription" "user_updates_sqs_target" { 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`, `lambda`, or `application`. (`email`, `http`, `https`, `sms`, are options but unsupported, see below) +* `protocol` - (Required) The protocol to use. The possible values for this are: `sqs`, `lambda`, `application`. (`http` or `https` are partially supported, see below) (`email`, `sms`, are options but 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). ### Protocols supported @@ -61,12 +63,15 @@ 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 +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 -* `http` -- delivery via HTTP -* `http(s)` -- delivery via HTTPS * `sms` -- delivery text message These are unsupported because the endpoint needs to be authorized and does not