Skip to content

Commit

Permalink
Resource: 'azuread_group_member' (#100)
Browse files Browse the repository at this point in the history
continues #63 
fixed 1/2 of #36
  • Loading branch information
evenh authored and katbyte committed Jul 12, 2019
1 parent 6e31213 commit e9db6a8
Show file tree
Hide file tree
Showing 10 changed files with 806 additions and 11 deletions.
76 changes: 76 additions & 0 deletions azuread/helpers/graph/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package graph

import (
"context"
"fmt"
"log"

"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
)

func GroupAllMembers(client graphrbac.GroupsClient, ctx context.Context, groupId string) ([]string, error) {
it, err := client.GetGroupMembersComplete(ctx, groupId)

if err != nil {
return nil, fmt.Errorf("Error listing existing group members from Azure AD Group with ID %q: %+v", groupId, err)
}

existingMembers := make([]string, 0)

var memberObjectID string
for it.NotDone() {
// possible members are users, groups or service principals
// we try to 'cast' each result as the corresponding type and diff
// if we found the object we're looking for
user, _ := it.Value().AsUser()
if user != nil {
memberObjectID = *user.ObjectID
}

group, _ := it.Value().AsADGroup()
if group != nil {
memberObjectID = *group.ObjectID
}

servicePrincipal, _ := it.Value().AsServicePrincipal()
if servicePrincipal != nil {
memberObjectID = *servicePrincipal.ObjectID
}

existingMembers = append(existingMembers, memberObjectID)
if err := it.NextWithContext(ctx); err != nil {
return nil, fmt.Errorf("Error during pagination of group members from Azure AD Group with ID %q: %+v", groupId, err)
}
}

log.Printf("[DEBUG] %d members in Azure AD group with ID: %q", len(existingMembers), groupId)

return existingMembers, nil
}

func GroupAddMember(client graphrbac.GroupsClient, ctx context.Context, groupId string, member string) error {
memberGraphURL := fmt.Sprintf("https://graph.windows.net/%s/directoryObjects/%s", client.TenantID, member)

properties := graphrbac.GroupAddMemberParameters{
URL: &memberGraphURL,
}

log.Printf("[DEBUG] Adding member with id %q to Azure AD group with id %q", member, groupId)
if _, err := client.AddMember(ctx, groupId, properties); err != nil {
return fmt.Errorf("Error adding group member %q to Azure AD Group with ID %q: %+v", member, groupId, err)
}

return nil
}

func GroupAddMembers(client graphrbac.GroupsClient, ctx context.Context, groupId string, members []string) error {
for _, memberUuid := range members {
err := GroupAddMember(client, ctx, groupId, memberUuid)

if err != nil {
return fmt.Errorf("Error while adding members to Azure AD Group with ID %q: %+v", groupId, err)
}
}

return nil
}
16 changes: 16 additions & 0 deletions azuread/helpers/slices/slices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package slices

// difference returns the elements in `a` that aren't in `b`.
func Difference(a, b []string) []string {
mb := make(map[string]struct{}, len(b))
for _, x := range b {
mb[x] = struct{}{}
}
var diff []string
for _, x := range a {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
1 change: 1 addition & 0 deletions azuread/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func Provider() terraform.ResourceProvider {
"azuread_application": resourceApplication(),
"azuread_application_password": resourceApplicationPassword(),
"azuread_group": resourceGroup(),
"azuread_group_member": resourceGroupMember(),
"azuread_service_principal": resourceServicePrincipal(),
"azuread_service_principal_password": resourceServicePrincipalPassword(),
"azuread_user": resourceUser(),
Expand Down
65 changes: 65 additions & 0 deletions azuread/resource_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"fmt"
"log"

"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/slices"
"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/tf"
"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/validate"

"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
"github.com/google/uuid"
"github.com/hashicorp/terraform/helper/schema"
Expand All @@ -18,6 +22,7 @@ func resourceGroup() *schema.Resource {
return &schema.Resource{
Create: resourceGroupCreate,
Read: resourceGroupRead,
Update: resourceGroupUpdate,
Delete: resourceGroupDelete,

Importer: &schema.ResourceImporter{
Expand All @@ -36,6 +41,18 @@ func resourceGroup() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"members": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Set: schema.HashString,
ForceNew: false,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validate.UUID,
},
},
},
}
}
Expand All @@ -62,6 +79,15 @@ func resourceGroupCreate(d *schema.ResourceData, meta interface{}) error {
}
d.SetId(*group.ObjectID)

// Add members if specified
if v, ok := d.GetOk("members"); ok {
members := tf.ExpandStringSlicePtr(v.(*schema.Set).List())

if err := graph.GroupAddMembers(client, ctx, *group.ObjectID, *members); err != nil {
return err
}
}

_, err = graph.WaitForReplication(func() (interface{}, error) {
return client.Get(ctx, *group.ObjectID)
})
Expand Down Expand Up @@ -89,9 +115,48 @@ func resourceGroupRead(d *schema.ResourceData, meta interface{}) error {

d.Set("name", resp.DisplayName)
d.Set("object_id", resp.ObjectID)

members, err := graph.GroupAllMembers(client, ctx, d.Id())
if err != nil {
return err
}

d.Set("members", members)

return nil
}

func resourceGroupUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).groupsClient
ctx := meta.(*ArmClient).StopContext

if v, ok := d.GetOkExists("members"); ok && d.HasChange("members") {
existingMembers, err := graph.GroupAllMembers(client, ctx, d.Id())
if err != nil {
return err
}

desiredMembers := *tf.ExpandStringSlicePtr(v.(*schema.Set).List())
membersForRemoval := slices.Difference(existingMembers, desiredMembers)
membersToAdd := slices.Difference(desiredMembers, existingMembers)

for _, existingMember := range membersForRemoval {
log.Printf("[DEBUG] Removing member with id %q from Azure AD group with id %q", existingMember, d.Id())
if resp, err := client.RemoveMember(ctx, d.Id(), existingMember); err != nil {
if !ar.ResponseWasNotFound(resp) {
return fmt.Errorf("Error Deleting group member %q from Azure AD Group with ID %q: %+v", existingMember, d.Id(), err)
}
}
}

if err := graph.GroupAddMembers(client, ctx, d.Id(), membersToAdd); err != nil {
return err
}
}

return resourceGroupRead(d, meta)
}

func resourceGroupDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).groupsClient
ctx := meta.(*ArmClient).StopContext
Expand Down
113 changes: 113 additions & 0 deletions azuread/resource_group_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package azuread

import (
"fmt"
"strings"

"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/graph"

"github.com/hashicorp/terraform/helper/schema"
"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/ar"
"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/validate"
)

func resourceGroupMember() *schema.Resource {
return &schema.Resource{
Create: resourceGroupMemberCreate,
Read: resourceGroupMemberRead,
Delete: resourceGroupMemberDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"group_object_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.UUID,
},
"member_object_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.UUID,
},
},
}
}

func resourceGroupMemberCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).groupsClient
ctx := meta.(*ArmClient).StopContext

groupID := d.Get("group_object_id").(string)
memberID := d.Get("member_object_id").(string)

if err := graph.GroupAddMember(client, ctx, groupID, memberID); err != nil {
return err
}

id := fmt.Sprintf("%s/member/%s", groupID, memberID)
d.SetId(id)

return resourceGroupMemberRead(d, meta)
}

func resourceGroupMemberRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).groupsClient
ctx := meta.(*ArmClient).StopContext

id := strings.Split(d.Id(), "/member/")
if len(id) != 2 {
return fmt.Errorf("ID should be in the format {groupObjectId}/member/{memberObjectId} - but got %q", d.Id())
}

groupID := id[0]
memberID := id[1]

members, err := graph.GroupAllMembers(client, ctx, groupID)
if err != nil {
return fmt.Errorf("Error retrieving Azure AD Group members (groupObjectId: %q): %+v", groupID, err)
}

var memberObjectID string

for _, objectID := range members {
if objectID == memberID {
memberObjectID = objectID
}
}

if memberObjectID == "" {
d.SetId("")
return fmt.Errorf("Azure AD Group Member not found - groupObjectId:%q / memberObjectId:%q", groupID, memberID)
}

d.Set("group_object_id", groupID)
d.Set("member_object_id", memberObjectID)

return nil
}

func resourceGroupMemberDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).groupsClient
ctx := meta.(*ArmClient).StopContext

id := strings.Split(d.Id(), "/member/")
if len(id) != 2 {
return fmt.Errorf("ID should be in the format {groupObjectId}/member/{memberObjectId} - but got %q", d.Id())
}

groupID := id[0]
memberID := id[1]

resp, err := client.RemoveMember(ctx, groupID, memberID)
if err != nil {
if !ar.ResponseWasNotFound(resp) {
return fmt.Errorf("Error removing Member (memberObjectId: %q) from Azure AD Group (groupObjectId: %q): %+v", memberID, groupID, err)
}
}

return nil
}
Loading

0 comments on commit e9db6a8

Please sign in to comment.