Skip to content

Commit

Permalink
feat: update team role
Browse files Browse the repository at this point in the history
  • Loading branch information
jianyuan committed Dec 9, 2023
1 parent d49fb1f commit 8c1fd86
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 27 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.12.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0
github.com/jianyuan/go-sentry/v2 v2.5.1-0.20231207013239-9f4943bc48d7
github.com/jianyuan/go-sentry/v2 v2.5.1-0.20231209111347-e63a506909a6
github.com/mitchellh/mapstructure v1.5.0
golang.org/x/oauth2 v0.11.0
golang.org/x/sync v0.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ github.com/jianyuan/go-sentry/v2 v2.5.0 h1:bRlJWve/ScwUg5l4cSYxm+pl6b6GsdT2HF7pQ
github.com/jianyuan/go-sentry/v2 v2.5.0/go.mod h1:lj3rm5L8y4fpA7hN2P35KvNxAlnxgQMS5F1iHYPfJM0=
github.com/jianyuan/go-sentry/v2 v2.5.1-0.20231207013239-9f4943bc48d7 h1:4A0tZPEvtgHFHz/UOePsGdeWThX67+ES8mau7vaUNQM=
github.com/jianyuan/go-sentry/v2 v2.5.1-0.20231207013239-9f4943bc48d7/go.mod h1:YN4yg9u6/b5TfsmXy8OauRu3cyvVXRe1mJY7Pe+/Dt4=
github.com/jianyuan/go-sentry/v2 v2.5.1-0.20231209111347-e63a506909a6 h1:4kL6kXwbaulfGKc7kjYFdwcXTITzTY5AW3WDMz+vPhM=
github.com/jianyuan/go-sentry/v2 v2.5.1-0.20231209111347-e63a506909a6/go.mod h1:YN4yg9u6/b5TfsmXy8OauRu3cyvVXRe1mJY7Pe+/Dt4=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down
125 changes: 104 additions & 21 deletions internal/provider/resource_team_member.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
"sync"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
Expand All @@ -22,13 +23,16 @@ func NewTeamMemberResource() resource.Resource {

type TeamMemberResource struct {
client *sentry.Client

roleMu sync.Mutex
}

type TeamMemberResourceModel struct {
Id types.String `tfsdk:"id"`
Organization types.String `tfsdk:"organization"`
MemberId types.String `tfsdk:"member_id"`
MemberID types.String `tfsdk:"member_id"`
TeamSlug types.String `tfsdk:"team_slug"`
Role types.String `tfsdk:"role"`
}

func (r *TeamMemberResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
Expand Down Expand Up @@ -65,6 +69,10 @@ func (r *TeamMemberResource) Schema(ctx context.Context, req resource.SchemaRequ
stringplanmodifier.RequiresReplace(),
},
},
"role": schema.StringAttribute{
Description: "The role of the member in the team. When not set, resolve to the minimum team role given by this member's organization role.",
Optional: true,
},
},
}
}
Expand All @@ -89,6 +97,63 @@ func (r *TeamMemberResource) Configure(ctx context.Context, req resource.Configu
r.client = client
}

func (r *TeamMemberResource) readRole(ctx context.Context, orgSlug string, memberID string, teamSlug string) (*string, error) {
r.roleMu.Lock()
defer r.roleMu.Unlock()

orgMember, _, err := r.client.OrganizationMembers.Get(ctx, orgSlug, memberID)
if err != nil {
return nil, fmt.Errorf("unable to read organization member, got error: %s", err)
}

for _, teamRole := range orgMember.TeamRoles {
if teamRole.TeamSlug == teamSlug {
return &teamRole.Role, nil
}
}

return nil, fmt.Errorf("unable to find team member")
}

func (r *TeamMemberResource) updateRole(ctx context.Context, orgSlug string, memberID string, teamSlug string, role string) (*string, error) {
r.roleMu.Lock()
defer r.roleMu.Unlock()

orgMember, _, err := r.client.OrganizationMembers.Get(ctx, orgSlug, memberID)
if err != nil {
return nil, fmt.Errorf("unable to read organization member, got error: %s", err)
}

teamRoles := make([]sentry.TeamRole, 0, len(orgMember.TeamRoles))
for _, teamRole := range orgMember.TeamRoles {
if teamRole.TeamSlug == teamSlug {
teamRole.Role = role
}
teamRoles = append(teamRoles, teamRole)
}

orgMember, _, err = r.client.OrganizationMembers.Update(
ctx,
orgSlug,
memberID,
&sentry.UpdateOrganizationMemberParams{
OrganizationRole: orgMember.OrganizationRole,
TeamRoles: teamRoles,
},
)
if err != nil {
return nil, fmt.Errorf("unable to update organization member's team role, got error: %s", err)
}

for _, teamRole := range orgMember.TeamRoles {
if teamRole.TeamSlug == teamSlug {
return &teamRole.Role, nil
}
}

return nil, fmt.Errorf("unable to find team member")
}

func (r *TeamMemberResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data TeamMemberResourceModel

Expand All @@ -102,17 +167,26 @@ func (r *TeamMemberResource) Create(ctx context.Context, req resource.CreateRequ
member, _, err := r.client.TeamMembers.Create(
ctx,
data.Organization.ValueString(),
data.MemberId.ValueString(),
data.MemberID.ValueString(),
data.TeamSlug.ValueString(),
)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to add member to team, got error: %s", err))
return
}

data.Id = types.StringValue(buildThreePartID(data.Organization.ValueString(), sentry.StringValue(member.Slug), data.MemberId.ValueString()))
data.Id = types.StringValue(buildThreePartID(data.Organization.ValueString(), sentry.StringValue(member.Slug), data.MemberID.ValueString()))
data.TeamSlug = types.StringPointerValue(member.Slug)

if !data.Role.IsNull() {
role, err := r.updateRole(ctx, data.Organization.ValueString(), data.MemberID.ValueString(), data.TeamSlug.ValueString(), data.Role.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", err.Error())
return
}
data.Role = types.StringPointerValue(role)
}

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Expand All @@ -127,32 +201,41 @@ func (r *TeamMemberResource) Read(ctx context.Context, req resource.ReadRequest,
return
}

member, _, err := r.client.OrganizationMembers.Get(ctx, data.Organization.ValueString(), data.MemberId.ValueString())
role, err := r.readRole(ctx, data.Organization.ValueString(), data.MemberID.ValueString(), data.TeamSlug.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read organization member, got error: %s", err))
resp.Diagnostics.AddError("Client Error", err.Error())
resp.State.RemoveResource(ctx)
return
}

for _, teamSlug := range member.Teams {
if teamSlug == data.TeamSlug.ValueString() {
data.Id = types.StringValue(buildThreePartID(data.Organization.ValueString(), teamSlug, data.MemberId.ValueString()))
data.TeamSlug = types.StringValue(teamSlug)
data.Id = types.StringValue(buildThreePartID(data.Organization.ValueString(), data.TeamSlug.ValueString(), data.MemberID.ValueString()))
data.Role = types.StringPointerValue(role)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
func (r *TeamMemberResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state TeamMemberResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

// Update the role if it has changed
if !plan.Role.Equal(state.Role) {
role, err := r.updateRole(ctx, plan.Organization.ValueString(), plan.MemberID.ValueString(), plan.TeamSlug.ValueString(), plan.Role.ValueString())
if err != nil {
resp.Diagnostics.AddError("Client Error", err.Error())
return
}
state.Role = types.StringPointerValue(role)
}

resp.Diagnostics.AddError("Client Error", "Unable to find team member")
resp.State.RemoveResource(ctx)
}

func (r *TeamMemberResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
resp.Diagnostics.AddError(
"Unexpected Resource Update",
"Resource does not support updates. Please report this issue to the provider developers.",
)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *TeamMemberResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
Expand All @@ -168,7 +251,7 @@ func (r *TeamMemberResource) Delete(ctx context.Context, req resource.DeleteRequ
_, _, err := r.client.TeamMembers.Delete(
ctx,
data.Organization.ValueString(),
data.MemberId.ValueString(),
data.MemberID.ValueString(),
data.TeamSlug.ValueString(),
)
if err != nil {
Expand Down
23 changes: 18 additions & 5 deletions internal/provider/resource_team_member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,28 @@ func TestAccTeamMemberResource(t *testing.T) {
CheckDestroy: testAccCheckTeamMemberDestroy,
Steps: []resource.TestStep{
{
Config: testAccTeamMemberConfig(teamSlug, member1Email, member2Email, "sentry_organization_member.test_1"),
Config: testAccTeamMemberConfig(teamSlug, member1Email, member2Email, "sentry_organization_member.test_1", "contributor"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(rn, "organization", acctest.TestOrganization),
resource.TestCheckResourceAttr(rn, "role", "contributor"),
resource.TestCheckResourceAttrPair(rn, "member_id", "sentry_organization_member.test_1", "internal_id"),
resource.TestCheckResourceAttrPair(rn, "team_slug", "sentry_team.test", "slug"),
),
}, {
Config: testAccTeamMemberConfig(teamSlug, member1Email, member2Email, "sentry_organization_member.test_2"),
},
{
Config: testAccTeamMemberConfig(teamSlug, member1Email, member2Email, "sentry_organization_member.test_1", "admin"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(rn, "organization", acctest.TestOrganization),
resource.TestCheckResourceAttr(rn, "role", "admin"),
resource.TestCheckResourceAttrPair(rn, "member_id", "sentry_organization_member.test_1", "internal_id"),
resource.TestCheckResourceAttrPair(rn, "team_slug", "sentry_team.test", "slug"),
),
},
{
Config: testAccTeamMemberConfig(teamSlug, member1Email, member2Email, "sentry_organization_member.test_2", "contributor"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(rn, "organization", acctest.TestOrganization),
resource.TestCheckResourceAttr(rn, "role", "contributor"),
resource.TestCheckResourceAttrPair(rn, "member_id", "sentry_organization_member.test_2", "internal_id"),
resource.TestCheckResourceAttrPair(rn, "team_slug", "sentry_team.test", "slug"),
),
Expand All @@ -69,7 +81,7 @@ func TestAccTeamMemberResource(t *testing.T) {
})
}

func testAccTeamMemberConfig(teamName, member1Email, member2Email, memberResourceName string) string {
func testAccTeamMemberConfig(teamName, member1Email, member2Email, memberResourceName, memberRole string) string {
return testAccOrganizationDataSourceConfig + fmt.Sprintf(`
resource "sentry_team" "test" {
organization = data.sentry_organization.test.id
Expand All @@ -93,6 +105,7 @@ resource "sentry_team_member" "test" {
organization = data.sentry_organization.test.id
team_slug = sentry_team.test.slug
member_id = %[4]s.internal_id
role = "%[5]s"
}
`, teamName, member1Email, member2Email, memberResourceName)
`, teamName, member1Email, member2Email, memberResourceName, memberRole)
}

0 comments on commit 8c1fd86

Please sign in to comment.