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