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