From 98d82137031287790c26058938a6910758200b85 Mon Sep 17 00:00:00 2001 From: Gareth Oakley Date: Mon, 21 Jan 2019 22:26:35 +0800 Subject: [PATCH] Add aws_ram_principal_association resource --- aws/provider.go | 1 + aws/resource_aws_ram_principal_association.go | 186 ++++++++++++++++++ ...urce_aws_ram_principal_association_test.go | 126 ++++++++++++ .../docs/r/ram_principal_association.markdown | 41 ++++ 4 files changed, 354 insertions(+) create mode 100644 aws/resource_aws_ram_principal_association.go create mode 100644 aws/resource_aws_ram_principal_association_test.go create mode 100644 website/docs/r/ram_principal_association.markdown diff --git a/aws/provider.go b/aws/provider.go index a26572f72c99..6a4346e727c4 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -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(), diff --git a/aws/resource_aws_ram_principal_association.go b/aws/resource_aws_ram_principal_association.go new file mode 100644 index 000000000000..b1a74c5266a5 --- /dev/null +++ b/aws/resource_aws_ram_principal_association.go @@ -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 + } +} diff --git a/aws/resource_aws_ram_principal_association_test.go b/aws/resource_aws_ram_principal_association_test.go new file mode 100644 index 000000000000..2b9cf0bdc56e --- /dev/null +++ b/aws/resource_aws_ram_principal_association_test.go @@ -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) +} diff --git a/website/docs/r/ram_principal_association.markdown b/website/docs/r/ram_principal_association.markdown new file mode 100644 index 000000000000..c72d68006014 --- /dev/null +++ b/website/docs/r/ram_principal_association.markdown @@ -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 +```