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