Skip to content

Commit

Permalink
Merge pull request #8654 from TimeIncOSS/f-aws-sns-policy
Browse files Browse the repository at this point in the history
provider/aws: Add aws_sns_topic_policy
  • Loading branch information
stack72 authored Sep 4, 2016
2 parents c36b05c + c117896 commit 04c16b8
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ func Provider() terraform.ResourceProvider {
"aws_spot_fleet_request": resourceAwsSpotFleetRequest(),
"aws_sqs_queue": resourceAwsSqsQueue(),
"aws_sns_topic": resourceAwsSnsTopic(),
"aws_sns_topic_policy": resourceAwsSnsTopicPolicy(),
"aws_sns_topic_subscription": resourceAwsSnsTopicSubscription(),
"aws_subnet": resourceAwsSubnet(),
"aws_volume_attachment": resourceAwsVolumeAttachment(),
Expand Down
177 changes: 177 additions & 0 deletions builtin/providers/aws/resource_aws_sns_topic_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package aws

import (
"fmt"
"log"
"regexp"
"time"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/sns"
)

func resourceAwsSnsTopicPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSnsTopicPolicyUpsert,
Read: resourceAwsSnsTopicPolicyRead,
Update: resourceAwsSnsTopicPolicyUpsert,
Delete: resourceAwsSnsTopicPolicyDelete,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"policy": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
},
},
}
}

func resourceAwsSnsTopicPolicyUpsert(d *schema.ResourceData, meta interface{}) error {
arn := d.Get("arn").(string)
req := sns.SetTopicAttributesInput{
TopicArn: aws.String(arn),
AttributeName: aws.String("Policy"),
AttributeValue: aws.String(d.Get("policy").(string)),
}

d.SetId(arn)

// Retry the update in the event of an eventually consistent style of
// error, where say an IAM resource is successfully created but not
// actually available. See https://github.com/hashicorp/terraform/issues/3660
log.Printf("[DEBUG] Updating SNS Topic Policy: %s", req)
stateConf := &resource.StateChangeConf{
Pending: []string{"retrying"},
Target: []string{"success"},
Refresh: resourceAwsSNSUpdateRefreshFunc(meta, req),
Timeout: 3 * time.Minute,
MinTimeout: 3 * time.Second,
}
_, err := stateConf.WaitForState()
if err != nil {
return err
}

return resourceAwsSnsTopicPolicyRead(d, meta)
}

func resourceAwsSnsTopicPolicyRead(d *schema.ResourceData, meta interface{}) error {
snsconn := meta.(*AWSClient).snsconn

attributeOutput, err := snsconn.GetTopicAttributes(&sns.GetTopicAttributesInput{
TopicArn: aws.String(d.Id()),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFound" {
log.Printf("[WARN] SNS Topic (%s) not found, error code (404)", d.Id())
d.SetId("")
return nil
}

return err
}

if attributeOutput.Attributes == nil {
log.Printf("[WARN] SNS Topic (%q) attributes not found (nil)", d.Id())
d.SetId("")
return nil
}
attrmap := attributeOutput.Attributes

policy, ok := attrmap["Policy"]
if !ok {
log.Printf("[WARN] SNS Topic (%q) policy not found in attributes", d.Id())
d.SetId("")
return nil
}

d.Set("policy", policy)

return nil
}

func resourceAwsSnsTopicPolicyDelete(d *schema.ResourceData, meta interface{}) error {
accountId, err := getAccountIdFromSnsTopicArn(d.Id())
if err != nil {
return err
}

req := sns.SetTopicAttributesInput{
TopicArn: aws.String(d.Id()),
AttributeName: aws.String("Policy"),
// It is impossible to delete a policy or set to empty
// (confirmed by AWS Support representative)
// so we instead set it back to the default one
AttributeValue: aws.String(buildDefaultSnsTopicPolicy(d.Id(), accountId)),
}

// Retry the update in the event of an eventually consistent style of
// error, where say an IAM resource is successfully created but not
// actually available. See https://github.com/hashicorp/terraform/issues/3660
log.Printf("[DEBUG] Resetting SNS Topic Policy to default: %s", req)
stateConf := &resource.StateChangeConf{
Pending: []string{"retrying"},
Target: []string{"success"},
Refresh: resourceAwsSNSUpdateRefreshFunc(meta, req),
Timeout: 3 * time.Minute,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return err
}
return nil
}

func getAccountIdFromSnsTopicArn(arn string) (string, error) {
// arn:aws:sns:us-west-2:123456789012:test-new
re := regexp.MustCompile("^arn:aws:sns:[^:]+:([0-9]{12}):.+")
matches := re.FindStringSubmatch(arn)
if len(matches) != 2 {
return "", fmt.Errorf("Unable to get account ID from ARN (%q)", arn)
}
return matches[1], nil
}

func buildDefaultSnsTopicPolicy(topicArn, accountId string) string {
return fmt.Sprintf(`{
"Version": "2008-10-17",
"Id": "__default_policy_ID",
"Statement": [
{
"Sid": "__default_statement_ID",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic",
"SNS:Subscribe",
"SNS:ListSubscriptionsByTopic",
"SNS:Publish",
"SNS:Receive"
],
"Resource": "%s",
"Condition": {
"StringEquals": {
"AWS:SourceOwner": "%s"
}
}
}
]
}`, topicArn, accountId)
}
49 changes: 49 additions & 0 deletions builtin/providers/aws/resource_aws_sns_topic_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package aws

import (
"regexp"
"testing"

"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSSNSTopicPolicy_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSNSTopicDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSNSTopicConfig_withPolicy,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSNSTopicExists("aws_sns_topic.test"),
resource.TestMatchResourceAttr("aws_sns_topic_policy.custom", "policy",
regexp.MustCompile("^{\"Version\":\"2012-10-17\".+")),
),
},
},
})
}

const testAccAWSSNSTopicConfig_withPolicy = `
resource "aws_sns_topic" "test" {
name = "tf-acc-test-topic-with-policy"
}
resource "aws_sns_topic_policy" "custom" {
arn = "${aws_sns_topic.test.arn}"
policy = <<POLICY
{
"Version":"2012-10-17",
"Id": "default",
"Statement":[{
"Sid":"default",
"Effect":"Allow",
"Principal":{"AWS":"*"},
"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic"],
"Resource":"${aws_sns_topic.test.arn}"
}]
}
POLICY
}
`
49 changes: 49 additions & 0 deletions website/source/docs/providers/aws/r/sns_topic_policy.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
layout: "aws"
page_title: "AWS: sns_topic_policy"
sidebar_current: "docs-aws-resource-sns-topic-policy"
description: |-
Provides an SNS topic policy resource.
---

# aws\_sns\_topic\_policy

Provides an SNS topic policy resource

## Example Usage

```
resource "aws_sns_topic" "test" {
name = "my-topic-with-policy"
}
resource "aws_sns_topic_policy" "custom" {
arn = "${aws_sns_topic.test.arn}"
policy = <<POLICY
{
"Version": "2012-10-17",
"Id": "default",
"Statement":[{
"Sid": "default",
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": [
"SNS:GetTopicAttributes",
"SNS:SetTopicAttributes",
"SNS:AddPermission",
"SNS:RemovePermission",
"SNS:DeleteTopic"
],
"Resource": "${aws_sns_topic.test.arn}"
}]
}
POLICY
}
```

## Argument Reference

The following arguments are supported:

* `arn` - (Required) The ARN of the SNS topic
* `policy` - (Required) The fully-formed AWS policy as JSON
4 changes: 4 additions & 0 deletions website/source/layouts/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,10 @@
<a href="/docs/providers/aws/r/sns_topic.html">aws_sns_topic</a>
</li>

<li<%= sidebar_current("docs-aws-resource-sns-topic-policy") %>>
<a href="/docs/providers/aws/r/sns_topic_policy.html">aws_sns_topic_policy</a>
</li>

<li<%= sidebar_current("docs-aws-resource-sns-topic-subscription") %>>
<a href="/docs/providers/aws/r/sns_topic_subscription.html">aws_sns_topic_subscription</a>
</li>
Expand Down

0 comments on commit 04c16b8

Please sign in to comment.