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  }