github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/aws/resource_aws_sns_topic_subscription.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/terraform/helper/resource" 9 "github.com/hashicorp/terraform/helper/schema" 10 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/service/sns" 16 ) 17 18 const awsSNSPendingConfirmationMessage = "pending confirmation" 19 const awsSNSPendingConfirmationMessageWithoutSpaces = "pendingconfirmation" 20 21 func resourceAwsSnsTopicSubscription() *schema.Resource { 22 return &schema.Resource{ 23 Create: resourceAwsSnsTopicSubscriptionCreate, 24 Read: resourceAwsSnsTopicSubscriptionRead, 25 Update: resourceAwsSnsTopicSubscriptionUpdate, 26 Delete: resourceAwsSnsTopicSubscriptionDelete, 27 Importer: &schema.ResourceImporter{ 28 State: schema.ImportStatePassthrough, 29 }, 30 31 Schema: map[string]*schema.Schema{ 32 "protocol": &schema.Schema{ 33 Type: schema.TypeString, 34 Required: true, 35 ForceNew: false, 36 ValidateFunc: validateSNSSubscriptionProtocol, 37 }, 38 "endpoint": &schema.Schema{ 39 Type: schema.TypeString, 40 Required: true, 41 }, 42 "endpoint_auto_confirms": &schema.Schema{ 43 Type: schema.TypeBool, 44 Optional: true, 45 Default: false, 46 }, 47 "confirmation_timeout_in_minutes": &schema.Schema{ 48 Type: schema.TypeInt, 49 Optional: true, 50 Default: 1, 51 }, 52 "topic_arn": &schema.Schema{ 53 Type: schema.TypeString, 54 Required: true, 55 }, 56 "delivery_policy": &schema.Schema{ 57 Type: schema.TypeString, 58 Optional: true, 59 }, 60 "raw_message_delivery": &schema.Schema{ 61 Type: schema.TypeBool, 62 Optional: true, 63 Default: false, 64 }, 65 "arn": &schema.Schema{ 66 Type: schema.TypeString, 67 Computed: true, 68 }, 69 }, 70 } 71 } 72 73 func resourceAwsSnsTopicSubscriptionCreate(d *schema.ResourceData, meta interface{}) error { 74 snsconn := meta.(*AWSClient).snsconn 75 76 output, err := subscribeToSNSTopic(d, snsconn) 77 78 if err != nil { 79 return err 80 } 81 82 if subscriptionHasPendingConfirmation(output.SubscriptionArn) { 83 log.Printf("[WARN] Invalid SNS Subscription, received a \"%s\" ARN", awsSNSPendingConfirmationMessage) 84 return nil 85 } 86 87 log.Printf("New subscription ARN: %s", *output.SubscriptionArn) 88 d.SetId(*output.SubscriptionArn) 89 90 // Write the ARN to the 'arn' field for export 91 d.Set("arn", *output.SubscriptionArn) 92 93 return resourceAwsSnsTopicSubscriptionUpdate(d, meta) 94 } 95 96 func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interface{}) error { 97 snsconn := meta.(*AWSClient).snsconn 98 99 // If any changes happened, un-subscribe and re-subscribe 100 if d.HasChange("protocol") || d.HasChange("endpoint") || d.HasChange("topic_arn") { 101 log.Printf("[DEBUG] Updating subscription %s", d.Id()) 102 // Unsubscribe 103 _, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{ 104 SubscriptionArn: aws.String(d.Id()), 105 }) 106 107 if err != nil { 108 return fmt.Errorf("Error unsubscribing from SNS topic: %s", err) 109 } 110 111 // Re-subscribe and set id 112 output, err := subscribeToSNSTopic(d, snsconn) 113 d.SetId(*output.SubscriptionArn) 114 d.Set("arn", *output.SubscriptionArn) 115 } 116 117 if d.HasChange("raw_message_delivery") { 118 _, n := d.GetChange("raw_message_delivery") 119 120 attrValue := "false" 121 122 if n.(bool) { 123 attrValue = "true" 124 } 125 126 req := &sns.SetSubscriptionAttributesInput{ 127 SubscriptionArn: aws.String(d.Id()), 128 AttributeName: aws.String("RawMessageDelivery"), 129 AttributeValue: aws.String(attrValue), 130 } 131 _, err := snsconn.SetSubscriptionAttributes(req) 132 133 if err != nil { 134 return fmt.Errorf("Unable to set raw message delivery attribute on subscription") 135 } 136 } 137 138 return resourceAwsSnsTopicSubscriptionRead(d, meta) 139 } 140 141 func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{}) error { 142 snsconn := meta.(*AWSClient).snsconn 143 144 log.Printf("[DEBUG] Loading subscription %s", d.Id()) 145 146 attributeOutput, err := snsconn.GetSubscriptionAttributes(&sns.GetSubscriptionAttributesInput{ 147 SubscriptionArn: aws.String(d.Id()), 148 }) 149 if err != nil { 150 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFound" { 151 log.Printf("[WARN] SNS Topic Subscription (%s) not found, error code (404)", d.Id()) 152 d.SetId("") 153 return nil 154 } 155 156 return err 157 } 158 159 if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 { 160 attrHash := attributeOutput.Attributes 161 log.Printf("[DEBUG] raw message delivery: %s", *attrHash["RawMessageDelivery"]) 162 if *attrHash["RawMessageDelivery"] == "true" { 163 d.Set("raw_message_delivery", true) 164 } else { 165 d.Set("raw_message_delivery", false) 166 } 167 } 168 169 return nil 170 } 171 172 func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { 173 snsconn := meta.(*AWSClient).snsconn 174 175 log.Printf("[DEBUG] SNS delete topic subscription: %s", d.Id()) 176 _, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{ 177 SubscriptionArn: aws.String(d.Id()), 178 }) 179 if err != nil { 180 return err 181 } 182 return nil 183 } 184 185 func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns.SubscribeOutput, err error) { 186 protocol := d.Get("protocol").(string) 187 endpoint := d.Get("endpoint").(string) 188 topic_arn := d.Get("topic_arn").(string) 189 endpoint_auto_confirms := d.Get("endpoint_auto_confirms").(bool) 190 confirmation_timeout_in_minutes := d.Get("confirmation_timeout_in_minutes").(int) 191 192 if strings.Contains(protocol, "http") && !endpoint_auto_confirms { 193 return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!") 194 } 195 196 log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn) 197 198 req := &sns.SubscribeInput{ 199 Protocol: aws.String(protocol), 200 Endpoint: aws.String(endpoint), 201 TopicArn: aws.String(topic_arn), 202 } 203 204 output, err = snsconn.Subscribe(req) 205 if err != nil { 206 return nil, fmt.Errorf("Error creating SNS topic: %s", err) 207 } 208 209 log.Printf("[DEBUG] Finished subscribing to topic %s with subscription arn %s", topic_arn, *output.SubscriptionArn) 210 211 if strings.Contains(protocol, "http") && subscriptionHasPendingConfirmation(output.SubscriptionArn) { 212 213 log.Printf("[DEBUG] SNS create topic subscription is pending so fetching the subscription list for topic : %s (%s) @ '%s'", endpoint, protocol, topic_arn) 214 215 err = resource.Retry(time.Duration(confirmation_timeout_in_minutes)*time.Minute, func() *resource.RetryError { 216 217 subscription, err := findSubscriptionByNonID(d, snsconn) 218 219 if subscription != nil { 220 output.SubscriptionArn = subscription.SubscriptionArn 221 return nil 222 } 223 224 if err != nil { 225 return resource.RetryableError( 226 fmt.Errorf("Error fetching subscriptions for SNS topic %s: %s", topic_arn, err)) 227 } 228 229 return resource.RetryableError( 230 fmt.Errorf("Endpoint (%s) did not autoconfirm the subscription for topic %s", endpoint, topic_arn)) 231 }) 232 233 if err != nil { 234 return nil, err 235 } 236 } 237 238 log.Printf("[DEBUG] Created new subscription! %s", *output.SubscriptionArn) 239 return output, nil 240 } 241 242 // finds a subscription using protocol, endpoint and topic_arn (which is a key in sns subscription) 243 func findSubscriptionByNonID(d *schema.ResourceData, snsconn *sns.SNS) (*sns.Subscription, error) { 244 protocol := d.Get("protocol").(string) 245 endpoint := d.Get("endpoint").(string) 246 topic_arn := d.Get("topic_arn").(string) 247 248 req := &sns.ListSubscriptionsByTopicInput{ 249 TopicArn: aws.String(topic_arn), 250 } 251 252 for { 253 254 res, err := snsconn.ListSubscriptionsByTopic(req) 255 256 if err != nil { 257 return nil, fmt.Errorf("Error fetching subscripitions for topic %s : %s", topic_arn, err) 258 } 259 260 for _, subscription := range res.Subscriptions { 261 log.Printf("[DEBUG] check subscription with EndPoint %s, Protocol %s, topicARN %s and SubscriptionARN %s", *subscription.Endpoint, *subscription.Protocol, *subscription.TopicArn, *subscription.SubscriptionArn) 262 if *subscription.Endpoint == endpoint && *subscription.Protocol == protocol && *subscription.TopicArn == topic_arn && !subscriptionHasPendingConfirmation(subscription.SubscriptionArn) { 263 return subscription, nil 264 } 265 } 266 267 // if there are more than 100 subscriptions then go to the next 100 otherwise return nil 268 if res.NextToken != nil { 269 req.NextToken = res.NextToken 270 } else { 271 return nil, nil 272 } 273 } 274 } 275 276 // returns true if arn is nil or has both pending and confirmation words in the arn 277 func subscriptionHasPendingConfirmation(arn *string) bool { 278 if arn != nil && !strings.Contains(strings.Replace(strings.ToLower(*arn), " ", "", -1), awsSNSPendingConfirmationMessageWithoutSpaces) { 279 return false 280 } 281 282 return true 283 }