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