2015-05-15 01:17:18 +02:00
package aws
import (
"fmt"
"log"
2016-01-11 22:54:57 +01:00
"strings"
2015-05-15 01:17:18 +02:00
2016-02-13 21:15:29 +01:00
"github.com/hashicorp/terraform/helper/resource"
2015-05-15 01:17:18 +02:00
"github.com/hashicorp/terraform/helper/schema"
2016-02-24 20:55:01 +01:00
"time"
2015-06-03 20:36:57 +02:00
"github.com/aws/aws-sdk-go/aws"
2016-05-12 14:46:22 +02:00
"github.com/aws/aws-sdk-go/aws/awserr"
2015-06-03 20:36:57 +02:00
"github.com/aws/aws-sdk-go/service/sns"
2015-05-15 01:17:18 +02:00
)
2016-01-11 22:54:57 +01:00
const awsSNSPendingConfirmationMessage = "pending confirmation"
2016-02-12 21:21:52 +01:00
const awsSNSPendingConfirmationMessageWithoutSpaces = "pendingconfirmation"
2016-01-11 22:54:57 +01:00
2015-05-15 01:17:18 +02:00
func resourceAwsSnsTopicSubscription ( ) * schema . Resource {
return & schema . Resource {
Create : resourceAwsSnsTopicSubscriptionCreate ,
Read : resourceAwsSnsTopicSubscriptionRead ,
Update : resourceAwsSnsTopicSubscriptionUpdate ,
Delete : resourceAwsSnsTopicSubscriptionDelete ,
2016-06-27 15:44:05 +02:00
Importer : & schema . ResourceImporter {
State : schema . ImportStatePassthrough ,
} ,
2015-05-15 01:17:18 +02:00
Schema : map [ string ] * schema . Schema {
"protocol" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : false ,
2016-01-11 22:54:57 +01:00
ValidateFunc : func ( v interface { } , k string ) ( ws [ ] string , errors [ ] error ) {
value := v . ( string )
2016-01-17 21:24:32 +01:00
forbidden := [ ] string { "email" , "sms" }
2016-01-11 22:54:57 +01:00
for _ , f := range forbidden {
if strings . Contains ( value , f ) {
errors = append (
errors ,
fmt . Errorf ( "Unsupported protocol (%s) for SNS Topic" , value ) ,
)
}
}
return
} ,
2015-05-15 01:17:18 +02:00
} ,
"endpoint" : & schema . Schema {
2015-06-03 15:46:03 +02:00
Type : schema . TypeString ,
Required : true ,
2015-05-15 01:17:18 +02:00
} ,
2016-01-17 21:24:32 +01:00
"endpoint_auto_confirms" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
Default : false ,
} ,
2016-02-12 21:21:52 +01:00
"confirmation_timeout_in_minutes" : & schema . Schema {
2016-01-17 21:24:32 +01:00
Type : schema . TypeInt ,
Optional : true ,
Default : 1 ,
} ,
2015-05-15 01:17:18 +02:00
"topic_arn" : & schema . Schema {
2015-06-03 15:46:03 +02:00
Type : schema . TypeString ,
Required : true ,
2015-05-15 01:17:18 +02:00
} ,
"delivery_policy" : & schema . Schema {
2015-06-03 15:46:03 +02:00
Type : schema . TypeString ,
Optional : true ,
2015-05-15 01:17:18 +02:00
} ,
"raw_message_delivery" : & schema . Schema {
2015-06-03 15:46:03 +02:00
Type : schema . TypeBool ,
Optional : true ,
Default : false ,
2015-05-15 01:17:18 +02:00
} ,
2015-05-23 06:12:25 +02:00
"arn" : & schema . Schema {
2015-06-03 15:46:03 +02:00
Type : schema . TypeString ,
Computed : true ,
2015-05-23 06:12:25 +02:00
} ,
2015-05-15 01:17:18 +02:00
} ,
}
}
func resourceAwsSnsTopicSubscriptionCreate ( d * schema . ResourceData , meta interface { } ) error {
snsconn := meta . ( * AWSClient ) . snsconn
output , err := subscribeToSNSTopic ( d , snsconn )
if err != nil {
return err
}
2016-01-19 00:38:34 +01:00
if subscriptionHasPendingConfirmation ( output . SubscriptionArn ) {
2016-01-11 22:54:57 +01:00
log . Printf ( "[WARN] Invalid SNS Subscription, received a \"%s\" ARN" , awsSNSPendingConfirmationMessage )
return nil
}
2015-08-17 20:27:16 +02:00
log . Printf ( "New subscription ARN: %s" , * output . SubscriptionArn )
d . SetId ( * output . SubscriptionArn )
2015-05-15 01:17:18 +02:00
2015-05-23 06:12:25 +02:00
// Write the ARN to the 'arn' field for export
2015-08-17 20:27:16 +02:00
d . Set ( "arn" , * output . SubscriptionArn )
2015-05-23 06:12:25 +02:00
2015-05-15 01:17:18 +02:00
return resourceAwsSnsTopicSubscriptionUpdate ( d , meta )
}
func resourceAwsSnsTopicSubscriptionUpdate ( d * schema . ResourceData , meta interface { } ) error {
snsconn := meta . ( * AWSClient ) . snsconn
// If any changes happened, un-subscribe and re-subscribe
if d . HasChange ( "protocol" ) || d . HasChange ( "endpoint" ) || d . HasChange ( "topic_arn" ) {
log . Printf ( "[DEBUG] Updating subscription %s" , d . Id ( ) )
// Unsubscribe
_ , err := snsconn . Unsubscribe ( & sns . UnsubscribeInput {
2015-08-17 20:27:16 +02:00
SubscriptionArn : aws . String ( d . Id ( ) ) ,
2015-05-15 01:17:18 +02:00
} )
if err != nil {
return fmt . Errorf ( "Error unsubscribing from SNS topic: %s" , err )
}
// Re-subscribe and set id
output , err := subscribeToSNSTopic ( d , snsconn )
2015-08-17 20:27:16 +02:00
d . SetId ( * output . SubscriptionArn )
2016-01-11 22:54:57 +01:00
d . Set ( "arn" , * output . SubscriptionArn )
2015-05-15 01:17:18 +02:00
}
if d . HasChange ( "raw_message_delivery" ) {
_ , n := d . GetChange ( "raw_message_delivery" )
attrValue := "false"
if n . ( bool ) {
attrValue = "true"
}
req := & sns . SetSubscriptionAttributesInput {
2015-08-17 20:27:16 +02:00
SubscriptionArn : aws . String ( d . Id ( ) ) ,
2015-06-03 15:46:03 +02:00
AttributeName : aws . String ( "RawMessageDelivery" ) ,
AttributeValue : aws . String ( attrValue ) ,
2015-05-15 01:17:18 +02:00
}
_ , err := snsconn . SetSubscriptionAttributes ( req )
if err != nil {
return fmt . Errorf ( "Unable to set raw message delivery attribute on subscription" )
}
}
return resourceAwsSnsTopicSubscriptionRead ( d , meta )
}
func resourceAwsSnsTopicSubscriptionRead ( d * schema . ResourceData , meta interface { } ) error {
snsconn := meta . ( * AWSClient ) . snsconn
log . Printf ( "[DEBUG] Loading subscription %s" , d . Id ( ) )
attributeOutput , err := snsconn . GetSubscriptionAttributes ( & sns . GetSubscriptionAttributesInput {
2015-08-17 20:27:16 +02:00
SubscriptionArn : aws . String ( d . Id ( ) ) ,
2015-05-15 01:17:18 +02:00
} )
if err != nil {
2016-05-12 14:46:22 +02:00
if awsErr , ok := err . ( awserr . Error ) ; ok && awsErr . Code ( ) == "NotFound" {
log . Printf ( "[WARN] SNS Topic Subscription (%s) not found, error code (404)" , d . Id ( ) )
d . SetId ( "" )
return nil
}
2015-05-15 01:17:18 +02:00
return err
}
2015-06-03 15:46:03 +02:00
if attributeOutput . Attributes != nil && len ( attributeOutput . Attributes ) > 0 {
attrHash := attributeOutput . Attributes
2015-05-15 01:17:18 +02:00
log . Printf ( "[DEBUG] raw message delivery: %s" , * attrHash [ "RawMessageDelivery" ] )
if * attrHash [ "RawMessageDelivery" ] == "true" {
d . Set ( "raw_message_delivery" , true )
} else {
d . Set ( "raw_message_delivery" , false )
}
}
return nil
}
func resourceAwsSnsTopicSubscriptionDelete ( d * schema . ResourceData , meta interface { } ) error {
snsconn := meta . ( * AWSClient ) . snsconn
log . Printf ( "[DEBUG] SNS delete topic subscription: %s" , d . Id ( ) )
_ , err := snsconn . Unsubscribe ( & sns . UnsubscribeInput {
2015-08-17 20:27:16 +02:00
SubscriptionArn : aws . String ( d . Id ( ) ) ,
2015-05-15 01:17:18 +02:00
} )
if err != nil {
return err
}
return nil
}
2015-06-03 15:46:03 +02:00
func subscribeToSNSTopic ( d * schema . ResourceData , snsconn * sns . SNS ) ( output * sns . SubscribeOutput , err error ) {
2015-05-15 01:17:18 +02:00
protocol := d . Get ( "protocol" ) . ( string )
endpoint := d . Get ( "endpoint" ) . ( string )
topic_arn := d . Get ( "topic_arn" ) . ( string )
2016-01-17 21:24:32 +01:00
endpoint_auto_confirms := d . Get ( "endpoint_auto_confirms" ) . ( bool )
2016-02-13 21:15:29 +01:00
confirmation_timeout_in_minutes := d . Get ( "confirmation_timeout_in_minutes" ) . ( int )
2016-01-17 21:24:32 +01:00
if strings . Contains ( protocol , "http" ) && ! endpoint_auto_confirms {
return nil , fmt . Errorf ( "Protocol http/https is only supported for endpoints which auto confirms!" )
}
2015-05-15 01:17:18 +02:00
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 ) ,
2015-08-17 20:27:16 +02:00
TopicArn : aws . String ( topic_arn ) ,
2015-05-15 01:17:18 +02:00
}
output , err = snsconn . Subscribe ( req )
if err != nil {
return nil , fmt . Errorf ( "Error creating SNS topic: %s" , err )
}
2016-01-19 00:38:34 +01:00
log . Printf ( "[DEBUG] Finished subscribing to topic %s with subscription arn %s" , topic_arn , * output . SubscriptionArn )
if strings . Contains ( protocol , "http" ) && subscriptionHasPendingConfirmation ( output . SubscriptionArn ) {
2016-01-17 21:24:32 +01:00
2016-02-12 21:21:52 +01:00
log . Printf ( "[DEBUG] SNS create topic subscription is pending so fetching the subscription list for topic : %s (%s) @ '%s'" , endpoint , protocol , topic_arn )
2016-01-17 21:24:32 +01:00
2016-03-09 23:53:32 +01:00
err = resource . Retry ( time . Duration ( confirmation_timeout_in_minutes ) * time . Minute , func ( ) * resource . RetryError {
2016-01-17 21:24:32 +01:00
subscription , err := findSubscriptionByNonID ( d , snsconn )
if subscription != nil {
output . SubscriptionArn = subscription . SubscriptionArn
2016-02-12 21:21:52 +01:00
return nil
2016-01-17 21:24:32 +01:00
}
2016-02-12 21:21:52 +01:00
if err != nil {
2016-03-09 23:53:32 +01:00
return resource . RetryableError (
fmt . Errorf ( "Error fetching subscriptions for SNS topic %s: %s" , topic_arn , err ) )
2016-02-12 21:21:52 +01:00
}
2016-03-09 23:53:32 +01:00
return resource . RetryableError (
fmt . Errorf ( "Endpoint (%s) did not autoconfirm the subscription for topic %s" , endpoint , topic_arn ) )
2016-02-12 21:21:52 +01:00
} )
2016-01-17 21:24:32 +01:00
2016-02-12 21:21:52 +01:00
if err != nil {
return nil , err
2016-01-17 21:24:32 +01:00
}
}
2016-01-19 00:38:34 +01:00
log . Printf ( "[DEBUG] Created new subscription! %s" , * output . SubscriptionArn )
2015-05-15 01:17:18 +02:00
return output , nil
2015-06-03 15:46:03 +02:00
}
2016-01-17 21:24:32 +01:00
// 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 {
2016-01-19 00:38:34 +01:00
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 ) {
2016-01-17 21:24:32 +01:00
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
}
}
}
2016-01-19 00:38:34 +01:00
// returns true if arn is nil or has both pending and confirmation words in the arn
func subscriptionHasPendingConfirmation ( arn * string ) bool {
2016-02-12 21:21:52 +01:00
if arn != nil && ! strings . Contains ( strings . Replace ( strings . ToLower ( * arn ) , " " , "" , - 1 ) , awsSNSPendingConfirmationMessageWithoutSpaces ) {
2016-01-19 00:38:34 +01:00
return false
}
return true
}