github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 var SNSSubscriptionAttributeMap = map[string]string{ 22 "topic_arn": "TopicArn", 23 "endpoint": "Endpoint", 24 "protocol": "Protocol", 25 "raw_message_delivery": "RawMessageDelivery", 26 } 27 28 func resourceAwsSnsTopicSubscription() *schema.Resource { 29 return &schema.Resource{ 30 Create: resourceAwsSnsTopicSubscriptionCreate, 31 Read: resourceAwsSnsTopicSubscriptionRead, 32 Update: resourceAwsSnsTopicSubscriptionUpdate, 33 Delete: resourceAwsSnsTopicSubscriptionDelete, 34 Importer: &schema.ResourceImporter{ 35 State: schema.ImportStatePassthrough, 36 }, 37 38 Schema: map[string]*schema.Schema{ 39 "protocol": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: false, 43 ValidateFunc: validateSNSSubscriptionProtocol, 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 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFound" { 158 log.Printf("[WARN] SNS Topic Subscription (%s) not found, error code (404)", d.Id()) 159 d.SetId("") 160 return nil 161 } 162 163 return err 164 } 165 166 if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 { 167 attrHash := attributeOutput.Attributes 168 resource := *resourceAwsSnsTopicSubscription() 169 170 for iKey, oKey := range SNSSubscriptionAttributeMap { 171 log.Printf("[DEBUG] Reading %s => %s", iKey, oKey) 172 173 if attrHash[oKey] != nil { 174 if resource.Schema[iKey] != nil { 175 var value string 176 value = *attrHash[oKey] 177 log.Printf("[DEBUG] Reading %s => %s -> %s", iKey, oKey, value) 178 d.Set(iKey, value) 179 } 180 } 181 } 182 } 183 184 return nil 185 } 186 187 func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { 188 snsconn := meta.(*AWSClient).snsconn 189 190 log.Printf("[DEBUG] SNS delete topic subscription: %s", d.Id()) 191 _, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{ 192 SubscriptionArn: aws.String(d.Id()), 193 }) 194 if err != nil { 195 return err 196 } 197 return nil 198 } 199 200 func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns.SubscribeOutput, err error) { 201 protocol := d.Get("protocol").(string) 202 endpoint := d.Get("endpoint").(string) 203 topic_arn := d.Get("topic_arn").(string) 204 endpoint_auto_confirms := d.Get("endpoint_auto_confirms").(bool) 205 confirmation_timeout_in_minutes := d.Get("confirmation_timeout_in_minutes").(int) 206 207 if strings.Contains(protocol, "http") && !endpoint_auto_confirms { 208 return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!") 209 } 210 211 log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn) 212 213 req := &sns.SubscribeInput{ 214 Protocol: aws.String(protocol), 215 Endpoint: aws.String(endpoint), 216 TopicArn: aws.String(topic_arn), 217 } 218 219 output, err = snsconn.Subscribe(req) 220 if err != nil { 221 return nil, fmt.Errorf("Error creating SNS topic: %s", err) 222 } 223 224 log.Printf("[DEBUG] Finished subscribing to topic %s with subscription arn %s", topic_arn, *output.SubscriptionArn) 225 226 if strings.Contains(protocol, "http") && subscriptionHasPendingConfirmation(output.SubscriptionArn) { 227 228 log.Printf("[DEBUG] SNS create topic subscription is pending so fetching the subscription list for topic : %s (%s) @ '%s'", endpoint, protocol, topic_arn) 229 230 err = resource.Retry(time.Duration(confirmation_timeout_in_minutes)*time.Minute, func() *resource.RetryError { 231 232 subscription, err := findSubscriptionByNonID(d, snsconn) 233 234 if subscription != nil { 235 output.SubscriptionArn = subscription.SubscriptionArn 236 return nil 237 } 238 239 if err != nil { 240 return resource.RetryableError( 241 fmt.Errorf("Error fetching subscriptions for SNS topic %s: %s", topic_arn, err)) 242 } 243 244 return resource.RetryableError( 245 fmt.Errorf("Endpoint (%s) did not autoconfirm the subscription for topic %s", endpoint, topic_arn)) 246 }) 247 248 if err != nil { 249 return nil, err 250 } 251 } 252 253 log.Printf("[DEBUG] Created new subscription! %s", *output.SubscriptionArn) 254 return output, nil 255 } 256 257 // finds a subscription using protocol, endpoint and topic_arn (which is a key in sns subscription) 258 func findSubscriptionByNonID(d *schema.ResourceData, snsconn *sns.SNS) (*sns.Subscription, error) { 259 protocol := d.Get("protocol").(string) 260 endpoint := d.Get("endpoint").(string) 261 topic_arn := d.Get("topic_arn").(string) 262 263 req := &sns.ListSubscriptionsByTopicInput{ 264 TopicArn: aws.String(topic_arn), 265 } 266 267 for { 268 269 res, err := snsconn.ListSubscriptionsByTopic(req) 270 271 if err != nil { 272 return nil, fmt.Errorf("Error fetching subscripitions for topic %s : %s", topic_arn, err) 273 } 274 275 for _, subscription := range res.Subscriptions { 276 log.Printf("[DEBUG] check subscription with EndPoint %s, Protocol %s, topicARN %s and SubscriptionARN %s", *subscription.Endpoint, *subscription.Protocol, *subscription.TopicArn, *subscription.SubscriptionArn) 277 if *subscription.Endpoint == endpoint && *subscription.Protocol == protocol && *subscription.TopicArn == topic_arn && !subscriptionHasPendingConfirmation(subscription.SubscriptionArn) { 278 return subscription, nil 279 } 280 } 281 282 // if there are more than 100 subscriptions then go to the next 100 otherwise return nil 283 if res.NextToken != nil { 284 req.NextToken = res.NextToken 285 } else { 286 return nil, nil 287 } 288 } 289 } 290 291 // returns true if arn is nil or has both pending and confirmation words in the arn 292 func subscriptionHasPendingConfirmation(arn *string) bool { 293 if arn != nil && !strings.Contains(strings.Replace(strings.ToLower(*arn), " ", "", -1), awsSNSPendingConfirmationMessageWithoutSpaces) { 294 return false 295 } 296 297 return true 298 }