github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/aws/resource_aws_sns_topic.go (about)

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