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