From 0419c6b8acc0ea1c01d7aa02887a63754091189f Mon Sep 17 00:00:00 2001 From: Steve Hipwell Date: Fri, 17 Jan 2025 01:03:55 +0000 Subject: [PATCH] feat: Updated repo collaborators to support ignoring teams (#2481) Signed-off-by: Steve Hipwell --- ...esource_github_repository_collaborators.go | 117 ++++++++--- ...ce_github_repository_collaborators_test.go | 191 +++++++++++++++--- .../r/repository_collaborators.html.markdown | 25 ++- 3 files changed, 259 insertions(+), 74 deletions(-) diff --git a/github/resource_github_repository_collaborators.go b/github/resource_github_repository_collaborators.go index dc2cbcddc8..54ee75d119 100644 --- a/github/resource_github_repository_collaborators.go +++ b/github/resource_github_repository_collaborators.go @@ -32,7 +32,7 @@ func resourceGithubRepositoryCollaborators() *schema.Resource { "user": { Type: schema.TypeSet, Optional: true, - Description: "List of users", + Description: "List of users.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "permission": { @@ -52,7 +52,7 @@ func resourceGithubRepositoryCollaborators() *schema.Resource { "team": { Type: schema.TypeSet, Optional: true, - Description: "List of teams", + Description: "List of teams.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "permission": { @@ -76,6 +76,20 @@ func resourceGithubRepositoryCollaborators() *schema.Resource { }, Computed: true, }, + "ignore_team": { + Type: schema.TypeSet, + Optional: true, + Description: "List of teams to ignore.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeString, + Description: "ID or slug of the team to ignore.", + Required: true, + }, + }, + }, + }, }, CustomizeDiff: customdiff.Sequence( @@ -145,16 +159,16 @@ func (c teamCollaborator) Empty() bool { return c == teamCollaborator{} } -func flattenTeamCollaborator(obj teamCollaborator, teamIDs []int64) interface{} { +func flattenTeamCollaborator(obj teamCollaborator, teamSlugs []string) interface{} { if obj.Empty() { return nil } var teamIDString string - if slices.Contains(teamIDs, obj.teamID) { - teamIDString = strconv.FormatInt(obj.teamID, 10) - } else { + if slices.Contains(teamSlugs, obj.teamSlug) { teamIDString = obj.teamSlug + } else { + teamIDString = strconv.FormatInt(obj.teamID, 10) } transformed := map[string]interface{}{ @@ -165,7 +179,7 @@ func flattenTeamCollaborator(obj teamCollaborator, teamIDs []int64) interface{} return transformed } -func flattenTeamCollaborators(objs []teamCollaborator, teamIDs []int64) []interface{} { +func flattenTeamCollaborators(objs []teamCollaborator, teamSlugs []string) []interface{} { if objs == nil { return nil } @@ -176,14 +190,14 @@ func flattenTeamCollaborators(objs []teamCollaborator, teamIDs []int64) []interf items := make([]interface{}, len(objs)) for i, obj := range objs { - items[i] = flattenTeamCollaborator(obj, teamIDs) + items[i] = flattenTeamCollaborator(obj, teamSlugs) } return items } func listUserCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]userCollaborator, error) { - var userCollaborators []userCollaborator + userCollaborators := make([]userCollaborator, 0) affiliations := []string{"direct", "outside"} for _, affiliation := range affiliations { opt := &github.ListCollaboratorsOptions{ListOptions: github.ListOptions{ @@ -217,7 +231,7 @@ func listUserCollaborators(client *github.Client, isOrg bool, ctx context.Contex } func listInvitations(client *github.Client, ctx context.Context, owner, repoName string) ([]invitedCollaborator, error) { - var invitedCollaborators []invitedCollaborator + invitedCollaborators := make([]invitedCollaborator, 0) opt := &github.ListOptions{PerPage: maxPerPage} for { @@ -230,7 +244,8 @@ func listInvitations(client *github.Client, ctx context.Context, owner, repoName permissionName := getPermission(i.GetPermissions()) invitedCollaborators = append(invitedCollaborators, invitedCollaborator{ - userCollaborator{permissionName, i.GetInvitee().GetLogin()}, i.GetID()}) + userCollaborator{permissionName, i.GetInvitee().GetLogin()}, i.GetID(), + }) } if resp.NextPage == 0 { @@ -241,11 +256,11 @@ func listInvitations(client *github.Client, ctx context.Context, owner, repoName return invitedCollaborators, nil } -func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]teamCollaborator, error) { - var teamCollaborators []teamCollaborator +func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string, ignoreTeamIds []int64) ([]teamCollaborator, error) { + allTeams := make([]teamCollaborator, 0) if !isOrg { - return teamCollaborators, nil + return allTeams, nil } opt := &github.ListOptions{PerPage: maxPerPage} @@ -256,9 +271,11 @@ func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, re } for _, t := range repoTeams { - permissionName := getPermission(t.GetPermission()) + if slices.Contains(ignoreTeamIds, t.GetID()) { + continue + } - teamCollaborators = append(teamCollaborators, teamCollaborator{permissionName, t.GetID(), t.GetSlug()}) + allTeams = append(allTeams, teamCollaborator{permission: getPermission(t.GetPermission()), teamID: t.GetID(), teamSlug: t.GetSlug()}) } if resp.NextPage == 0 { @@ -266,10 +283,11 @@ func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, re } opt.Page = resp.NextPage } - return teamCollaborators, nil + + return allTeams, nil } -func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]userCollaborator, []invitedCollaborator, []teamCollaborator, error) { +func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string, ignoreTeamIds []int64) ([]userCollaborator, []invitedCollaborator, []teamCollaborator, error) { userCollaborators, err := listUserCollaborators(client, isOrg, ctx, owner, repoName) if err != nil { return nil, nil, nil, err @@ -278,16 +296,14 @@ func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context if err != nil { return nil, nil, nil, err } - teamCollaborators, err := listTeams(client, isOrg, ctx, owner, repoName) + teamCollaborators, err := listTeams(client, isOrg, ctx, owner, repoName, ignoreTeamIds) if err != nil { return nil, nil, nil, err } return userCollaborators, invitations, teamCollaborators, err } -func matchUserCollaboratorsAndInvites( - repoName string, want []interface{}, hasUsers []userCollaborator, hasInvites []invitedCollaborator, - meta interface{}) error { +func matchUserCollaboratorsAndInvites(repoName string, want []interface{}, hasUsers []userCollaborator, hasInvites []invitedCollaborator, meta interface{}) error { client := meta.(*Owner).v3client owner := meta.(*Owner).name @@ -383,8 +399,7 @@ func matchUserCollaboratorsAndInvites( return nil } -func matchTeamCollaborators( - repoName string, want []interface{}, has []teamCollaborator, meta interface{}) error { +func matchTeamCollaborators(repoName string, want []interface{}, has []teamCollaborator, meta interface{}) error { client := meta.(*Owner).v3client orgID := meta.(*Owner).id owner := meta.(*Owner).name @@ -471,7 +486,7 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in repoName := d.Get("repository").(string) ctx := context.Background() - teamsMap := make(map[string]struct{}) + teamsMap := make(map[string]struct{}, len(teams)) for _, team := range teams { teamIDString := team.(map[string]interface{})["team_id"].(string) if _, found := teamsMap[teamIDString]; found { @@ -479,7 +494,7 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in } teamsMap[teamIDString] = struct{}{} } - usersMap := make(map[string]struct{}) + usersMap := make(map[string]struct{}, len(users)) for _, user := range users { username := user.(map[string]interface{})["username"].(string) if _, found := usersMap[username]; found { @@ -488,7 +503,12 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in usersMap[username] = struct{}{} } - userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName) + ignoreTeamIds, err := getIgnoreTeamIds(d, meta) + if err != nil { + return err + } + + userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds) if err != nil { return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName) } @@ -516,7 +536,12 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte repoName := d.Id() ctx := context.WithValue(context.Background(), ctxId, d.Id()) - userCollaborators, invitedCollaborators, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName) + ignoreTeamIds, err := getIgnoreTeamIds(d, meta) + if err != nil { + return err + } + + userCollaborators, invitedCollaborators, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds) if err != nil { return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName) } @@ -526,9 +551,14 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte invitationIds[i.username] = strconv.FormatInt(i.invitationID, 10) } - teamIDs := make([]int64, len(teamCollaborators)) - for i, t := range teamCollaborators { - teamIDs[i] = t.teamID + sourceTeams := d.Get("team").(*schema.Set).List() + teamSlugs := make([]string, len(sourceTeams)) + for i, t := range sourceTeams { + teamIdString := t.(map[string]interface{})["team_id"].(string) + _, parseIntErr := strconv.ParseInt(teamIdString, 10, 64) + if parseIntErr != nil { + teamSlugs[i] = teamIdString + } } err = d.Set("repository", repoName) @@ -539,7 +569,7 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte if err != nil { return err } - err = d.Set("team", flattenTeamCollaborators(teamCollaborators, teamIDs)) + err = d.Set("team", flattenTeamCollaborators(teamCollaborators, teamSlugs)) if err != nil { return err } @@ -563,7 +593,12 @@ func resourceGithubRepositoryCollaboratorsDelete(d *schema.ResourceData, meta in repoName := d.Get("repository").(string) ctx := context.Background() - userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName) + ignoreTeamIds, err := getIgnoreTeamIds(d, meta) + if err != nil { + return err + } + + userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds) if err != nil { return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName) } @@ -580,3 +615,19 @@ func resourceGithubRepositoryCollaboratorsDelete(d *schema.ResourceData, meta in err = matchTeamCollaborators(repoName, nil, teamCollaborators, meta) return err } + +func getIgnoreTeamIds(d *schema.ResourceData, meta interface{}) ([]int64, error) { + ignoreTeams := d.Get("ignore_team").(*schema.Set).List() + ignoreTeamIds := make([]int64, len(ignoreTeams)) + + for i, t := range ignoreTeams { + s := t.(map[string]interface{})["team_id"].(string) + id, err := getTeamID(s, meta) + if err != nil { + return nil, err + } + ignoreTeamIds[i] = id + } + + return ignoreTeamIds, nil +} diff --git a/github/resource_github_repository_collaborators_test.go b/github/resource_github_repository_collaborators_test.go index 4e4d60ae12..53c6694bc3 100644 --- a/github/resource_github_repository_collaborators_test.go +++ b/github/resource_github_repository_collaborators_test.go @@ -14,30 +14,28 @@ import ( ) func TestAccGithubRepositoryCollaborators(t *testing.T) { - inOrgUser := os.Getenv("GITHUB_IN_ORG_USER") inOrgUser2 := os.Getenv("GITHUB_IN_ORG_USER2") - if inOrgUser == "" || inOrgUser2 == "" { - t.Skip("set inOrgUser and inOrgUser2 to unskip this test run") - } - - if inOrgUser == testOwnerFunc() || inOrgUser2 == testOwnerFunc() { - t.Skip("inOrgUser and inOrgUser2 can't be same as owner") - } - config := Config{BaseURL: "https://api.github.com/", Owner: testOwnerFunc(), Token: testToken} meta, err := config.Meta() if err != nil { t.Fatalf("failed to return meta without error: %s", err.Error()) } - randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) - t.Run("creates collaborators without error", func(t *testing.T) { + if inOrgUser == "" { + t.Skip("set inOrgUser to unskip this test run") + } + + if inOrgUser == testOwnerFunc() { + t.Skip("inOrgUser can't be same as owner") + } + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) conn := meta.(*Owner).v3client repoName := fmt.Sprintf("tf-acc-test-%s", randomID) + teamName := fmt.Sprintf("tf-acc-test-team-%s", randomID) individualConfig := fmt.Sprintf(` resource "github_repository" "test" { @@ -64,7 +62,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { } resource "github_team" "test" { - name = "test" + name = "%s" } resource "github_repository_collaborators" "test_repo_collaborators" { @@ -79,7 +77,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { permission = "pull" } } - `, repoName, inOrgUser) + `, repoName, teamName, inOrgUser) testCase := func(t *testing.T, mode, config string, testCheck func(state *terraform.State) error) { resource.Test(t, resource.TestCase{ @@ -192,9 +190,19 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { }) t.Run("updates collaborators without error", func(t *testing.T) { + if inOrgUser == "" || inOrgUser2 == "" { + t.Skip("set inOrgUser and inOrgUser2 to unskip this test run") + } + if inOrgUser == testOwnerFunc() || inOrgUser2 == testOwnerFunc() { + t.Skip("inOrgUser or inOrgUser2 can't be same as owner") + } + + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) conn := meta.(*Owner).v3client repoName := fmt.Sprintf("tf-acc-test-%s", randomID) + team0Name := fmt.Sprintf("tf-acc-test-team-0-%s", randomID) + team1Name := fmt.Sprintf("tf-acc-test-team-1-%s", randomID) individualConfig := fmt.Sprintf(` resource "github_repository" "test" { @@ -237,12 +245,12 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { visibility = "private" } - resource "github_team" "test" { - name = "test" + resource "github_team" "test_0" { + name = "%s" } - resource "github_team" "test2" { - name = "test2" + resource "github_team" "test_1" { + name = "%s" } resource "github_repository_collaborators" "test_repo_collaborators" { @@ -265,7 +273,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { permission = "pull" } } - `, repoName, inOrgUser, inOrgUser2) + `, repoName, team0Name, team1Name, inOrgUser, inOrgUser2) orgConfigUpdate := fmt.Sprintf(` resource "github_repository" "test" { @@ -274,12 +282,12 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { visibility = "private" } - resource "github_team" "test" { - name = "test" + resource "github_team" "test_0" { + name = "%s" } - resource "github_team" "test2" { - name = "test2" + resource "github_team" "test_1" { + name = "%s" } resource "github_repository_collaborators" "test_repo_collaborators" { @@ -290,11 +298,11 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { permission = "push" } team { - team_id = github_team.test.id + team_id = github_team.test_0.id permission = "push" } } - `, repoName, inOrgUser) + `, repoName, team0Name, team1Name, inOrgUser) testCase := func(t *testing.T, mode, config, configUpdate string, testCheck func(state *terraform.State) error) { resource.Test(t, resource.TestCase{ @@ -319,9 +327,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { t.Run("with an individual account", func(t *testing.T) { check := resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "user.#"), - resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "team.#"), resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "user.#", "1"), - resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "team.#", "0"), func(state *terraform.State) error { owner := meta.(*Owner).name @@ -364,7 +370,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { func(state *terraform.State) error { owner := testOrganizationFunc() - teamAttrs := state.RootModule().Resources["github_team.test"].Primary.Attributes + teamAttrs := state.RootModule().Resources["github_team.test_0"].Primary.Attributes collaborators := state.RootModule().Resources["github_repository_collaborators.test_repo_collaborators"].Primary for name, val := range collaborators.Attributes { if strings.HasPrefix(name, "user.") && strings.HasSuffix(name, ".username") && val != inOrgUser { @@ -411,9 +417,18 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { }) t.Run("removes collaborators without error", func(t *testing.T) { + if inOrgUser == "" || inOrgUser2 == "" { + t.Skip("set inOrgUser and inOrgUser2 to unskip this test run") + } + if inOrgUser == testOwnerFunc() || inOrgUser2 == testOwnerFunc() { + t.Skip("inOrgUser or inOrgUser2 can't be same as owner") + } + + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) conn := meta.(*Owner).v3client repoName := fmt.Sprintf("tf-acc-test-%s", randomID) + teamName := fmt.Sprintf("tf-acc-test-team-%s", randomID) individualConfig := fmt.Sprintf(` resource "github_repository" "test" { @@ -448,7 +463,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { } resource "github_team" "test" { - name = "test" + name = "%s" } resource "github_repository_collaborators" "test_repo_collaborators" { @@ -467,7 +482,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { permission = "pull" } } - `, repoName, inOrgUser, inOrgUser2) + `, repoName, teamName, inOrgUser, inOrgUser2) orgConfigUpdate := fmt.Sprintf(` resource "github_repository" "test" { @@ -477,9 +492,9 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { } resource "github_team" "test" { - name = "test" + name = "%s" } - `, repoName) + `, repoName, teamName) testCase := func(t *testing.T, mode, config, configUpdate string, testCheck func(state *terraform.State) error) { resource.Test(t, resource.TestCase{ @@ -544,4 +559,118 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { testCase(t, organization, orgConfig, orgConfigUpdate, check) }) }) + + t.Run("does not churn on team slug", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("tf-acc-test-%s", randomID) + team0Name := fmt.Sprintf("tf-acc-test-team-0-%s", randomID) + team1Name := fmt.Sprintf("tf-acc-test-team-1-%s", randomID) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + auto_init = true + visibility = "private" + } + + resource "github_team" "test_0" { + name = "%s" + } + + resource "github_team" "test_1" { + name = "%s" + } + + resource "github_repository_collaborators" "test_repo_collaborators" { + repository = "${github_repository.test.name}" + + team { + team_id = github_team.test_0.id + permission = "pull" + } + + team { + team_id = github_team.test_1.name + permission = "pull" + } + } + `, repoName, team0Name, team1Name) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "team.#"), + resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "team.#", "2"), + ), + }, + { + Config: config, + ExpectNonEmptyPlan: false, + }, + }, + }) + }) + + t.Run("ignores specified teams", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + repoName := fmt.Sprintf("tf-acc-test-%s", randomID) + team0Name := fmt.Sprintf("tf-acc-test-team-0-%s", randomID) + team1Name := fmt.Sprintf("tf-acc-test-team-1-%s", randomID) + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "%s" + auto_init = true + visibility = "private" + } + + resource "github_team" "test_0" { + name = "%s" + } + + resource "github_team_repository" "some_team_repo" { + team_id = github_team.test_0.id + repository = github_repository.test.name + } + + resource "github_team" "test_1" { + name = "%s" + } + + resource "github_repository_collaborators" "test_repo_collaborators" { + repository = "${github_repository.test.name}" + + team { + team_id = github_team.test_1.id + permission = "pull" + } + + ignore_team { + team_id = github_team.test_0.id + } + } + `, repoName, team0Name, team1Name) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "team.#"), + resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "team.#", "1"), + ), + }, + { + Config: config, + ExpectNonEmptyPlan: false, + }, + }, + }) + }) } diff --git a/website/docs/r/repository_collaborators.html.markdown b/website/docs/r/repository_collaborators.html.markdown index 24cd38485e..2da2b78b68 100644 --- a/website/docs/r/repository_collaborators.html.markdown +++ b/website/docs/r/repository_collaborators.html.markdown @@ -14,10 +14,10 @@ github_team_repository or they will fight over what your policy should be. This resource allows you to manage all collaborators for repositories in your organization or personal account. For organization repositories, collaborators can -have explicit (and differing levels of) read, write, or administrator access to -specific repositories, without giving the user full organization membership. +have explicit (and differing levels of) read, write, or administrator access to +specific repositories, without giving the user full organization membership. For personal repositories, collaborators can only be granted write -(implicitly includes read) permission. +(implicitly includes read) permission. When applied, an invitation will be sent to the user to become a collaborators on a repository. When destroyed, either the invitation will be cancelled or the @@ -31,7 +31,7 @@ Further documentation on GitHub collaborators: - [Adding outside collaborators to your personal repositories](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/managing-access-to-your-personal-repositories) - [Adding outside collaborators to repositories in your organization](https://help.github.com/articles/adding-outside-collaborators-to-repositories-in-your-organization/) - [Converting an organization member to an outside collaborators](https://help.github.com/articles/converting-an-organization-member-to-an-outside-collaborator/) - + ## Example Usage ```hcl @@ -52,7 +52,7 @@ resource "github_repository_collaborators" "some_repo_collaborators" { permission = "admin" username = "SomeUser" } - + team { permission = "pull" team_id = github_team.some_team.slug @@ -64,9 +64,10 @@ resource "github_repository_collaborators" "some_repo_collaborators" { The following arguments are supported: -* `repository` - (Required) The GitHub repository -* `user` - (Optional) List of users -* `team` - (Optional) List of teams +* `repository` - (Required) The GitHub repository. +* `user` - (Optional) List of users to grant access to the repository. +* `team` - (Optional) List of teams to grant access to the repository. +* `ignore_team` - (Optional) List of teams to ignore when checking for repository access. This supports ignoring teams granted access at an organizational level. The `user` block supports: @@ -77,16 +78,20 @@ The `user` block supports: The `team` block supports: -* `team_id` - (Required) The GitHub team id or the GitHub team slug +* `team_id` - (Required) The GitHub team id or the GitHub team slug. * `permission` - (Optional) The permission of the outside collaborators for the repository. Must be one of `pull`, `triage`, `push`, `maintain`, `admin` or the name of an existing [custom repository role](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization) within the organisation. Defaults to `pull`. Must be `push` for personal repositories. Defaults to `push`. +The `team_ignore` block supports: + +* `team_id` - (Required) The GitHub team id or the GitHub team slug. + ## Attribute Reference In addition to the above arguments, the following attributes are exported: -* `invitation_ids` - Map of usernames to invitation ID for any users added as part of creation of this resource to +* `invitation_ids` - Map of usernames to invitation ID for any users added as part of creation of this resource to be used in [`github_user_invitation_accepter`](./user_invitation_accepter.html). ## Import