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