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