Skip to content

Commit ae1d4ce

Browse files
authored
Merge pull request hashicorp#3283 from terraform-providers/f-aws_sns_application
New Resource: aws_sns_platform_application
2 parents 8c8e550 + ac62d09 commit ae1d4ce

File tree

5 files changed

+862
-0
lines changed

5 files changed

+862
-0
lines changed

aws/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ func Provider() terraform.ResourceProvider {
505505
"aws_sqs_queue": resourceAwsSqsQueue(),
506506
"aws_sqs_queue_policy": resourceAwsSqsQueuePolicy(),
507507
"aws_snapshot_create_volume_permission": resourceAwsSnapshotCreateVolumePermission(),
508+
"aws_sns_platform_application": resourceAwsSnsPlatformApplication(),
508509
"aws_sns_topic": resourceAwsSnsTopic(),
509510
"aws_sns_topic_policy": resourceAwsSnsTopicPolicy(),
510511
"aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(),
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
package aws
2+
3+
import (
4+
"crypto/sha256"
5+
"fmt"
6+
"log"
7+
"strings"
8+
"time"
9+
10+
"github.com/aws/aws-sdk-go/aws"
11+
"github.com/aws/aws-sdk-go/aws/arn"
12+
"github.com/aws/aws-sdk-go/service/sns"
13+
"github.com/hashicorp/terraform/helper/resource"
14+
"github.com/hashicorp/terraform/helper/schema"
15+
)
16+
17+
var snsPlatformRequiresPlatformPrincipal = map[string]bool{
18+
"APNS": true,
19+
"APNS_SANDBOX": true,
20+
}
21+
22+
// Mutable attributes
23+
// http://docs.aws.amazon.com/sns/latest/api/API_SetPlatformApplicationAttributes.html
24+
var snsPlatformApplicationAttributeMap = map[string]string{
25+
"event_delivery_failure_topic_arn": "EventDeliveryFailure",
26+
"event_endpoint_created_topic_arn": "EventEndpointCreated",
27+
"event_endpoint_deleted_topic_arn": "EventEndpointDeleted",
28+
"event_endpoint_updated_topic_arn": "EventEndpointUpdated",
29+
"failure_feedback_role_arn": "FailureFeedbackRoleArn",
30+
"platform_principal": "PlatformPrincipal",
31+
"success_feedback_role_arn": "SuccessFeedbackRoleArn",
32+
"success_feedback_sample_rate": "SuccessFeedbackSampleRate",
33+
}
34+
35+
func resourceAwsSnsPlatformApplication() *schema.Resource {
36+
return &schema.Resource{
37+
Create: resourceAwsSnsPlatformApplicationCreate,
38+
Read: resourceAwsSnsPlatformApplicationRead,
39+
Update: resourceAwsSnsPlatformApplicationUpdate,
40+
Delete: resourceAwsSnsPlatformApplicationDelete,
41+
Importer: &schema.ResourceImporter{
42+
State: schema.ImportStatePassthrough,
43+
},
44+
45+
CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error {
46+
return validateAwsSnsPlatformApplication(diff)
47+
},
48+
49+
Schema: map[string]*schema.Schema{
50+
"name": {
51+
Type: schema.TypeString,
52+
Required: true,
53+
ForceNew: true,
54+
},
55+
"platform": {
56+
Type: schema.TypeString,
57+
Required: true,
58+
ForceNew: true,
59+
},
60+
"platform_credential": {
61+
Type: schema.TypeString,
62+
Required: true,
63+
StateFunc: hashSum,
64+
},
65+
"arn": {
66+
Type: schema.TypeString,
67+
Computed: true,
68+
},
69+
"event_delivery_failure_topic_arn": {
70+
Type: schema.TypeString,
71+
Optional: true,
72+
},
73+
"event_endpoint_created_topic_arn": {
74+
Type: schema.TypeString,
75+
Optional: true,
76+
},
77+
"event_endpoint_deleted_topic_arn": {
78+
Type: schema.TypeString,
79+
Optional: true,
80+
},
81+
"event_endpoint_updated_topic_arn": {
82+
Type: schema.TypeString,
83+
Optional: true,
84+
},
85+
"failure_feedback_role_arn": {
86+
Type: schema.TypeString,
87+
Optional: true,
88+
},
89+
"platform_principal": {
90+
Type: schema.TypeString,
91+
Optional: true,
92+
StateFunc: hashSum,
93+
},
94+
"success_feedback_role_arn": {
95+
Type: schema.TypeString,
96+
Optional: true,
97+
},
98+
"success_feedback_sample_rate": {
99+
Type: schema.TypeString,
100+
Optional: true,
101+
},
102+
},
103+
}
104+
}
105+
106+
func resourceAwsSnsPlatformApplicationCreate(d *schema.ResourceData, meta interface{}) error {
107+
snsconn := meta.(*AWSClient).snsconn
108+
109+
attributes := make(map[string]*string)
110+
name := d.Get("name").(string)
111+
platform := d.Get("platform").(string)
112+
113+
attributes["PlatformCredential"] = aws.String(d.Get("platform_credential").(string))
114+
if v, ok := d.GetOk("platform_principal"); ok {
115+
attributes["PlatformPrincipal"] = aws.String(v.(string))
116+
}
117+
118+
req := &sns.CreatePlatformApplicationInput{
119+
Name: aws.String(name),
120+
Platform: aws.String(platform),
121+
Attributes: attributes,
122+
}
123+
124+
log.Printf("[DEBUG] SNS create application: %s", req)
125+
126+
output, err := snsconn.CreatePlatformApplication(req)
127+
if err != nil {
128+
return fmt.Errorf("Error creating SNS platform application: %s", err)
129+
}
130+
131+
d.SetId(*output.PlatformApplicationArn)
132+
133+
return resourceAwsSnsPlatformApplicationUpdate(d, meta)
134+
}
135+
136+
func resourceAwsSnsPlatformApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
137+
snsconn := meta.(*AWSClient).snsconn
138+
139+
attributes := make(map[string]*string)
140+
141+
for k, _ := range resourceAwsSnsPlatformApplication().Schema {
142+
if attrKey, ok := snsPlatformApplicationAttributeMap[k]; ok {
143+
if d.HasChange(k) {
144+
log.Printf("[DEBUG] Updating %s", attrKey)
145+
_, n := d.GetChange(k)
146+
attributes[attrKey] = aws.String(n.(string))
147+
}
148+
}
149+
}
150+
151+
if d.HasChange("platform_credential") {
152+
attributes["PlatformCredential"] = aws.String(d.Get("platform_credential").(string))
153+
// If the platform requires a principal it must also be specified, even if it didn't change
154+
// since credential is stored as a hash, the only way to update principal is to update both
155+
// as they must be specified together in the request.
156+
if v, ok := d.GetOk("platform_principal"); ok {
157+
attributes["PlatformPrincipal"] = aws.String(v.(string))
158+
}
159+
}
160+
161+
// Make API call to update attributes
162+
req := &sns.SetPlatformApplicationAttributesInput{
163+
PlatformApplicationArn: aws.String(d.Id()),
164+
Attributes: attributes,
165+
}
166+
167+
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
168+
_, err := snsconn.SetPlatformApplicationAttributes(req)
169+
if err != nil {
170+
if isAWSErr(err, sns.ErrCodeInvalidParameterException, "is not a valid role to allow SNS to write to Cloudwatch Logs") {
171+
return resource.RetryableError(err)
172+
}
173+
return resource.NonRetryableError(err)
174+
}
175+
return nil
176+
})
177+
178+
if err != nil {
179+
return fmt.Errorf("Error updating SNS platform application: %s", err)
180+
}
181+
182+
return resourceAwsSnsPlatformApplicationRead(d, meta)
183+
}
184+
185+
func resourceAwsSnsPlatformApplicationRead(d *schema.ResourceData, meta interface{}) error {
186+
snsconn := meta.(*AWSClient).snsconn
187+
188+
// There is no SNS Describe/GetPlatformApplication to fetch attributes like name and platform
189+
// We will use the ID, which should be a platform application ARN, to:
190+
// * Validate its an appropriate ARN on import
191+
// * Parse out the name and platform
192+
arn, name, platform, err := decodeResourceAwsSnsPlatformApplicationID(d.Id())
193+
if err != nil {
194+
return err
195+
}
196+
197+
d.Set("arn", arn)
198+
d.Set("name", name)
199+
d.Set("platform", platform)
200+
201+
attributeOutput, err := snsconn.GetPlatformApplicationAttributes(&sns.GetPlatformApplicationAttributesInput{
202+
PlatformApplicationArn: aws.String(arn),
203+
})
204+
205+
if err != nil {
206+
return err
207+
}
208+
209+
if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
210+
attrmap := attributeOutput.Attributes
211+
resource := *resourceAwsSnsPlatformApplication()
212+
// iKey = internal struct key, oKey = AWS Attribute Map key
213+
for iKey, oKey := range snsPlatformApplicationAttributeMap {
214+
log.Printf("[DEBUG] Updating %s => %s", iKey, oKey)
215+
216+
if attrmap[oKey] != nil {
217+
// Some of the fetched attributes are stateful properties such as
218+
// the number of subscriptions, the owner, etc. skip those
219+
if resource.Schema[iKey] != nil {
220+
value := *attrmap[oKey]
221+
log.Printf("[DEBUG] Updating %s => %s -> %s", iKey, oKey, value)
222+
d.Set(iKey, *attrmap[oKey])
223+
}
224+
}
225+
}
226+
}
227+
228+
return nil
229+
}
230+
231+
func resourceAwsSnsPlatformApplicationDelete(d *schema.ResourceData, meta interface{}) error {
232+
snsconn := meta.(*AWSClient).snsconn
233+
234+
log.Printf("[DEBUG] SNS Delete Application: %s", d.Id())
235+
_, err := snsconn.DeletePlatformApplication(&sns.DeletePlatformApplicationInput{
236+
PlatformApplicationArn: aws.String(d.Id()),
237+
})
238+
if err != nil {
239+
return err
240+
}
241+
return nil
242+
}
243+
244+
func decodeResourceAwsSnsPlatformApplicationID(input string) (arnS, name, platform string, err error) {
245+
platformApplicationArn, err := arn.Parse(input)
246+
if err != nil {
247+
err = fmt.Errorf(
248+
"SNS Platform Application ID must be of the form "+
249+
"arn:PARTITION:sns:REGION:ACCOUNTID:app/PLATFORM/NAME, "+
250+
"was provided %q and received error: %s", input, err)
251+
return
252+
}
253+
254+
platformApplicationArnResourceParts := strings.Split(platformApplicationArn.Resource, "/")
255+
if len(platformApplicationArnResourceParts) != 3 || platformApplicationArnResourceParts[0] != "app" {
256+
err = fmt.Errorf(
257+
"SNS Platform Application ID must be of the form "+
258+
"arn:PARTITION:sns:REGION:ACCOUNTID:app/PLATFORM/NAME, "+
259+
"was provided: %s", input)
260+
return
261+
}
262+
263+
arnS = platformApplicationArn.String()
264+
name = platformApplicationArnResourceParts[2]
265+
platform = platformApplicationArnResourceParts[1]
266+
return
267+
}
268+
269+
func hashSum(contents interface{}) string {
270+
return fmt.Sprintf("%x", sha256.Sum256([]byte(contents.(string))))
271+
}
272+
273+
func validateAwsSnsPlatformApplication(d *schema.ResourceDiff) error {
274+
platform := d.Get("platform").(string)
275+
if snsPlatformRequiresPlatformPrincipal[platform] {
276+
if v, ok := d.GetOk("platform_principal"); ok {
277+
value := v.(string)
278+
if len(value) == 0 {
279+
return fmt.Errorf("platform_principal must be non-empty when platform = %s", platform)
280+
}
281+
return nil
282+
}
283+
return fmt.Errorf("platform_principal is required when platform = %s", platform)
284+
}
285+
return nil
286+
}

0 commit comments

Comments
 (0)