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