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(¶ms); 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 }