From df6c2fc3e3f9aef0ea447e2a5f2b7eccb7f1b774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Mon, 10 May 2021 17:11:48 -0600 Subject: [PATCH 01/18] feat: added new resources, tests and docs for member, invitation and invitation accepter --- aws/resource_aws_macie2_account.go | 24 ++- aws/resource_aws_macie2_invitation.go | 114 ++++++++++ ...resource_aws_macie2_invitation_accepter.go | 147 +++++++++++++ ...rce_aws_macie2_invitation_accepter_test.go | 202 ++++++++++++++++++ aws/resource_aws_macie2_invitation_test.go | 103 +++++++++ aws/resource_aws_macie2_member.go | 199 +++++++++++++++++ aws/resource_aws_macie2_member_test.go | 183 ++++++++++++++++ aws/resource_aws_macie2_test.go | 19 ++ .../docs/r/macie2_invitation.html.markdown | 42 ++++ .../macie2_invitation_accepter.html.markdown | 60 ++++++ website/docs/r/macie2_member.html.markdown | 52 +++++ 11 files changed, 1144 insertions(+), 1 deletion(-) create mode 100644 aws/resource_aws_macie2_invitation.go create mode 100644 aws/resource_aws_macie2_invitation_accepter.go create mode 100644 aws/resource_aws_macie2_invitation_accepter_test.go create mode 100644 aws/resource_aws_macie2_invitation_test.go create mode 100644 aws/resource_aws_macie2_member.go create mode 100644 aws/resource_aws_macie2_member_test.go create mode 100644 website/docs/r/macie2_invitation.html.markdown create mode 100644 website/docs/r/macie2_invitation_accepter.html.markdown create mode 100644 website/docs/r/macie2_member.html.markdown diff --git a/aws/resource_aws_macie2_account.go b/aws/resource_aws_macie2_account.go index ede039c2e4cc..66262d6917f3 100644 --- a/aws/resource_aws_macie2_account.go +++ b/aws/resource_aws_macie2_account.go @@ -148,7 +148,28 @@ func resourceMacie2AccountDelete(ctx context.Context, d *schema.ResourceData, me input := &macie2.DisableMacieInput{} - _, err := conn.DisableMacieWithContext(ctx, input) + err := resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + _, err := conn.DisableMacieWithContext(ctx, input) + + if tfawserr.ErrMessageContains(err, macie2.ErrCodeConflictException, "Cannot disable Macie while associated with an administrator account") { + return resource.RetryableError(err) + } + + if err != nil { + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + return nil + } + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.DisableMacieWithContext(ctx, input) + } + if err != nil { if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { @@ -156,5 +177,6 @@ func resourceMacie2AccountDelete(ctx context.Context, d *schema.ResourceData, me } return diag.FromErr(fmt.Errorf("error disabling Macie Account (%s): %w", d.Id(), err)) } + return nil } diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go new file mode 100644 index 000000000000..ef60c4764c97 --- /dev/null +++ b/aws/resource_aws_macie2_invitation.go @@ -0,0 +1,114 @@ +package aws + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceAwsMacie2Invitation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceMacie2InvitationCreate, + ReadWithoutTimeout: resourceMacie2InvitationRead, + DeleteWithoutTimeout: resourceMacie2InvitationDelete, + Schema: map[string]*schema.Schema{ + "disable_email_notification": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "account_ids": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "message": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(1 * time.Minute), + }, + } +} + +func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + input := &macie2.CreateInvitationsInput{ + AccountIds: expandStringList(d.Get("account_ids").([]interface{})), + } + + if v, ok := d.GetOk("disable_email_notification"); ok { + input.DisableEmailNotification = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("message"); ok { + input.Message = aws.String(v.(string)) + } + + var err error + err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + _, err := conn.CreateInvitationsWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, macie2.ErrorCodeClientError) { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.CreateInvitationsWithContext(ctx, input) + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %w", err)) + } + + d.SetId(meta.(*AWSClient).accountid) + + return resourceMacie2InvitationRead(ctx, d, meta) +} + +func resourceMacie2InvitationRead(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil +} + +func resourceMacie2InvitationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + input := &macie2.DeleteInvitationsInput{ + AccountIds: expandStringList(d.Get("account_ids").([]interface{})), + } + + output, err := conn.DeleteInvitationsWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + return nil + } + return diag.FromErr(fmt.Errorf("error deleting Macie Invitation (%s): %w", d.Id(), err)) + } + + for _, invitation := range output.UnprocessedAccounts { + if strings.Contains(aws.StringValue(invitation.ErrorMessage), "either because no such invitation exists") { + return nil + } + } + return nil +} diff --git a/aws/resource_aws_macie2_invitation_accepter.go b/aws/resource_aws_macie2_invitation_accepter.go new file mode 100644 index 000000000000..bcf521e47154 --- /dev/null +++ b/aws/resource_aws_macie2_invitation_accepter.go @@ -0,0 +1,147 @@ +package aws + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceAwsMacie2InvitationAccepter() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceMacie2InvitationAccepterCreate, + ReadWithoutTimeout: resourceMacie2InvitationAccepterRead, + DeleteWithoutTimeout: resourceMacie2InvitationAccepterDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "administrator_account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAwsAccountId, + }, + "invitation_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(1 * time.Minute), + }, + } +} + +func resourceMacie2InvitationAccepterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + adminAccountID := d.Get("administrator_account_id").(string) + var invitationID string + + listInvitationsInput := &macie2.ListInvitationsInput{} + + err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { + err := conn.ListInvitationsPages(listInvitationsInput, func(page *macie2.ListInvitationsOutput, lastPage bool) bool { + for _, invitation := range page.Invitations { + if aws.StringValue(invitation.AccountId) == adminAccountID { + invitationID = aws.StringValue(invitation.InvitationId) + return false + } + } + return !lastPage + }) + + if err != nil { + return resource.NonRetryableError(err) + } + + if invitationID == "" { + return resource.RetryableError(fmt.Errorf("unable to find pending Macie Invitation for administrator account ID (%s)", adminAccountID)) + } + + return nil + }) + + if isResourceTimeoutError(err) { + err = conn.ListInvitationsPages(listInvitationsInput, func(page *macie2.ListInvitationsOutput, lastPage bool) bool { + for _, invitation := range page.Invitations { + if aws.StringValue(invitation.AccountId) == adminAccountID { + invitationID = aws.StringValue(invitation.InvitationId) + return false + } + } + return !lastPage + }) + } + if err != nil { + return diag.FromErr(fmt.Errorf("error listing Macie InvitationAccepter (%s): %w", d.Id(), err)) + } + + acceptInvitationInput := &macie2.AcceptInvitationInput{ + InvitationId: aws.String(invitationID), + AdministratorAccountId: aws.String(adminAccountID), + } + + _, err = conn.AcceptInvitationWithContext(ctx, acceptInvitationInput) + + if err != nil { + return diag.FromErr(fmt.Errorf("error accepting Macie InvitationAccepter (%s): %w", d.Id(), err)) + } + + d.SetId(adminAccountID) + + return resourceMacie2InvitationAccepterRead(ctx, d, meta) +} + +func resourceMacie2InvitationAccepterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + var err error + + input := &macie2.GetAdministratorAccountInput{} + + output, err := conn.GetAdministratorAccountWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + log.Printf("[WARN] Macie InvitationAccepter (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading Macie InvitationAccepter (%s): %w", d.Id(), err)) + } + + if output == nil || output.Administrator == nil { + return diag.FromErr(fmt.Errorf("error reading Macie InvitationAccepter (%s): %w", d.Id(), err)) + } + + d.Set("administrator_account_id", output.Administrator.AccountId) + d.Set("invitation_id", output.Administrator.InvitationId) + return nil +} + +func resourceMacie2InvitationAccepterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + input := &macie2.DisassociateFromAdministratorAccountInput{} + + _, err := conn.DisassociateFromAdministratorAccountWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + return nil + } + return diag.FromErr(fmt.Errorf("error disassociating Macie InvitationAccepter (%s): %w", d.Id(), err)) + } + return nil +} diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go new file mode 100644 index 000000000000..387cc250ad9a --- /dev/null +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -0,0 +1,202 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/guardduty" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { + var providers []*schema.Provider + resourceName := "aws_macie2_invitation_accepter.test" + adminAccountID := "124861550386" + accountID, email := testAccAWSMacie2MemberFromEnv(t) + accountIds := []string{accountID} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsMacie2InvitationAccepterDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID, accountIds), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + ), + }, + { + Config: testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID, accountIds), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { + var providers []*schema.Provider + resourceName := "aws_macie2_invitation_accepter.test" + adminAccountID := "124861550386" + accountID, email := testAccAWSMacie2MemberFromEnv(t) + accountIds := []string{accountID} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsMacie2InvitationAccepterDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(accountID, email, adminAccountID, macie2.MacieStatusEnabled, accountIds), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + ), + }, + { + Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(accountID, email, adminAccountID, macie2.MacieStatusPaused, accountIds), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + ), + }, + { + Config: testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID, accountIds), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsMacie2InvitationAccepterExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("resource (%s) has empty ID", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).macie2conn + input := &macie2.GetAdministratorAccountInput{} + output, err := conn.GetAdministratorAccount(input) + + if err != nil { + return err + } + + if output == nil || output.Administrator == nil || aws.StringValue(output.Administrator.AccountId) == "" { + return fmt.Errorf("no administrator account found for: %s", resourceName) + } + + return nil + } +} + +func testAccCheckAwsMacie2InvitationAccepterDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).macie2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_macie2_invitation_accepter" { + continue + } + + input := &macie2.GetAdministratorAccountInput{} + output, err := conn.GetAdministratorAccount(input) + + if isAWSErr(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") { + return nil + } + + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + continue + } + + if output == nil || output.Administrator == nil || aws.StringValue(output.Administrator.AccountId) != rs.Primary.Attributes["administrator_account_id"] { + continue + } + + return fmt.Errorf("macie InvitationAccepter %q still exists", rs.Primary.ID) + + } + + return nil + +} + +func testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID string, accountIDs []string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +resource "aws_macie2_account" "primary" { + provider = "awsalternate" +} + +resource "aws_macie2_account" "member" {} + +resource "aws_macie2_member" "primary" { + provider = "awsalternate" + account_id = %[1]q + email = %[2]q + depends_on = [aws_macie2_account.primary] +} + +resource "aws_macie2_invitation" "primary" { + provider = "awsalternate" + account_ids = %[3]q + depends_on = [aws_macie2_member.primary] +} + +resource "aws_macie2_invitation_accepter" "test" { + administrator_account_id = %[4]q + depends_on = [aws_macie2_invitation.primary] +} + +`, accountID, email, accountIDs, adminAccountID) +} + +func testAccAwsMacieInvitationAccepterConfigMemberStatus(accountID, email, adminAccountID, memberStatus string, accountIDs []string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +resource "aws_macie2_account" "primary" { + provider = "awsalternate" +} + +resource "aws_macie2_account" "member" {} + +resource "aws_macie2_member" "primary" { + provider = "awsalternate" + account_id = %[1]q + email = %[2]q + status = %[5]q + depends_on = [aws_macie2_account.primary] +} + +resource "aws_macie2_invitation" "primary" { + provider = "awsalternate" + account_ids = %[3]q + depends_on = [aws_macie2_member.primary] +} + +resource "aws_macie2_invitation_accepter" "test" { + administrator_account_id = %[4]q + depends_on = [aws_macie2_invitation.primary] +} + +`, accountID, email, accountIDs, adminAccountID, memberStatus) +} diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go new file mode 100644 index 000000000000..62a67de41fde --- /dev/null +++ b/aws/resource_aws_macie2_invitation_test.go @@ -0,0 +1,103 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccAwsMacie2Invitation_basic(t *testing.T) { + accountID, email := testAccAWSMacie2MemberFromEnv(t) + accountIds := []string{accountID} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsMacie2InvitationDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieInvitationConfigBasic(accountID, email, accountIds), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func testAccAwsMacie2Invitation_disappears(t *testing.T) { + resourceName := "aws_macie2_invitation.test" + accountID, email := testAccAWSMacie2MemberFromEnv(t) + accountIds := []string{accountID} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsMacie2InvitationDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieInvitationConfigBasic(accountID, email, accountIds), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceDisappears(testAccProvider, resourceAwsMacie2Invitation(), resourceName), + ), + }, + }, + }) +} + +func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).macie2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_macie2_invitation" { + continue + } + + empty := true + err := conn.ListInvitationsPages(&macie2.ListInvitationsInput{}, func(page *macie2.ListInvitationsOutput, lastPage bool) bool { + if len(page.Invitations) > 0 { + empty = false + return false + } + + return true + }) + + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + continue + } + + if err != nil { + return err + } + + if !empty { + return fmt.Errorf("macie Invitation %q still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccAwsMacieInvitationConfigBasic(accountID, email string, accountIDs []string) string { + return fmt.Sprintf(` +resource "aws_macie2_account" "test" {} + +resource "aws_macie2_member" "test" { + account_id = %[1]q + email = %[2]q + depends_on = [aws_macie2_account.test] +} + +resource "aws_macie2_invitation" "test" { + account_ids = %[3]q + depends_on = [aws_macie2_member.test] +} +`, accountID, email, accountIDs) +} diff --git a/aws/resource_aws_macie2_member.go b/aws/resource_aws_macie2_member.go new file mode 100644 index 000000000000..71839179c3d2 --- /dev/null +++ b/aws/resource_aws_macie2_member.go @@ -0,0 +1,199 @@ +package aws + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsMacie2Member() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceMacie2MemberCreate, + ReadWithoutTimeout: resourceMacie2MemberRead, + UpdateWithoutTimeout: resourceMacie2MemberUpdate, + DeleteWithoutTimeout: resourceMacie2MemberDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "email": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "relationship_status": { + Type: schema.TypeString, + Computed: true, + }, + "administrator_account_id": { + Type: schema.TypeString, + Computed: true, + }, + "invited_at": { + Type: schema.TypeString, + Computed: true, + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(macie2.MacieStatus_Values(), false), + }, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Second), + Update: schema.DefaultTimeout(60 * time.Second), + }, + } +} + +func resourceMacie2MemberCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + accountId := d.Get("account_id").(string) + input := &macie2.CreateMemberInput{ + Account: &macie2.AccountDetail{ + AccountId: aws.String(accountId), + Email: aws.String(d.Get("email").(string)), + }, + } + + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().Macie2Tags() + } + + var err error + err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + _, err := conn.CreateMemberWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, macie2.ErrorCodeClientError) { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.CreateMemberWithContext(ctx, input) + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Macie Member: %w", err)) + } + + d.SetId(accountId) + + return resourceMacie2MemberRead(ctx, d, meta) +} + +func resourceMacie2MemberRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + input := &macie2.GetMemberInput{ + Id: aws.String(d.Id()), + } + + resp, err := conn.GetMemberWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") || + tfawserr.ErrMessageContains(err, macie2.ErrCodeConflictException, "member accounts are associated with your account") || + tfawserr.ErrMessageContains(err, macie2.ErrCodeValidationException, "account is not associated with your account") { + log.Printf("[WARN] Macie Member (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + return diag.FromErr(fmt.Errorf("error reading Macie Member (%s): %w", d.Id(), err)) + } + + d.Set("account_id", resp.AccountId) + d.Set("email", resp.Email) + d.Set("relationship_status", resp.RelationshipStatus) + d.Set("administrator_account_id", resp.AdministratorAccountId) + d.Set("invited_at", aws.TimeValue(resp.InvitedAt).Format(time.RFC3339)) + d.Set("updated_at", aws.TimeValue(resp.UpdatedAt).Format(time.RFC3339)) + d.Set("arn", resp.Arn) + tags := keyvaluetags.Macie2KeyValueTags(resp.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + if err = d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for Macie Member (%s): %w", "tags", d.Id(), err)) + } + + if err = d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting `%s` for Macie Member (%s): %w", "tags_all", d.Id(), err)) + } + + return nil +} + +func resourceMacie2MemberUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + input := &macie2.UpdateMemberSessionInput{ + Id: aws.String(d.Id()), + } + + if d.HasChange("status") { + input.Status = aws.String(d.Get("status").(string)) + } + + _, err := conn.UpdateMemberSessionWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating Macie Member (%s): %w", d.Id(), err)) + } + + return resourceMacie2MemberRead(ctx, d, meta) +} + +func resourceMacie2MemberDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + input := &macie2.DeleteMemberInput{ + Id: aws.String(d.Id()), + } + + _, err := conn.DeleteMemberWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") || + tfawserr.ErrMessageContains(err, macie2.ErrCodeConflictException, "member accounts are associated with your account") || + tfawserr.ErrMessageContains(err, macie2.ErrCodeValidationException, "account is not associated with your account") { + return nil + } + return diag.FromErr(fmt.Errorf("error deleting Macie Member (%s): %w", d.Id(), err)) + } + return nil +} diff --git a/aws/resource_aws_macie2_member_test.go b/aws/resource_aws_macie2_member_test.go new file mode 100644 index 000000000000..40e00e793444 --- /dev/null +++ b/aws/resource_aws_macie2_member_test.go @@ -0,0 +1,183 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccAwsMacie2Member_basic(t *testing.T) { + var macie2Output macie2.GetMemberOutput + resourceName := "aws_macie2_member.test" + accountID := "520433213222" + email := "test@test.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsMacie2MemberDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieMemberConfigBasic(accountID, email), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusCreated), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAwsMacie2Member_disappears(t *testing.T) { + var macie2Output macie2.GetMemberOutput + resourceName := "aws_macie2_member.test" + accountID := "520433213222" + email := "test@test.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsMacie2MemberDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieMemberConfigBasic(accountID, email), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + testAccCheckResourceDisappears(testAccProvider, resourceAwsMacie2Member(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAwsMacie2Member_withTags(t *testing.T) { + var macie2Output macie2.GetMemberOutput + resourceName := "aws_macie2_member.test" + accountID := "520433213222" + email := "test@test.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAwsMacie2MemberDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieMemberConfigWithTags(accountID, email), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), + resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsMacie2MemberExists(resourceName string, macie2Session *macie2.GetMemberOutput) 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).macie2conn + input := &macie2.GetMemberInput{Id: aws.String(rs.Primary.ID)} + + resp, err := conn.GetMember(input) + + if err != nil { + return err + } + + if resp == nil { + return fmt.Errorf("macie Member %q does not exist", rs.Primary.ID) + } + + *macie2Session = *resp + + return nil + } +} + +func testAccCheckAwsMacie2MemberDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).macie2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_macie2_member" { + continue + } + + input := &macie2.GetMemberInput{Id: aws.String(rs.Primary.ID)} + resp, err := conn.GetMember(input) + + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") || + tfawserr.ErrMessageContains(err, macie2.ErrCodeConflictException, "member accounts are associated with your account") || + tfawserr.ErrMessageContains(err, macie2.ErrCodeValidationException, "account is not associated with your account") { + continue + } + + if err != nil { + return err + } + + if resp != nil { + return fmt.Errorf("macie Member %q still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccAwsMacieMemberConfigBasic(accountID, email string) string { + return fmt.Sprintf(` +resource "aws_macie2_account" "test" {} + +resource "aws_macie2_member" "test" { + account_id = %[1]q + email = %[2]q + depends_on = [aws_macie2_account.test] +} +`, accountID, email) +} + +func testAccAwsMacieMemberConfigWithTags(accountID, email string) string { + return fmt.Sprintf(` +resource "aws_macie2_account" "test" {} + +resource "aws_macie2_member" "test" { + account_id = %[1]q + email = %[2]q + tags = { + Key = "value" + } + depends_on = [aws_macie2_account.test] +} +`, accountID, email) +} diff --git a/aws/resource_aws_macie2_test.go b/aws/resource_aws_macie2_test.go index 38285120c7c5..ad0af16d2d87 100644 --- a/aws/resource_aws_macie2_test.go +++ b/aws/resource_aws_macie2_test.go @@ -1,6 +1,7 @@ package aws import ( + "os" "testing" ) @@ -58,3 +59,21 @@ func TestAccAWSMacie2_serial(t *testing.T) { }) } } + +func testAccAWSMacie2MemberFromEnv(t *testing.T) (string, string) { + accountID := os.Getenv("AWS_MACIE_MEMBER_ACCOUNT_ID") + if accountID == "" { + t.Skip( + "Environment variable AWS_MACIE_MEMBER_ACCOUNT_ID is not set. " + + "To properly test inviting MACIE member accounts, " + + "a valid AWS account ID must be provided.") + } + email := os.Getenv("AWS_MACIE_MEMBER_EMAIL") + if email == "" { + t.Skip( + "Environment variable AWS_MACIE_MEMBER_EMAIL is not set. " + + "To properly test inviting MACIE member accounts, " + + "a valid email associated with the AWS_MACIE_MEMBER_ACCOUNT_ID must be provided.") + } + return accountID, email +} diff --git a/website/docs/r/macie2_invitation.html.markdown b/website/docs/r/macie2_invitation.html.markdown new file mode 100644 index 000000000000..5a4715002525 --- /dev/null +++ b/website/docs/r/macie2_invitation.html.markdown @@ -0,0 +1,42 @@ +--- +subcategory: "Macie" +layout: "aws" +page_title: "AWS: aws_macie2_invitation" +description: |- + Provides a resource to manage an Amazon Macie Invitation. +--- + +# Resource: aws_macie2_invitation + +Provides a resource to manage an [Amazon Macie Invitation](https://docs.aws.amazon.com/macie/latest/APIReference/invitations.html). + +## Example Usage + +```terraform +resource "aws_macie2_account" "test" {} + +resource "aws_macie2_member" "test" { + account_id = "AWS ACCOUNT ID" + email = "EMAIL" + depends_on = [aws_macie2_account.test] +} + +resource "aws_macie2_invitation" "test" { + account_ids = ["ACCOUNT IDS"] + depends_on = [aws_macie2_member.test] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `account_ids` - (Required) An array that lists AWS account IDs, one for each account to send the invitation to. +* `administrator_account_id` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. +* `message` - (Optional) A custom message to include in the invitation. Amazon Macie adds this message to the standard content that it sends for an invitation. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique identifier (ID) of the macie invitation. diff --git a/website/docs/r/macie2_invitation_accepter.html.markdown b/website/docs/r/macie2_invitation_accepter.html.markdown new file mode 100644 index 000000000000..391c153bd132 --- /dev/null +++ b/website/docs/r/macie2_invitation_accepter.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "Macie" +layout: "aws" +page_title: "AWS: aws_macie2_invitation_accepter" +description: |- + Provides a resource to manage an Amazon Macie Invitation Accepter. +--- + +# Resource: aws_macie2_invitation_accepter + +Provides a resource to manage an [Amazon Macie Invitation Accepter](https://docs.aws.amazon.com/macie/latest/APIReference/invitations-accept.html). + +## Example Usage + +```terraform +resource "aws_macie2_account" "primary" { + provider = "awsalternate" +} + +resource "aws_macie2_account" "member" {} + +resource "aws_macie2_member" "primary" { + provider = "awsalternate" + account_id = "ACCOUNT ID" + email = "EMAIL" + depends_on = [aws_macie2_account.primary] +} + +resource "aws_macie2_invitation" "primary" { + provider = "awsalternate" + account_ids = ["ACCOUNT IDS"] + depends_on = [aws_macie2_member.primary] +} + +resource "aws_macie2_invitation_accepter" "test" { + administrator_account_id = "ADMINISTRATOR ACCOUNT ID" + depends_on = [aws_macie2_invitation.primary] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `administrator_account_id` - (Required) The AWS account ID for the account that sent the invitation. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique identifier (ID) of the macie invitation accepter. +* `invitation_id` - The unique identifier for the invitation. + +## Import + +`aws_macie2_invitation_accepter` can be imported using the id, e.g. + +``` +$ terraform import aws_macie2_invitation_accepter.example abcd1 +``` diff --git a/website/docs/r/macie2_member.html.markdown b/website/docs/r/macie2_member.html.markdown new file mode 100644 index 000000000000..59df700997b6 --- /dev/null +++ b/website/docs/r/macie2_member.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "Macie" +layout: "aws" +page_title: "AWS: aws_macie2_member" +description: |- + Provides a resource to manage an AWS Macie Member. +--- + +# Resource: aws_macie2_member + +Provides a resource to manage an [AWS Macie Member](https://docs.aws.amazon.com/macie/latest/APIReference/members-id.html). + +## Example Usage + +```terraform +resource "aws_macie2_account" "example" {} + + +resource "aws_macie2_member" "test" { + account_id = "NAME OF THE MEMBER" + email = "EMAIL" + depends_on = [aws_macie2_account.test] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `account_id` - (Required) The AWS account ID for the account. +* `email` - (Required) The email address for the account. +* `tags` - (Optional) A map of key-value pairs that specifies the tags to associate with the account in Amazon Macie. +* `status` - (Optional) Specifies the status for the account. To enable Amazon Macie and start all Macie activities for the account, set this value to `ENABLED`. Valid values are `ENABLED` or `PAUSED`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The unique identifier (ID) of the macie Member. +* `arn` - The Amazon Resource Name (ARN) of the account. +* `relationship_status` - The current status of the relationship between the account and the administrator account. +* `administrator_account_id` - The AWS account ID for the administrator account. +* `invited_at` - The date and time, in UTC and extended RFC 3339 format, when an Amazon Macie membership invitation was last sent to the account. This value is null if a Macie invitation hasn't been sent to the account. +* `updated_at` - The date and time, in UTC and extended RFC 3339 format, of the most recent change to the status of the relationship between the account and the administrator account. + +## Import + +`aws_macie2_member` can be imported using the id, e.g. + +``` +$ terraform import aws_macie2_member.example abcd1 +``` From 47c40386cdc410500cd069d014b12486ccb13812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Mon, 10 May 2021 17:34:03 -0600 Subject: [PATCH 02/18] added changelog file --- .changelog/19304.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changelog/19304.txt diff --git a/.changelog/19304.txt b/.changelog/19304.txt new file mode 100644 index 000000000000..32f4808b0c0d --- /dev/null +++ b/.changelog/19304.txt @@ -0,0 +1,11 @@ +```release-note:new-resource +aws_macie2_member +``` + +```release-note:new-resource +aws_macie2_invitation +``` + +```release-note:new-resource +aws_macie2_invitation_accepter +``` From e37b7c48da67e2e4b183db38b2e369d9f8000c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Mon, 10 May 2021 17:34:54 -0600 Subject: [PATCH 03/18] docs: changed aws to amazon --- website/docs/r/macie2_member.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/macie2_member.html.markdown b/website/docs/r/macie2_member.html.markdown index 59df700997b6..6ca0fff5baa1 100644 --- a/website/docs/r/macie2_member.html.markdown +++ b/website/docs/r/macie2_member.html.markdown @@ -3,12 +3,12 @@ subcategory: "Macie" layout: "aws" page_title: "AWS: aws_macie2_member" description: |- - Provides a resource to manage an AWS Macie Member. + Provides a resource to manage an Amazon Macie Member. --- # Resource: aws_macie2_member -Provides a resource to manage an [AWS Macie Member](https://docs.aws.amazon.com/macie/latest/APIReference/members-id.html). +Provides a resource to manage an [Amazon Macie Member](https://docs.aws.amazon.com/macie/latest/APIReference/members-id.html). ## Example Usage From 850a0ba281ee2c03907801de7a3cf44370842638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Tue, 11 May 2021 17:16:29 -0600 Subject: [PATCH 04/18] made changes suggested --- aws/resource_aws_macie2_invitation.go | 70 ++++++++++--- ...rce_aws_macie2_invitation_accepter_test.go | 70 ++++++------- aws/resource_aws_macie2_invitation_test.go | 99 ++++++++++++++----- aws/resource_aws_macie2_member.go | 5 + aws/resource_aws_macie2_member_test.go | 69 ++++++++----- aws/resource_aws_macie2_test.go | 19 ---- .../docs/r/macie2_invitation.html.markdown | 2 +- website/docs/r/macie2_member.html.markdown | 3 +- 8 files changed, 222 insertions(+), 115 deletions(-) diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index ef60c4764c97..e6e2b821e076 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -3,6 +3,7 @@ package aws import ( "context" "fmt" + "log" "strings" "time" @@ -25,20 +26,20 @@ func resourceAwsMacie2Invitation() *schema.Resource { Optional: true, ForceNew: true, }, - "account_ids": { - Type: schema.TypeList, + "account_id": { + Type: schema.TypeString, Required: true, ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, }, "message": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - }, - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(1 * time.Minute), + "invited_at": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -46,8 +47,9 @@ func resourceAwsMacie2Invitation() *schema.Resource { func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).macie2conn + accountID := d.Get("account_id").(string) input := &macie2.CreateInvitationsInput{ - AccountIds: expandStringList(d.Get("account_ids").([]interface{})), + AccountIds: []*string{aws.String(accountID)}, } if v, ok := d.GetOk("disable_email_notification"); ok { @@ -58,8 +60,9 @@ func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, } var err error + var output *macie2.CreateInvitationsOutput err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { - _, err := conn.CreateInvitationsWithContext(ctx, input) + output, err = conn.CreateInvitationsWithContext(ctx, input) if tfawserr.ErrCodeEquals(err, macie2.ErrorCodeClientError) { return resource.RetryableError(err) @@ -69,23 +72,63 @@ func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, return resource.NonRetryableError(err) } + if len(output.UnprocessedAccounts) > 0 { + return resource.NonRetryableError(err) + } + return nil }) if isResourceTimeoutError(err) { - _, err = conn.CreateInvitationsWithContext(ctx, input) + output, err = conn.CreateInvitationsWithContext(ctx, input) } if err != nil { return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %w", err)) } + if len(output.UnprocessedAccounts) != 0 { + return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %w", fmt.Errorf("%s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage)))) + } + d.SetId(meta.(*AWSClient).accountid) return resourceMacie2InvitationRead(ctx, d, meta) } -func resourceMacie2InvitationRead(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { +func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).macie2conn + + var err error + + input := &macie2.ListMembersInput{ + OnlyAssociated: aws.String("false"), + } + var result *macie2.Member + err = conn.ListMembersPages(input, func(page *macie2.ListMembersOutput, lastPage bool) bool { + for _, member := range page.Members { + if aws.StringValue(member.AdministratorAccountId) == d.Id() { + result = member + return false + } + } + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + log.Printf("[WARN] Macie Invitation (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error reading Macie Invitation (%s): %w", d.Id(), err)) + } + + d.Set("invited_at", aws.TimeValue(result.InvitedAt).Format(time.RFC3339)) + d.Set("account_id", result.AccountId) + return nil } @@ -93,7 +136,7 @@ func resourceMacie2InvitationDelete(ctx context.Context, d *schema.ResourceData, conn := meta.(*AWSClient).macie2conn input := &macie2.DeleteInvitationsInput{ - AccountIds: expandStringList(d.Get("account_ids").([]interface{})), + AccountIds: []*string{aws.String(d.Id())}, } output, err := conn.DeleteInvitationsWithContext(ctx, input) @@ -110,5 +153,10 @@ func resourceMacie2InvitationDelete(ctx context.Context, d *schema.ResourceData, return nil } } + + if len(output.UnprocessedAccounts) != 0 { + return diag.FromErr(fmt.Errorf("error deleting Macie Invitation: %w", fmt.Errorf("%s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage)))) + } + return nil } diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index 387cc250ad9a..56bea06616db 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/guardduty" "github.com/aws/aws-sdk-go/service/macie2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -16,9 +15,7 @@ import ( func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation_accepter.test" - adminAccountID := "124861550386" - accountID, email := testAccAWSMacie2MemberFromEnv(t) - accountIds := []string{accountID} + email := "required@example.com" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -30,13 +27,13 @@ func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID, accountIds), + Config: testAccAwsMacieInvitationAccepterConfigBasic(email), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2InvitationAccepterExists(resourceName), ), }, { - Config: testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID, accountIds), + Config: testAccAwsMacieInvitationAccepterConfigBasic(email), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -48,9 +45,7 @@ func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation_accepter.test" - adminAccountID := "124861550386" - accountID, email := testAccAWSMacie2MemberFromEnv(t) - accountIds := []string{accountID} + email := "required@example.com" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -62,19 +57,19 @@ func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(accountID, email, adminAccountID, macie2.MacieStatusEnabled, accountIds), + Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusEnabled), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2InvitationAccepterExists(resourceName), ), }, { - Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(accountID, email, adminAccountID, macie2.MacieStatusPaused, accountIds), + Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusPaused), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2InvitationAccepterExists(resourceName), ), }, { - Config: testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID, accountIds), + Config: testAccAwsMacieInvitationAccepterConfigBasic(email), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -120,11 +115,6 @@ func testAccCheckAwsMacie2InvitationAccepterDestroy(s *terraform.State) error { input := &macie2.GetAdministratorAccountInput{} output, err := conn.GetAdministratorAccount(input) - - if isAWSErr(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") { - return nil - } - if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { continue @@ -142,8 +132,14 @@ func testAccCheckAwsMacie2InvitationAccepterDestroy(s *terraform.State) error { } -func testAccAwsMacieInvitationAccepterConfigBasic(accountID, email, adminAccountID string, accountIDs []string) string { +func testAccAwsMacieInvitationAccepterConfigBasic(email string) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "primary" { + provider = "awsalternate" +} + +data "aws_caller_identity" "current" {} + resource "aws_macie2_account" "primary" { provider = "awsalternate" } @@ -152,27 +148,33 @@ resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "primary" { provider = "awsalternate" - account_id = %[1]q - email = %[2]q + account_id = data.aws_caller_identity.current.account_id + email = %[1]q depends_on = [aws_macie2_account.primary] } resource "aws_macie2_invitation" "primary" { - provider = "awsalternate" - account_ids = %[3]q - depends_on = [aws_macie2_member.primary] + provider = "awsalternate" + account_id = data.aws_caller_identity.current.account_id + depends_on = [aws_macie2_member.primary] } resource "aws_macie2_invitation_accepter" "test" { - administrator_account_id = %[4]q + administrator_account_id = data.aws_caller_identity.primary.account_id depends_on = [aws_macie2_invitation.primary] } -`, accountID, email, accountIDs, adminAccountID) +`, email) } -func testAccAwsMacieInvitationAccepterConfigMemberStatus(accountID, email, adminAccountID, memberStatus string, accountIDs []string) string { +func testAccAwsMacieInvitationAccepterConfigMemberStatus(email, memberStatus string) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "primary" { + provider = "awsalternate" +} + +data "aws_caller_identity" "current" {} + resource "aws_macie2_account" "primary" { provider = "awsalternate" } @@ -181,22 +183,22 @@ resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "primary" { provider = "awsalternate" - account_id = %[1]q - email = %[2]q - status = %[5]q + account_id = data.aws_caller_identity.current.account_id + email = %[1]q + status = %[2]q depends_on = [aws_macie2_account.primary] } resource "aws_macie2_invitation" "primary" { - provider = "awsalternate" - account_ids = %[3]q - depends_on = [aws_macie2_member.primary] + provider = "awsalternate" + account_id = data.aws_caller_identity.current.account_id + depends_on = [aws_macie2_member.primary] } resource "aws_macie2_invitation_accepter" "test" { - administrator_account_id = %[4]q + administrator_account_id = data.aws_caller_identity.primary.account_id depends_on = [aws_macie2_invitation.primary] } -`, accountID, email, accountIDs, adminAccountID, memberStatus) +`, email, memberStatus) } diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go index 62a67de41fde..5bfadf6b4d73 100644 --- a/aws/resource_aws_macie2_invitation_test.go +++ b/aws/resource_aws_macie2_invitation_test.go @@ -4,44 +4,57 @@ import ( "fmt" "testing" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/macie2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func testAccAwsMacie2Invitation_basic(t *testing.T) { - accountID, email := testAccAWSMacie2MemberFromEnv(t) - accountIds := []string{accountID} + var providers []*schema.Provider + resourceName := "aws_macie2_invitation.test" + email := "required@example.com" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsMacie2InvitationDestroy, ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieInvitationConfigBasic(accountID, email, accountIds), - Check: resource.ComposeTestCheckFunc(), + Config: testAccAwsMacieInvitationConfigBasic(email), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationExists(resourceName), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + ), }, }, }) } func testAccAwsMacie2Invitation_disappears(t *testing.T) { + var providers []*schema.Provider resourceName := "aws_macie2_invitation.test" - accountID, email := testAccAWSMacie2MemberFromEnv(t) - accountIds := []string{accountID} + email := "required@example.com" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsMacie2InvitationDestroy, ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieInvitationConfigBasic(accountID, email, accountIds), + Config: testAccAwsMacieInvitationConfigBasic(email), Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationExists(resourceName), testAccCheckResourceDisappears(testAccProvider, resourceAwsMacie2Invitation(), resourceName), ), }, @@ -49,6 +62,41 @@ func testAccAwsMacie2Invitation_disappears(t *testing.T) { }) } +func testAccCheckAwsMacie2InvitationExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("resource (%s) has empty ID", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).macie2conn + exists := false + err := conn.ListMembersPages(&macie2.ListMembersInput{OnlyAssociated: aws.String("false")}, func(page *macie2.ListMembersOutput, lastPage bool) bool { + for _, member := range page.Members { + if aws.StringValue(member.AdministratorAccountId) == rs.Primary.ID { + exists = true + return false + } + } + return true + }) + + if err != nil { + return err + } + + if !exists { + return fmt.Errorf("no administrator account found for: %s", resourceName) + } + + return nil + } +} + func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).macie2conn @@ -58,12 +106,13 @@ func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { } empty := true - err := conn.ListInvitationsPages(&macie2.ListInvitationsInput{}, func(page *macie2.ListInvitationsOutput, lastPage bool) bool { - if len(page.Invitations) > 0 { - empty = false - return false + err := conn.ListMembersPages(&macie2.ListMembersInput{OnlyAssociated: aws.String("false")}, func(page *macie2.ListMembersOutput, lastPage bool) bool { + for _, member := range page.Members { + if aws.StringValue(member.AdministratorAccountId) == rs.Primary.ID { + empty = false + return false + } } - return true }) @@ -85,19 +134,23 @@ func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { } -func testAccAwsMacieInvitationConfigBasic(accountID, email string, accountIDs []string) string { - return fmt.Sprintf(` +func testAccAwsMacieInvitationConfigBasic(email string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "inviter" { + provider = "awsalternate" +} + resource "aws_macie2_account" "test" {} resource "aws_macie2_member" "test" { - account_id = %[1]q - email = %[2]q + account_id = data.aws_caller_identity.inviter.account_id + email = %[1]q depends_on = [aws_macie2_account.test] } resource "aws_macie2_invitation" "test" { - account_ids = %[3]q - depends_on = [aws_macie2_member.test] + account_id = data.aws_caller_identity.inviter.account_id + depends_on = [aws_macie2_member.test] } -`, accountID, email, accountIDs) +`, email) } diff --git a/aws/resource_aws_macie2_member.go b/aws/resource_aws_macie2_member.go index 71839179c3d2..af66b37cd904 100644 --- a/aws/resource_aws_macie2_member.go +++ b/aws/resource_aws_macie2_member.go @@ -50,6 +50,10 @@ func resourceAwsMacie2Member() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "master_account_id": { + Type: schema.TypeString, + Computed: true, + }, "invited_at": { Type: schema.TypeString, Computed: true, @@ -143,6 +147,7 @@ func resourceMacie2MemberRead(ctx context.Context, d *schema.ResourceData, meta d.Set("email", resp.Email) d.Set("relationship_status", resp.RelationshipStatus) d.Set("administrator_account_id", resp.AdministratorAccountId) + d.Set("master_account_id", resp.MasterAccountId) d.Set("invited_at", aws.TimeValue(resp.InvitedAt).Format(time.RFC3339)) d.Set("updated_at", aws.TimeValue(resp.UpdatedAt).Format(time.RFC3339)) d.Set("arn", resp.Arn) diff --git a/aws/resource_aws_macie2_member_test.go b/aws/resource_aws_macie2_member_test.go index 40e00e793444..7b93c2bec5d3 100644 --- a/aws/resource_aws_macie2_member_test.go +++ b/aws/resource_aws_macie2_member_test.go @@ -8,23 +8,27 @@ import ( "github.com/aws/aws-sdk-go/service/macie2" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func testAccAwsMacie2Member_basic(t *testing.T) { + var providers []*schema.Provider var macie2Output macie2.GetMemberOutput resourceName := "aws_macie2_member.test" - accountID := "520433213222" - email := "test@test.com" + email := "required@example.com" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsMacie2MemberDestroy, ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieMemberConfigBasic(accountID, email), + Config: testAccAwsMacieMemberConfigBasic(email), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusCreated), @@ -33,6 +37,7 @@ func testAccAwsMacie2Member_basic(t *testing.T) { ), }, { + Config: testAccAwsMacieMemberConfigBasic(email), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -42,19 +47,22 @@ func testAccAwsMacie2Member_basic(t *testing.T) { } func testAccAwsMacie2Member_disappears(t *testing.T) { + var providers []*schema.Provider var macie2Output macie2.GetMemberOutput resourceName := "aws_macie2_member.test" - accountID := "520433213222" - email := "test@test.com" + email := "required@example.com" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsMacie2MemberDestroy, ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieMemberConfigBasic(accountID, email), + Config: testAccAwsMacieMemberConfigBasic(email), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), testAccCheckResourceDisappears(testAccProvider, resourceAwsMacie2Member(), resourceName), @@ -66,19 +74,22 @@ func testAccAwsMacie2Member_disappears(t *testing.T) { } func testAccAwsMacie2Member_withTags(t *testing.T) { + var providers []*schema.Provider var macie2Output macie2.GetMemberOutput resourceName := "aws_macie2_member.test" - accountID := "520433213222" - email := "test@test.com" + email := "required@example.com" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProviderFactories: testAccProviderFactories, + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), CheckDestroy: testAccCheckAwsMacie2MemberDestroy, ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieMemberConfigWithTags(accountID, email), + Config: testAccAwsMacieMemberConfigWithTags(email), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), @@ -155,29 +166,37 @@ func testAccCheckAwsMacie2MemberDestroy(s *terraform.State) error { } -func testAccAwsMacieMemberConfigBasic(accountID, email string) string { - return fmt.Sprintf(` +func testAccAwsMacieMemberConfigBasic(accountID string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "inviter" { + provider = "awsalternate" +} + resource "aws_macie2_account" "test" {} resource "aws_macie2_member" "test" { - account_id = %[1]q - email = %[2]q + account_id = data.aws_caller_identity.inviter.account_id + email = %[1]q depends_on = [aws_macie2_account.test] } -`, accountID, email) +`, accountID) +} + +func testAccAwsMacieMemberConfigWithTags(email string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "inviter" { + provider = "awsalternate" } -func testAccAwsMacieMemberConfigWithTags(accountID, email string) string { - return fmt.Sprintf(` resource "aws_macie2_account" "test" {} resource "aws_macie2_member" "test" { - account_id = %[1]q - email = %[2]q + account_id = data.aws_caller_identity.inviter.account_id + email = %[1]q tags = { Key = "value" } depends_on = [aws_macie2_account.test] } -`, accountID, email) +`, email) } diff --git a/aws/resource_aws_macie2_test.go b/aws/resource_aws_macie2_test.go index ad0af16d2d87..38285120c7c5 100644 --- a/aws/resource_aws_macie2_test.go +++ b/aws/resource_aws_macie2_test.go @@ -1,7 +1,6 @@ package aws import ( - "os" "testing" ) @@ -59,21 +58,3 @@ func TestAccAWSMacie2_serial(t *testing.T) { }) } } - -func testAccAWSMacie2MemberFromEnv(t *testing.T) (string, string) { - accountID := os.Getenv("AWS_MACIE_MEMBER_ACCOUNT_ID") - if accountID == "" { - t.Skip( - "Environment variable AWS_MACIE_MEMBER_ACCOUNT_ID is not set. " + - "To properly test inviting MACIE member accounts, " + - "a valid AWS account ID must be provided.") - } - email := os.Getenv("AWS_MACIE_MEMBER_EMAIL") - if email == "" { - t.Skip( - "Environment variable AWS_MACIE_MEMBER_EMAIL is not set. " + - "To properly test inviting MACIE member accounts, " + - "a valid email associated with the AWS_MACIE_MEMBER_ACCOUNT_ID must be provided.") - } - return accountID, email -} diff --git a/website/docs/r/macie2_invitation.html.markdown b/website/docs/r/macie2_invitation.html.markdown index 5a4715002525..871eb243cec9 100644 --- a/website/docs/r/macie2_invitation.html.markdown +++ b/website/docs/r/macie2_invitation.html.markdown @@ -32,7 +32,7 @@ resource "aws_macie2_invitation" "test" { The following arguments are supported: * `account_ids` - (Required) An array that lists AWS account IDs, one for each account to send the invitation to. -* `administrator_account_id` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. +* `disable_email_notification` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. * `message` - (Optional) A custom message to include in the invitation. Amazon Macie adds this message to the standard content that it sends for an invitation. ## Attributes Reference diff --git a/website/docs/r/macie2_member.html.markdown b/website/docs/r/macie2_member.html.markdown index 6ca0fff5baa1..49c1d0dce186 100644 --- a/website/docs/r/macie2_member.html.markdown +++ b/website/docs/r/macie2_member.html.markdown @@ -15,9 +15,8 @@ Provides a resource to manage an [Amazon Macie Member](https://docs.aws.amazon.c ```terraform resource "aws_macie2_account" "example" {} - resource "aws_macie2_member" "test" { - account_id = "NAME OF THE MEMBER" + account_id = "AWS ACCOUNT ID" email = "EMAIL" depends_on = [aws_macie2_account.test] } From 9d808f31a7ed1116fc6b89c7ec57249e0980d537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 01:59:56 -0600 Subject: [PATCH 05/18] added validation for relationship status --- aws/resource_aws_macie2_invitation.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index e6e2b821e076..eaae58de23b7 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -116,7 +116,8 @@ func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, m }) if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || - tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") || + result == nil { log.Printf("[WARN] Macie Invitation (%s) not found, removing from state", d.Id()) d.SetId("") return nil @@ -126,6 +127,17 @@ func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(fmt.Errorf("error reading Macie Invitation (%s): %w", d.Id(), err)) } + if aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusRemoved || + aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusResigned { + log.Printf("[WARN] Macie InvitationAccepter (%s) %s, removing from state", d.Id(), aws.StringValue(result.RelationshipStatus)) + d.SetId("") + return nil + } + + if aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusEmailVerificationFailed { + return diag.FromErr(fmt.Errorf("error reading Macie Invitation: %s", macie2.RelationshipStatusEmailVerificationFailed)) + } + d.Set("invited_at", aws.TimeValue(result.InvitedAt).Format(time.RFC3339)) d.Set("account_id", result.AccountId) From 5cd19d4448de800aa9deec75f646a9c44f5a1301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 02:25:16 -0600 Subject: [PATCH 06/18] added import for invitation --- aws/resource_aws_macie2_invitation.go | 3 +++ aws/resource_aws_macie2_invitation_test.go | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index eaae58de23b7..e0283600cbeb 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -20,6 +20,9 @@ func resourceAwsMacie2Invitation() *schema.Resource { CreateWithoutTimeout: resourceMacie2InvitationCreate, ReadWithoutTimeout: resourceMacie2InvitationRead, DeleteWithoutTimeout: resourceMacie2InvitationDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, Schema: map[string]*schema.Schema{ "disable_email_notification": { Type: schema.TypeString, diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go index 5bfadf6b4d73..1b0a33be92d2 100644 --- a/aws/resource_aws_macie2_invitation_test.go +++ b/aws/resource_aws_macie2_invitation_test.go @@ -33,6 +33,12 @@ func testAccAwsMacie2Invitation_basic(t *testing.T) { testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), ), }, + { + Config: testAccAwsMacieInvitationConfigBasic(email), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -58,6 +64,12 @@ func testAccAwsMacie2Invitation_disappears(t *testing.T) { testAccCheckResourceDisappears(testAccProvider, resourceAwsMacie2Invitation(), resourceName), ), }, + { + Config: testAccAwsMacieInvitationConfigBasic(email), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } From 01b74efbf3b1dcfae0ddfafc43c102da00b28f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 02:31:22 -0600 Subject: [PATCH 07/18] docs: added import for docs invitation --- website/docs/r/macie2_invitation.html.markdown | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/docs/r/macie2_invitation.html.markdown b/website/docs/r/macie2_invitation.html.markdown index 871eb243cec9..e26b0c478638 100644 --- a/website/docs/r/macie2_invitation.html.markdown +++ b/website/docs/r/macie2_invitation.html.markdown @@ -40,3 +40,11 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: * `id` - The unique identifier (ID) of the macie invitation. + +## Import + +`aws_macie2_invitation` can be imported using the id, e.g. + +``` +$ terraform import aws_macie2_invitation.example abcd1 +``` From f323d8437ea12d47c336e524c8deb16b90e92aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 02:59:49 -0600 Subject: [PATCH 08/18] docs: added invited_at in attributes reference --- website/docs/r/macie2_invitation.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/r/macie2_invitation.html.markdown b/website/docs/r/macie2_invitation.html.markdown index e26b0c478638..1b853aea867f 100644 --- a/website/docs/r/macie2_invitation.html.markdown +++ b/website/docs/r/macie2_invitation.html.markdown @@ -40,6 +40,7 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: * `id` - The unique identifier (ID) of the macie invitation. +* `invited_at` - The date and time, in UTC and extended RFC 3339 format, when an Amazon Macie membership invitation was last sent to the account. This value is null if a Macie invitation hasn't been sent to the account. ## Import From f58166a63a24d5ff6f769c02dc65c79a3fac8e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 10:59:31 -0600 Subject: [PATCH 09/18] changed validation for email verification to only warning instead of return an error --- aws/resource_aws_macie2_invitation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index e0283600cbeb..ef590ed32c1a 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -138,7 +138,7 @@ func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, m } if aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusEmailVerificationFailed { - return diag.FromErr(fmt.Errorf("error reading Macie Invitation: %s", macie2.RelationshipStatusEmailVerificationFailed)) + log.Printf("[WARN] Macie InvitationAccepter (%s) %s", d.Id(), aws.StringValue(result.RelationshipStatus)) } d.Set("invited_at", aws.TimeValue(result.InvitedAt).Format(time.RFC3339)) From f0735bb4eb8fb6df8569ef674f7960e37a9ac6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 10:59:54 -0600 Subject: [PATCH 10/18] added env var for email, if empty will send a example email --- aws/resource_aws_macie2_invitation_accepter_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index 56bea06616db..e245607d6375 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "os" "testing" "github.com/aws/aws-sdk-go/aws" @@ -15,7 +16,10 @@ import ( func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation_accepter.test" - email := "required@example.com" + email := os.Getenv("AWS_MACIE_MEMBER_EMAIL") + if email == "" { + email = "required@example.com" + } resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -45,7 +49,10 @@ func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation_accepter.test" - email := "required@example.com" + email := os.Getenv("AWS_MACIE_MEMBER_EMAIL") + if email == "" { + email = "required@example.com" + } resource.Test(t, resource.TestCase{ PreCheck: func() { From dd0478dc94bf7e5873c78a15df53a2f8ed43d41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 15:46:58 -0600 Subject: [PATCH 11/18] added waiter for list macie members --- aws/internal/service/macie2/waiter/status.go | 53 ++++++++++++++++++++ aws/internal/service/macie2/waiter/waiter.go | 32 ++++++++++++ aws/resource_aws_macie2_invitation.go | 16 ++---- 3 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 aws/internal/service/macie2/waiter/status.go create mode 100644 aws/internal/service/macie2/waiter/waiter.go diff --git a/aws/internal/service/macie2/waiter/status.go b/aws/internal/service/macie2/waiter/status.go new file mode 100644 index 000000000000..5e5c845d500a --- /dev/null +++ b/aws/internal/service/macie2/waiter/status.go @@ -0,0 +1,53 @@ +package waiter + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +// MemberRelationshipStatus fetches the Member and its relationship status +func MemberRelationshipStatus(conn *macie2.Macie2, adminAccountID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + adminAccount, err := getMemberNotAssociated(conn, adminAccountID) + + if err != nil { + return nil, "Unknown", err + } + + if adminAccount == nil { + return adminAccount, "NotFound", nil + } + + return adminAccount, aws.StringValue(adminAccount.RelationshipStatus), nil + } +} + +// TODO: Migrate to shared internal package for aws package and this package +func getMemberNotAssociated(conn *macie2.Macie2, adminAccountID string) (*macie2.Member, error) { + input := &macie2.ListMembersInput{ + OnlyAssociated: aws.String("false"), + } + var result *macie2.Member + + err := conn.ListMembersPages(input, func(page *macie2.ListMembersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, member := range page.Members { + if member == nil { + continue + } + + if aws.StringValue(member.AdministratorAccountId) == adminAccountID { + result = member + return false + } + } + + return !lastPage + }) + + return result, err +} diff --git a/aws/internal/service/macie2/waiter/waiter.go b/aws/internal/service/macie2/waiter/waiter.go new file mode 100644 index 000000000000..7ee204c84e38 --- /dev/null +++ b/aws/internal/service/macie2/waiter/waiter.go @@ -0,0 +1,32 @@ +package waiter + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + // Maximum amount of time to wait for an Member to return NotFound + MemeberNotFoundTimeout = 5 * time.Minute +) + +// MemberInvited waits for an AdminAccount to return Invited, Enabled and Paused +func MemberInvited(ctx context.Context, conn *macie2.Macie2, adminAccountID string) (*macie2.Member, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{macie2.RelationshipStatusCreated, macie2.RelationshipStatusEmailVerificationInProgress}, + Target: []string{macie2.RelationshipStatusInvited, macie2.RelationshipStatusEnabled, macie2.RelationshipStatusPaused}, + Refresh: MemberRelationshipStatus(conn, adminAccountID), + Timeout: MemeberNotFoundTimeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*macie2.Member); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index ef590ed32c1a..becb771c199a 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/macie2/waiter" ) func resourceAwsMacie2Invitation() *schema.Resource { @@ -96,6 +97,10 @@ func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, d.SetId(meta.(*AWSClient).accountid) + if _, err = waiter.MemberInvited(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for Macie Invitation (%s) creation: %w", d.Id(), err)) + } + return resourceMacie2InvitationRead(ctx, d, meta) } @@ -130,17 +135,6 @@ func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(fmt.Errorf("error reading Macie Invitation (%s): %w", d.Id(), err)) } - if aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusRemoved || - aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusResigned { - log.Printf("[WARN] Macie InvitationAccepter (%s) %s, removing from state", d.Id(), aws.StringValue(result.RelationshipStatus)) - d.SetId("") - return nil - } - - if aws.StringValue(result.RelationshipStatus) == macie2.RelationshipStatusEmailVerificationFailed { - log.Printf("[WARN] Macie InvitationAccepter (%s) %s", d.Id(), aws.StringValue(result.RelationshipStatus)) - } - d.Set("invited_at", aws.TimeValue(result.InvitedAt).Format(time.RFC3339)) d.Set("account_id", result.AccountId) From 2ee7fc2ec2f5331fea51665c522a4c415c50d5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 15:47:37 -0600 Subject: [PATCH 12/18] added skip test with env var --- aws/resource_aws_macie2_invitation_accepter_test.go | 12 +++--------- aws/resource_aws_macie2_invitation_test.go | 11 +++++++++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index e245607d6375..5a4301e53f8c 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -2,7 +2,6 @@ package aws import ( "fmt" - "os" "testing" "github.com/aws/aws-sdk-go/aws" @@ -11,15 +10,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/envvar" ) func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation_accepter.test" - email := os.Getenv("AWS_MACIE_MEMBER_EMAIL") - if email == "" { - email = "required@example.com" - } + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -49,10 +46,7 @@ func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation_accepter.test" - email := os.Getenv("AWS_MACIE_MEMBER_EMAIL") - if email == "" { - email = "required@example.com" - } + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go index 1b0a33be92d2..9691235044ae 100644 --- a/aws/resource_aws_macie2_invitation_test.go +++ b/aws/resource_aws_macie2_invitation_test.go @@ -10,12 +10,19 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/envvar" +) + +const ( + EnvVarMacie2MemberEmail = "AWS_MACIE2_MEMBER_EMAIL" + EnvVarMacie2MemberEmailMessageError = "Environment variable AWS_MACIE2_MEMBER_EMAIL is not set. " + + "To properly test inviting Macie member account must be provided." ) func testAccAwsMacie2Invitation_basic(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation.test" - email := "required@example.com" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -46,7 +53,7 @@ func testAccAwsMacie2Invitation_basic(t *testing.T) { func testAccAwsMacie2Invitation_disappears(t *testing.T) { var providers []*schema.Provider resourceName := "aws_macie2_invitation.test" - email := "required@example.com" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { From b9613d30eb53312dd53be8c6ff1a58f939075c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Wed, 12 May 2021 19:00:44 -0600 Subject: [PATCH 13/18] made changes suggested --- aws/internal/service/macie2/finder/finder.go | 35 ++++++++++++ aws/internal/service/macie2/waiter/status.go | 32 +---------- aws/internal/service/macie2/waiter/waiter.go | 6 +- aws/resource_aws_macie2_invitation.go | 10 ++-- ...rce_aws_macie2_invitation_accepter_test.go | 50 ++-------------- aws/resource_aws_macie2_invitation_test.go | 18 +++--- aws/resource_aws_macie2_member_test.go | 57 +++++++++++++++---- .../docs/r/macie2_invitation.html.markdown | 16 +++--- .../macie2_invitation_accepter.html.markdown | 4 +- website/docs/r/macie2_member.html.markdown | 8 +-- 10 files changed, 121 insertions(+), 115 deletions(-) create mode 100644 aws/internal/service/macie2/finder/finder.go diff --git a/aws/internal/service/macie2/finder/finder.go b/aws/internal/service/macie2/finder/finder.go new file mode 100644 index 000000000000..b9a895ef8475 --- /dev/null +++ b/aws/internal/service/macie2/finder/finder.go @@ -0,0 +1,35 @@ +package finder + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/macie2" +) + +// GetMemberNotAssociated Return a list of members not associated and compare with account ID +func GetMemberNotAssociated(conn *macie2.Macie2, accountID string) (*macie2.Member, error) { + input := &macie2.ListMembersInput{ + OnlyAssociated: aws.String("false"), + } + var result *macie2.Member + + err := conn.ListMembersPages(input, func(page *macie2.ListMembersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, member := range page.Members { + if member == nil { + continue + } + + if aws.StringValue(member.AccountId) == accountID { + result = member + return false + } + } + + return !lastPage + }) + + return result, err +} diff --git a/aws/internal/service/macie2/waiter/status.go b/aws/internal/service/macie2/waiter/status.go index 5e5c845d500a..7c2f58ea91b7 100644 --- a/aws/internal/service/macie2/waiter/status.go +++ b/aws/internal/service/macie2/waiter/status.go @@ -4,12 +4,13 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/macie2" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/macie2/finder" ) // MemberRelationshipStatus fetches the Member and its relationship status func MemberRelationshipStatus(conn *macie2.Macie2, adminAccountID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - adminAccount, err := getMemberNotAssociated(conn, adminAccountID) + adminAccount, err := finder.GetMemberNotAssociated(conn, adminAccountID) if err != nil { return nil, "Unknown", err @@ -22,32 +23,3 @@ func MemberRelationshipStatus(conn *macie2.Macie2, adminAccountID string) resour return adminAccount, aws.StringValue(adminAccount.RelationshipStatus), nil } } - -// TODO: Migrate to shared internal package for aws package and this package -func getMemberNotAssociated(conn *macie2.Macie2, adminAccountID string) (*macie2.Member, error) { - input := &macie2.ListMembersInput{ - OnlyAssociated: aws.String("false"), - } - var result *macie2.Member - - err := conn.ListMembersPages(input, func(page *macie2.ListMembersOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, member := range page.Members { - if member == nil { - continue - } - - if aws.StringValue(member.AdministratorAccountId) == adminAccountID { - result = member - return false - } - } - - return !lastPage - }) - - return result, err -} diff --git a/aws/internal/service/macie2/waiter/waiter.go b/aws/internal/service/macie2/waiter/waiter.go index 7ee204c84e38..7811f6c54dfa 100644 --- a/aws/internal/service/macie2/waiter/waiter.go +++ b/aws/internal/service/macie2/waiter/waiter.go @@ -9,8 +9,8 @@ import ( ) const ( - // Maximum amount of time to wait for an Member to return NotFound - MemeberNotFoundTimeout = 5 * time.Minute + // Maximum amount of time to wait for the MemberRelationshipStatus to be Invited, Enabled, or Paused + MemberInvitedTimeout = 5 * time.Minute ) // MemberInvited waits for an AdminAccount to return Invited, Enabled and Paused @@ -19,7 +19,7 @@ func MemberInvited(ctx context.Context, conn *macie2.Macie2, adminAccountID stri Pending: []string{macie2.RelationshipStatusCreated, macie2.RelationshipStatusEmailVerificationInProgress}, Target: []string{macie2.RelationshipStatusInvited, macie2.RelationshipStatusEnabled, macie2.RelationshipStatusPaused}, Refresh: MemberRelationshipStatus(conn, adminAccountID), - Timeout: MemeberNotFoundTimeout, + Timeout: MemberInvitedTimeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index becb771c199a..2144c773d864 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -76,7 +76,7 @@ func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, return resource.NonRetryableError(err) } - if len(output.UnprocessedAccounts) > 0 { + if len(output.UnprocessedAccounts) != 0 { return resource.NonRetryableError(err) } @@ -92,10 +92,10 @@ func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, } if len(output.UnprocessedAccounts) != 0 { - return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %w", fmt.Errorf("%s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage)))) + return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage))) } - d.SetId(meta.(*AWSClient).accountid) + d.SetId(accountID) if _, err = waiter.MemberInvited(ctx, conn, d.Id()); err != nil { return diag.FromErr(fmt.Errorf("error waiting for Macie Invitation (%s) creation: %w", d.Id(), err)) @@ -115,7 +115,7 @@ func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, m var result *macie2.Member err = conn.ListMembersPages(input, func(page *macie2.ListMembersOutput, lastPage bool) bool { for _, member := range page.Members { - if aws.StringValue(member.AdministratorAccountId) == d.Id() { + if aws.StringValue(member.AccountId) == d.Id() { result = member return false } @@ -164,7 +164,7 @@ func resourceMacie2InvitationDelete(ctx context.Context, d *schema.ResourceData, } if len(output.UnprocessedAccounts) != 0 { - return diag.FromErr(fmt.Errorf("error deleting Macie Invitation: %w", fmt.Errorf("%s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage)))) + return diag.FromErr(fmt.Errorf("error deleting Macie Invitation: %s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage))) } return nil diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index 5a4301e53f8c..1c68a6280845 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -43,42 +43,6 @@ func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { }) } -func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { - var providers []*schema.Provider - resourceName := "aws_macie2_invitation_accepter.test" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, - ProviderFactories: testAccProviderFactoriesAlternate(&providers), - CheckDestroy: testAccCheckAwsMacie2InvitationAccepterDestroy, - ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), - Steps: []resource.TestStep{ - { - Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusEnabled), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsMacie2InvitationAccepterExists(resourceName), - ), - }, - { - Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusPaused), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsMacie2InvitationAccepterExists(resourceName), - ), - }, - { - Config: testAccAwsMacieInvitationAccepterConfigBasic(email), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func testAccCheckAwsMacie2InvitationAccepterExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -139,7 +103,7 @@ data "aws_caller_identity" "primary" { provider = "awsalternate" } -data "aws_caller_identity" "current" {} +data "aws_caller_identity" "member" {} resource "aws_macie2_account" "primary" { provider = "awsalternate" @@ -149,14 +113,14 @@ resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "primary" { provider = "awsalternate" - account_id = data.aws_caller_identity.current.account_id + account_id = data.aws_caller_identity.member.account_id email = %[1]q depends_on = [aws_macie2_account.primary] } resource "aws_macie2_invitation" "primary" { provider = "awsalternate" - account_id = data.aws_caller_identity.current.account_id + account_id = data.aws_caller_identity.member.account_id depends_on = [aws_macie2_member.primary] } @@ -164,7 +128,6 @@ resource "aws_macie2_invitation_accepter" "test" { administrator_account_id = data.aws_caller_identity.primary.account_id depends_on = [aws_macie2_invitation.primary] } - `, email) } @@ -174,7 +137,7 @@ data "aws_caller_identity" "primary" { provider = "awsalternate" } -data "aws_caller_identity" "current" {} +data "aws_caller_identity" "member" {} resource "aws_macie2_account" "primary" { provider = "awsalternate" @@ -184,7 +147,7 @@ resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "primary" { provider = "awsalternate" - account_id = data.aws_caller_identity.current.account_id + account_id = data.aws_caller_identity.member.account_id email = %[1]q status = %[2]q depends_on = [aws_macie2_account.primary] @@ -192,7 +155,7 @@ resource "aws_macie2_member" "primary" { resource "aws_macie2_invitation" "primary" { provider = "awsalternate" - account_id = data.aws_caller_identity.current.account_id + account_id = data.aws_caller_identity.member.account_id depends_on = [aws_macie2_member.primary] } @@ -200,6 +163,5 @@ resource "aws_macie2_invitation_accepter" "test" { administrator_account_id = data.aws_caller_identity.primary.account_id depends_on = [aws_macie2_invitation.primary] } - `, email, memberStatus) } diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go index 9691235044ae..178a13af6df8 100644 --- a/aws/resource_aws_macie2_invitation_test.go +++ b/aws/resource_aws_macie2_invitation_test.go @@ -96,7 +96,7 @@ func testAccCheckAwsMacie2InvitationExists(resourceName string) resource.TestChe exists := false err := conn.ListMembersPages(&macie2.ListMembersInput{OnlyAssociated: aws.String("false")}, func(page *macie2.ListMembersOutput, lastPage bool) bool { for _, member := range page.Members { - if aws.StringValue(member.AdministratorAccountId) == rs.Primary.ID { + if aws.StringValue(member.AccountId) == rs.Primary.ID { exists = true return false } @@ -127,7 +127,7 @@ func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { empty := true err := conn.ListMembersPages(&macie2.ListMembersInput{OnlyAssociated: aws.String("false")}, func(page *macie2.ListMembersOutput, lastPage bool) bool { for _, member := range page.Members { - if aws.StringValue(member.AdministratorAccountId) == rs.Primary.ID { + if aws.StringValue(member.AccountId) == rs.Primary.ID { empty = false return false } @@ -155,21 +155,21 @@ func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { func testAccAwsMacieInvitationConfigBasic(email string) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` -data "aws_caller_identity" "inviter" { +data "aws_caller_identity" "member" { provider = "awsalternate" } -resource "aws_macie2_account" "test" {} +resource "aws_macie2_account" "primary" {} -resource "aws_macie2_member" "test" { - account_id = data.aws_caller_identity.inviter.account_id +resource "aws_macie2_member" "primary" { + account_id = data.aws_caller_identity.member.account_id email = %[1]q - depends_on = [aws_macie2_account.test] + depends_on = [aws_macie2_account.primary] } resource "aws_macie2_invitation" "test" { - account_id = data.aws_caller_identity.inviter.account_id - depends_on = [aws_macie2_member.test] + account_id = data.aws_caller_identity.member.account_id + depends_on = [aws_macie2_member.primary] } `, email) } diff --git a/aws/resource_aws_macie2_member_test.go b/aws/resource_aws_macie2_member_test.go index 7b93c2bec5d3..052515c80ff7 100644 --- a/aws/resource_aws_macie2_member_test.go +++ b/aws/resource_aws_macie2_member_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/envvar" ) func testAccAwsMacie2Member_basic(t *testing.T) { @@ -73,6 +74,42 @@ func testAccAwsMacie2Member_disappears(t *testing.T) { }) } +func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { + var providers []*schema.Provider + resourceName := "aws_macie2_invitation_accepter.test" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsMacie2InvitationAccepterDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusEnabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + ), + }, + { + Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusPaused), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + ), + }, + { + Config: testAccAwsMacieInvitationAccepterConfigBasic(email), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccAwsMacie2Member_withTags(t *testing.T) { var providers []*schema.Provider var macie2Output macie2.GetMemberOutput @@ -166,37 +203,37 @@ func testAccCheckAwsMacie2MemberDestroy(s *terraform.State) error { } -func testAccAwsMacieMemberConfigBasic(accountID string) string { +func testAccAwsMacieMemberConfigBasic(email string) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` -data "aws_caller_identity" "inviter" { +data "aws_caller_identity" "member" { provider = "awsalternate" } -resource "aws_macie2_account" "test" {} +resource "aws_macie2_account" "primary" {} resource "aws_macie2_member" "test" { - account_id = data.aws_caller_identity.inviter.account_id + account_id = data.aws_caller_identity.member.account_id email = %[1]q - depends_on = [aws_macie2_account.test] + depends_on = [aws_macie2_account.primary] } -`, accountID) +`, email) } func testAccAwsMacieMemberConfigWithTags(email string) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` -data "aws_caller_identity" "inviter" { +data "aws_caller_identity" "member" { provider = "awsalternate" } -resource "aws_macie2_account" "test" {} +resource "aws_macie2_account" "primary" {} resource "aws_macie2_member" "test" { - account_id = data.aws_caller_identity.inviter.account_id + account_id = data.aws_caller_identity.member.account_id email = %[1]q tags = { Key = "value" } - depends_on = [aws_macie2_account.test] + depends_on = [aws_macie2_account.primary] } `, email) } diff --git a/website/docs/r/macie2_invitation.html.markdown b/website/docs/r/macie2_invitation.html.markdown index 1b853aea867f..5ee0dbca02c2 100644 --- a/website/docs/r/macie2_invitation.html.markdown +++ b/website/docs/r/macie2_invitation.html.markdown @@ -13,17 +13,17 @@ Provides a resource to manage an [Amazon Macie Invitation](https://docs.aws.amaz ## Example Usage ```terraform -resource "aws_macie2_account" "test" {} +resource "aws_macie2_account" "example" {} -resource "aws_macie2_member" "test" { +resource "aws_macie2_member" "example" { account_id = "AWS ACCOUNT ID" email = "EMAIL" - depends_on = [aws_macie2_account.test] + depends_on = [aws_macie2_account.example] } -resource "aws_macie2_invitation" "test" { - account_ids = ["ACCOUNT IDS"] - depends_on = [aws_macie2_member.test] +resource "aws_macie2_invitation" "example" { + account_id = "ACCOUNT ID" + depends_on = [aws_macie2_member.example] } ``` @@ -44,8 +44,8 @@ In addition to all arguments above, the following attributes are exported: ## Import -`aws_macie2_invitation` can be imported using the id, e.g. +`aws_macie2_invitation` can be imported using the account ID of the invited account, e.g. ``` -$ terraform import aws_macie2_invitation.example abcd1 +$ terraform import aws_macie2_invitation.example 123456789012 ``` diff --git a/website/docs/r/macie2_invitation_accepter.html.markdown b/website/docs/r/macie2_invitation_accepter.html.markdown index 391c153bd132..c1d9e91ccf97 100644 --- a/website/docs/r/macie2_invitation_accepter.html.markdown +++ b/website/docs/r/macie2_invitation_accepter.html.markdown @@ -53,8 +53,8 @@ In addition to all arguments above, the following attributes are exported: ## Import -`aws_macie2_invitation_accepter` can be imported using the id, e.g. +`aws_macie2_invitation_accepter` can be imported using the admin account ID, e.g. ``` -$ terraform import aws_macie2_invitation_accepter.example abcd1 +$ terraform import aws_macie2_invitation_accepter.example 123456789012 ``` diff --git a/website/docs/r/macie2_member.html.markdown b/website/docs/r/macie2_member.html.markdown index 49c1d0dce186..375a01d1e653 100644 --- a/website/docs/r/macie2_member.html.markdown +++ b/website/docs/r/macie2_member.html.markdown @@ -15,10 +15,10 @@ Provides a resource to manage an [Amazon Macie Member](https://docs.aws.amazon.c ```terraform resource "aws_macie2_account" "example" {} -resource "aws_macie2_member" "test" { +resource "aws_macie2_member" "example" { account_id = "AWS ACCOUNT ID" email = "EMAIL" - depends_on = [aws_macie2_account.test] + depends_on = [aws_macie2_account.example] } ``` @@ -44,8 +44,8 @@ In addition to all arguments above, the following attributes are exported: ## Import -`aws_macie2_member` can be imported using the id, e.g. +`aws_macie2_member` can be imported using the account ID of the member account, e.g. ``` -$ terraform import aws_macie2_member.example abcd1 +$ terraform import aws_macie2_member.example 123456789012 ``` From 8fbf2a3817e55977765c5e6247673d485c96e54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Thu, 13 May 2021 18:35:22 -0600 Subject: [PATCH 14/18] made changes suggested --- aws/internal/service/macie2/finder/finder.go | 4 +- aws/internal/service/macie2/waiter/status.go | 2 +- aws/resource_aws_macie2_invitation.go | 4 - ...rce_aws_macie2_invitation_accepter_test.go | 58 +++--------- aws/resource_aws_macie2_invitation_test.go | 25 ++--- aws/resource_aws_macie2_member_test.go | 94 +++++++++++++++---- 6 files changed, 103 insertions(+), 84 deletions(-) diff --git a/aws/internal/service/macie2/finder/finder.go b/aws/internal/service/macie2/finder/finder.go index b9a895ef8475..f7bf5f556b11 100644 --- a/aws/internal/service/macie2/finder/finder.go +++ b/aws/internal/service/macie2/finder/finder.go @@ -5,8 +5,8 @@ import ( "github.com/aws/aws-sdk-go/service/macie2" ) -// GetMemberNotAssociated Return a list of members not associated and compare with account ID -func GetMemberNotAssociated(conn *macie2.Macie2, accountID string) (*macie2.Member, error) { +// MemberNotAssociated Return a list of members not associated and compare with account ID +func MemberNotAssociated(conn *macie2.Macie2, accountID string) (*macie2.Member, error) { input := &macie2.ListMembersInput{ OnlyAssociated: aws.String("false"), } diff --git a/aws/internal/service/macie2/waiter/status.go b/aws/internal/service/macie2/waiter/status.go index 7c2f58ea91b7..0641f0931065 100644 --- a/aws/internal/service/macie2/waiter/status.go +++ b/aws/internal/service/macie2/waiter/status.go @@ -10,7 +10,7 @@ import ( // MemberRelationshipStatus fetches the Member and its relationship status func MemberRelationshipStatus(conn *macie2.Macie2, adminAccountID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - adminAccount, err := finder.GetMemberNotAssociated(conn, adminAccountID) + adminAccount, err := finder.MemberNotAssociated(conn, adminAccountID) if err != nil { return nil, "Unknown", err diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go index 2144c773d864..d92a111f7c6f 100644 --- a/aws/resource_aws_macie2_invitation.go +++ b/aws/resource_aws_macie2_invitation.go @@ -76,10 +76,6 @@ func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, return resource.NonRetryableError(err) } - if len(output.UnprocessedAccounts) != 0 { - return resource.NonRetryableError(err) - } - return nil }) diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index 1c68a6280845..d0bcbc12caf7 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -15,8 +15,8 @@ import ( func testAccAwsMacie2InvitationAccepter_basic(t *testing.T) { var providers []*schema.Provider - resourceName := "aws_macie2_invitation_accepter.test" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) + resourceName := "aws_macie2_invitation_accepter.member" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2PrincipalEmail, EnvVarMacie2PrincipalEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -99,69 +99,33 @@ func testAccCheckAwsMacie2InvitationAccepterDestroy(s *terraform.State) error { func testAccAwsMacieInvitationAccepterConfigBasic(email string) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` -data "aws_caller_identity" "primary" { +data "aws_caller_identity" "admin" { provider = "awsalternate" } data "aws_caller_identity" "member" {} -resource "aws_macie2_account" "primary" { +resource "aws_macie2_account" "admin" { provider = "awsalternate" } resource "aws_macie2_account" "member" {} -resource "aws_macie2_member" "primary" { +resource "aws_macie2_member" "member" { provider = "awsalternate" account_id = data.aws_caller_identity.member.account_id email = %[1]q - depends_on = [aws_macie2_account.primary] + depends_on = [aws_macie2_account.admin] } -resource "aws_macie2_invitation" "primary" { +resource "aws_macie2_invitation" "member" { provider = "awsalternate" - account_id = data.aws_caller_identity.member.account_id - depends_on = [aws_macie2_member.primary] + account_id = aws_macie2_member.member.account_id } -resource "aws_macie2_invitation_accepter" "test" { - administrator_account_id = data.aws_caller_identity.primary.account_id - depends_on = [aws_macie2_invitation.primary] +resource "aws_macie2_invitation_accepter" "member" { + administrator_account_id = data.aws_caller_identity.admin.account_id + depends_on = [aws_macie2_invitation.member] } `, email) } - -func testAccAwsMacieInvitationAccepterConfigMemberStatus(email, memberStatus string) string { - return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` -data "aws_caller_identity" "primary" { - provider = "awsalternate" -} - -data "aws_caller_identity" "member" {} - -resource "aws_macie2_account" "primary" { - provider = "awsalternate" -} - -resource "aws_macie2_account" "member" {} - -resource "aws_macie2_member" "primary" { - provider = "awsalternate" - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - status = %[2]q - depends_on = [aws_macie2_account.primary] -} - -resource "aws_macie2_invitation" "primary" { - provider = "awsalternate" - account_id = data.aws_caller_identity.member.account_id - depends_on = [aws_macie2_member.primary] -} - -resource "aws_macie2_invitation_accepter" "test" { - administrator_account_id = data.aws_caller_identity.primary.account_id - depends_on = [aws_macie2_invitation.primary] -} -`, email, memberStatus) -} diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go index 178a13af6df8..a57951c941d6 100644 --- a/aws/resource_aws_macie2_invitation_test.go +++ b/aws/resource_aws_macie2_invitation_test.go @@ -14,15 +14,18 @@ import ( ) const ( - EnvVarMacie2MemberEmail = "AWS_MACIE2_MEMBER_EMAIL" - EnvVarMacie2MemberEmailMessageError = "Environment variable AWS_MACIE2_MEMBER_EMAIL is not set. " + + EnvVarMacie2PrincipalEmail = "AWS_MACIE2_ACCOUNT_EMAIL" + EnvVarMacie2AlternateEmail = "AWS_MACIE2_ALTERNATE_ACCOUNT_EMAIL" + EnvVarMacie2PrincipalEmailMessageError = "Environment variable AWS_MACIE2_ACCOUNT_EMAIL is not set. " + + "To properly test inviting Macie member account must be provided." + EnvVarMacie2AlternateEmailMessageError = "Environment variable AWS_MACIE2_ALTERNATE_ACCOUNT_EMAIL is not set. " + "To properly test inviting Macie member account must be provided." ) func testAccAwsMacie2Invitation_basic(t *testing.T) { var providers []*schema.Provider - resourceName := "aws_macie2_invitation.test" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) + resourceName := "aws_macie2_invitation.member" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -52,8 +55,8 @@ func testAccAwsMacie2Invitation_basic(t *testing.T) { func testAccAwsMacie2Invitation_disappears(t *testing.T) { var providers []*schema.Provider - resourceName := "aws_macie2_invitation.test" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) + resourceName := "aws_macie2_invitation.member" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -159,17 +162,17 @@ data "aws_caller_identity" "member" { provider = "awsalternate" } -resource "aws_macie2_account" "primary" {} +resource "aws_macie2_account" "admin" {} -resource "aws_macie2_member" "primary" { +resource "aws_macie2_member" "member" { account_id = data.aws_caller_identity.member.account_id email = %[1]q - depends_on = [aws_macie2_account.primary] + depends_on = [aws_macie2_account.admin] } -resource "aws_macie2_invitation" "test" { +resource "aws_macie2_invitation" "member" { account_id = data.aws_caller_identity.member.account_id - depends_on = [aws_macie2_member.primary] + depends_on = [aws_macie2_member.member] } `, email) } diff --git a/aws/resource_aws_macie2_member_test.go b/aws/resource_aws_macie2_member_test.go index 052515c80ff7..27216168b8c1 100644 --- a/aws/resource_aws_macie2_member_test.go +++ b/aws/resource_aws_macie2_member_test.go @@ -16,8 +16,9 @@ import ( func testAccAwsMacie2Member_basic(t *testing.T) { var providers []*schema.Provider var macie2Output macie2.GetMemberOutput - resourceName := "aws_macie2_member.test" + resourceName := "aws_macie2_member.member" email := "required@example.com" + dataSourceAlternate := "data.aws_caller_identity.member" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -33,6 +34,9 @@ func testAccAwsMacie2Member_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusCreated), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), ), @@ -50,7 +54,7 @@ func testAccAwsMacie2Member_basic(t *testing.T) { func testAccAwsMacie2Member_disappears(t *testing.T) { var providers []*schema.Provider var macie2Output macie2.GetMemberOutput - resourceName := "aws_macie2_member.test" + resourceName := "aws_macie2_member.member" email := "required@example.com" resource.Test(t, resource.TestCase{ @@ -75,9 +79,11 @@ func testAccAwsMacie2Member_disappears(t *testing.T) { } func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { + var macie2Output macie2.GetMemberOutput var providers []*schema.Provider - resourceName := "aws_macie2_invitation_accepter.test" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2MemberEmail, EnvVarMacie2MemberEmailMessageError) + resourceName := "aws_macie2_member.member" + dataSourceAlternate := "data.aws_caller_identity.member" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -89,22 +95,35 @@ func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusEnabled), + Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusEnabled), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusCreated), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), ), }, { - Config: testAccAwsMacieInvitationAccepterConfigMemberStatus(email, macie2.MacieStatusPaused), + Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusPaused), Check: resource.ComposeTestCheckFunc( - testAccCheckAwsMacie2InvitationAccepterExists(resourceName), + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusPaused), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), ), }, { - Config: testAccAwsMacieInvitationAccepterConfigBasic(email), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusPaused), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"status"}, }, }, }) @@ -113,8 +132,9 @@ func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { func testAccAwsMacie2Member_withTags(t *testing.T) { var providers []*schema.Provider var macie2Output macie2.GetMemberOutput - resourceName := "aws_macie2_member.test" + resourceName := "aws_macie2_member.member" email := "required@example.com" + dataSourceAlternate := "data.aws_caller_identity.member" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -135,6 +155,9 @@ func testAccAwsMacie2Member_withTags(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.Key", "value"), resource.TestCheckResourceAttr(resourceName, "tags_all.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags_all.Key", "value"), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), ), }, { @@ -209,12 +232,12 @@ data "aws_caller_identity" "member" { provider = "awsalternate" } -resource "aws_macie2_account" "primary" {} +resource "aws_macie2_account" "admin" {} -resource "aws_macie2_member" "test" { +resource "aws_macie2_member" "member" { account_id = data.aws_caller_identity.member.account_id email = %[1]q - depends_on = [aws_macie2_account.primary] + depends_on = [aws_macie2_account.admin] } `, email) } @@ -225,15 +248,48 @@ data "aws_caller_identity" "member" { provider = "awsalternate" } -resource "aws_macie2_account" "primary" {} +resource "aws_macie2_account" "admin" {} -resource "aws_macie2_member" "test" { +resource "aws_macie2_member" "member" { account_id = data.aws_caller_identity.member.account_id email = %[1]q tags = { Key = "value" } - depends_on = [aws_macie2_account.primary] + depends_on = [aws_macie2_account.admin] } `, email) } + +func testAccAwsMacieMemberConfigStatus(email, memberStatus string) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "member" { + provider = "awsalternate" +} + +data "aws_caller_identity" "admin" {} + +resource "aws_macie2_account" "admin" {} + +resource "aws_macie2_account" "member" { + provider = "awsalternate" +} + +resource "aws_macie2_member" "member" { + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + status = %[2]q + depends_on = [aws_macie2_account.admin] +} + +resource "aws_macie2_invitation" "member" { + account_id = aws_macie2_member.member.account_id +} + +resource "aws_macie2_invitation_accepter" "member" { + provider = "awsalternate" + administrator_account_id = data.aws_caller_identity.admin.account_id + depends_on = [aws_macie2_invitation.member] +} +`, email, memberStatus) +} From 26149eee252f0a474665822543bb7b4d0f70134e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Fri, 14 May 2021 16:03:43 -0600 Subject: [PATCH 15/18] deleted stuff for invitation and put the workflow inside of resource member --- aws/provider.go | 2 + aws/resource_aws_macie2_invitation.go | 167 ---------------- ...rce_aws_macie2_invitation_accepter_test.go | 17 +- aws/resource_aws_macie2_invitation_test.go | 178 ------------------ aws/resource_aws_macie2_member.go | 164 +++++++++++++++- aws/resource_aws_macie2_member_test.go | 167 ++++++++++++++-- aws/resource_aws_macie2_test.go | 11 ++ .../docs/r/macie2_invitation.html.markdown | 51 ----- .../macie2_invitation_accepter.html.markdown | 18 +- website/docs/r/macie2_member.html.markdown | 12 +- 10 files changed, 346 insertions(+), 441 deletions(-) delete mode 100644 aws/resource_aws_macie2_invitation.go delete mode 100644 aws/resource_aws_macie2_invitation_test.go delete mode 100644 website/docs/r/macie2_invitation.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 6380a83004b3..21d5b32945b5 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -847,6 +847,8 @@ func Provider() *schema.Provider { "aws_macie2_custom_data_identifier": resourceAwsMacie2CustomDataIdentifier(), "aws_macie2_findings_filter": resourceAwsMacie2FindingsFilter(), "aws_macie2_organization_admin_account": resourceAwsMacie2OrganizationAdminAccount(), + "aws_macie2_invitation_accepter": resourceAwsMacie2InvitationAccepter(), + "aws_macie2_member": resourceAwsMacie2Member(), "aws_macie_member_account_association": resourceAwsMacieMemberAccountAssociation(), "aws_macie_s3_bucket_association": resourceAwsMacieS3BucketAssociation(), "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), diff --git a/aws/resource_aws_macie2_invitation.go b/aws/resource_aws_macie2_invitation.go deleted file mode 100644 index d92a111f7c6f..000000000000 --- a/aws/resource_aws_macie2_invitation.go +++ /dev/null @@ -1,167 +0,0 @@ -package aws - -import ( - "context" - "fmt" - "log" - "strings" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/macie2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/macie2/waiter" -) - -func resourceAwsMacie2Invitation() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceMacie2InvitationCreate, - ReadWithoutTimeout: resourceMacie2InvitationRead, - DeleteWithoutTimeout: resourceMacie2InvitationDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - Schema: map[string]*schema.Schema{ - "disable_email_notification": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "account_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "message": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "invited_at": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func resourceMacie2InvitationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*AWSClient).macie2conn - - accountID := d.Get("account_id").(string) - input := &macie2.CreateInvitationsInput{ - AccountIds: []*string{aws.String(accountID)}, - } - - if v, ok := d.GetOk("disable_email_notification"); ok { - input.DisableEmailNotification = aws.Bool(v.(bool)) - } - if v, ok := d.GetOk("message"); ok { - input.Message = aws.String(v.(string)) - } - - var err error - var output *macie2.CreateInvitationsOutput - err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { - output, err = conn.CreateInvitationsWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, macie2.ErrorCodeClientError) { - return resource.RetryableError(err) - } - - if err != nil { - return resource.NonRetryableError(err) - } - - return nil - }) - - if isResourceTimeoutError(err) { - output, err = conn.CreateInvitationsWithContext(ctx, input) - } - - if err != nil { - return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %w", err)) - } - - if len(output.UnprocessedAccounts) != 0 { - return diag.FromErr(fmt.Errorf("error creating Macie Invitation: %s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage))) - } - - d.SetId(accountID) - - if _, err = waiter.MemberInvited(ctx, conn, d.Id()); err != nil { - return diag.FromErr(fmt.Errorf("error waiting for Macie Invitation (%s) creation: %w", d.Id(), err)) - } - - return resourceMacie2InvitationRead(ctx, d, meta) -} - -func resourceMacie2InvitationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*AWSClient).macie2conn - - var err error - - input := &macie2.ListMembersInput{ - OnlyAssociated: aws.String("false"), - } - var result *macie2.Member - err = conn.ListMembersPages(input, func(page *macie2.ListMembersOutput, lastPage bool) bool { - for _, member := range page.Members { - if aws.StringValue(member.AccountId) == d.Id() { - result = member - return false - } - } - return !lastPage - }) - - if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || - tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") || - result == nil { - log.Printf("[WARN] Macie Invitation (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil - } - - if err != nil { - return diag.FromErr(fmt.Errorf("error reading Macie Invitation (%s): %w", d.Id(), err)) - } - - d.Set("invited_at", aws.TimeValue(result.InvitedAt).Format(time.RFC3339)) - d.Set("account_id", result.AccountId) - - return nil -} - -func resourceMacie2InvitationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*AWSClient).macie2conn - - input := &macie2.DeleteInvitationsInput{ - AccountIds: []*string{aws.String(d.Id())}, - } - - output, err := conn.DeleteInvitationsWithContext(ctx, input) - if err != nil { - if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || - tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { - return nil - } - return diag.FromErr(fmt.Errorf("error deleting Macie Invitation (%s): %w", d.Id(), err)) - } - - for _, invitation := range output.UnprocessedAccounts { - if strings.Contains(aws.StringValue(invitation.ErrorMessage), "either because no such invitation exists") { - return nil - } - } - - if len(output.UnprocessedAccounts) != 0 { - return diag.FromErr(fmt.Errorf("error deleting Macie Invitation: %s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage))) - } - - return nil -} diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index d0bcbc12caf7..539f33acf0fc 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -112,20 +112,17 @@ resource "aws_macie2_account" "admin" { resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "member" { - provider = "awsalternate" - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - depends_on = [aws_macie2_account.admin] -} - -resource "aws_macie2_invitation" "member" { - provider = "awsalternate" - account_id = aws_macie2_member.member.account_id + provider = "awsalternate" + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + invite = true + invite_message = "This is a message of the invite" + depends_on = [aws_macie2_account.admin] } resource "aws_macie2_invitation_accepter" "member" { administrator_account_id = data.aws_caller_identity.admin.account_id - depends_on = [aws_macie2_invitation.member] + depends_on = [aws_macie2_member.member] } `, email) } diff --git a/aws/resource_aws_macie2_invitation_test.go b/aws/resource_aws_macie2_invitation_test.go deleted file mode 100644 index a57951c941d6..000000000000 --- a/aws/resource_aws_macie2_invitation_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/macie2" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/envvar" -) - -const ( - EnvVarMacie2PrincipalEmail = "AWS_MACIE2_ACCOUNT_EMAIL" - EnvVarMacie2AlternateEmail = "AWS_MACIE2_ALTERNATE_ACCOUNT_EMAIL" - EnvVarMacie2PrincipalEmailMessageError = "Environment variable AWS_MACIE2_ACCOUNT_EMAIL is not set. " + - "To properly test inviting Macie member account must be provided." - EnvVarMacie2AlternateEmailMessageError = "Environment variable AWS_MACIE2_ALTERNATE_ACCOUNT_EMAIL is not set. " + - "To properly test inviting Macie member account must be provided." -) - -func testAccAwsMacie2Invitation_basic(t *testing.T) { - var providers []*schema.Provider - resourceName := "aws_macie2_invitation.member" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, - ProviderFactories: testAccProviderFactoriesAlternate(&providers), - CheckDestroy: testAccCheckAwsMacie2InvitationDestroy, - ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), - Steps: []resource.TestStep{ - { - Config: testAccAwsMacieInvitationConfigBasic(email), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsMacie2InvitationExists(resourceName), - testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), - ), - }, - { - Config: testAccAwsMacieInvitationConfigBasic(email), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccAwsMacie2Invitation_disappears(t *testing.T) { - var providers []*schema.Provider - resourceName := "aws_macie2_invitation.member" - email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccAlternateAccountPreCheck(t) - }, - ProviderFactories: testAccProviderFactoriesAlternate(&providers), - CheckDestroy: testAccCheckAwsMacie2InvitationDestroy, - ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), - Steps: []resource.TestStep{ - { - Config: testAccAwsMacieInvitationConfigBasic(email), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsMacie2InvitationExists(resourceName), - testAccCheckResourceDisappears(testAccProvider, resourceAwsMacie2Invitation(), resourceName), - ), - }, - { - Config: testAccAwsMacieInvitationConfigBasic(email), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccCheckAwsMacie2InvitationExists(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) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("resource (%s) has empty ID", resourceName) - } - - conn := testAccProvider.Meta().(*AWSClient).macie2conn - exists := false - err := conn.ListMembersPages(&macie2.ListMembersInput{OnlyAssociated: aws.String("false")}, func(page *macie2.ListMembersOutput, lastPage bool) bool { - for _, member := range page.Members { - if aws.StringValue(member.AccountId) == rs.Primary.ID { - exists = true - return false - } - } - return true - }) - - if err != nil { - return err - } - - if !exists { - return fmt.Errorf("no administrator account found for: %s", resourceName) - } - - return nil - } -} - -func testAccCheckAwsMacie2InvitationDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).macie2conn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_macie2_invitation" { - continue - } - - empty := true - err := conn.ListMembersPages(&macie2.ListMembersInput{OnlyAssociated: aws.String("false")}, func(page *macie2.ListMembersOutput, lastPage bool) bool { - for _, member := range page.Members { - if aws.StringValue(member.AccountId) == rs.Primary.ID { - empty = false - return false - } - } - return true - }) - - if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || - tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { - continue - } - - if err != nil { - return err - } - - if !empty { - return fmt.Errorf("macie Invitation %q still exists", rs.Primary.ID) - } - } - - return nil - -} - -func testAccAwsMacieInvitationConfigBasic(email string) string { - return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` -data "aws_caller_identity" "member" { - provider = "awsalternate" -} - -resource "aws_macie2_account" "admin" {} - -resource "aws_macie2_member" "member" { - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - depends_on = [aws_macie2_account.admin] -} - -resource "aws_macie2_invitation" "member" { - account_id = data.aws_caller_identity.member.account_id - depends_on = [aws_macie2_member.member] -} -`, email) -} diff --git a/aws/resource_aws_macie2_member.go b/aws/resource_aws_macie2_member.go index af66b37cd904..91fa4f8e652e 100644 --- a/aws/resource_aws_macie2_member.go +++ b/aws/resource_aws_macie2_member.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/macie2/waiter" ) func resourceAwsMacie2Member() *schema.Resource { @@ -65,8 +66,22 @@ func resourceAwsMacie2Member() *schema.Resource { "status": { Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: validation.StringInSlice(macie2.MacieStatus_Values(), false), }, + "invite": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "invite_disable_email_notification": { + Type: schema.TypeString, + Optional: true, + }, + "invite_message": { + Type: schema.TypeString, + Optional: true, + }, }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(60 * time.Second), @@ -118,6 +133,56 @@ func resourceMacie2MemberCreate(ctx context.Context, d *schema.ResourceData, met d.SetId(accountId) + if !d.Get("invite").(bool) { + return resourceMacie2MemberRead(ctx, d, meta) + } + + // Invitation workflow + + inputInvite := &macie2.CreateInvitationsInput{ + AccountIds: []*string{aws.String(d.Id())}, + } + + if v, ok := d.GetOk("invite_disable_email_notification"); ok { + inputInvite.DisableEmailNotification = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("invite_message"); ok { + inputInvite.Message = aws.String(v.(string)) + } + + log.Printf("[INFO] Inviting Macie2 Member: %s", inputInvite) + + var output *macie2.CreateInvitationsOutput + err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + output, err = conn.CreateInvitationsWithContext(ctx, inputInvite) + + if tfawserr.ErrCodeEquals(err, macie2.ErrorCodeClientError) { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + output, err = conn.CreateInvitationsWithContext(ctx, inputInvite) + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error inviting Macie Member: %w", err)) + } + + if len(output.UnprocessedAccounts) != 0 { + return diag.FromErr(fmt.Errorf("error inviting Macie Member: %s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage))) + } + + if _, err = waiter.MemberInvited(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for Macie Member (%s) invitation: %w", d.Id(), err)) + } + return resourceMacie2MemberRead(ctx, d, meta) } @@ -161,23 +226,108 @@ func resourceMacie2MemberRead(ctx context.Context, d *schema.ResourceData, meta return diag.FromErr(fmt.Errorf("error setting `%s` for Macie Member (%s): %w", "tags_all", d.Id(), err)) } + status := aws.StringValue(resp.RelationshipStatus) + log.Printf("[DEBUG] print resp.RelationshipStatus: %v", aws.StringValue(resp.RelationshipStatus)) + if status == macie2.RelationshipStatusEnabled || + status == macie2.RelationshipStatusInvited || status == macie2.RelationshipStatusEmailVerificationInProgress || + status == macie2.RelationshipStatusPaused { + d.Set("invite", true) + } + + if status == macie2.RelationshipStatusRemoved { + d.Set("invite", false) + } + + // To fake a result for status in order to avoid an error related to difference for ImportVerifyState + // It sets to MacieStatusPaused because it can only be changed to PAUSED, normally when it's accepted its status is ENABLED + status = macie2.MacieStatusEnabled + if aws.StringValue(resp.RelationshipStatus) == macie2.RelationshipStatusPaused { + status = macie2.MacieStatusPaused + } + d.Set("status", status) + return nil } func resourceMacie2MemberUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).macie2conn - input := &macie2.UpdateMemberSessionInput{ - Id: aws.String(d.Id()), + // Invitation workflow + + if d.HasChange("invite") { + if d.Get("invite").(bool) { + inputInvite := &macie2.CreateInvitationsInput{ + AccountIds: []*string{aws.String(d.Id())}, + } + + if v, ok := d.GetOk("invite_disable_email_notification"); ok { + inputInvite.DisableEmailNotification = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("invite_message"); ok { + inputInvite.Message = aws.String(v.(string)) + } + + log.Printf("[INFO] Inviting Macie2 Member: %s", inputInvite) + var output *macie2.CreateInvitationsOutput + var err error + err = resource.RetryContext(ctx, 4*time.Minute, func() *resource.RetryError { + output, err = conn.CreateInvitationsWithContext(ctx, inputInvite) + + if tfawserr.ErrCodeEquals(err, macie2.ErrorCodeClientError) { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + output, err = conn.CreateInvitationsWithContext(ctx, inputInvite) + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error inviting Macie Member: %w", err)) + } + + if len(output.UnprocessedAccounts) != 0 { + return diag.FromErr(fmt.Errorf("error inviting Macie Member: %s: %s", aws.StringValue(output.UnprocessedAccounts[0].ErrorCode), aws.StringValue(output.UnprocessedAccounts[0].ErrorMessage))) + } + + if _, err = waiter.MemberInvited(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error waiting for Macie Member (%s) invitation: %w", d.Id(), err)) + } + } else { + input := &macie2.DisassociateMemberInput{ + Id: aws.String(d.Id()), + } + + _, err := conn.DisassociateMemberWithContext(ctx, input) + if err != nil { + if tfawserr.ErrCodeEquals(err, macie2.ErrCodeResourceNotFoundException) || + tfawserr.ErrMessageContains(err, macie2.ErrCodeAccessDeniedException, "Macie is not enabled") { + return nil + } + return diag.FromErr(fmt.Errorf("error disassociating Macie Member invite (%s): %w", d.Id(), err)) + } + } } + // End Invitation workflow + if d.HasChange("status") { - input.Status = aws.String(d.Get("status").(string)) - } + input := &macie2.UpdateMemberSessionInput{ + Id: aws.String(d.Id()), + Status: aws.String(d.Get("status").(string)), + } + + _, err := conn.UpdateMemberSessionWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating Macie Member (%s): %w", d.Id(), err)) + } - _, err := conn.UpdateMemberSessionWithContext(ctx, input) - if err != nil { - return diag.FromErr(fmt.Errorf("error updating Macie Member (%s): %w", d.Id(), err)) } return resourceMacie2MemberRead(ctx, d, meta) diff --git a/aws/resource_aws_macie2_member_test.go b/aws/resource_aws_macie2_member_test.go index 27216168b8c1..4dee61610ecf 100644 --- a/aws/resource_aws_macie2_member_test.go +++ b/aws/resource_aws_macie2_member_test.go @@ -13,6 +13,15 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/envvar" ) +const ( + EnvVarMacie2PrincipalEmail = "AWS_MACIE2_ACCOUNT_EMAIL" + EnvVarMacie2AlternateEmail = "AWS_MACIE2_ALTERNATE_ACCOUNT_EMAIL" + EnvVarMacie2PrincipalEmailMessageError = "Environment variable AWS_MACIE2_ACCOUNT_EMAIL is not set. " + + "To properly test inviting Macie member account must be provided." + EnvVarMacie2AlternateEmailMessageError = "Environment variable AWS_MACIE2_ALTERNATE_ACCOUNT_EMAIL is not set. " + + "To properly test inviting Macie member account must be provided." +) + func testAccAwsMacie2Member_basic(t *testing.T) { var providers []*schema.Provider var macie2Output macie2.GetMemberOutput @@ -78,7 +87,7 @@ func testAccAwsMacie2Member_disappears(t *testing.T) { }) } -func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { +func testAccAwsMacie2Member_invite(t *testing.T) { var macie2Output macie2.GetMemberOutput var providers []*schema.Provider resourceName := "aws_macie2_member.member" @@ -95,10 +104,117 @@ func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), Steps: []resource.TestStep{ { - Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusEnabled), + Config: testAccAwsMacieMemberConfigInvite(email, false), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusCreated), + resource.TestCheckResourceAttr(resourceName, "invite", "false"), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + ), + }, + { + Config: testAccAwsMacieMemberConfigInvite(email, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusInvited), + resource.TestCheckResourceAttr(resourceName, "invite", "true"), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + ), + }, + { + Config: testAccAwsMacieMemberConfigInvite(email, true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"invite_message"}, + }, + }, + }) +} + +func testAccAwsMacie2Member_inviteRemoved(t *testing.T) { + var macie2Output macie2.GetMemberOutput + var providers []*schema.Provider + resourceName := "aws_macie2_member.member" + dataSourceAlternate := "data.aws_caller_identity.member" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsMacie2InvitationAccepterDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieMemberConfigInvite(email, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusInvited), + resource.TestCheckResourceAttr(resourceName, "invite", "true"), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + ), + }, + { + Config: testAccAwsMacieMemberConfigInvite(email, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusRemoved), + resource.TestCheckResourceAttr(resourceName, "invite", "false"), + testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), + testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), + testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), + testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + ), + }, + { + Config: testAccAwsMacieMemberConfigInvite(email, false), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"invite_message"}, + }, + }, + }) +} + +func testAccAwsMacie2Member_status(t *testing.T) { + var macie2Output macie2.GetMemberOutput + var providers []*schema.Provider + resourceName := "aws_macie2_member.member" + dataSourceAlternate := "data.aws_caller_identity.member" + email := envvar.TestSkipIfEmpty(t, EnvVarMacie2AlternateEmail, EnvVarMacie2AlternateEmailMessageError) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccAlternateAccountPreCheck(t) + }, + ProviderFactories: testAccProviderFactoriesAlternate(&providers), + CheckDestroy: testAccCheckAwsMacie2InvitationAccepterDestroy, + ErrorCheck: testAccErrorCheck(t, macie2.EndpointsID), + Steps: []resource.TestStep{ + { + Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusEnabled, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), + resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusInvited), + resource.TestCheckResourceAttr(resourceName, "invite", "true"), testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), @@ -107,10 +223,11 @@ func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { ), }, { - Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusPaused), + Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusPaused, true), Check: resource.ComposeTestCheckFunc( testAccCheckAwsMacie2MemberExists(resourceName, &macie2Output), resource.TestCheckResourceAttr(resourceName, "relationship_status", macie2.RelationshipStatusPaused), + resource.TestCheckResourceAttr(resourceName, "invite", "true"), testAccCheckResourceAttrAccountID(resourceName, "administrator_account_id"), testAccCheckResourceAttrAccountID(resourceName, "master_account_id"), resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), @@ -119,11 +236,11 @@ func testAccAwsMacie2InvitationAccepter_memberStatus(t *testing.T) { ), }, { - Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusPaused), + Config: testAccAwsMacieMemberConfigStatus(email, macie2.MacieStatusPaused, true), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"status"}, + ImportStateVerifyIgnore: []string{"invite_message"}, }, }, }) @@ -261,7 +378,7 @@ resource "aws_macie2_member" "member" { `, email) } -func testAccAwsMacieMemberConfigStatus(email, memberStatus string) string { +func testAccAwsMacieMemberConfigInvite(email string, invite bool) string { return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` data "aws_caller_identity" "member" { provider = "awsalternate" @@ -276,20 +393,42 @@ resource "aws_macie2_account" "member" { } resource "aws_macie2_member" "member" { - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - status = %[2]q - depends_on = [aws_macie2_account.admin] + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + invite = %[2]t + invite_message = "This is a message of the invitation" + depends_on = [aws_macie2_account.admin] +} +`, email, invite) } -resource "aws_macie2_invitation" "member" { - account_id = aws_macie2_member.member.account_id +func testAccAwsMacieMemberConfigStatus(email, memberStatus string, invite bool) string { + return testAccAlternateAccountProviderConfig() + fmt.Sprintf(` +data "aws_caller_identity" "member" { + provider = "awsalternate" +} + +data "aws_caller_identity" "admin" {} + +resource "aws_macie2_account" "admin" {} + +resource "aws_macie2_account" "member" { + provider = "awsalternate" +} + +resource "aws_macie2_member" "member" { + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + status = %[2]q + invite = %[3]t + invite_message = "This is a message of the invitation" + depends_on = [aws_macie2_account.admin] } resource "aws_macie2_invitation_accepter" "member" { provider = "awsalternate" administrator_account_id = data.aws_caller_identity.admin.account_id - depends_on = [aws_macie2_invitation.member] + depends_on = [aws_macie2_member.member] } -`, email, memberStatus) +`, email, memberStatus, invite) } diff --git a/aws/resource_aws_macie2_test.go b/aws/resource_aws_macie2_test.go index 38285120c7c5..4a738731b3b6 100644 --- a/aws/resource_aws_macie2_test.go +++ b/aws/resource_aws_macie2_test.go @@ -44,6 +44,17 @@ func TestAccAWSMacie2_serial(t *testing.T) { "basic": testAccAwsMacie2OrganizationAdminAccount_basic, "disappears": testAccAwsMacie2OrganizationAdminAccount_disappears, }, + "Member": { + "basic": testAccAwsMacie2Member_basic, + "disappears": testAccAwsMacie2Member_disappears, + "tags": testAccAwsMacie2Member_withTags, + "invite": testAccAwsMacie2Member_invite, + "invite_removed": testAccAwsMacie2Member_inviteRemoved, + "status": testAccAwsMacie2Member_status, + }, + "InvitationAccepter": { + "basic": testAccAwsMacie2InvitationAccepter_basic, + }, } for group, m := range testCases { diff --git a/website/docs/r/macie2_invitation.html.markdown b/website/docs/r/macie2_invitation.html.markdown deleted file mode 100644 index 5ee0dbca02c2..000000000000 --- a/website/docs/r/macie2_invitation.html.markdown +++ /dev/null @@ -1,51 +0,0 @@ ---- -subcategory: "Macie" -layout: "aws" -page_title: "AWS: aws_macie2_invitation" -description: |- - Provides a resource to manage an Amazon Macie Invitation. ---- - -# Resource: aws_macie2_invitation - -Provides a resource to manage an [Amazon Macie Invitation](https://docs.aws.amazon.com/macie/latest/APIReference/invitations.html). - -## Example Usage - -```terraform -resource "aws_macie2_account" "example" {} - -resource "aws_macie2_member" "example" { - account_id = "AWS ACCOUNT ID" - email = "EMAIL" - depends_on = [aws_macie2_account.example] -} - -resource "aws_macie2_invitation" "example" { - account_id = "ACCOUNT ID" - depends_on = [aws_macie2_member.example] -} -``` - -## Argument Reference - -The following arguments are supported: - -* `account_ids` - (Required) An array that lists AWS account IDs, one for each account to send the invitation to. -* `disable_email_notification` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. -* `message` - (Optional) A custom message to include in the invitation. Amazon Macie adds this message to the standard content that it sends for an invitation. - -## Attributes Reference - -In addition to all arguments above, the following attributes are exported: - -* `id` - The unique identifier (ID) of the macie invitation. -* `invited_at` - The date and time, in UTC and extended RFC 3339 format, when an Amazon Macie membership invitation was last sent to the account. This value is null if a Macie invitation hasn't been sent to the account. - -## Import - -`aws_macie2_invitation` can be imported using the account ID of the invited account, e.g. - -``` -$ terraform import aws_macie2_invitation.example 123456789012 -``` diff --git a/website/docs/r/macie2_invitation_accepter.html.markdown b/website/docs/r/macie2_invitation_accepter.html.markdown index c1d9e91ccf97..5d483f6d0115 100644 --- a/website/docs/r/macie2_invitation_accepter.html.markdown +++ b/website/docs/r/macie2_invitation_accepter.html.markdown @@ -20,21 +20,17 @@ resource "aws_macie2_account" "primary" { resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "primary" { - provider = "awsalternate" - account_id = "ACCOUNT ID" - email = "EMAIL" - depends_on = [aws_macie2_account.primary] -} - -resource "aws_macie2_invitation" "primary" { - provider = "awsalternate" - account_ids = ["ACCOUNT IDS"] - depends_on = [aws_macie2_member.primary] + provider = "awsalternate" + account_id = "ACCOUNT ID" + email = "EMAIL" + invite = true + invite_message = "Message of the invite" + depends_on = [aws_macie2_account.primary] } resource "aws_macie2_invitation_accepter" "test" { administrator_account_id = "ADMINISTRATOR ACCOUNT ID" - depends_on = [aws_macie2_invitation.primary] + depends_on = [aws_macie2_member.primary] } ``` diff --git a/website/docs/r/macie2_member.html.markdown b/website/docs/r/macie2_member.html.markdown index 375a01d1e653..4b12ff5b8cd1 100644 --- a/website/docs/r/macie2_member.html.markdown +++ b/website/docs/r/macie2_member.html.markdown @@ -16,9 +16,12 @@ Provides a resource to manage an [Amazon Macie Member](https://docs.aws.amazon.c resource "aws_macie2_account" "example" {} resource "aws_macie2_member" "example" { - account_id = "AWS ACCOUNT ID" - email = "EMAIL" - depends_on = [aws_macie2_account.example] + account_id = "AWS ACCOUNT ID" + email = "EMAIL" + invite = true + invite_message = "Message of the invitation" + invite_disable_email_notification = true + depends_on = [aws_macie2_account.example] } ``` @@ -30,6 +33,9 @@ The following arguments are supported: * `email` - (Required) The email address for the account. * `tags` - (Optional) A map of key-value pairs that specifies the tags to associate with the account in Amazon Macie. * `status` - (Optional) Specifies the status for the account. To enable Amazon Macie and start all Macie activities for the account, set this value to `ENABLED`. Valid values are `ENABLED` or `PAUSED`. +* `invite` - (Optional) Send an invitation to a member +* `invite_message` - (Optional) A custom message to include in the invitation. Amazon Macie adds this message to the standard content that it sends for an invitation. +* `invite_disable_email_notification` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. ## Attributes Reference From abbdceca965e190bfea429fbbe555ae0946b6b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Fri, 14 May 2021 16:41:15 -0600 Subject: [PATCH 16/18] updated changelog file --- .changelog/19304.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.changelog/19304.txt b/.changelog/19304.txt index 32f4808b0c0d..183dd6c33bcb 100644 --- a/.changelog/19304.txt +++ b/.changelog/19304.txt @@ -2,10 +2,6 @@ aws_macie2_member ``` -```release-note:new-resource -aws_macie2_invitation -``` - ```release-note:new-resource aws_macie2_invitation_accepter ``` From bf803d5ba2686da5b281507f49b77df27a0798f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Fri, 14 May 2021 20:15:59 -0600 Subject: [PATCH 17/18] refactor: renamed invite to invitation --- ...rce_aws_macie2_invitation_accepter_test.go | 12 +++---- aws/resource_aws_macie2_member.go | 12 +++---- aws/resource_aws_macie2_member_test.go | 35 +++++++++++-------- .../macie2_invitation_accepter.html.markdown | 14 ++++---- website/docs/r/macie2_member.html.markdown | 16 ++++----- 5 files changed, 48 insertions(+), 41 deletions(-) diff --git a/aws/resource_aws_macie2_invitation_accepter_test.go b/aws/resource_aws_macie2_invitation_accepter_test.go index 539f33acf0fc..f7b33508599b 100644 --- a/aws/resource_aws_macie2_invitation_accepter_test.go +++ b/aws/resource_aws_macie2_invitation_accepter_test.go @@ -112,12 +112,12 @@ resource "aws_macie2_account" "admin" { resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "member" { - provider = "awsalternate" - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - invite = true - invite_message = "This is a message of the invite" - depends_on = [aws_macie2_account.admin] + provider = "awsalternate" + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + invite = true + invitation_message = "This is a message of the invite" + depends_on = [aws_macie2_account.admin] } resource "aws_macie2_invitation_accepter" "member" { diff --git a/aws/resource_aws_macie2_member.go b/aws/resource_aws_macie2_member.go index 91fa4f8e652e..d908c0c2a984 100644 --- a/aws/resource_aws_macie2_member.go +++ b/aws/resource_aws_macie2_member.go @@ -74,11 +74,11 @@ func resourceAwsMacie2Member() *schema.Resource { Optional: true, Computed: true, }, - "invite_disable_email_notification": { + "invitation_disable_email_notification": { Type: schema.TypeString, Optional: true, }, - "invite_message": { + "invitation_message": { Type: schema.TypeString, Optional: true, }, @@ -143,10 +143,10 @@ func resourceMacie2MemberCreate(ctx context.Context, d *schema.ResourceData, met AccountIds: []*string{aws.String(d.Id())}, } - if v, ok := d.GetOk("invite_disable_email_notification"); ok { + if v, ok := d.GetOk("invitation_disable_email_notification"); ok { inputInvite.DisableEmailNotification = aws.Bool(v.(bool)) } - if v, ok := d.GetOk("invite_message"); ok { + if v, ok := d.GetOk("invitation_message"); ok { inputInvite.Message = aws.String(v.(string)) } @@ -260,10 +260,10 @@ func resourceMacie2MemberUpdate(ctx context.Context, d *schema.ResourceData, met AccountIds: []*string{aws.String(d.Id())}, } - if v, ok := d.GetOk("invite_disable_email_notification"); ok { + if v, ok := d.GetOk("invitation_disable_email_notification"); ok { inputInvite.DisableEmailNotification = aws.Bool(v.(bool)) } - if v, ok := d.GetOk("invite_message"); ok { + if v, ok := d.GetOk("invitation_message"); ok { inputInvite.Message = aws.String(v.(string)) } diff --git a/aws/resource_aws_macie2_member_test.go b/aws/resource_aws_macie2_member_test.go index 4dee61610ecf..a8a87ef2db2d 100644 --- a/aws/resource_aws_macie2_member_test.go +++ b/aws/resource_aws_macie2_member_test.go @@ -48,6 +48,7 @@ func testAccAwsMacie2Member_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusEnabled), ), }, { @@ -114,6 +115,7 @@ func testAccAwsMacie2Member_invite(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusEnabled), ), }, { @@ -127,6 +129,7 @@ func testAccAwsMacie2Member_invite(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusEnabled), ), }, { @@ -134,7 +137,7 @@ func testAccAwsMacie2Member_invite(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"invite_message"}, + ImportStateVerifyIgnore: []string{"invitation_message"}, }, }, }) @@ -167,6 +170,7 @@ func testAccAwsMacie2Member_inviteRemoved(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusEnabled), ), }, { @@ -180,6 +184,7 @@ func testAccAwsMacie2Member_inviteRemoved(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusEnabled), ), }, { @@ -187,7 +192,7 @@ func testAccAwsMacie2Member_inviteRemoved(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"invite_message"}, + ImportStateVerifyIgnore: []string{"invitation_message"}, }, }, }) @@ -220,6 +225,7 @@ func testAccAwsMacie2Member_status(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusEnabled), ), }, { @@ -233,6 +239,7 @@ func testAccAwsMacie2Member_status(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "account_id", dataSourceAlternate, "account_id"), testAccCheckResourceAttrRfc3339(resourceName, "invited_at"), testAccCheckResourceAttrRfc3339(resourceName, "updated_at"), + resource.TestCheckResourceAttr(resourceName, "status", macie2.MacieStatusPaused), ), }, { @@ -240,7 +247,7 @@ func testAccAwsMacie2Member_status(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"invite_message"}, + ImportStateVerifyIgnore: []string{"invitation_message"}, }, }, }) @@ -393,11 +400,11 @@ resource "aws_macie2_account" "member" { } resource "aws_macie2_member" "member" { - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - invite = %[2]t - invite_message = "This is a message of the invitation" - depends_on = [aws_macie2_account.admin] + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + invite = %[2]t + invitation_message = "This is a message of the invitation" + depends_on = [aws_macie2_account.admin] } `, email, invite) } @@ -417,12 +424,12 @@ resource "aws_macie2_account" "member" { } resource "aws_macie2_member" "member" { - account_id = data.aws_caller_identity.member.account_id - email = %[1]q - status = %[2]q - invite = %[3]t - invite_message = "This is a message of the invitation" - depends_on = [aws_macie2_account.admin] + account_id = data.aws_caller_identity.member.account_id + email = %[1]q + status = %[2]q + invite = %[3]t + invitation_message = "This is a message of the invitation" + depends_on = [aws_macie2_account.admin] } resource "aws_macie2_invitation_accepter" "member" { diff --git a/website/docs/r/macie2_invitation_accepter.html.markdown b/website/docs/r/macie2_invitation_accepter.html.markdown index 5d483f6d0115..3f3734155502 100644 --- a/website/docs/r/macie2_invitation_accepter.html.markdown +++ b/website/docs/r/macie2_invitation_accepter.html.markdown @@ -20,15 +20,15 @@ resource "aws_macie2_account" "primary" { resource "aws_macie2_account" "member" {} resource "aws_macie2_member" "primary" { - provider = "awsalternate" - account_id = "ACCOUNT ID" - email = "EMAIL" - invite = true - invite_message = "Message of the invite" - depends_on = [aws_macie2_account.primary] + provider = "awsalternate" + account_id = "ACCOUNT ID" + email = "EMAIL" + invite = true + invitation_message = "Message of the invite" + depends_on = [aws_macie2_account.primary] } -resource "aws_macie2_invitation_accepter" "test" { +resource "aws_macie2_invitation_accepter" "member" { administrator_account_id = "ADMINISTRATOR ACCOUNT ID" depends_on = [aws_macie2_member.primary] } diff --git a/website/docs/r/macie2_member.html.markdown b/website/docs/r/macie2_member.html.markdown index 4b12ff5b8cd1..fe1cfb3429c9 100644 --- a/website/docs/r/macie2_member.html.markdown +++ b/website/docs/r/macie2_member.html.markdown @@ -16,12 +16,12 @@ Provides a resource to manage an [Amazon Macie Member](https://docs.aws.amazon.c resource "aws_macie2_account" "example" {} resource "aws_macie2_member" "example" { - account_id = "AWS ACCOUNT ID" - email = "EMAIL" - invite = true - invite_message = "Message of the invitation" - invite_disable_email_notification = true - depends_on = [aws_macie2_account.example] + account_id = "AWS ACCOUNT ID" + email = "EMAIL" + invite = true + invitation_message = "Message of the invitation" + invitation_disable_email_notification = true + depends_on = [aws_macie2_account.example] } ``` @@ -34,8 +34,8 @@ The following arguments are supported: * `tags` - (Optional) A map of key-value pairs that specifies the tags to associate with the account in Amazon Macie. * `status` - (Optional) Specifies the status for the account. To enable Amazon Macie and start all Macie activities for the account, set this value to `ENABLED`. Valid values are `ENABLED` or `PAUSED`. * `invite` - (Optional) Send an invitation to a member -* `invite_message` - (Optional) A custom message to include in the invitation. Amazon Macie adds this message to the standard content that it sends for an invitation. -* `invite_disable_email_notification` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. +* `invitation_message` - (Optional) A custom message to include in the invitation. Amazon Macie adds this message to the standard content that it sends for an invitation. +* `invitation_disable_email_notification` - (Optional) Specifies whether to send an email notification to the root user of each account that the invitation will be sent to. This notification is in addition to an alert that the root user receives in AWS Personal Health Dashboard. To send an email notification to the root user of each account, set this value to `true`. ## Attributes Reference From 2da52c4f727dbce723c963d1ba871a034062dc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20L=C3=B3pez?= Date: Mon, 17 May 2021 12:29:13 -0600 Subject: [PATCH 18/18] sorted alphabetically in provider for macie2 --- aws/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/provider.go b/aws/provider.go index 21d5b32945b5..edac20f3750d 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -846,9 +846,9 @@ func Provider() *schema.Provider { "aws_macie2_classification_job": resourceAwsMacie2ClassificationJob(), "aws_macie2_custom_data_identifier": resourceAwsMacie2CustomDataIdentifier(), "aws_macie2_findings_filter": resourceAwsMacie2FindingsFilter(), - "aws_macie2_organization_admin_account": resourceAwsMacie2OrganizationAdminAccount(), "aws_macie2_invitation_accepter": resourceAwsMacie2InvitationAccepter(), "aws_macie2_member": resourceAwsMacie2Member(), + "aws_macie2_organization_admin_account": resourceAwsMacie2OrganizationAdminAccount(), "aws_macie_member_account_association": resourceAwsMacieMemberAccountAssociation(), "aws_macie_s3_bucket_association": resourceAwsMacieS3BucketAssociation(), "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(),