diff --git a/internal/provider/group_resource.go b/internal/provider/group_resource.go index 1afaebb..46bc6c9 100644 --- a/internal/provider/group_resource.go +++ b/internal/provider/group_resource.go @@ -240,19 +240,24 @@ func (r *GroupResource) Update(ctx context.Context, req resource.UpdateRequest, resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group, got error: %s", err)) return } - var newMembers []string - resp.Diagnostics.Append( - data.Members.ElementsAs(ctx, &newMembers, false)..., - ) - if resp.Diagnostics.HasError() { - return - } var add []string var remove []string if !data.Members.IsNull() { - add, remove = memberDiff(group.Members, newMembers) + var plannedMembers []UUID + resp.Diagnostics.Append( + data.Members.ElementsAs(ctx, &plannedMembers, false)..., + ) + if resp.Diagnostics.HasError() { + return + } + curMembers := make([]uuid.UUID, 0, len(group.Members)) + for _, member := range group.Members { + curMembers = append(curMembers, member.ID) + } + add, remove = memberDiff(curMembers, plannedMembers) } tflog.Trace(ctx, "updating group", map[string]any{ + "id": groupID, "new_members": add, "removed_members": remove, "new_name": data.Name, @@ -293,7 +298,9 @@ func (r *GroupResource) Delete(ctx context.Context, req resource.DeleteRequest, client := r.data.Client groupID := data.ID.ValueUUID() - tflog.Trace(ctx, "deleting group") + tflog.Trace(ctx, "deleting group", map[string]any{ + "id": groupID, + }) err := client.DeleteGroup(ctx, groupID) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete group, got error: %s", err)) @@ -320,24 +327,3 @@ func (r *GroupResource) ImportState(ctx context.Context, req resource.ImportStat } resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } - -func memberDiff(curMembers []codersdk.ReducedUser, newMembers []string) (add, remove []string) { - curSet := make(map[string]struct{}, len(curMembers)) - newSet := make(map[string]struct{}, len(newMembers)) - - for _, user := range curMembers { - curSet[user.ID.String()] = struct{}{} - } - for _, userID := range newMembers { - newSet[userID] = struct{}{} - if _, exists := curSet[userID]; !exists { - add = append(add, userID) - } - } - for _, user := range curMembers { - if _, exists := newSet[user.ID.String()]; !exists { - remove = append(remove, user.ID.String()) - } - } - return add, remove -} diff --git a/internal/provider/group_resource_test.go b/internal/provider/group_resource_test.go index 2b90868..b049976 100644 --- a/internal/provider/group_resource_test.go +++ b/internal/provider/group_resource_test.go @@ -62,49 +62,66 @@ func TestAccGroupResource(t *testing.T) { cfg3 := cfg2 cfg3.Members = nil - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read - { - Config: cfg1.String(t), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group"), - resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group"), - resource.TestCheckResourceAttr("coderd_group.test", "avatar_url", "https://google.com"), - resource.TestCheckResourceAttr("coderd_group.test", "quota_allowance", "100"), - resource.TestCheckResourceAttr("coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()), - resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"), - resource.TestCheckResourceAttr("coderd_group.test", "members.0", user1.ID.String()), - ), + t.Run("CreateImportUpdateReadOk", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: cfg1.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group"), + resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group"), + resource.TestCheckResourceAttr("coderd_group.test", "avatar_url", "https://google.com"), + resource.TestCheckResourceAttr("coderd_group.test", "quota_allowance", "100"), + resource.TestCheckResourceAttr("coderd_group.test", "organization_id", firstUser.OrganizationIDs[0].String()), + resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"), + resource.TestCheckResourceAttr("coderd_group.test", "members.0", user1.ID.String()), + ), + }, + // Import + { + Config: cfg1.String(t), + ResourceName: "coderd_group.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"members"}, + }, + // Update and Read + { + Config: cfg2.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group-new"), + resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group New"), + resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"), + resource.TestCheckResourceAttr("coderd_group.test", "members.0", user2.ID.String()), + ), + }, + // Unmanaged members + { + Config: cfg3.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckNoResourceAttr("coderd_group.test", "members"), + ), + }, }, - // Import - { - Config: cfg1.String(t), - ResourceName: "coderd_group.test", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"members"}, - }, - // Update and Read - { - Config: cfg2.String(t), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("coderd_group.test", "name", "example-group-new"), - resource.TestCheckResourceAttr("coderd_group.test", "display_name", "Example Group New"), - resource.TestCheckResourceAttr("coderd_group.test", "members.#", "1"), - resource.TestCheckResourceAttr("coderd_group.test", "members.0", user2.ID.String()), - ), - }, - // Unmanaged members - { - Config: cfg3.String(t), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckNoResourceAttr("coderd_group.test", "members"), - ), + }) + }) + + t.Run("CreateUnmanagedMembersOk", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg3.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckNoResourceAttr("coderd_group.test", "members"), + ), + }, }, - }, + }) }) } diff --git a/internal/provider/util.go b/internal/provider/util.go index 75f5196..bb1386e 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -6,6 +6,8 @@ import ( "fmt" "os" "path/filepath" + + "github.com/google/uuid" ) func PtrTo[T any](v T) *T { @@ -76,3 +78,26 @@ func computeDirectoryHash(directory string) (string, error) { } return hex.EncodeToString(hash.Sum(nil)), nil } + +// memberDiff returns the members to add and remove from the group, given the current members and the planned members. +// plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set. +func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) { + curSet := make(map[uuid.UUID]struct{}, len(curMembers)) + planSet := make(map[uuid.UUID]struct{}, len(plannedMembers)) + + for _, userID := range curMembers { + curSet[userID] = struct{}{} + } + for _, plannedUserID := range plannedMembers { + planSet[plannedUserID.ValueUUID()] = struct{}{} + if _, exists := curSet[plannedUserID.ValueUUID()]; !exists { + add = append(add, plannedUserID.ValueString()) + } + } + for _, curUserID := range curMembers { + if _, exists := planSet[curUserID]; !exists { + remove = append(remove, curUserID.String()) + } + } + return add, remove +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy