-
Notifications
You must be signed in to change notification settings - Fork 775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support team organization role assignment #2322
Open
felixlut
wants to merge
15
commits into
integrations:main
Choose a base branch
from
felixlut:github_team_organization_role_assignment
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
2091fd0
add octokit sdk client
d612d52
add github_team_org_role_assignment resource
0942afc
add tests for github_team_org_role_assignment
dddd5f6
formatting
e304177
use go-github for listing all team role assignments for a role
1004bf1
implement proper import logic
df99246
add docs
1e94354
test adding .gitattributes to make vendor pr changes not slow down th…
73bacd9
test adding .gitattributes to make vendor pr changes not slow down th…
2b91eca
test adding .gitattributes to make vendor pr changes not slow down th…
826d49a
remove .gitattributes, seems like it had no effect
5442846
add break iteration over teams assigned to organization role
4653863
Merge remote-tracking branch 'origin/main' into github_team_organizat…
felixlut 02b0cca
org role team assignment no longer use go-sdk
felixlut a13f0b2
purge go-sdk completely
felixlut File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 181 additions & 0 deletions
181
github/resource_github_team_organization_role_assignment.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"strconv" | ||
|
||
"github.com/google/go-github/v66/github" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
) | ||
|
||
func resourceGithubTeamOrganizationRoleAssignment() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceGithubTeamOrganizationRoleAssignmentCreate, | ||
Read: resourceGithubTeamOrganizationRoleAssignmentRead, | ||
Delete: resourceGithubTeamOrganizationRoleAssignmentDelete, | ||
Importer: &schema.ResourceImporter{ | ||
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
teamIdString, roleID, err := parseTwoPartID(d.Id(), "team_id", "role_id") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
teamSlug, err := getTeamSlug(teamIdString, meta) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
d.SetId(buildTwoPartID(teamSlug, roleID)) | ||
return []*schema.ResourceData{d}, nil | ||
}, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"team_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "The GitHub team id or the GitHub team slug.", | ||
ForceNew: true, | ||
}, | ||
"role_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "The GitHub Organization Role id.", | ||
ForceNew: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceGithubTeamOrganizationRoleAssignmentCreate(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() | ||
|
||
// The given team id could be an id or a slug | ||
givenTeamId := d.Get("team_id").(string) | ||
teamSlug, err := getTeamSlug(givenTeamId, meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
roleIDString := d.Get("role_id").(string) | ||
roleID, err := strconv.ParseInt(roleIDString, 10, 32) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = client.Organizations.AssignOrgRoleToTeam(ctx, orgName, teamSlug, roleID) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
d.SetId(buildTwoPartID(teamSlug, roleIDString)) | ||
return resourceGithubTeamOrganizationRoleAssignmentRead(d, meta) | ||
} | ||
|
||
func resourceGithubTeamOrganizationRoleAssignmentRead(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 | ||
|
||
teamIdString, roleIDString, err := parseTwoPartID(d.Id(), "team_id", "role_id") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// The given team id could be an id or a slug | ||
teamSlug, err := getTeamSlug(teamIdString, meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
roleID, err := strconv.ParseInt(roleIDString, 10, 32) | ||
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 | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceGithubTeamOrganizationRoleAssignmentDelete(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() | ||
|
||
teamIdString, roleIDString, err := parseTwoPartID(d.Id(), "team_id", "role_id") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// The given team id could be an id or a slug | ||
teamSlug, err := getTeamSlug(teamIdString, meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
roleID, err := strconv.ParseInt(roleIDString, 10, 32) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = client.Organizations.RemoveOrgRoleFromTeam(ctx, orgName, teamSlug, roleID) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
142 changes: 142 additions & 0 deletions
142
github/resource_github_team_organization_role_assignment_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package github | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
) | ||
|
||
func TestAccGithubTeamOrganizationRoleAssignment(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/)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since custom organization roles are a Enterprise level feature, I have elected to not use them in the tests and instead use the built-in roles instead. I've created a manual mapping for their |
||
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) { | ||
|
||
config := fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-test-team-repo-%s" | ||
description = "test" | ||
} | ||
resource "github_team_organization_role_assignment" "test" { | ||
team_id = github_team.test.id | ||
role_id = "%s" | ||
} | ||
`, randomID, githubPredefinedRoleMapping["all_repo_read"]) | ||
|
||
check := resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrSet( | ||
"github_team_organization_role_assignment.test", "id", | ||
), | ||
resource.TestCheckResourceAttrSet( | ||
"github_team_organization_role_assignment.test", "team_id", | ||
), | ||
resource.TestCheckResourceAttr( | ||
"github_team_organization_role_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) { | ||
|
||
configs := map[string]string{ | ||
"before": fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-test-team-repo-%s" | ||
description = "test" | ||
} | ||
resource "github_team_organization_role_assignment" "test" { | ||
team_id = github_team.test.id | ||
role_id = "%s" | ||
} | ||
`, randomID, githubPredefinedRoleMapping["all_repo_read"]), | ||
"after": fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-test-team-repo-%s" | ||
description = "test" | ||
} | ||
resource "github_team_organization_role_assignment" "test" { | ||
team_id = github_team.test.id | ||
role_id = "%s" | ||
} | ||
`, randomID, githubPredefinedRoleMapping["all_repo_write"]), | ||
} | ||
|
||
checks := map[string]resource.TestCheckFunc{ | ||
"before": resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr( | ||
"github_team_organization_role_assignment.test", "role_id", githubPredefinedRoleMapping["all_repo_read"], | ||
), | ||
), | ||
"after": resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr( | ||
"github_team_organization_role_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) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
website/docs/r/team_organization_role_assignment.html.markdown
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
--- | ||
layout: "github" | ||
page_title: "GitHub: github_team_organization_role_assignment" | ||
description: |- | ||
Manages the associations between teams and organization roles. | ||
--- | ||
|
||
# github_team_organization_role_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 a 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_team_organization_role_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_id` - (Required) The GitHub team id or 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_id:role_id` or `team_slug:role_id`, e.g. | ||
|
||
``` | ||
$ terraform import github_team_organization_role_assignment.role_assignment 1234567:8132 | ||
$ terraform import github_team_organization_role_assignment.role_assignment test-team:8132 | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
google/go-github
here instead ofoctokit/go-sdk
since the former doesn't support pagination