github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/aws/resource_aws_sns_topic.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/sns"
    12  	"github.com/hashicorp/errwrap"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  // Mutable attributes
    18  var SNSAttributeMap = map[string]string{
    19  	"arn":             "TopicArn",
    20  	"display_name":    "DisplayName",
    21  	"policy":          "Policy",
    22  	"delivery_policy": "DeliveryPolicy",
    23  }
    24  
    25  func resourceAwsSnsTopic() *schema.Resource {
    26  	return &schema.Resource{
    27  		Create: resourceAwsSnsTopicCreate,
    28  		Read:   resourceAwsSnsTopicRead,
    29  		Update: resourceAwsSnsTopicUpdate,
    30  		Delete: resourceAwsSnsTopicDelete,
    31  		Importer: &schema.ResourceImporter{
    32  			State: schema.ImportStatePassthrough,
    33  		},
    34  
    35  		Schema: map[string]*schema.Schema{
    36  			"name": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Required: true,
    39  				ForceNew: true,
    40  			},
    41  			"display_name": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  				ForceNew: false,
    45  			},
    46  			"policy": &schema.Schema{
    47  				Type:             schema.TypeString,
    48  				Optional:         true,
    49  				Computed:         true,
    50  				ValidateFunc:     validateJsonString,
    51  				DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
    52  				StateFunc: func(v interface{}) string {
    53  					json, _ := normalizeJsonString(v)
    54  					return json
    55  				},
    56  			},
    57  			"delivery_policy": &schema.Schema{
    58  				Type:             schema.TypeString,
    59  				Optional:         true,
    60  				ForceNew:         false,
    61  				ValidateFunc:     validateJsonString,
    62  				DiffSuppressFunc: suppressEquivalentJsonDiffs,
    63  				StateFunc: func(v interface{}) string {
    64  					json, _ := normalizeJsonString(v)
    65  					return json
    66  				},
    67  			},
    68  			"arn": &schema.Schema{
    69  				Type:     schema.TypeString,
    70  				Computed: true,
    71  			},
    72  		},
    73  	}
    74  }
    75  
    76  func resourceAwsSnsTopicCreate(d *schema.ResourceData, meta interface{}) error {
    77  	snsconn := meta.(*AWSClient).snsconn
    78  
    79  	name := d.Get("name").(string)
    80  
    81  	log.Printf("[DEBUG] SNS create topic: %s", name)
    82  
    83  	req := &sns.CreateTopicInput{
    84  		Name: aws.String(name),
    85  	}
    86  
    87  	output, err := snsconn.CreateTopic(req)
    88  	if err != nil {
    89  		return fmt.Errorf("Error creating SNS topic: %s", err)
    90  	}
    91  
    92  	d.SetId(*output.TopicArn)
    93  
    94  	// Write the ARN to the 'arn' field for export
    95  	d.Set("arn", *output.TopicArn)
    96  
    97  	return resourceAwsSnsTopicUpdate(d, meta)
    98  }
    99  
   100  func resourceAwsSnsTopicUpdate(d *schema.ResourceData, meta interface{}) error {
   101  	r := *resourceAwsSnsTopic()
   102  
   103  	for k, _ := range r.Schema {
   104  		if attrKey, ok := SNSAttributeMap[k]; ok {
   105  			if d.HasChange(k) {
   106  				log.Printf("[DEBUG] Updating %s", attrKey)
   107  				_, n := d.GetChange(k)
   108  				// Ignore an empty policy
   109  				if !(k == "policy" && n == "") {
   110  					// Make API call to update attributes
   111  					req := sns.SetTopicAttributesInput{
   112  						TopicArn:       aws.String(d.Id()),
   113  						AttributeName:  aws.String(attrKey),
   114  						AttributeValue: aws.String(n.(string)),
   115  					}
   116  
   117  					// Retry the update in the event of an eventually consistent style of
   118  					// error, where say an IAM resource is successfully created but not
   119  					// actually available. See https://github.com/hashicorp/terraform/issues/3660
   120  					log.Printf("[DEBUG] Updating SNS Topic (%s) attributes request: %s", d.Id(), req)
   121  					stateConf := &resource.StateChangeConf{
   122  						Pending:    []string{"retrying"},
   123  						Target:     []string{"success"},
   124  						Refresh:    resourceAwsSNSUpdateRefreshFunc(meta, req),
   125  						Timeout:    1 * time.Minute,
   126  						MinTimeout: 3 * time.Second,
   127  					}
   128  					_, err := stateConf.WaitForState()
   129  					if err != nil {
   130  						return err
   131  					}
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	return resourceAwsSnsTopicRead(d, meta)
   138  }
   139  
   140  func resourceAwsSNSUpdateRefreshFunc(
   141  	meta interface{}, params sns.SetTopicAttributesInput) resource.StateRefreshFunc {
   142  	return func() (interface{}, string, error) {
   143  		snsconn := meta.(*AWSClient).snsconn
   144  		if _, err := snsconn.SetTopicAttributes(&params); err != nil {
   145  			log.Printf("[WARN] Erroring updating topic attributes: %s", err)
   146  			if awsErr, ok := err.(awserr.Error); ok {
   147  				// if the error contains the PrincipalNotFound message, we can retry
   148  				if strings.Contains(awsErr.Message(), "PrincipalNotFound") {
   149  					log.Printf("[DEBUG] Retrying AWS SNS Topic Update: %s", params)
   150  					return nil, "retrying", nil
   151  				}
   152  			}
   153  			return nil, "failed", err
   154  		}
   155  		return 42, "success", nil
   156  	}
   157  }
   158  
   159  func resourceAwsSnsTopicRead(d *schema.ResourceData, meta interface{}) error {
   160  	snsconn := meta.(*AWSClient).snsconn
   161  
   162  	attributeOutput, err := snsconn.GetTopicAttributes(&sns.GetTopicAttributesInput{
   163  		TopicArn: aws.String(d.Id()),
   164  	})
   165  	if err != nil {
   166  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFound" {
   167  			log.Printf("[WARN] SNS Topic (%s) not found, error code (404)", d.Id())
   168  			d.SetId("")
   169  			return nil
   170  		}
   171  
   172  		return err
   173  	}
   174  
   175  	if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
   176  		attrmap := attributeOutput.Attributes
   177  		resource := *resourceAwsSnsTopic()
   178  		// iKey = internal struct key, oKey = AWS Attribute Map key
   179  		for iKey, oKey := range SNSAttributeMap {
   180  			log.Printf("[DEBUG] Reading %s => %s", iKey, oKey)
   181  
   182  			if attrmap[oKey] != nil {
   183  				// Some of the fetched attributes are stateful properties such as
   184  				// the number of subscriptions, the owner, etc. skip those
   185  				if resource.Schema[iKey] != nil {
   186  					var value string
   187  					if iKey == "policy" {
   188  						value, err = normalizeJsonString(*attrmap[oKey])
   189  						if err != nil {
   190  							return errwrap.Wrapf("policy contains an invalid JSON: {{err}}", err)
   191  						}
   192  					} else {
   193  						value = *attrmap[oKey]
   194  					}
   195  					log.Printf("[DEBUG] Reading %s => %s -> %s", iKey, oKey, value)
   196  					d.Set(iKey, value)
   197  				}
   198  			}
   199  		}
   200  	}
   201  
   202  	// If we have no name set (import) then determine it from the ARN.
   203  	// This is a bit of a heuristic for now since AWS provides no other
   204  	// way to get it.
   205  	if _, ok := d.GetOk("name"); !ok {
   206  		arn := d.Get("arn").(string)
   207  		idx := strings.LastIndex(arn, ":")
   208  		if idx > -1 {
   209  			d.Set("name", arn[idx+1:])
   210  		}
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  func resourceAwsSnsTopicDelete(d *schema.ResourceData, meta interface{}) error {
   217  	snsconn := meta.(*AWSClient).snsconn
   218  
   219  	log.Printf("[DEBUG] SNS Delete Topic: %s", d.Id())
   220  	_, err := snsconn.DeleteTopic(&sns.DeleteTopicInput{
   221  		TopicArn: aws.String(d.Id()),
   222  	})
   223  	if err != nil {
   224  		return err
   225  	}
   226  	return nil
   227  }