Skip to content

Commit bd56d13

Browse files
cailenkfcampbell
andauthored
feat: Add GitHub Organization Custom Role Resource and Data Source (#1700)
* feat: Add GitHub organization custom role resource This commit adds a new Terraform resource for creating, reading, updating and deleting GitHub organization custom roles. The `resourceGithubOrganizationCustomRole` function is added to the `github/provider.go` file. The function creates a new schema with four fields: name, base_role, permissions and description. It also includes functions for create, read, update and delete operations on the resource. A new data source is also added in this commit that allows users to query an existing custom repository role by its name. The `dataSourceGithubOrganizationCustomRole` function is added to the `github/data_source_github_organization_custom_role.go` file. Finally, a test case is included in the `github/data_source_github_organization_custom_role_test.go` file that tests querying of an existing custom repository role using the newly created data source. * refactor: Update test and resource files for Github Organization Custom Role This commit updates the formatting of the test and resource files for Github Organization Custom Role. It also removes the ForceNew attribute from one of the required fields in the resource file and updates the tests to reflect it. * docs: Fix arguments for organization custom role This commit fixes the arguments for creating an organization custom role. * docs: Update documentation with organization_custom_role Updates to the documentation to reflect this change. * docs: Fix errant parenthesis * Fix bad merge --------- Co-authored-by: Keegan Campbell <[email protected]>
1 parent 2dfebfe commit bd56d13

8 files changed

+685
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/google/go-github/v53/github"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
11+
)
12+
13+
func dataSourceGithubOrganizationCustomRole() *schema.Resource {
14+
return &schema.Resource{
15+
Read: dataSourceGithubOrganizationCustomRoleRead,
16+
17+
Schema: map[string]*schema.Schema{
18+
"name": {
19+
Type: schema.TypeString,
20+
Required: true,
21+
},
22+
"base_role": {
23+
Type: schema.TypeString,
24+
Computed: true,
25+
},
26+
"permissions": {
27+
Type: schema.TypeSet,
28+
Computed: true,
29+
Elem: &schema.Schema{Type: schema.TypeString},
30+
},
31+
"description": {
32+
Type: schema.TypeString,
33+
Computed: true,
34+
},
35+
},
36+
}
37+
}
38+
39+
func dataSourceGithubOrganizationCustomRoleRead(d *schema.ResourceData, meta interface{}) error {
40+
client := meta.(*Owner).v3client
41+
ctx := context.Background()
42+
orgName := meta.(*Owner).name
43+
44+
err := checkOrganization(meta)
45+
if err != nil {
46+
return err
47+
}
48+
49+
// ListCustomRepoRoles returns a list of all custom repository roles for an organization.
50+
// There is an API endpoint for getting a single custom repository role, but is not
51+
// implemented in the go-github library.
52+
roleList, _, err := client.Organizations.ListCustomRepoRoles(ctx, orgName)
53+
if err != nil {
54+
return fmt.Errorf("error querying GitHub custom repository roles %s: %s", orgName, err)
55+
}
56+
57+
var role *github.CustomRepoRoles
58+
for _, r := range roleList.CustomRepoRoles {
59+
if fmt.Sprint(*r.Name) == d.Get("name").(string) {
60+
role = r
61+
break
62+
}
63+
}
64+
65+
if role == nil {
66+
log.Printf("[WARN] GitHub custom repository role (%s) not found.", d.Get("name").(string))
67+
d.SetId("")
68+
return nil
69+
}
70+
71+
d.SetId(fmt.Sprint(*role.ID))
72+
d.Set("name", role.Name)
73+
d.Set("description", role.Description)
74+
d.Set("base_role", role.BaseRole)
75+
d.Set("permissions", role.Permissions)
76+
77+
return nil
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package github
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
8+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
9+
)
10+
11+
func TestAccGithubOrganizationCustomRoleDataSource(t *testing.T) {
12+
13+
t.Run("queries a custom repo role", func(t *testing.T) {
14+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
15+
16+
config := fmt.Sprintf(`
17+
resource "github_organization_custom_role" "test" {
18+
name = "tf-acc-test-%s"
19+
description = "Test role description"
20+
base_role = "read"
21+
permissions = [
22+
"reopen_issue",
23+
"reopen_pull_request",
24+
]
25+
}
26+
`, randomID)
27+
28+
config2 := config + `
29+
data "github_organization_custom_role" "test" {
30+
name = github_organization_custom_role.test.name
31+
}
32+
`
33+
34+
check := resource.ComposeTestCheckFunc(
35+
resource.TestCheckResourceAttrSet(
36+
"data.github_organization_custom_role.test", "name",
37+
),
38+
resource.TestCheckResourceAttr(
39+
"data.github_organization_custom_role.test", "name",
40+
fmt.Sprintf(`tf-acc-test-%s`, randomID),
41+
),
42+
resource.TestCheckResourceAttr(
43+
"data.github_organization_custom_role.test", "description",
44+
"Test role description",
45+
),
46+
resource.TestCheckResourceAttr(
47+
"data.github_organization_custom_role.test", "base_role",
48+
"read",
49+
),
50+
resource.TestCheckResourceAttr(
51+
"data.github_organization_custom_role.test", "permissions.#",
52+
"2",
53+
),
54+
)
55+
56+
testCase := func(t *testing.T, mode string) {
57+
resource.Test(t, resource.TestCase{
58+
PreCheck: func() { skipUnlessMode(t, mode) },
59+
Providers: testAccProviders,
60+
Steps: []resource.TestStep{
61+
{
62+
Config: config,
63+
Check: resource.ComposeTestCheckFunc(),
64+
},
65+
{
66+
Config: config2,
67+
Check: check,
68+
},
69+
},
70+
})
71+
}
72+
73+
t.Run("with an anonymous account", func(t *testing.T) {
74+
t.Skip("anonymous account not supported for this operation")
75+
})
76+
77+
t.Run("with an individual account", func(t *testing.T) {
78+
t.Skip("individual account not supported for this operation")
79+
})
80+
81+
t.Run("with an organization account", func(t *testing.T) {
82+
testCase(t, organization)
83+
})
84+
})
85+
}

github/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func Provider() terraform.ResourceProvider {
122122
"github_issue_label": resourceGithubIssueLabel(),
123123
"github_membership": resourceGithubMembership(),
124124
"github_organization_block": resourceOrganizationBlock(),
125+
"github_organization_custom_role": resourceGithubOrganizationCustomRole(),
125126
"github_organization_project": resourceGithubOrganizationProject(),
126127
"github_organization_security_manager": resourceGithubOrganizationSecurityManager(),
127128
"github_organization_settings": resourceGithubOrganizationSettings(),
@@ -181,6 +182,7 @@ func Provider() terraform.ResourceProvider {
181182
"github_issue_labels": dataSourceGithubIssueLabels(),
182183
"github_membership": dataSourceGithubMembership(),
183184
"github_organization": dataSourceGithubOrganization(),
185+
"github_organization_custom_role": dataSourceGithubOrganizationCustomRole(),
184186
"github_organization_ip_allow_list": dataSourceGithubOrganizationIpAllowList(),
185187
"github_organization_team_sync_groups": dataSourceGithubOrganizationTeamSyncGroups(),
186188
"github_organization_teams": dataSourceGithubOrganizationTeams(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/google/go-github/v53/github"
9+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
10+
)
11+
12+
func resourceGithubOrganizationCustomRole() *schema.Resource {
13+
return &schema.Resource{
14+
Create: resourceGithubOrganizationCustomRoleCreate,
15+
Read: resourceGithubOrganizationCustomRoleRead,
16+
Update: resourceGithubOrganizationCustomRoleUpdate,
17+
Delete: resourceGithubOrganizationCustomRoleDelete,
18+
Importer: &schema.ResourceImporter{
19+
State: schema.ImportStatePassthrough,
20+
},
21+
22+
Schema: map[string]*schema.Schema{
23+
"name": {
24+
Type: schema.TypeString,
25+
Required: true,
26+
Description: "The organization custom repository role to create.",
27+
},
28+
"base_role": {
29+
Type: schema.TypeString,
30+
Required: true,
31+
Description: "The base role for the custom repository role.",
32+
ValidateFunc: validateValueFunc([]string{"read", "triage", "write", "maintain"}),
33+
},
34+
"permissions": {
35+
Type: schema.TypeSet,
36+
Required: true,
37+
Elem: &schema.Schema{Type: schema.TypeString},
38+
MinItems: 1, // At least one permission should be passed.
39+
Description: "The permissions for the custom repository role.",
40+
},
41+
"description": {
42+
Type: schema.TypeString,
43+
Optional: true,
44+
Description: "The description of the custom repository role.",
45+
},
46+
},
47+
}
48+
}
49+
50+
func resourceGithubOrganizationCustomRoleCreate(d *schema.ResourceData, meta interface{}) error {
51+
client := meta.(*Owner).v3client
52+
orgName := meta.(*Owner).name
53+
ctx := context.Background()
54+
55+
err := checkOrganization(meta)
56+
if err != nil {
57+
return err
58+
}
59+
60+
permissions := d.Get("permissions").(*schema.Set).List()
61+
permissionsStr := make([]string, len(permissions))
62+
for i, v := range permissions {
63+
permissionsStr[i] = v.(string)
64+
}
65+
66+
role, _, err := client.Organizations.CreateCustomRepoRole(ctx, orgName, &github.CreateOrUpdateCustomRoleOptions{
67+
Name: github.String(d.Get("name").(string)),
68+
Description: github.String(d.Get("description").(string)),
69+
BaseRole: github.String(d.Get("base_role").(string)),
70+
Permissions: permissionsStr,
71+
})
72+
73+
if err != nil {
74+
return fmt.Errorf("error creating GitHub custom repository role %s (%s): %s", orgName, d.Get("name").(string), err)
75+
}
76+
77+
d.SetId(fmt.Sprint(*role.ID))
78+
return resourceGithubOrganizationCustomRoleRead(d, meta)
79+
}
80+
81+
func resourceGithubOrganizationCustomRoleRead(d *schema.ResourceData, meta interface{}) error {
82+
client := meta.(*Owner).v3client
83+
ctx := context.Background()
84+
orgName := meta.(*Owner).name
85+
86+
err := checkOrganization(meta)
87+
if err != nil {
88+
return err
89+
}
90+
91+
roleID := d.Id()
92+
93+
// ListCustomRepoRoles returns a list of all custom repository roles for an organization.
94+
// There is an API endpoint for getting a single custom repository role, but is not
95+
// implemented in the go-github library.
96+
roleList, _, err := client.Organizations.ListCustomRepoRoles(ctx, orgName)
97+
if err != nil {
98+
return fmt.Errorf("error querying GitHub custom repository roles %s: %s", orgName, err)
99+
}
100+
101+
var role *github.CustomRepoRoles
102+
for _, r := range roleList.CustomRepoRoles {
103+
if fmt.Sprint(*r.ID) == roleID {
104+
role = r
105+
break
106+
}
107+
}
108+
109+
if role == nil {
110+
log.Printf("[WARN] GitHub custom repository role (%s/%s) not found, removing from state", orgName, roleID)
111+
d.SetId("")
112+
return nil
113+
}
114+
115+
d.Set("name", role.Name)
116+
d.Set("description", role.Description)
117+
d.Set("base_role", role.BaseRole)
118+
d.Set("permissions", role.Permissions)
119+
120+
return nil
121+
}
122+
123+
func resourceGithubOrganizationCustomRoleUpdate(d *schema.ResourceData, meta interface{}) error {
124+
client := meta.(*Owner).v3client
125+
ctx := context.Background()
126+
orgName := meta.(*Owner).name
127+
128+
err := checkOrganization(meta)
129+
if err != nil {
130+
return err
131+
}
132+
133+
roleID := d.Id()
134+
permissions := d.Get("permissions").(*schema.Set).List()
135+
permissionsStr := make([]string, len(permissions))
136+
for i, v := range permissions {
137+
permissionsStr[i] = v.(string)
138+
}
139+
140+
update := &github.CreateOrUpdateCustomRoleOptions{
141+
Name: github.String(d.Get("name").(string)),
142+
Description: github.String(d.Get("description").(string)),
143+
BaseRole: github.String(d.Get("base_role").(string)),
144+
Permissions: permissionsStr,
145+
}
146+
147+
if _, _, err := client.Organizations.UpdateCustomRepoRole(ctx, orgName, roleID, update); err != nil {
148+
return fmt.Errorf("error updating GitHub custom repository role %s (%s): %s", orgName, roleID, err)
149+
}
150+
151+
return resourceGithubOrganizationCustomRoleRead(d, meta)
152+
}
153+
154+
func resourceGithubOrganizationCustomRoleDelete(d *schema.ResourceData, meta interface{}) error {
155+
client := meta.(*Owner).v3client
156+
ctx := context.Background()
157+
orgName := meta.(*Owner).name
158+
159+
err := checkOrganization(meta)
160+
if err != nil {
161+
return err
162+
}
163+
roleID := d.Id()
164+
165+
_, err = client.Organizations.DeleteCustomRepoRole(ctx, orgName, roleID)
166+
if err != nil {
167+
return fmt.Errorf("Error deleting GitHub custom repository role %s (%s): %s", orgName, roleID, err)
168+
}
169+
170+
return nil
171+
}

0 commit comments

Comments
 (0)