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