Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] r/aws_ram_principal_association: Add aws_ram_principal_association resource #7219

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ func Provider() terraform.ResourceProvider {
"aws_organizations_policy_attachment": resourceAwsOrganizationsPolicyAttachment(),
"aws_placement_group": resourceAwsPlacementGroup(),
"aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(),
"aws_ram_principal_association": resourceAwsRamPrincipalAssociation(),
"aws_ram_resource_share": resourceAwsRamResourceShare(),
"aws_rds_cluster": resourceAwsRDSCluster(),
"aws_rds_cluster_endpoint": resourceAwsRDSClusterEndpoint(),
Expand Down
186 changes: 186 additions & 0 deletions aws/resource_aws_ram_principal_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package aws

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

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ram"

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

func resourceAwsRamPrincipalAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceAwsRamPrincipalAssociationCreate,
Read: resourceAwsRamPrincipalAssociationRead,
Delete: resourceAwsRamPrincipalAssociationDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},

Schema: map[string]*schema.Schema{
"resource_share_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateArn,
},

"principal": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

resourceShareArn := d.Get("resource_share_arn").(string)
principal := d.Get("principal").(string)

request := &ram.AssociateResourceShareInput{
ResourceShareArn: aws.String(resourceShareArn),
Principals: []*string{aws.String(principal)},
}

log.Println("[DEBUG] Create RAM principal association request:", request)
_, err := conn.AssociateResourceShare(request)
if err != nil {
return fmt.Errorf("Error associating principal with RAM resource share: %s", err)
}

d.SetId(fmt.Sprintf("%s,%s", resourceShareArn, principal))

stateConf := &resource.StateChangeConf{
Pending: []string{ram.ResourceShareAssociationStatusAssociating},
Target: []string{ram.ResourceShareAssociationStatusAssociated},
Refresh: resourceAwsRamPrincipalAssociationStateRefreshFunc(conn, resourceShareArn, principal),
Timeout: d.Timeout(schema.TimeoutCreate),
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for RAM principal association (%s) to become ready: %s", d.Id(), err)
}

return resourceAwsRamPrincipalAssociationRead(d, meta)
}

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

resourceShareArn, principal, err := resourceAwsRamPrincipalAssociationParseId(d.Id())
if err != nil {
return err
}

request := &ram.GetResourceShareAssociationsInput{
ResourceShareArns: []*string{aws.String(resourceShareArn)},
AssociationType: aws.String(ram.ResourceShareAssociationTypePrincipal),
Principal: aws.String(principal),
}

output, err := conn.GetResourceShareAssociations(request)

if err != nil {
return fmt.Errorf("Error reading RAM principal association %s: %s", d.Id(), err)
}

if len(output.ResourceShareAssociations) == 0 {
log.Printf("[WARN] RAM principal (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

association := output.ResourceShareAssociations[0]

if aws.StringValue(association.Status) == ram.ResourceShareAssociationStatusAssociated {
log.Printf("[WARN] RAM principal (%s) disassociat(ing|ed), removing from state", d.Id())
d.SetId("")
return nil
}

d.Set("resource_share_arn", resourceShareArn)
d.Set("principal", principal)

return nil
}

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

resourceShareArn, principal, err := resourceAwsRamPrincipalAssociationParseId(d.Id())
if err != nil {
return err
}

request := &ram.DisassociateResourceShareInput{
ResourceShareArn: aws.String(resourceShareArn),
Principals: []*string{aws.String(principal)},
}

log.Println("[DEBUG] Delete RAM principal association request:", request)
_, err = conn.DisassociateResourceShare(request)
if err != nil {
return fmt.Errorf("Error disassociating principals from RAM Resource Share: %s", err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{ram.ResourceShareAssociationStatusDisassociating},
Target: []string{ram.ResourceShareAssociationStatusDisassociated},
Refresh: resourceAwsRamPrincipalAssociationStateRefreshFunc(conn, resourceShareArn, principal),
Timeout: d.Timeout(schema.TimeoutDelete),
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for RAM principal association (%s) to become ready: %s", d.Id(), err)
}

return nil
}

func resourceAwsRamPrincipalAssociationParseId(id string) (string, string, error) {
parts := strings.SplitN(id, ",", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("Expected RAM principal association ID in the form resource_share_arn,principal - received: %s", id)
}
return parts[0], parts[1], nil
}

func resourceAwsRamPrincipalAssociationStateRefreshFunc(conn *ram.RAM, resourceShareArn, principal string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
request := &ram.GetResourceShareAssociationsInput{
ResourceShareArns: []*string{aws.String(resourceShareArn)},
AssociationType: aws.String(ram.ResourceShareAssociationTypePrincipal),
Principal: aws.String(principal),
}

output, err := conn.GetResourceShareAssociations(request)

if err != nil {
return nil, ram.ResourceShareAssociationStatusFailed, err
}

if len(output.ResourceShareAssociations) == 0 {
return nil, ram.ResourceShareAssociationStatusDisassociated, nil
}

association := output.ResourceShareAssociations[0]

return association, aws.StringValue(association.Status), nil
}
}
126 changes: 126 additions & 0 deletions aws/resource_aws_ram_principal_association_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package aws

import (
"fmt"
"testing"

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

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ram"
)

func TestAccAwsRamPrincipalAssociation_basic(t *testing.T) {
var association ram.ResourceShareAssociation
resourceName := "aws_ram_principal_association.example"
shareName := fmt.Sprintf("tf-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
principal := "111111111111"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSClusterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsRamPrincipalAssociationConfig_basic(shareName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsRamPrincipalAssociationExists(resourceName, &association),
resource.TestCheckResourceAttr(resourceName, "name", shareName),
resource.TestCheckResourceAttr(resourceName, "allow_external_principals", "true"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckAwsRamPrincipalAssociationExists(resourceName string, resourceShare *ram.ResourceShareAssociation) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ramconn

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %s", resourceName)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

request := &ram.GetResourceShareAssociationsInput{
ResourceShareArns: []*string{aws.String(rs.Primary.ID)},
ResourceOwner: aws.String(ram.ResourceOwnerSelf),
}

output, err := conn.GetResourceShareAssociations(request)
if err != nil {
return err
}

if len(output.ResourceShareAssociations) == 0 {
return fmt.Errorf("No RAM resource share found")
}

resourceShare = output.ResourceShareAssociations[0]

if aws.StringValue(resourceShare.Status) != ram.ResourceShareAssociationStatusActive {
return fmt.Errorf("RAM resource share (%s) delet(ing|ed)", rs.Primary.ID)
}

return nil
}
}

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

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

request := &ram.GetResourceShareAssociationsInput{
ResourceShareArns: []*string{aws.String(rs.Primary.ID)},
ResourceOwner: aws.String(ram.ResourceOwnerSelf),
}

output, err := conn.GetResourceShareAssociations(request)
if err != nil {
return err
}

if len(output.ResourceShareAssociations) > 0 {
resourceShare := output.ResourceShareAssociations[0]
if aws.StringValue(resourceShare.Status) != ram.ResourceShareAssociationStatusDeleted {
return fmt.Errorf("RAM resource share (%s) still exists", rs.Primary.ID)
}
return fmt.Errorf("No RAM resource share found")
}
}

return nil
}

func testAccAwsRamPrincipalAssociationConfig_basic(shareName, principal string) string {
return fmt.Sprintf(`
resource "aws_ram_resource_share" "example" {
name = "%s"
allow_external_principals = true

tags {
Environment = "Production"
}
}

resource "aws_ram_principal_association" "example" {
resource_share_arn = "${aws_ram_resource_share.example.id}"
principal = "%s"
}
`, shareName)
}
41 changes: 41 additions & 0 deletions website/docs/r/ram_principal_association.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
layout: "aws"
page_title: "AWS: aws_ram_principal_association"
sidebar_current: "docs-aws-resource-ram-principal-association"
description: |-
Provides a Resource Access Manager (RAM) principal association.
---

# aws_ram_principal_association

Provides a Resource Access Manager (RAM) principal association.

## Example Usage

```hcl
resource "aws_ram_principal_association" "example" {
resource_share_arn = "arn:aws:ram:eu-west-1:123456789012:resource-share/73da1ab9-b94a-4ba3-8eb4-45917f7f4b12"
principal = "111111111111"
}
```

## Argument Reference

The following arguments are supported:

* `resource_share_arn` - (Required) The Amazon Resource Names (ARN) of the resources to associate with the resource share.
* `principal` - (Required) The principal to associate with the resource share. Possible values are the ID of an AWS account, the ARN of an OU or organization from AWS Organizations.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

* `id` - The Amazon Resource Name (ARN) of the resource share.

## Import

Resource Shares can be imported using the `id`, e.g.

```
$ terraform import aws_ram_resource_share.example arn:aws:ram:eu-west-1:123456789012:resource-share/73da1ab9-b94a-4ba3-8eb4-45917f7f4b12
```