Skip to content

Commit

Permalink
Merge pull request #4253 from terraform-providers/f-aws_organizations…
Browse files Browse the repository at this point in the history
…_policy_attachment

New Resource: aws_organizations_policy_attachment
  • Loading branch information
bflad authored Apr 23, 2018
2 parents 66bbe2b + a327b8e commit 67d7744
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 0 deletions.
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ func Provider() terraform.ResourceProvider {
"aws_organizations_organization": resourceAwsOrganizationsOrganization(),
"aws_organizations_account": resourceAwsOrganizationsAccount(),
"aws_organizations_policy": resourceAwsOrganizationsPolicy(),
"aws_organizations_policy_attachment": resourceAwsOrganizationsPolicyAttachment(),
"aws_placement_group": resourceAwsPlacementGroup(),
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_rds_cluster": resourceAwsRDSCluster(),
Expand Down
154 changes: 154 additions & 0 deletions aws/resource_aws_organizations_policy_attachment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package aws

import (
"fmt"
"log"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/organizations"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsOrganizationsPolicyAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceAwsOrganizationsPolicyAttachmentCreate,
Read: resourceAwsOrganizationsPolicyAttachmentRead,
Delete: resourceAwsOrganizationsPolicyAttachmentDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"policy_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"target_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceAwsOrganizationsPolicyAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).organizationsconn

policyID := d.Get("policy_id").(string)
targetID := d.Get("target_id").(string)

input := &organizations.AttachPolicyInput{
PolicyId: aws.String(policyID),
TargetId: aws.String(targetID),
}

log.Printf("[DEBUG] Creating Organizations Policy Attachment: %s", input)

err := resource.Retry(4*time.Minute, func() *resource.RetryError {
_, err := conn.AttachPolicy(input)

if err != nil {
if isAWSErr(err, organizations.ErrCodeFinalizingOrganizationException, "") {
log.Printf("[DEBUG] Trying to create policy attachment again: %q", err.Error())
return resource.RetryableError(err)
}

return resource.NonRetryableError(err)
}

return nil
})

if err != nil {
return fmt.Errorf("error creating Organizations Policy Attachment: %s", err)
}

d.SetId(fmt.Sprintf("%s:%s", targetID, policyID))

return resourceAwsOrganizationsPolicyAttachmentRead(d, meta)
}

func resourceAwsOrganizationsPolicyAttachmentRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).organizationsconn

targetID, policyID, err := decodeAwsOrganizationsPolicyAttachmentID(d.Id())
if err != nil {
return err
}

input := &organizations.ListPoliciesForTargetInput{
Filter: aws.String(organizations.PolicyTypeServiceControlPolicy),
TargetId: aws.String(targetID),
}

log.Printf("[DEBUG] Listing Organizations Policies for Target: %s", input)
var output *organizations.PolicySummary
err = conn.ListPoliciesForTargetPages(input, func(page *organizations.ListPoliciesForTargetOutput, lastPage bool) bool {
for _, policySummary := range page.Policies {
if aws.StringValue(policySummary.Id) == policyID {
output = policySummary
return true
}
}
return !lastPage
})

if err != nil {
if isAWSErr(err, organizations.ErrCodeTargetNotFoundException, "") {
log.Printf("[WARN] Target does not exist, removing from state: %s", d.Id())
d.SetId("")
return nil
}
return err
}

if output == nil {
log.Printf("[WARN] Attachment does not exist, removing from state: %s", d.Id())
d.SetId("")
return nil
}

d.Set("policy_id", policyID)
d.Set("target_id", targetID)
return nil
}

func resourceAwsOrganizationsPolicyAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).organizationsconn

targetID, policyID, err := decodeAwsOrganizationsPolicyAttachmentID(d.Id())
if err != nil {
return err
}

input := &organizations.DetachPolicyInput{
PolicyId: aws.String(policyID),
TargetId: aws.String(targetID),
}

log.Printf("[DEBUG] Detaching Organizations Policy %q from %q", policyID, targetID)
_, err = conn.DetachPolicy(input)
if err != nil {
if isAWSErr(err, organizations.ErrCodePolicyNotFoundException, "") {
return nil
}
if isAWSErr(err, organizations.ErrCodeTargetNotFoundException, "") {
return nil
}
return err
}
return nil
}

func decodeAwsOrganizationsPolicyAttachmentID(id string) (string, string, error) {
idParts := strings.Split(id, ":")
if len(idParts) != 2 {
return "", "", fmt.Errorf("expected ID in format of TARGETID:POLICYID, received: %s", id)
}
return idParts[0], idParts[1], nil
}
146 changes: 146 additions & 0 deletions aws/resource_aws_organizations_policy_attachment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package aws

import (
"fmt"
"log"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/organizations"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAwsOrganizationsPolicyAttachment_account(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "aws_organizations_policy_attachment.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsOrganizationsPolicyAttachmentDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsOrganizationsPolicyAttachmentConfig_Account(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsOrganizationsPolicyAttachmentExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "policy_id"),
resource.TestCheckResourceAttrSet(resourceName, "target_id"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckAwsOrganizationsPolicyAttachmentDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).organizationsconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_organizations_policy_attachment" {
continue
}

targetID, policyID, err := decodeAwsOrganizationsPolicyAttachmentID(rs.Primary.ID)
if err != nil {
return err
}

input := &organizations.ListPoliciesForTargetInput{
Filter: aws.String(organizations.PolicyTypeServiceControlPolicy),
TargetId: aws.String(targetID),
}

log.Printf("[DEBUG] Listing Organizations Policies for Target: %s", input)
var output *organizations.PolicySummary
err = conn.ListPoliciesForTargetPages(input, func(page *organizations.ListPoliciesForTargetOutput, lastPage bool) bool {
for _, policySummary := range page.Policies {
if aws.StringValue(policySummary.Id) == policyID {
output = policySummary
return true
}
}
return !lastPage
})

if err != nil {
if isAWSErr(err, organizations.ErrCodeTargetNotFoundException, "") {
return nil
}
return err
}

if output == nil {
return nil
}

return fmt.Errorf("Policy attachment %q still exists", rs.Primary.ID)
}

return nil

}

func testAccCheckAwsOrganizationsPolicyAttachmentExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %s", resourceName)
}

conn := testAccProvider.Meta().(*AWSClient).organizationsconn

targetID, policyID, err := decodeAwsOrganizationsPolicyAttachmentID(rs.Primary.ID)
if err != nil {
return err
}

input := &organizations.ListPoliciesForTargetInput{
Filter: aws.String(organizations.PolicyTypeServiceControlPolicy),
TargetId: aws.String(targetID),
}

log.Printf("[DEBUG] Listing Organizations Policies for Target: %s", input)
var output *organizations.PolicySummary
err = conn.ListPoliciesForTargetPages(input, func(page *organizations.ListPoliciesForTargetOutput, lastPage bool) bool {
for _, policySummary := range page.Policies {
if aws.StringValue(policySummary.Id) == policyID {
output = policySummary
return true
}
}
return !lastPage
})

if err != nil {
return err
}

if output == nil {
return fmt.Errorf("Policy attachment %q does not exist", rs.Primary.ID)
}

return nil
}
}

func testAccAwsOrganizationsPolicyAttachmentConfig_Account(rName string) string {
return fmt.Sprintf(`
data "aws_caller_identity" "current" {}
resource "aws_organizations_policy" "test" {
content = "{\"Version\": \"2012-10-17\", \"Statement\": { \"Effect\": \"Allow\", \"Action\": \"*\", \"Resource\": \"*\"}}"
name = "%s"
}
resource "aws_organizations_policy_attachment" "test" {
policy_id = "${aws_organizations_policy.test.id}"
target_id = "${data.aws_caller_identity.current.account_id}"
}
`, rName)
}
3 changes: 3 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,9 @@
<li<%= sidebar_current("docs-aws-resource-organizations-policy") %>>
<a href="/docs/providers/aws/r/organizations_policy.html">aws_organizations_policy</a>
</li>
<li<%= sidebar_current("docs-aws-resource-organizations-policy-attachment") %>>
<a href="/docs/providers/aws/r/organizations_policy_attachment.html">aws_organizations_policy_attachment</a>
</li>
</ul>
</li>

Expand Down
55 changes: 55 additions & 0 deletions website/docs/r/organizations_policy_attachment.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
layout: "aws"
page_title: "AWS: aws_organizations_policy_attachment"
sidebar_current: "docs-aws-resource-organizations-policy-attachment"
description: |-
Provides a resource to attach an AWS Organizations policy to an organization account, root, or unit.
---

# aws_organizations_policy_attachment

Provides a resource to attach an AWS Organizations policy to an organization account, root, or unit.

## Example Usage

### Organization Account

```hcl
resource "aws_organizations_policy_attachment" "account" {
policy_id = "${aws_organizations_policy.example.id}"
target_id = "123456789012"
}
```

### Organization Root

```hcl
resource "aws_organizations_policy_attachment" "root" {
policy_id = "${aws_organizations_policy.example.id}"
target_id = "r-12345678"
}
```

### Organization Unit

```hcl
resource "aws_organizations_policy_attachment" "unit" {
policy_id = "${aws_organizations_policy.example.id}"
target_id = "ou-12345678"
}
```

## Argument Reference

The following arguments are supported:

* `policy_id` - (Required) The unique identifier (ID) of the policy that you want to attach to the target.
* `target_id` - (Required) The unique identifier (ID) of the root, organizational unit, or account number that you want to attach the policy to.

## Import

`aws_organizations_policy_attachment` can be imported by using the target ID and policy ID, e.g. with an account target

```
$ terraform import aws_organization_policy_attachment.account 123456789012:p-12345678
```

0 comments on commit 67d7744

Please sign in to comment.