Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2091fd0
add octokit sdk client
Jul 12, 2024
d612d52
add github_team_org_role_assignment resource
Jul 16, 2024
0942afc
add tests for github_team_org_role_assignment
Jul 16, 2024
dddd5f6
formatting
Jul 16, 2024
e304177
use go-github for listing all team role assignments for a role
Jul 17, 2024
1004bf1
implement proper import logic
Jul 17, 2024
df99246
add docs
Jul 17, 2024
1e94354
test adding .gitattributes to make vendor pr changes not slow down th…
Jul 17, 2024
73bacd9
test adding .gitattributes to make vendor pr changes not slow down th…
Jul 17, 2024
2b91eca
test adding .gitattributes to make vendor pr changes not slow down th…
Jul 17, 2024
826d49a
remove .gitattributes, seems like it had no effect
Jul 17, 2024
5442846
add break iteration over teams assigned to organization role
Jul 17, 2024
4653863
Merge remote-tracking branch 'origin/main' into github_team_organizat…
felixlut Nov 27, 2024
02b0cca
org role team assignment no longer use go-sdk
felixlut Nov 27, 2024
a13f0b2
purge go-sdk completely
felixlut Nov 27, 2024
4f55b6c
Merge branch 'main' into github_team_organization_role_assignment
felixlut Apr 7, 2025
7ceddb7
support either role_name or role_id
felixlut Apr 8, 2025
525de6a
update docs
felixlut Apr 8, 2025
8135e6b
resources should follow convention from gh api (org role before team)
felixlut Apr 8, 2025
805e6ca
update docs
felixlut Apr 8, 2025
9caf4b4
finalize the renaming
felixlut Apr 8, 2025
28c69ef
fix importer
felixlut Apr 8, 2025
87ef13d
go back to only allowing github_slug and role_id
felixlut Apr 8, 2025
e9a1721
linting fix
felixlut Apr 8, 2025
4d732e7
revert go.mod changes
felixlut Apr 8, 2025
1dfba89
Merge branch 'main' into github_team_organization_role_assignment
nickfloyd Oct 20, 2025
79c14f4
Merge branch 'main' into github_team_organization_role_assignment
nickfloyd Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func Provider() *schema.Provider {
"github_organization_custom_properties": resourceGithubOrganizationCustomProperties(),
"github_organization_project": resourceGithubOrganizationProject(),
"github_organization_security_manager": resourceGithubOrganizationSecurityManager(),
"github_organization_role_team_assignment": resourceGithubOrganizationRoleTeamAssignment(),
"github_organization_ruleset": resourceGithubOrganizationRuleset(),
"github_organization_settings": resourceGithubOrganizationSettings(),
"github_organization_webhook": resourceGithubOrganizationWebhook(),
Expand Down
153 changes: 153 additions & 0 deletions github/resource_github_organization_role_team_assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package github

import (
"context"
"log"
"strconv"

"github.com/google/go-github/v66/github"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceGithubOrganizationRoleTeamAssignment() *schema.Resource {
return &schema.Resource{
Create: resourceGithubOrganizationRoleTeamAssignmentCreate,
Read: resourceGithubOrganizationRoleTeamAssignmentRead,
Delete: resourceGithubOrganizationRoleTeamAssignmentDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"team_slug": {
Type: schema.TypeString,
Required: true,
Description: "The GitHub team slug.",
ForceNew: true,
},
"role_id": {
Type: schema.TypeString,
Required: true,
Description: "The GitHub organization role id",
ForceNew: true,
},
},
}
}

func resourceGithubOrganizationRoleTeamAssignmentCreate(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

client := meta.(*Owner).v3client
orgName := meta.(*Owner).name
ctx := context.Background()

teamSlug := d.Get("team_slug").(string)
roleIDString := d.Get("role_id").(string)

roleID, err := strconv.ParseInt(roleIDString, 10, 64)
if err != nil {
return err
}

_, err = client.Organizations.AssignOrgRoleToTeam(ctx, orgName, teamSlug, roleID)
if err != nil {
return err
}

d.SetId(buildTwoPartID(teamSlug, roleIDString))
return resourceGithubOrganizationRoleTeamAssignmentRead(d, meta)
}

func resourceGithubOrganizationRoleTeamAssignmentRead(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

client := meta.(*Owner).v3client
ctx := context.Background()
orgName := meta.(*Owner).name

teamSlug, roleIDString, err := parseTwoPartID(d.Id(), "team_slug", "role_id")
if err != nil {
return err
}

roleID, err := strconv.ParseInt(roleIDString, 10, 64)
if err != nil {
return err
}

// There is no api for checking a specific team role assignment, so instead we iterate over all teams assigned to the role
// go-github pagination (https://github.com/google/go-github?tab=readme-ov-file#pagination)
options := &github.ListOptions{
PerPage: 100,
}
var foundTeam *github.Team
for {
teams, resp, err := client.Organizations.ListTeamsAssignedToOrgRole(ctx, orgName, roleID, options)
if err != nil {
return err
}

for _, team := range teams {
if team.GetSlug() == teamSlug {
foundTeam = team
break
}

}

if resp.NextPage == 0 {
break
}
options.Page = resp.NextPage
}

if foundTeam == nil {
log.Printf("[WARN] Removing team organization role association %s from state because it no longer exists in GitHub", d.Id())
d.SetId("")
return nil
}

if err = d.Set("team_slug", teamSlug); err != nil {
return err
}
if err = d.Set("role_id", roleIDString); err != nil {
return err
}

return nil
}

func resourceGithubOrganizationRoleTeamAssignmentDelete(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

client := meta.(*Owner).v3client
orgName := meta.(*Owner).name
ctx := context.Background()

teamSlug, roleIDString, err := parseTwoPartID(d.Id(), "team_slug", "role_id")
if err != nil {
return err
}

roleID, err := strconv.ParseInt(roleIDString, 10, 64)
if err != nil {
return err
}

_, err = client.Organizations.RemoveOrgRoleFromTeam(ctx, orgName, teamSlug, roleID)
if err != nil {
return err
}

return nil
}
144 changes: 144 additions & 0 deletions github/resource_github_organization_role_team_assignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package github

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccGithubOrganizationRoleTeamAssignment(t *testing.T) {

randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

// Using the predefined roles since custom roles are a strictly Enterprise feature ((https://github.blog/changelog/2024-07-10-pre-defined-organization-roles-that-grant-access-to-all-repositories/))
githubPredefinedRoleMapping := make(map[string]string)
githubPredefinedRoleMapping["all_repo_read"] = "8132"
githubPredefinedRoleMapping["all_repo_triage"] = "8133"
githubPredefinedRoleMapping["all_repo_write"] = "8134"
githubPredefinedRoleMapping["all_repo_maintain"] = "8135"
githubPredefinedRoleMapping["all_repo_admin"] = "8136"

t.Run("creates repo assignment without error", func(t *testing.T) {

teamSlug := fmt.Sprintf("tf-acc-test-team-repo-%s", randomID)
config := fmt.Sprintf(`
resource "github_team" "test" {
name = "%s"
description = "test"
}
resource github_organization_role_team_assignment "test" {
team_slug = github_team.test.slug
role_id = "%s"
}
`, teamSlug, githubPredefinedRoleMapping["all_repo_read"])

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_organization_role_team_assignment.test", "id", fmt.Sprintf("%s:%s", teamSlug, githubPredefinedRoleMapping["all_repo_read"]),
),
resource.TestCheckResourceAttr(
"github_organization_role_team_assignment.test", "team_slug", teamSlug,
),
resource.TestCheckResourceAttr(
"github_organization_role_team_assignment.test", "role_id", githubPredefinedRoleMapping["all_repo_read"],
),
)

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,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

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)
})
})

// More tests can go here following the same format...
t.Run("create and re-creates role assignment without error", func(t *testing.T) {

teamSlug := fmt.Sprintf("tf-acc-test-team-repo-%s", randomID)
configs := map[string]string{
"before": fmt.Sprintf(`
resource "github_team" "test" {
name = "%s"
description = "test"
}
resource github_organization_role_team_assignment "test" {
team_slug = github_team.test.slug
role_id = "%s"
}
`, teamSlug, githubPredefinedRoleMapping["all_repo_read"]),
"after": fmt.Sprintf(`
resource "github_team" "test" {
name = "%s"
description = "test"
}
resource github_organization_role_team_assignment "test" {
team_slug = github_team.test.slug
role_id = "%s"
}
`, teamSlug, githubPredefinedRoleMapping["all_repo_write"]),
}

checks := map[string]resource.TestCheckFunc{
"before": resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_organization_role_team_assignment.test", "role_id", githubPredefinedRoleMapping["all_repo_read"],
),
),
"after": resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_organization_role_team_assignment.test", "role_id", githubPredefinedRoleMapping["all_repo_write"],
),
),
}

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: configs["before"],
Check: checks["before"],
},
{
Config: configs["after"],
Check: checks["after"],
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

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)
})
})
}
44 changes: 44 additions & 0 deletions website/docs/r/organization_role_team_assignment.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
layout: "github"
page_title: "GitHub: github_organization_role_team_assignment"
description: |-
Manages the associations between teams and organization roles.
---

# github_organization_role_team_assignment

This resource manages relationships between teams and organization roles
in your GitHub organization. This works on predefined roles, and custom roles, where the latter is an Enterprise feature.

Creating this resource assigns the role to a team.

The organization role and team must both belong to the same organization
on GitHub.

## Example Usage

```hcl
resource "github_team" "test-team" {
name = "test-team"
}

resource "github_organization_role_team_assignment" "test-team-role-assignment" {
team_slug = github_team.test-team.slug
role_id = "8132" # all_repo_read (predefined)
}
```

## Argument Reference

The following arguments are supported:

* `team_slug` - (Required) The GitHub team slug
* `role_id` - (Required) The GitHub organization role id

## Import

GitHub Team Organization Role Assignment can be imported using an ID made up of `team_slug:role_id`

```
$ terraform import github_organization_role_team_assignment.role_assignment test-team:8132
```
Loading