diff --git a/CHANGELOG.md b/CHANGELOG.md index 780810e268..16c248c3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 4.4.0 (February 5, 2021) + +BUG FIXES: + +- Add `create_default_maintainer` option to `github_team` ([#527](https://github.com/integrations/terraform-provider-github/pull/527)), ([#104](https://github.com/integrations/terraform-provider-github/pull/104)), ([#130](https://github.com/integrations/terraform-provider-github/pull/130)) + + ## 4.3.2 (February 2, 2021) BUG FIXES: diff --git a/github/resource_github_team.go b/github/resource_github_team.go index 16d4db2098..d06a397adf 100644 --- a/github/resource_github_team.go +++ b/github/resource_github_team.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-github/v32/github" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/shurcooL/githubv4" ) func resourceGithubTeam() *schema.Resource { @@ -43,6 +44,11 @@ func resourceGithubTeam() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "create_default_maintainer": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "slug": { Type: schema.TypeString, Computed: true, @@ -55,6 +61,10 @@ func resourceGithubTeam() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "members_count": { + Type: schema.TypeInt, + Computed: true, + }, }, } } @@ -67,26 +77,36 @@ func resourceGithubTeamCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*Owner).v3client - orgName := meta.(*Owner).name + ownerName := meta.(*Owner).name name := d.Get("name").(string) + newTeam := github.NewTeam{ Name: name, Description: github.String(d.Get("description").(string)), Privacy: github.String(d.Get("privacy").(string)), } + if parentTeamID, ok := d.GetOk("parent_team_id"); ok { id := int64(parentTeamID.(int)) newTeam.ParentTeamID = &id } ctx := context.Background() - log.Printf("[DEBUG] Creating team: %s (%s)", name, orgName) + log.Printf("[DEBUG] Creating team: %s (%s)", name, ownerName) githubTeam, _, err := client.Teams.CreateTeam(ctx, - orgName, newTeam) + ownerName, newTeam) if err != nil { return err } + create_default_maintainer := d.Get("create_default_maintainer").(bool) + if !create_default_maintainer { + log.Printf("[DEBUG] Removing default maintainer from team: %s (%s)", name, ownerName) + if err := removeDefaultMaintainer(*githubTeam.Slug, meta); err != nil { + return err + } + } + if ldapDN := d.Get("ldap_dn").(string); ldapDN != "" { mapping := &github.TeamLDAPMapping{ LDAPDN: github.String(ldapDN), @@ -148,6 +168,7 @@ func resourceGithubTeamRead(d *schema.ResourceData, meta interface{}) error { d.Set("ldap_dn", team.GetLDAPDN()) d.Set("slug", team.GetSlug()) d.Set("node_id", team.GetNodeID()) + d.Set("members_count", team.GetMembersCount()) return nil } @@ -217,3 +238,43 @@ func resourceGithubTeamDelete(d *schema.ResourceData, meta interface{}) error { _, err = client.Teams.DeleteTeamByID(ctx, orgId, id) return err } + +func removeDefaultMaintainer(teamSlug string, meta interface{}) error { + + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + v4client := meta.(*Owner).v4client + + type User struct { + Login githubv4.String + } + + var query struct { + Organization struct { + Team struct { + Members struct { + Nodes []User + } + } `graphql:"team(slug:$slug)"` + } `graphql:"organization(login:$login)"` + } + variables := map[string]interface{}{ + "slug": githubv4.String(teamSlug), + "login": githubv4.String(orgName), + } + + err := v4client.Query(meta.(*Owner).StopContext, &query, variables) + if err != nil { + return err + } + + for _, user := range query.Organization.Team.Members.Nodes { + log.Printf("[DEBUG] Removing default maintainer from team: %s", user.Login) + _, err := client.Teams.RemoveTeamMembershipBySlug(meta.(*Owner).StopContext, orgName, teamSlug, string(user.Login)) + if err != nil { + return err + } + } + + return nil +} diff --git a/github/resource_github_team_test.go b/github/resource_github_team_test.go index 253a8e3a31..c6ccd203f1 100644 --- a/github/resource_github_team_test.go +++ b/github/resource_github_team_test.go @@ -1,261 +1,153 @@ package github import ( - "context" "fmt" - "strconv" "testing" - "github.com/google/go-github/v32/github" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -func TestAccGithubTeam_basic(t *testing.T) { - if err := testAccCheckOrganization(); err != nil { - t.Skipf("Skipping because %s.", err.Error()) - } - - var team github.Team - - rn := "github_team.foo" - randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - name := fmt.Sprintf("tf-acc-test-%s", randString) - updatedName := fmt.Sprintf("tf-acc-test-updated-%s", randString) - description := "Terraform acc test group" - updatedDescription := "Terraform acc test group - updated" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckGithubTeamDestroy, - Steps: []resource.TestStep{ - { - Config: testAccGithubTeamConfig(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckGithubTeamExists(rn, &team), - testAccCheckGithubTeamAttributes(&team, name, description, nil), - resource.TestCheckResourceAttr(rn, "name", name), - resource.TestCheckResourceAttr(rn, "description", description), - resource.TestCheckResourceAttr(rn, "privacy", "secret"), - resource.TestCheckNoResourceAttr(rn, "parent_team_id"), - resource.TestCheckResourceAttr(rn, "ldap_dn", ""), - resource.TestCheckResourceAttr(rn, "slug", name), - ), - }, - { - Config: testAccGithubTeamUpdateConfig(randString), - Check: resource.ComposeTestCheckFunc( - testAccCheckGithubTeamExists(rn, &team), - testAccCheckGithubTeamAttributes(&team, updatedName, updatedDescription, nil), - resource.TestCheckResourceAttr(rn, "name", updatedName), - resource.TestCheckResourceAttr(rn, "description", updatedDescription), - resource.TestCheckResourceAttr(rn, "privacy", "closed"), - resource.TestCheckNoResourceAttr(rn, "parent_team_id"), - resource.TestCheckResourceAttr(rn, "ldap_dn", ""), - resource.TestCheckResourceAttr(rn, "slug", updatedName), - ), - }, - { - ResourceName: rn, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} +func TestAccGithubTeam(t *testing.T) { -func TestAccGithubTeam_slug(t *testing.T) { - if err := testAccCheckOrganization(); err != nil { - t.Skipf("Skipping because %s.", err.Error()) - } - - var team github.Team - - rn := "github_team.foo" - randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - name := fmt.Sprintf("TF Acc Test %s", randString) - description := "Terraform acc test group" - expectedSlug := fmt.Sprintf("tf-acc-test-%s", randString) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckGithubTeamDestroy, - Steps: []resource.TestStep{ - { - Config: testAccGithubTeamConfig(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckGithubTeamExists(rn, &team), - testAccCheckGithubTeamAttributes(&team, name, description, nil), - resource.TestCheckResourceAttr(rn, "name", name), - resource.TestCheckResourceAttr(rn, "description", description), - resource.TestCheckResourceAttr(rn, "privacy", "secret"), - resource.TestCheckNoResourceAttr(rn, "parent_team_id"), - resource.TestCheckResourceAttr(rn, "ldap_dn", ""), - resource.TestCheckResourceAttr(rn, "slug", expectedSlug), - ), - }, - { - ResourceName: rn, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) -func TestAccGithubTeam_hierarchical(t *testing.T) { - if err := testAccCheckOrganization(); err != nil { - t.Skipf("Skipping because %s.", err.Error()) - } - - var parent, child github.Team - - rn := "github_team.parent" - randString := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - parentName := fmt.Sprintf("tf-acc-parent-%s", randString) - childName := fmt.Sprintf("tf-acc-child-%s", randString) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckGithubTeamDestroy, - Steps: []resource.TestStep{ - { - Config: testAccGithubTeamHierarchicalConfig(randString), - Check: resource.ComposeTestCheckFunc( - testAccCheckGithubTeamExists(rn, &parent), - testAccCheckGithubTeamAttributes(&parent, parentName, "Terraform acc test parent team", nil), - testAccCheckGithubTeamExists("github_team.child", &child), - testAccCheckGithubTeamAttributes(&child, childName, "Terraform acc test child team", &parent), - ), - }, - { - ResourceName: rn, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} + t.Run("creates a team configured with defaults", func(t *testing.T) { -func testAccCheckGithubTeamExists(n string, team *github.Team) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not Found: %s", n) + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "tf-acc-%s" + } + `, randomID) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("github_team.test", "slug"), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) } - if rs.Primary.ID == "" { - return fmt.Errorf("No Team ID is set") - } + t.Run("with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) - conn := testAccProvider.Meta().(*Owner).v3client - id, err := strconv.ParseInt(rs.Primary.ID, 10, 64) - if err != nil { - return unconvertibleIdErr(rs.Primary.ID, err) - } + t.Run("with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + + }) - githubTeam, _, err := conn.Teams.GetTeamByID(context.TODO(), testAccProvider.Meta().(*Owner).id, id) - if err != nil { - return err - } - *team = *githubTeam - return nil - } } -func testAccCheckGithubTeamAttributes(team *github.Team, name, description string, parentTeam *github.Team) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *team.Name != name { - return fmt.Errorf("Team name does not match: %s, %s", *team.Name, name) - } +func TestAccGithubTeamHierarchical(t *testing.T) { - if *team.Description != description { - return fmt.Errorf("Team description does not match: %s, %s", *team.Description, description) - } + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - if parentTeam == nil && team.Parent != nil { - return fmt.Errorf("Team parent ID was expected to be empty, but was %d", team.Parent.GetID()) - } else if parentTeam != nil && team.Parent == nil { - return fmt.Errorf("Team parent ID was expected to be %d, but was not present", parentTeam.GetID()) - } else if parentTeam != nil && team.Parent.GetID() != parentTeam.GetID() { - return fmt.Errorf("Team parent ID does not match: %d, %d", team.Parent.GetID(), parentTeam.GetID()) + t.Run("creates a hierarchy of teams", func(t *testing.T) { + + config := fmt.Sprintf(` + resource "github_team" "parent" { + name = "tf-acc-parent-%s" + description = "Terraform acc test parent team" + privacy = "closed" + } + + resource "github_team" "child" { + name = "tf-acc-child-%[1]s" + description = "Terraform acc test child team" + privacy = "closed" + parent_team_id = "${github_team.parent.id}" + } + `, randomID) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("github_team.child", "parent_team_id"), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) } - return nil - } -} + t.Run("with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) -func testAccCheckGithubTeamDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*Owner).v3client - orgId := testAccProvider.Meta().(*Owner).id + t.Run("with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) - for _, rs := range s.RootModule().Resources { - if rs.Type != "github_team" { - continue - } + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) - id, err := strconv.ParseInt(rs.Primary.ID, 10, 64) - if err != nil { - return unconvertibleIdErr(rs.Primary.ID, err) - } + }) - team, resp, err := conn.Teams.GetTeamByID(context.TODO(), orgId, id) - if err != nil { - if resp.StatusCode != 404 { - return err - } - } +} +func TestAccGithubTeamRemovesDefaultMaintainer(t *testing.T) { - var teamID string - if team != nil { - teamID = strconv.FormatInt(team.GetID(), 10) - } - if teamID == rs.Primary.ID { - return fmt.Errorf("Team still exists") + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + t.Run("creates a team and removes the default maintainer", func(t *testing.T) { + + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "tf-acc-%s" + create_default_maintainer = false + } + `, randomID) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("github_team.test", "members_count", "0"), + ) + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) } - return nil - } + t.Run("with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) - return nil -} + t.Run("with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) -func testAccGithubTeamConfig(teamName string) string { - return fmt.Sprintf(` -resource "github_team" "foo" { - name = "%s" - description = "Terraform acc test group" - privacy = "secret" -} -`, teamName) -} + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) -func testAccGithubTeamUpdateConfig(randString string) string { - return fmt.Sprintf(` -resource "github_team" "foo" { - name = "tf-acc-test-updated-%s" - description = "Terraform acc test group - updated" - privacy = "closed" -} -`, randString) -} + }) -func testAccGithubTeamHierarchicalConfig(randString string) string { - return fmt.Sprintf(` -resource "github_team" "parent" { - name = "tf-acc-parent-%s" - description = "Terraform acc test parent team" - privacy = "closed" -} -resource "github_team" "child" { - name = "tf-acc-child-%s" - description = "Terraform acc test child team" - privacy = "closed" - parent_team_id = "${github_team.parent.id}" -} -`, randString, randString) } diff --git a/website/docs/r/team.html.markdown b/website/docs/r/team.html.markdown index b903d86bd3..76c354bc0f 100644 --- a/website/docs/r/team.html.markdown +++ b/website/docs/r/team.html.markdown @@ -33,6 +33,7 @@ The following arguments are supported: Defaults to `secret`. * `parent_team_id` - (Optional) The ID of the parent team, if this is a nested team. * `ldap_dn` - (Optional) The LDAP Distinguished Name of the group where membership will be synchronized. Only available in GitHub Enterprise Server. +* `create_default_maintainer` - (Optional) Adds a default maintainer to the team. Defaults to `true` and removes the default maintaner when `false`. ## Attributes Reference