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  }