github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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 "display_name": "DisplayName", 22 "policy": "Policy", 23 "delivery_policy": "DeliveryPolicy", 24 } 25 26 func resourceAwsSnsTopic() *schema.Resource { 27 return &schema.Resource{ 28 Create: resourceAwsSnsTopicCreate, 29 Read: resourceAwsSnsTopicRead, 30 Update: resourceAwsSnsTopicUpdate, 31 Delete: resourceAwsSnsTopicDelete, 32 33 Schema: map[string]*schema.Schema{ 34 "name": &schema.Schema{ 35 Type: schema.TypeString, 36 Required: true, 37 ForceNew: true, 38 }, 39 "display_name": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 ForceNew: false, 43 }, 44 "policy": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 Computed: true, 48 StateFunc: func(v interface{}) string { 49 s, ok := v.(string) 50 if !ok || s == "" { 51 return "" 52 } 53 jsonb := []byte(s) 54 buffer := new(bytes.Buffer) 55 if err := json.Compact(buffer, jsonb); err != nil { 56 log.Printf("[WARN] Error compacting JSON for Policy in SNS Topic") 57 return "" 58 } 59 value := normalizeJson(buffer.String()) 60 log.Printf("[DEBUG] topic policy before save: %s", value) 61 return value 62 }, 63 }, 64 "delivery_policy": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 ForceNew: false, 68 }, 69 "arn": &schema.Schema{ 70 Type: schema.TypeString, 71 Computed: true, 72 }, 73 }, 74 } 75 } 76 77 func resourceAwsSnsTopicCreate(d *schema.ResourceData, meta interface{}) error { 78 snsconn := meta.(*AWSClient).snsconn 79 80 name := d.Get("name").(string) 81 82 log.Printf("[DEBUG] SNS create topic: %s", name) 83 84 req := &sns.CreateTopicInput{ 85 Name: aws.String(name), 86 } 87 88 output, err := snsconn.CreateTopic(req) 89 if err != nil { 90 return fmt.Errorf("Error creating SNS topic: %s", err) 91 } 92 93 d.SetId(*output.TopicArn) 94 95 // Write the ARN to the 'arn' field for export 96 d.Set("arn", *output.TopicArn) 97 98 return resourceAwsSnsTopicUpdate(d, meta) 99 } 100 101 func resourceAwsSnsTopicUpdate(d *schema.ResourceData, meta interface{}) error { 102 r := *resourceAwsSnsTopic() 103 104 for k, _ := range r.Schema { 105 if attrKey, ok := SNSAttributeMap[k]; ok { 106 if d.HasChange(k) { 107 log.Printf("[DEBUG] Updating %s", attrKey) 108 _, n := d.GetChange(k) 109 // Ignore an empty policy 110 if !(k == "policy" && n == "") { 111 // Make API call to update attributes 112 req := sns.SetTopicAttributesInput{ 113 TopicArn: aws.String(d.Id()), 114 AttributeName: aws.String(attrKey), 115 AttributeValue: aws.String(n.(string)), 116 } 117 118 // Retry the update in the event of an eventually consistent style of 119 // error, where say an IAM resource is successfully created but not 120 // actually available. See https://github.com/hashicorp/terraform/issues/3660 121 log.Printf("[DEBUG] Updating SNS Topic (%s) attributes request: %s", d.Id(), req) 122 stateConf := &resource.StateChangeConf{ 123 Pending: []string{"retrying"}, 124 Target: []string{"success"}, 125 Refresh: resourceAwsSNSUpdateRefreshFunc(meta, req), 126 Timeout: 1 * time.Minute, 127 MinTimeout: 3 * time.Second, 128 } 129 _, err := stateConf.WaitForState() 130 if err != nil { 131 return err 132 } 133 } 134 } 135 } 136 } 137 138 return resourceAwsSnsTopicRead(d, meta) 139 } 140 141 func resourceAwsSNSUpdateRefreshFunc( 142 meta interface{}, params sns.SetTopicAttributesInput) resource.StateRefreshFunc { 143 return func() (interface{}, string, error) { 144 snsconn := meta.(*AWSClient).snsconn 145 if _, err := snsconn.SetTopicAttributes(¶ms); err != nil { 146 log.Printf("[WARN] Erroring updating topic attributes: %s", err) 147 if awsErr, ok := err.(awserr.Error); ok { 148 // if the error contains the PrincipalNotFound message, we can retry 149 if strings.Contains(awsErr.Message(), "PrincipalNotFound") { 150 log.Printf("[DEBUG] Retrying AWS SNS Topic Update: %s", params) 151 return nil, "retrying", nil 152 } 153 } 154 return nil, "failed", err 155 } 156 return 42, "success", nil 157 } 158 } 159 160 func resourceAwsSnsTopicRead(d *schema.ResourceData, meta interface{}) error { 161 snsconn := meta.(*AWSClient).snsconn 162 163 attributeOutput, err := snsconn.GetTopicAttributes(&sns.GetTopicAttributesInput{ 164 TopicArn: aws.String(d.Id()), 165 }) 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 return nil 202 } 203 204 func resourceAwsSnsTopicDelete(d *schema.ResourceData, meta interface{}) error { 205 snsconn := meta.(*AWSClient).snsconn 206 207 log.Printf("[DEBUG] SNS Delete Topic: %s", d.Id()) 208 _, err := snsconn.DeleteTopic(&sns.DeleteTopicInput{ 209 TopicArn: aws.String(d.Id()), 210 }) 211 if err != nil { 212 return err 213 } 214 return nil 215 }