Skip to content

Commit

Permalink
Allow mapping many teams to one project (#33)
Browse files Browse the repository at this point in the history
* feat: allow adding more than one team to a project via  project member

* docs: improve naming in setTeams

* fix: remove unnescessary nil check

* refactor: minor readability improvements

* feat: use a typeset for allowed domain

* docs: improve comments of why we choose teams[0]

* refactor: improve memory performance and remove needless check
  • Loading branch information
taj-p authored Jul 12, 2022
1 parent b42e4dd commit ed88c94
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 25 deletions.
120 changes: 95 additions & 25 deletions sentry/resource_sentry_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,19 @@ func resourceSentryProject() *schema.Resource {
Required: true,
},
"team": {
Description: "The slug of the team to create the project for.",
Description: "The slug of the team to create the project for. One of 'team' or 'teams' must be set.",
Type: schema.TypeString,
Required: true,
Deprecated: "To be replaced by 'teams' in a future release.",
Optional: true,
},
"teams": {
Description: "The slugs of the teams to create the project for. One of 'team' or 'teams' must be set.",
Type: schema.TypeSet,
Elem: &schema.Schema{
Type: schema.TypeString,
},
ExactlyOneOf: []string{"team"},
Optional: true,
},
"name": {
Description: "The name for the project.",
Expand Down Expand Up @@ -120,7 +130,7 @@ func resourceSentryProject() *schema.Resource {
Default: false,
},
"allowed_domains": {
Type: schema.TypeList,
Type: schema.TypeSet,
Computed: true,
Description: "The domains allowed to be collected",
Optional: true,
Expand All @@ -143,14 +153,22 @@ func resourceSentryProjectCreate(ctx context.Context, d *schema.ResourceData, me

org := d.Get("organization").(string)
team := d.Get("team").(string)
var teams []interface{}
if team == "" {
// Since `Set.List()` produces deterministic ordering, `teams[0]` should always
// resolve to the same value given the same `teams`.
teams = d.Get("teams").(*schema.Set).List()
team = teams[0].(string)
}
params := &sentry.CreateProjectParams{
Name: d.Get("name").(string),
Slug: d.Get("slug").(string),
}

tflog.Debug(ctx, "Creating Sentry project", map[string]interface{}{
"teamName": team,
"org": org,
"team": team,
"teams": teams,
"org": org,
})
proj, _, err := client.Projects.Create(ctx, org, team, params)
if err != nil {
Expand Down Expand Up @@ -187,12 +205,31 @@ func resourceSentryProjectRead(ctx context.Context, d *schema.ResourceData, meta
"org": org,
})

setTeams := func() error {
if len(proj.Teams) <= 1 {
return multierror.Append(
d.Set("team", proj.Team.Slug),
d.Set("teams", nil),
)
}

teams := make([]string, len(proj.Teams))
for i, team := range proj.Teams {
teams[i] = *team.Slug
}

return multierror.Append(
d.Set("team", nil),
d.Set("teams", teams),
)
}

d.SetId(proj.Slug)
retErr := multierror.Append(
d.Set("organization", proj.Organization.Slug),
d.Set("team", proj.Team.Slug),
d.Set("name", proj.Name),
d.Set("slug", proj.Slug),
setTeams(),
d.Set("platform", proj.Platform),
d.Set("internal_id", proj.ID),
d.Set("is_public", proj.IsPublic),
Expand Down Expand Up @@ -248,30 +285,63 @@ func resourceSentryProjectUpdate(ctx context.Context, d *schema.ResourceData, me

d.SetId(proj.Slug)

oldTeams := map[string]struct{}{}
newTeams := map[string]struct{}{}
if d.HasChange("team") {
o, n := d.GetChange("team")

tflog.Debug(ctx, "Adding team to project", map[string]interface{}{
"org": org,
"project": project,
"team": n,
})
_, _, err = client.Projects.AddTeam(ctx, org, project, n.(string))
oldTeam, newTeam := d.GetChange("team")
if oldTeam.(string) != "" {
oldTeams[oldTeam.(string)] = struct{}{}
}
if newTeam.(string) != "" {
newTeams[newTeam.(string)] = struct{}{}
}
}

if d.HasChange("teams") {
o, n := d.GetChange("teams")
for _, oldTeam := range o.(*schema.Set).List() {
if oldTeam.(string) != "" {
oldTeams[oldTeam.(string)] = struct{}{}
}
}
for _, newTeam := range n.(*schema.Set).List() {
if newTeam.(string) != "" {
newTeams[newTeam.(string)] = struct{}{}
}
}
}

// Ensure old teams and new teams do not overlap.
for newTeam := range newTeams {
if _, exists := oldTeams[newTeam]; exists {
delete(oldTeams, newTeam)
}
}

tflog.Debug(ctx, "Adding teams to project", map[string]interface{}{
"org": org,
"project": project,
"teamsToAdd": newTeams,
})

for newTeam := range newTeams {
_, _, err = client.Projects.AddTeam(ctx, org, project, newTeam)
if err != nil {
return diag.FromErr(err)
}
}

tflog.Debug(ctx, "Removing teams from project", map[string]interface{}{
"org": org,
"project": project,
"teamsToRemove": oldTeams,
})

if o := o.(string); o != "" {
tflog.Debug(ctx, "Removing team from project", map[string]interface{}{
"org": org,
"project": project,
"team": o,
})
resp, err := client.Projects.RemoveTeam(ctx, org, project, o)
if err != nil {
if resp.Response.StatusCode != http.StatusNotFound {
return diag.FromErr(err)
}
for oldTeam := range oldTeams {
resp, err := client.Projects.RemoveTeam(ctx, org, project, oldTeam)
if err != nil {
if resp.Response.StatusCode != http.StatusNotFound {
return diag.FromErr(err)
}
}
}
Expand Down
106 changes: 106 additions & 0 deletions sentry/resource_sentry_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
Expand Down Expand Up @@ -98,6 +99,67 @@ func TestAccSentryProject_changeTeam(t *testing.T) {
})
}

func TestAccSentryProject_teams(t *testing.T) {
teamNamePrefix := "tf-team-"
teams := []string{
acctest.RandomWithPrefix(teamNamePrefix + "1"),
acctest.RandomWithPrefix(teamNamePrefix + "2"),
acctest.RandomWithPrefix(teamNamePrefix + "3"),
}
projectName := acctest.RandomWithPrefix("tf-project")
rn := "sentry_project." + projectName

check := func(projectName string, team string, teams []string) resource.TestCheckFunc {
var projectID string

testChecks := []resource.TestCheckFunc{
testAccCheckSentryProjectExists(rn, &projectID),
resource.TestCheckResourceAttr(rn, "organization", testOrganization),
resource.TestCheckResourceAttr(rn, "name", projectName),
resource.TestCheckResourceAttrSet(rn, "slug"),
resource.TestCheckResourceAttr(rn, "platform", "go"),
resource.TestCheckResourceAttrSet(rn, "internal_id"),
resource.TestCheckResourceAttrPtr(rn, "internal_id", &projectID),
resource.TestCheckResourceAttrPair(rn, "project_id", rn, "internal_id"),
}

if team != "" {
testChecks = append(testChecks, resource.TestCheckResourceAttr(rn, "team", team))
}

for _, team := range teams {
testChecks = append(testChecks, resource.TestCheckTypeSetElemAttr(rn, "teams.*", team))
}

return resource.ComposeTestCheckFunc(testChecks...)
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckSentryProjectDestroy,
Steps: []resource.TestStep{
{
Config: testAccSentryProjectConfig_teams(projectName, teams[0], []string{}),
Check: check(projectName, teams[0], []string{}),
},
{
Config: testAccSentryProjectConfig_teams(projectName, "", teams),
Check: check(projectName, "", teams),
},
{
ResourceName: rn,
ImportState: true,
ImportStateIdFunc: testAccSentryProjectImportStateIdFunc(rn),
ImportStateVerify: true,
// TODO: Until we update go-sentry to include these attributes in its project.Get function,
// we will ignore them for now.
ImportStateVerifyIgnore: []string{"allowed_domains", "remove_default_key", "remove_default_rule"},
},
},
})
}

func testAccCheckSentryProjectDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*sentry.Client)

Expand Down Expand Up @@ -194,3 +256,47 @@ resource "sentry_project" "test" {
}
`, teamName1, teamName2, projectName, teamResourceName)
}

func testAccSentryProjectConfig_teams(projectName string, team string, teams []string) string {

config := testAccSentryOrganizationDataSourceConfig
teamSlugs := make([]string, len(teams))

if team != "" {
config += testAccSentryTeam(team)
return config + fmt.Sprintf(`
resource "sentry_project" "%[1]s"{
organization = sentry_team.%[2]s.organization
team = "%[2]s"
name = "%[1]s"
platform = "go"
}
`, projectName, team)
}

for i, team := range teams {
config += testAccSentryTeam(team)
teamSlugs[i] = fmt.Sprintf("sentry_team.%[1]s.slug", team)
}

projectTeams := "[" + strings.Join(teamSlugs, ", ") + "]"

return config + fmt.Sprintf(`
resource "sentry_project" "%[1]s"{
organization = sentry_team.%[2]s.organization
teams = %[3]s
name = "%[1]s"
platform = "go"
}
`, projectName, teams[0], projectTeams)
}

func testAccSentryTeam(teamName string) string {
return fmt.Sprintf(`
resource "sentry_team" "%[1]s" {
organization = data.sentry_organization.test.id
name = "%[1]s"
slug = "%[1]s"
}
`, teamName)
}

0 comments on commit ed88c94

Please sign in to comment.