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

New Resource: aws_ram_principal_association #7563

Merged
merged 4 commits into from
Feb 14, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -599,6 +599,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_association": resourceAwsRamResourceAssociation(),
"aws_ram_resource_share": resourceAwsRamResourceShare(),
"aws_rds_cluster": resourceAwsRDSCluster(),
Expand Down
219 changes: 219 additions & 0 deletions aws/resource_aws_ram_principal_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package aws

import (
"fmt"
"log"
"regexp"
"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,
},

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{
ClientToken: aws.String(resource.UniqueId()),
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))

// AWS Account ID Principals need to be accepted to become ASSOCIATED
if ok, _ := regexp.MatchString(`^\d{12}$`, principal); ok {
return resourceAwsRamPrincipalAssociationRead(d, meta)
}

if err := waitForRamResourceSharePrincipalAssociation(conn, resourceShareArn, principal); 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
}

resourceShareAssociation, err := getRamResourceSharePrincipalAssociation(conn, resourceShareArn, principal)

if err != nil {
return fmt.Errorf("error reading RAM Resource Share (%s) Principal Association (%s): %s", resourceShareArn, principal, err)
}

if resourceShareAssociation == nil {
log.Printf("[WARN] RAM Resource Share (%s) Principal Association (%s) not found, removing from state", resourceShareArn, principal)
d.SetId("")
return nil
}

if aws.StringValue(resourceShareAssociation.Status) != ram.ResourceShareAssociationStatusAssociated && aws.StringValue(resourceShareAssociation.Status) != ram.ResourceShareAssociationStatusAssociating {
log.Printf("[WARN] RAM Resource Share (%s) Principal Association (%s) not associating or associated, removing from state", resourceShareArn, principal)
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 isAWSErr(err, ram.ErrCodeUnknownResourceException, "") {
return nil
}

if err != nil {
return fmt.Errorf("error disassociating RAM Resource Share (%s) Principal Association (%s): %s", resourceShareArn, principal, err)
}

if err := waitForRamResourceSharePrincipalDisassociation(conn, resourceShareArn, principal); err != nil {
return fmt.Errorf("error waiting for RAM Resource Share (%s) Principal Association (%s) disassociation: %s", resourceShareArn, principal, err)
}

return nil
}

func resourceAwsRamPrincipalAssociationParseId(id string) (string, string, error) {
idFormatErr := fmt.Errorf("unexpected format of ID (%s), expected SHARE,PRINCIPAL", id)

parts := strings.SplitN(id, ",", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", idFormatErr
}

return parts[0], parts[1], nil
}

func getRamResourceSharePrincipalAssociation(conn *ram.RAM, resourceShareARN, principal string) (*ram.ResourceShareAssociation, error) {
input := &ram.GetResourceShareAssociationsInput{
AssociationType: aws.String(ram.ResourceShareAssociationTypePrincipal),
Principal: aws.String(principal),
ResourceShareArns: aws.StringSlice([]string{resourceShareARN}),
}

output, err := conn.GetResourceShareAssociations(input)

if isAWSErr(err, ram.ErrCodeUnknownResourceException, "") {
return nil, nil
}

if err != nil {
return nil, err
}

if output == nil || len(output.ResourceShareAssociations) == 0 || output.ResourceShareAssociations[0] == nil {
return nil, nil
}

return output.ResourceShareAssociations[0], nil
}

func resourceAwsRamPrincipalAssociationStateRefreshFunc(conn *ram.RAM, resourceShareArn, principal string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
association, err := getRamResourceSharePrincipalAssociation(conn, resourceShareArn, principal)

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

if association == nil {
return nil, ram.ResourceShareAssociationStatusDisassociated, nil
}

if aws.StringValue(association.Status) == ram.ResourceShareAssociationStatusFailed {
extendedErr := fmt.Errorf("association status message: %s", aws.StringValue(association.StatusMessage))
return association, aws.StringValue(association.Status), extendedErr
}

return association, aws.StringValue(association.Status), nil
}
}

func waitForRamResourceSharePrincipalAssociation(conn *ram.RAM, resourceShareARN, principal string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{ram.ResourceShareAssociationStatusAssociating},
Target: []string{ram.ResourceShareAssociationStatusAssociated},
Refresh: resourceAwsRamPrincipalAssociationStateRefreshFunc(conn, resourceShareARN, principal),
Timeout: 5 * time.Minute,
}

_, err := stateConf.WaitForState()

return err
}

func waitForRamResourceSharePrincipalDisassociation(conn *ram.RAM, resourceShareARN, principal string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{ram.ResourceShareAssociationStatusAssociated, ram.ResourceShareAssociationStatusDisassociating},
Target: []string{ram.ResourceShareAssociationStatusDisassociated},
Refresh: resourceAwsRamPrincipalAssociationStateRefreshFunc(conn, resourceShareARN, principal),
Timeout: 5 * time.Minute,
}

_, err := stateConf.WaitForState()

return err
}
159 changes: 159 additions & 0 deletions aws/resource_aws_ram_principal_association_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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 resourceShareAssociation1 ram.ResourceShareAssociation
resourceName := "aws_ram_principal_association.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsRamPrincipalAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsRamPrincipalAssociationConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsRamPrincipalAssociationExists(resourceName, &resourceShareAssociation1),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAwsRamPrincipalAssociation_disappears(t *testing.T) {
var resourceShareAssociation1 ram.ResourceShareAssociation
resourceName := "aws_ram_principal_association.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsRamResourceAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsRamPrincipalAssociationConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsRamPrincipalAssociationExists(resourceName, &resourceShareAssociation1),
testAccCheckAwsRamPrincipalAssociationDisappears(&resourceShareAssociation1),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccCheckAwsRamPrincipalAssociationDisappears(resourceShareAssociation *ram.ResourceShareAssociation) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ramconn

input := &ram.DisassociateResourceShareInput{
Principals: []*string{resourceShareAssociation.AssociatedEntity},
ResourceShareArn: resourceShareAssociation.ResourceShareArn,
}

_, err := conn.DisassociateResourceShare(input)
if err != nil {
return err
}

return waitForRamResourceSharePrincipalDisassociation(conn, aws.StringValue(resourceShareAssociation.ResourceShareArn), aws.StringValue(resourceShareAssociation.AssociatedEntity))
}
}

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")
}

resourceShareARN, principal, err := resourceAwsRamPrincipalAssociationParseId(rs.Primary.ID)

if err != nil {
return err
}

resourceShareAssociation, err := getRamResourceSharePrincipalAssociation(conn, resourceShareARN, principal)

if err != nil {
return fmt.Errorf("error reading RAM Resource Share (%s) Principal Association (%s): %s", resourceShareARN, principal, err)
}

if resourceShareAssociation == nil {
return fmt.Errorf("RAM Resource Share (%s) Principal Association (%s) not found", resourceShareARN, principal)
}

if aws.StringValue(resourceShareAssociation.Status) != ram.ResourceShareAssociationStatusAssociated && aws.StringValue(resourceShareAssociation.Status) != ram.ResourceShareAssociationStatusAssociating {
return fmt.Errorf("RAM Resource Share (%s) Principal Association (%s) not associating or associated: %s", resourceShareARN, principal, aws.StringValue(resourceShareAssociation.Status))
}

*resourceShare = *resourceShareAssociation

return nil
}
}

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

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

resourceShareARN, principal, err := decodeRamResourceAssociationID(rs.Primary.ID)

if err != nil {
return err
}

resourceShareAssociation, err := getRamResourceSharePrincipalAssociation(conn, resourceShareARN, principal)

if err != nil {
return err
}

if resourceShareAssociation != nil && aws.StringValue(resourceShareAssociation.Status) != ram.ResourceShareAssociationStatusDisassociated {
return fmt.Errorf("RAM Resource Share (%s) Principal Association (%s) not disassociated: %s", resourceShareARN, principal, aws.StringValue(resourceShareAssociation.Status))
}
}

return nil
}

func testAccAwsRamPrincipalAssociationConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_ram_resource_share" "test" {
allow_external_principals = true
name = %[1]q
}

resource "aws_ram_principal_association" "test" {
principal = "111111111111"
resource_share_arn = "${aws_ram_resource_share.test.id}"
}
`, rName)
}
3 changes: 3 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,9 @@
<li<%= sidebar_current("docs-aws-resource-ram") %>>
<a href="#">RAM Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-aws-resource-ram-principal-association") %>>
<a href="/docs/providers/aws/r/ram_principal_association.html">aws_ram_principal_association</a>
</li>
<li<%= sidebar_current("docs-aws-resource-ram-resource-association") %>>
<a href="/docs/providers/aws/r/ram_resource_association.html">aws_ram_resource_association</a>
</li>
Expand Down
Loading