Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1d433f2

Browse files
committedNov 28, 2024··
feat: Updated repo collaborators to support ignoring teams
Signed-off-by: Steve Hipwell <steve.hipwell@gmail.com>
1 parent 1c11053 commit 1d433f2

3 files changed

+138
-29
lines changed
 

‎github/resource_github_repository_collaborators.go

+65-13
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
3232
"user": {
3333
Type: schema.TypeSet,
3434
Optional: true,
35-
Description: "List of users",
35+
Description: "List of users.",
3636
Elem: &schema.Resource{
3737
Schema: map[string]*schema.Schema{
3838
"permission": {
@@ -52,7 +52,7 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
5252
"team": {
5353
Type: schema.TypeSet,
5454
Optional: true,
55-
Description: "List of teams",
55+
Description: "List of teams.",
5656
Elem: &schema.Resource{
5757
Schema: map[string]*schema.Schema{
5858
"permission": {
@@ -76,6 +76,20 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
7676
},
7777
Computed: true,
7878
},
79+
"ignore_team": {
80+
Type: schema.TypeSet,
81+
Optional: true,
82+
Description: "List of teams to ignore.",
83+
Elem: &schema.Resource{
84+
Schema: map[string]*schema.Schema{
85+
"team_id": {
86+
Type: schema.TypeString,
87+
Description: "ID or slug of the team to ignore.",
88+
Required: true,
89+
},
90+
},
91+
},
92+
},
7993
},
8094

8195
CustomizeDiff: customdiff.Sequence(
@@ -230,7 +244,8 @@ func listInvitations(client *github.Client, ctx context.Context, owner, repoName
230244
permissionName := getPermission(i.GetPermissions())
231245

232246
invitedCollaborators = append(invitedCollaborators, invitedCollaborator{
233-
userCollaborator{permissionName, i.GetInvitee().GetLogin()}, i.GetID()})
247+
userCollaborator{permissionName, i.GetInvitee().GetLogin()}, i.GetID(),
248+
})
234249
}
235250

236251
if resp.NextPage == 0 {
@@ -241,7 +256,7 @@ func listInvitations(client *github.Client, ctx context.Context, owner, repoName
241256
return invitedCollaborators, nil
242257
}
243258

244-
func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]teamCollaborator, error) {
259+
func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string, ignoreTeamIds []int64) ([]teamCollaborator, error) {
245260
var teamCollaborators []teamCollaborator
246261

247262
if !isOrg {
@@ -256,6 +271,10 @@ func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, re
256271
}
257272

258273
for _, t := range repoTeams {
274+
if slices.Contains(ignoreTeamIds, t.GetID()) {
275+
continue
276+
}
277+
259278
permissionName := getPermission(t.GetPermission())
260279

261280
teamCollaborators = append(teamCollaborators, teamCollaborator{permissionName, t.GetID(), t.GetSlug()})
@@ -269,7 +288,7 @@ func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, re
269288
return teamCollaborators, nil
270289
}
271290

272-
func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]userCollaborator, []invitedCollaborator, []teamCollaborator, error) {
291+
func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string, ignoreTeamIds []int64) ([]userCollaborator, []invitedCollaborator, []teamCollaborator, error) {
273292
userCollaborators, err := listUserCollaborators(client, isOrg, ctx, owner, repoName)
274293
if err != nil {
275294
return nil, nil, nil, err
@@ -278,7 +297,7 @@ func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context
278297
if err != nil {
279298
return nil, nil, nil, err
280299
}
281-
teamCollaborators, err := listTeams(client, isOrg, ctx, owner, repoName)
300+
teamCollaborators, err := listTeams(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
282301
if err != nil {
283302
return nil, nil, nil, err
284303
}
@@ -287,7 +306,8 @@ func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context
287306

288307
func matchUserCollaboratorsAndInvites(
289308
repoName string, want []interface{}, hasUsers []userCollaborator, hasInvites []invitedCollaborator,
290-
meta interface{}) error {
309+
meta interface{},
310+
) error {
291311
client := meta.(*Owner).v3client
292312

293313
owner := meta.(*Owner).name
@@ -384,7 +404,8 @@ func matchUserCollaboratorsAndInvites(
384404
}
385405

386406
func matchTeamCollaborators(
387-
repoName string, want []interface{}, has []teamCollaborator, meta interface{}) error {
407+
repoName string, want []interface{}, has []teamCollaborator, meta interface{},
408+
) error {
388409
client := meta.(*Owner).v3client
389410
orgID := meta.(*Owner).id
390411
owner := meta.(*Owner).name
@@ -471,15 +492,15 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in
471492
repoName := d.Get("repository").(string)
472493
ctx := context.Background()
473494

474-
teamsMap := make(map[string]struct{})
495+
teamsMap := make(map[string]struct{}, len(teams))
475496
for _, team := range teams {
476497
teamIDString := team.(map[string]interface{})["team_id"].(string)
477498
if _, found := teamsMap[teamIDString]; found {
478499
return fmt.Errorf("duplicate set member: %s", teamIDString)
479500
}
480501
teamsMap[teamIDString] = struct{}{}
481502
}
482-
usersMap := make(map[string]struct{})
503+
usersMap := make(map[string]struct{}, len(users))
483504
for _, user := range users {
484505
username := user.(map[string]interface{})["username"].(string)
485506
if _, found := usersMap[username]; found {
@@ -488,7 +509,12 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in
488509
usersMap[username] = struct{}{}
489510
}
490511

491-
userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName)
512+
ignoreTeamIds, err := getIgnoreTeamIds(d, meta)
513+
if err != nil {
514+
return err
515+
}
516+
517+
userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
492518
if err != nil {
493519
return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName)
494520
}
@@ -516,7 +542,12 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte
516542
repoName := d.Id()
517543
ctx := context.WithValue(context.Background(), ctxId, d.Id())
518544

519-
userCollaborators, invitedCollaborators, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName)
545+
ignoreTeamIds, err := getIgnoreTeamIds(d, meta)
546+
if err != nil {
547+
return err
548+
}
549+
550+
userCollaborators, invitedCollaborators, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
520551
if err != nil {
521552
return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName)
522553
}
@@ -563,7 +594,12 @@ func resourceGithubRepositoryCollaboratorsDelete(d *schema.ResourceData, meta in
563594
repoName := d.Get("repository").(string)
564595
ctx := context.Background()
565596

566-
userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName)
597+
ignoreTeamIds, err := getIgnoreTeamIds(d, meta)
598+
if err != nil {
599+
return err
600+
}
601+
602+
userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
567603
if err != nil {
568604
return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName)
569605
}
@@ -580,3 +616,19 @@ func resourceGithubRepositoryCollaboratorsDelete(d *schema.ResourceData, meta in
580616
err = matchTeamCollaborators(repoName, nil, teamCollaborators, meta)
581617
return err
582618
}
619+
620+
func getIgnoreTeamIds(d *schema.ResourceData, meta interface{}) ([]int64, error) {
621+
ignoreTeams := d.Get("ignore_team").(*schema.Set).List()
622+
ignoreTeamIds := make([]int64, len(ignoreTeams))
623+
624+
for i, t := range ignoreTeams {
625+
s := t.(map[string]interface{})["team_id"].(string)
626+
id, err := getTeamID(s, meta)
627+
if err != nil {
628+
return nil, err
629+
}
630+
ignoreTeamIds[i] = id
631+
}
632+
633+
return ignoreTeamIds, nil
634+
}

‎github/resource_github_repository_collaborators_test.go

+58-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
)
1515

1616
func TestAccGithubRepositoryCollaborators(t *testing.T) {
17-
1817
inOrgUser := os.Getenv("GITHUB_IN_ORG_USER")
1918
inOrgUser2 := os.Getenv("GITHUB_IN_ORG_USER2")
2019

@@ -35,7 +34,6 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) {
3534
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
3635

3736
t.Run("creates collaborators without error", func(t *testing.T) {
38-
3937
conn := meta.(*Owner).v3client
4038
repoName := fmt.Sprintf("tf-acc-test-%s", randomID)
4139

@@ -192,7 +190,6 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) {
192190
})
193191

194192
t.Run("updates collaborators without error", func(t *testing.T) {
195-
196193
conn := meta.(*Owner).v3client
197194
repoName := fmt.Sprintf("tf-acc-test-%s", randomID)
198195

@@ -319,9 +316,7 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) {
319316
t.Run("with an individual account", func(t *testing.T) {
320317
check := resource.ComposeTestCheckFunc(
321318
resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "user.#"),
322-
resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "team.#"),
323319
resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "user.#", "1"),
324-
resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "team.#", "0"),
325320
func(state *terraform.State) error {
326321
owner := meta.(*Owner).name
327322

@@ -411,7 +406,6 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) {
411406
})
412407

413408
t.Run("removes collaborators without error", func(t *testing.T) {
414-
415409
conn := meta.(*Owner).v3client
416410
repoName := fmt.Sprintf("tf-acc-test-%s", randomID)
417411

@@ -544,4 +538,62 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) {
544538
testCase(t, organization, orgConfig, orgConfigUpdate, check)
545539
})
546540
})
541+
542+
t.Run("ignores specified teams", func(t *testing.T) {
543+
repoName := fmt.Sprintf("tf-acc-test-%s", randomID)
544+
team0Name := fmt.Sprintf("tf-acc-test-team0-%s", randomID)
545+
team1Name := fmt.Sprintf("tf-acc-test-team1-%s", randomID)
546+
547+
config := fmt.Sprintf(`
548+
resource "github_repository" "test" {
549+
name = "%s"
550+
auto_init = true
551+
visibility = "private"
552+
}
553+
554+
resource "github_team" "test_0" {
555+
name = "%s"
556+
}
557+
558+
resource "github_team_repository" "some_team_repo" {
559+
team_id = github_team.test_0.id
560+
repository = github_repository.test.name
561+
}
562+
563+
resource "github_team" "test_1" {
564+
name = "%s"
565+
}
566+
567+
resource "github_repository_collaborators" "test_repo_collaborators" {
568+
repository = "${github_repository.test.name}"
569+
570+
team {
571+
team_id = github_team.test_1.id
572+
permission = "pull"
573+
}
574+
575+
ignore_team {
576+
team_id = github_team.test_0.id
577+
}
578+
}
579+
`, repoName, team0Name, team1Name)
580+
581+
resource.Test(t, resource.TestCase{
582+
PreCheck: func() { skipUnlessMode(t, organization) },
583+
Providers: testAccProviders,
584+
Steps: []resource.TestStep{
585+
{
586+
Config: config,
587+
Check: resource.ComposeTestCheckFunc(
588+
resource.TestCheckResourceAttrSet("github_repository_collaborators.test_repo_collaborators", "team.#"),
589+
resource.TestCheckResourceAttr("github_repository_collaborators.test_repo_collaborators", "team.#", "1"),
590+
),
591+
},
592+
{
593+
Config: config,
594+
ExpectNonEmptyPlan: false,
595+
},
596+
},
597+
})
598+
})
547599
}

‎website/docs/r/repository_collaborators.html.markdown

+15-10
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ github_team_repository or they will fight over what your policy should be.
1414

1515
This resource allows you to manage all collaborators for repositories in your
1616
organization or personal account. For organization repositories, collaborators can
17-
have explicit (and differing levels of) read, write, or administrator access to
18-
specific repositories, without giving the user full organization membership.
17+
have explicit (and differing levels of) read, write, or administrator access to
18+
specific repositories, without giving the user full organization membership.
1919
For personal repositories, collaborators can only be granted write
20-
(implicitly includes read) permission.
20+
(implicitly includes read) permission.
2121

2222
When applied, an invitation will be sent to the user to become a collaborators
2323
on a repository. When destroyed, either the invitation will be cancelled or the
@@ -31,7 +31,7 @@ Further documentation on GitHub collaborators:
3131
- [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)
3232
- [Adding outside collaborators to repositories in your organization](https://help.github.com/articles/adding-outside-collaborators-to-repositories-in-your-organization/)
3333
- [Converting an organization member to an outside collaborators](https://help.github.com/articles/converting-an-organization-member-to-an-outside-collaborator/)
34-
34+
3535
## Example Usage
3636

3737
```hcl
@@ -52,7 +52,7 @@ resource "github_repository_collaborators" "some_repo_collaborators" {
5252
permission = "admin"
5353
username = "SomeUser"
5454
}
55-
55+
5656
team {
5757
permission = "pull"
5858
team_id = github_team.some_team.slug
@@ -64,9 +64,10 @@ resource "github_repository_collaborators" "some_repo_collaborators" {
6464

6565
The following arguments are supported:
6666

67-
* `repository` - (Required) The GitHub repository
68-
* `user` - (Optional) List of users
69-
* `team` - (Optional) List of teams
67+
* `repository` - (Required) The GitHub repository.
68+
* `user` - (Optional) List of users to grant access to the repository.
69+
* `team` - (Optional) List of teams to grant access to the repository.
70+
* `ignore_team` - (Optional) List of teams to ignore when checking for repository access. This supports ignoring teams granted access at an organizational level.
7071

7172
The `user` block supports:
7273

@@ -77,16 +78,20 @@ The `user` block supports:
7778

7879
The `team` block supports:
7980

80-
* `team_id` - (Required) The GitHub team id or the GitHub team slug
81+
* `team_id` - (Required) The GitHub team id or the GitHub team slug.
8182
* `permission` - (Optional) The permission of the outside collaborators for the repository.
8283
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`.
8384
Must be `push` for personal repositories. Defaults to `push`.
8485

86+
The `team_ignore` block supports:
87+
88+
* `team_id` - (Required) The GitHub team id or the GitHub team slug.
89+
8590
## Attribute Reference
8691

8792
In addition to the above arguments, the following attributes are exported:
8893

89-
* `invitation_ids` - Map of usernames to invitation ID for any users added as part of creation of this resource to
94+
* `invitation_ids` - Map of usernames to invitation ID for any users added as part of creation of this resource to
9095
be used in [`github_user_invitation_accepter`](./user_invitation_accepter.html).
9196

9297
## Import

0 commit comments

Comments
 (0)
Please sign in to comment.