Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add the group_member resource #135

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions ad/internal/winrmhelper/winrm_group_membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,27 +128,27 @@ func (g *GroupMembership) bulkGroupMembersOp(conf *config.ProviderConf, operatio
return nil
}

func (g *GroupMembership) addGroupMembers(conf *config.ProviderConf, members []*GroupMember) error {
func (g *GroupMembership) AddGroupMembers(conf *config.ProviderConf, members []*GroupMember) error {
return g.bulkGroupMembersOp(conf, "Add-ADGroupMember", members)
}

func (g *GroupMembership) removeGroupMembers(conf *config.ProviderConf, members []*GroupMember) error {
func (g *GroupMembership) RemoveGroupMembers(conf *config.ProviderConf, members []*GroupMember) error {
return g.bulkGroupMembersOp(conf, "Remove-ADGroupMember", members)
}

func (g *GroupMembership) Update(conf *config.ProviderConf, expected []*GroupMember) error {
func (g *GroupMembership) SetGroupMembers(conf *config.ProviderConf, expected []*GroupMember) error {
existing, err := g.getGroupMembers(conf)
if err != nil {
return err
}

toAdd, toRemove := diffGroupMemberLists(expected, existing)
err = g.addGroupMembers(conf, toAdd)
err = g.AddGroupMembers(conf, toAdd)
if err != nil {
return err
}

err = g.removeGroupMembers(conf, toRemove)
err = g.RemoveGroupMembers(conf, toRemove)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions ad/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func Provider() *schema.Provider {
"ad_user": resourceADUser(),
"ad_group": resourceADGroup(),
"ad_group_membership": resourceADGroupMembership(),
"ad_group_member": resourceADGroupMember(),
"ad_gpo": resourceADGPO(),
"ad_gpo_security": resourceADGPOSecurity(),
"ad_computer": resourceADComputer(),
Expand Down
131 changes: 131 additions & 0 deletions ad/resource_ad_group_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package ad

import (
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform-provider-ad/ad/internal/config"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-ad/ad/internal/winrmhelper"
)

func resourceADGroupMember() *schema.Resource {
return &schema.Resource{
Description: "`ad_group_member` manages a specific member of a given Active Directory group.",
Create: resourceADGroupMemberCreate,
Read: resourceADGroupMemberRead,
Delete: resourceADGroupMemberDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"group_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of the group. This can be a GUID, a SID, a Distinguished Name, or the SAM Account Name of the group.",
ForceNew: true,
},
"group_member": {
Type: schema.TypeString,
Required: true,
Description: "A member AD Principal. The principal can be identified by its GUID, SID, Distinguished Name, or SAM Account Name.",
ForceNew: true,
},
},
}
}

func composeGroupMemberID(groupID, memberID string) string {
return groupID + "_" + memberID
}

func parseGroupMemberID(groupMemberID string) (groupID, memberID string, err error) {
ids := strings.Split(groupMemberID, "_")

if len(ids) != 2 {
err = fmt.Errorf("invalid groupMemberID: %s", groupMemberID)
return
}

groupID = ids[0]
memberID = ids[1]

return
}

func resourceADGroupMemberRead(d *schema.ResourceData, meta interface{}) error {
groupID, memberID, err := parseGroupMemberID(d.Id())
if err != nil {
// This is a provider internal error. Let's return it.
return err
}

gm, err := winrmhelper.NewGroupMembershipFromHost(meta.(*config.ProviderConf), groupID)
if err != nil {
return err
}

for _, m := range gm.GroupMembers {
if memberID == m.GUID {

_ = d.Set("group_member", memberID)
_ = d.Set("group_id", groupID)

return nil
}
}

log.Printf("error finding member %s in membership of group %s", memberID, groupID)
d.SetId("")
return nil
}

func resourceADGroupMemberCreate(d *schema.ResourceData, meta interface{}) error {
groupID := d.Get("group_id").(string)
memberID := d.Get("group_member").(string)

gm := &winrmhelper.GroupMembership{
GroupGUID: groupID,
GroupMembers: []*winrmhelper.GroupMember{
{
GUID: memberID,
},
},
}

err := gm.Create(meta.(*config.ProviderConf))
if err != nil {
return err
}

d.SetId(composeGroupMemberID(groupID, memberID))

return nil
}

func resourceADGroupMemberDelete(d *schema.ResourceData, meta interface{}) error {
groupID, memberID, err := parseGroupMemberID(d.Id())
if err != nil {
// This is a provider internal error. Let's return it.
return err
}

gm := &winrmhelper.GroupMembership{
GroupGUID: groupID,
GroupMembers: []*winrmhelper.GroupMember{
{
GUID: memberID,
},
},
}

err = gm.RemoveGroupMembers(meta.(*config.ProviderConf), gm.GroupMembers)
if err != nil {
return err
}

d.SetId("")
return nil
}
207 changes: 207 additions & 0 deletions ad/resource_ad_group_member_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package ad

import (
"fmt"
"os"
"strings"
"testing"

"github.com/hashicorp/terraform-provider-ad/ad/internal/config"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-ad/ad/internal/winrmhelper"
)

func TestAccResourceADGroupMember_basic(t *testing.T) {
envVars := []string{
"TF_VAR_ad_group_name",
"TF_VAR_ad_group_sam",
"TF_VAR_ad_group_container",
"TF_VAR_ad_group2_name",
"TF_VAR_ad_group2_sam",
"TF_VAR_ad_group2_container",
"TF_VAR_ad_user_display_name",
"TF_VAR_ad_user_sam",
"TF_VAR_ad_user_password",
"TF_VAR_ad_user_principal_name",
"TF_VAR_ad_user_container",
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t, envVars) },
Providers: testAccProviders,
CheckDestroy: resource.ComposeTestCheckFunc(
testAccResourceADGroupMemberExists("ad_group_member.gm", false, ""),
),
Steps: []resource.TestStep{
{
Config: testAccResourceADGroupMemberConfigBasic(),
Check: resource.ComposeTestCheckFunc(
testAccResourceADGroupMemberExists("ad_group_member.gm", true, os.Getenv("TF_VAR_ad_user_principal_name")),
),
},
{
ResourceName: "ad_group_member.gm",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccResourceADGroupMember_Update(t *testing.T) {
envVars := []string{
"TF_VAR_ad_group_name",
"TF_VAR_ad_group_sam",
"TF_VAR_ad_group_container",
"TF_VAR_ad_group2_name",
"TF_VAR_ad_group2_sam",
"TF_VAR_ad_group2_container",
"TF_VAR_ad_group3_name",
"TF_VAR_ad_group3_sam",
"TF_VAR_ad_group3_container",
"TF_VAR_ad_user_display_name",
"TF_VAR_ad_user_sam",
"TF_VAR_ad_user_password",
"TF_VAR_ad_user_principal_name",
"TF_VAR_ad_user_container",
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t, envVars) },
Providers: testAccProviders,
CheckDestroy: resource.ComposeTestCheckFunc(
testAccResourceADGroupMemberExists("ad_group_member.gm", false, ""),
),
Steps: []resource.TestStep{
{
Config: testAccResourceADGroupMemberUpdate(),
Check: resource.ComposeTestCheckFunc(
testAccResourceADGroupMemberExists("ad_group_member.gm", true, os.Getenv("TF_VAR_ad_group2_name")),
),
},
},
})
}
func testAccResourceADGroupMemberExists(resourceName string, expected bool, member string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]

if !ok {
return fmt.Errorf("%s resource not found", resourceName)
}

toks := strings.Split(rs.Primary.ID, "_")
gm, err := winrmhelper.NewGroupMembershipFromHost(testAccProvider.Meta().(*config.ProviderConf), toks[0])
if err != nil {
if strings.Contains(err.Error(), "ADIdentityNotFoundException") && !expected {
return nil
}
return err
}

if expected && gm.GroupMembers[0].Name != member {
return fmt.Errorf("actual member (%s) does not match the expected member (%s)", gm.GroupMembers[0].Name, member)
}

return nil
}
}

func testAccResourceADGroupMemberConfigBasic() string {
return `

variable "ad_group_name" {}
variable "ad_group_sam" {}
variable "ad_group_container" {}

variable "ad_group2_name" {}
variable "ad_group2_sam" {}
variable "ad_group2_container" {}

variable "ad_user_display_name" {}
variable "ad_user_principal_name" {}
variable "ad_user_sam" {}
variable "ad_user_password" {}
variable "ad_user_container" {}

resource ad_group "g" {
name = var.ad_group_name
sam_account_name = var.ad_group_sam
container = var.ad_group_container
}

resource ad_group "g2" {
name = var.ad_group2_name
sam_account_name = var.ad_group2_sam
container = var.ad_group2_container
}

resource ad_user "u" {
display_name = var.ad_user_display_name
principal_name = var.ad_user_principal_name
sam_account_name = var.ad_user_sam
initial_password = var.ad_user_password
container = var.ad_user_container
}

resource ad_group_member "gm" {
group_id = ad_group.g.id
group_member = ad_user.u.id
}
`
}

func testAccResourceADGroupMemberUpdate() string {
return `
variable "ad_group_name" {}
variable "ad_group_sam" {}
variable "ad_group_container" {}

variable "ad_group2_name" {}
variable "ad_group2_sam" {}
variable "ad_group2_container" {}

variable "ad_group3_name" {}
variable "ad_group3_sam" {}
variable "ad_group3_container" {}

variable "ad_user_display_name" {}
variable "ad_user_principal_name" {}
variable "ad_user_sam" {}
variable "ad_user_password" {}
variable "ad_user_container" {}

resource ad_group "g" {
name = var.ad_group_name
sam_account_name = var.ad_group_sam
container = var.ad_group_container
}

resource ad_group "g2" {
name = var.ad_group2_name
sam_account_name = var.ad_group2_sam
container = var.ad_group2_container
}

resource ad_group "g3" {
name = var.ad_group3_name
sam_account_name = var.ad_group3_sam
container = var.ad_group3_container
}


resource ad_user "u" {
display_name = var.ad_user_display_name
principal_name = var.ad_user_principal_name
sam_account_name = var.ad_user_sam
initial_password = var.ad_user_password
container = var.ad_user_container
}

resource ad_group_member "gm" {
group_id = ad_group.g.id
group_member = ad_group.g2.id
}
`
}
2 changes: 1 addition & 1 deletion ad/resource_ad_group_membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func resourceADGroupMembershipUpdate(d *schema.ResourceData, meta interface{}) e
return err
}

err = gm.Update(meta.(*config.ProviderConf), gm.GroupMembers)
err = gm.SetGroupMembers(meta.(*config.ProviderConf), gm.GroupMembers)
if err != nil {
return err
}
Expand Down
Loading