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