Skip to content

Commit

Permalink
Merge pull request #4604 from terraform-providers/f-aws_guardduty_mem…
Browse files Browse the repository at this point in the history
…ber-support-update

resource/aws_guardduty_member: Support invite argument updates
  • Loading branch information
bflad authored May 22, 2018
2 parents cdd2619 + 0b869c0 commit 91e800a
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 45 deletions.
129 changes: 89 additions & 40 deletions aws/resource_aws_guardduty_member.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func resourceAwsGuardDutyMember() *schema.Resource {
return &schema.Resource{
Create: resourceAwsGuardDutyMemberCreate,
Read: resourceAwsGuardDutyMemberRead,
Update: resourceAwsGuardDutyMemberUpdate,
Delete: resourceAwsGuardDutyMemberDelete,

Importer: &schema.ResourceImporter{
Expand Down Expand Up @@ -46,8 +47,6 @@ func resourceAwsGuardDutyMember() *schema.Resource {
"invite": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Computed: true,
},
"disable_email_notification": {
Type: schema.TypeBool,
Expand All @@ -62,6 +61,7 @@ func resourceAwsGuardDutyMember() *schema.Resource {
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(60 * time.Second),
Update: schema.DefaultTimeout(60 * time.Second),
},
}
}
Expand Down Expand Up @@ -98,48 +98,15 @@ func resourceAwsGuardDutyMemberCreate(d *schema.ResourceData, meta interface{})
Message: aws.String(d.Get("invitation_message").(string)),
}

log.Printf("[INFO] Inviting GuardDuty Member: %s", input)
_, err = conn.InviteMembers(imi)
if err != nil {
return fmt.Errorf("Inviting GuardDuty Member failed: %s", err)
return fmt.Errorf("error inviting GuardDuty Member %q: %s", d.Id(), err)
}

// wait until e-mail verification finishes
if err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
input := guardduty.GetMembersInput{
DetectorId: imi.DetectorId,
AccountIds: imi.AccountIds,
}

log.Printf("[DEBUG] Reading GuardDuty Member: %s", input)
gmo, err := conn.GetMembers(&input)

if err != nil {
if isAWSErr(err, guardduty.ErrCodeBadRequestException, "The request is rejected because the input detectorId is not owned by the current account.") {
log.Printf("[WARN] GuardDuty detector %q not found, removing from state", d.Id())
d.SetId("")
return nil
}
return resource.NonRetryableError(fmt.Errorf("error reading GuardDuty Member %q: %s", d.Id(), err))
}

if gmo == nil || len(gmo.Members) == 0 {
return resource.RetryableError(fmt.Errorf("error reading GuardDuty Member %q: member missing from response", d.Id()))
}

member := gmo.Members[0]
status := aws.StringValue(member.RelationshipStatus)

if status == "Disabled" || status == "Enabled" || status == "Invited" {
return nil
}

if status == "Created" || status == "EmailVerificationInProgress" {
return resource.RetryableError(fmt.Errorf("Expected member to be invited but was in state: %s", status))
}

return resource.NonRetryableError(fmt.Errorf("error inviting GuardDuty Member %q: invalid status: %s", d.Id(), status))
}); err != nil {
return err
err = inviteGuardDutyMemberWaiter(accountID, detectorID, d.Timeout(schema.TimeoutUpdate), conn)
if err != nil {
return fmt.Errorf("error waiting for GuardDuty Member %q invite: %s", d.Id(), err)
}

return resourceAwsGuardDutyMemberRead(d, meta)
Expand Down Expand Up @@ -192,6 +159,54 @@ func resourceAwsGuardDutyMemberRead(d *schema.ResourceData, meta interface{}) er
return nil
}

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

accountID, detectorID, err := decodeGuardDutyMemberID(d.Id())
if err != nil {
return err
}

if d.HasChange("invite") {
if d.Get("invite").(bool) {
input := &guardduty.InviteMembersInput{
DetectorId: aws.String(detectorID),
AccountIds: []*string{aws.String(accountID)},
DisableEmailNotification: aws.Bool(d.Get("disable_email_notification").(bool)),
Message: aws.String(d.Get("invitation_message").(string)),
}

log.Printf("[INFO] Inviting GuardDuty Member: %s", input)
output, err := conn.InviteMembers(input)
if err != nil {
return fmt.Errorf("error inviting GuardDuty Member %q: %s", d.Id(), err)
}

// {"unprocessedAccounts":[{"result":"The request is rejected because the current account has already invited or is already the GuardDuty master of the given member account ID.","accountId":"067819342479"}]}
if len(output.UnprocessedAccounts) > 0 {
return fmt.Errorf("error inviting GuardDuty Member %q: %s", d.Id(), aws.StringValue(output.UnprocessedAccounts[0].Result))
}

err = inviteGuardDutyMemberWaiter(accountID, detectorID, d.Timeout(schema.TimeoutUpdate), conn)
if err != nil {
return fmt.Errorf("error waiting for GuardDuty Member %q invite: %s", d.Id(), err)
}
} else {
input := &guardduty.DisassociateMembersInput{
AccountIds: []*string{aws.String(accountID)},
DetectorId: aws.String(detectorID),
}
log.Printf("[INFO] Disassociating GuardDuty Member: %s", input)
_, err := conn.DisassociateMembers(input)
if err != nil {
return fmt.Errorf("error disassociating GuardDuty Member %q: %s", d.Id(), err)
}
}
}

return resourceAwsGuardDutyMemberRead(d, meta)
}

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

Expand All @@ -213,6 +228,40 @@ func resourceAwsGuardDutyMemberDelete(d *schema.ResourceData, meta interface{})
return nil
}

func inviteGuardDutyMemberWaiter(accountID, detectorID string, timeout time.Duration, conn *guardduty.GuardDuty) error {
input := guardduty.GetMembersInput{
DetectorId: aws.String(detectorID),
AccountIds: []*string{aws.String(accountID)},
}

// wait until e-mail verification finishes
return resource.Retry(timeout, func() *resource.RetryError {
log.Printf("[DEBUG] Reading GuardDuty Member: %s", input)
gmo, err := conn.GetMembers(&input)

if err != nil {
return resource.NonRetryableError(fmt.Errorf("error reading GuardDuty Member %q: %s", accountID, err))
}

if gmo == nil || len(gmo.Members) == 0 {
return resource.RetryableError(fmt.Errorf("error reading GuardDuty Member %q: member missing from response", accountID))
}

member := gmo.Members[0]
status := aws.StringValue(member.RelationshipStatus)

if status == "Disabled" || status == "Enabled" || status == "Invited" {
return nil
}

if status == "Created" || status == "EmailVerificationInProgress" {
return resource.RetryableError(fmt.Errorf("Expected member to be invited but was in state: %s", status))
}

return resource.NonRetryableError(fmt.Errorf("error inviting GuardDuty Member %q: invalid status: %s", accountID, status))
})
}

func decodeGuardDutyMemberID(id string) (accountID, detectorID string, err error) {
parts := strings.Split(id, ":")
if len(parts) != 2 {
Expand Down
98 changes: 95 additions & 3 deletions aws/resource_aws_guardduty_member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func testAccAwsGuardDutyMember_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "account_id", accountID),
resource.TestCheckResourceAttrSet(resourceName, "detector_id"),
resource.TestCheckResourceAttr(resourceName, "email", email),
resource.TestCheckResourceAttr(resourceName, "relationship_status", "Created"),
),
},
{
Expand All @@ -38,7 +39,83 @@ func testAccAwsGuardDutyMember_basic(t *testing.T) {
})
}

func testAccAwsGuardDutyMember_invite(t *testing.T) {
func testAccAwsGuardDutyMember_invite_disassociate(t *testing.T) {
resourceName := "aws_guardduty_member.test"
accountID, email := testAccAWSGuardDutyMemberFromEnv(t)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsGuardDutyMemberDestroy,
Steps: []resource.TestStep{
{
Config: testAccGuardDutyMemberConfig_invite(accountID, email, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsGuardDutyMemberExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "invite", "true"),
resource.TestCheckResourceAttr(resourceName, "relationship_status", "Invited"),
),
},
// Disassociate member
{
Config: testAccGuardDutyMemberConfig_invite(accountID, email, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsGuardDutyMemberExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "invite", "false"),
resource.TestCheckResourceAttr(resourceName, "relationship_status", "Removed"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"disable_email_notification",
},
},
},
})
}

func testAccAwsGuardDutyMember_invite_onUpdate(t *testing.T) {
resourceName := "aws_guardduty_member.test"
accountID, email := testAccAWSGuardDutyMemberFromEnv(t)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsGuardDutyMemberDestroy,
Steps: []resource.TestStep{
{
Config: testAccGuardDutyMemberConfig_invite(accountID, email, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsGuardDutyMemberExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "invite", "false"),
resource.TestCheckResourceAttr(resourceName, "relationship_status", "Created"),
),
},
// Invite member
{
Config: testAccGuardDutyMemberConfig_invite(accountID, email, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsGuardDutyMemberExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "invite", "true"),
resource.TestCheckResourceAttr(resourceName, "relationship_status", "Invited"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
"disable_email_notification",
},
},
},
})
}

func testAccAwsGuardDutyMember_invitationMessage(t *testing.T) {
resourceName := "aws_guardduty_member.test"
accountID, email := testAccAWSGuardDutyMemberFromEnv(t)
invitationMessage := "inviting"
Expand All @@ -49,7 +126,7 @@ func testAccAwsGuardDutyMember_invite(t *testing.T) {
CheckDestroy: testAccCheckAwsGuardDutyMemberDestroy,
Steps: []resource.TestStep{
{
Config: testAccGuardDutyMemberConfig_invite(accountID, email, invitationMessage),
Config: testAccGuardDutyMemberConfig_invitationMessage(accountID, email, invitationMessage),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsGuardDutyMemberExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "account_id", accountID),
Expand All @@ -58,6 +135,7 @@ func testAccAwsGuardDutyMember_invite(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "email", email),
resource.TestCheckResourceAttr(resourceName, "invite", "true"),
resource.TestCheckResourceAttr(resourceName, "invitation_message", invitationMessage),
resource.TestCheckResourceAttr(resourceName, "relationship_status", "Invited"),
),
},
{
Expand Down Expand Up @@ -152,7 +230,21 @@ resource "aws_guardduty_member" "test" {
`, testAccGuardDutyDetectorConfig_basic1, accountID, email)
}

func testAccGuardDutyMemberConfig_invite(accountID, email, invitationMessage string) string {
func testAccGuardDutyMemberConfig_invite(accountID, email string, invite bool) string {
return fmt.Sprintf(`
%[1]s
resource "aws_guardduty_member" "test" {
account_id = "%[2]s"
detector_id = "${aws_guardduty_detector.test.id}"
disable_email_notification = true
email = "%[3]s"
invite = %[4]t
}
`, testAccGuardDutyDetectorConfig_basic1, accountID, email, invite)
}

func testAccGuardDutyMemberConfig_invitationMessage(accountID, email, invitationMessage string) string {
return fmt.Sprintf(`
%[1]s
Expand Down
6 changes: 4 additions & 2 deletions aws/resource_aws_guardduty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ func TestAccAWSGuardDuty(t *testing.T) {
"import": testAccAwsGuardDutyThreatintelset_import,
},
"Member": {
"basic": testAccAwsGuardDutyMember_basic,
"invite": testAccAwsGuardDutyMember_invite,
"basic": testAccAwsGuardDutyMember_basic,
"inviteOnUpdate": testAccAwsGuardDutyMember_invite_onUpdate,
"inviteDisassociate": testAccAwsGuardDutyMember_invite_disassociate,
"invitationMessage": testAccAwsGuardDutyMember_invitationMessage,
},
}

Expand Down
1 change: 1 addition & 0 deletions website/docs/r/guardduty_member.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The following arguments are supported:
configuration options:

- `create` - (Default `60s`) How long to wait for a verification to be done against inviting GuardDuty member account.
- `update` - (Default `60s`) How long to wait for a verification to be done against inviting GuardDuty member account.


## Attributes Reference
Expand Down

0 comments on commit 91e800a

Please sign in to comment.