From 11cc1a6a85f2bccf339facb401160dd39212af1f Mon Sep 17 00:00:00 2001 From: Steve Hipwell Date: Fri, 29 Nov 2024 15:43:39 +0000 Subject: [PATCH] feat: Added organization role support Signed-off-by: Steve Hipwell --- .../data_source_github_organization_role.go | 80 ++++++++ ...a_source_github_organization_role_teams.go | 90 +++++++++ ...rce_github_organization_role_teams_test.go | 1 + ...ta_source_github_organization_role_test.go | 35 ++++ ...a_source_github_organization_role_users.go | 84 ++++++++ ...rce_github_organization_role_users_test.go | 1 + .../data_source_github_organization_roles.go | 88 +++++++++ ...a_source_github_organization_roles_test.go | 28 +++ github/provider.go | 9 +- github/resource_github_organization_role.go | 182 ++++++++++++++++++ .../resource_github_organization_role_team.go | 136 +++++++++++++ ...urce_github_organization_role_team_test.go | 1 + .../resource_github_organization_role_test.go | 73 +++++++ .../resource_github_organization_role_user.go | 136 +++++++++++++ ...urce_github_organization_role_user_test.go | 1 + ...thub_organization_security_manager_test.go | 2 +- 16 files changed, 945 insertions(+), 2 deletions(-) create mode 100644 github/data_source_github_organization_role.go create mode 100644 github/data_source_github_organization_role_teams.go create mode 100644 github/data_source_github_organization_role_teams_test.go create mode 100644 github/data_source_github_organization_role_test.go create mode 100644 github/data_source_github_organization_role_users.go create mode 100644 github/data_source_github_organization_role_users_test.go create mode 100644 github/data_source_github_organization_roles.go create mode 100644 github/data_source_github_organization_roles_test.go create mode 100644 github/resource_github_organization_role.go create mode 100644 github/resource_github_organization_role_team.go create mode 100644 github/resource_github_organization_role_team_test.go create mode 100644 github/resource_github_organization_role_test.go create mode 100644 github/resource_github_organization_role_user.go create mode 100644 github/resource_github_organization_role_user_test.go diff --git a/github/data_source_github_organization_role.go b/github/data_source_github_organization_role.go new file mode 100644 index 0000000000..9a1d5fff82 --- /dev/null +++ b/github/data_source_github_organization_role.go @@ -0,0 +1,80 @@ +package github + +import ( + "context" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubOrganizationRole() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGithubOrganizationRoleRead, + + Schema: map[string]*schema.Schema{ + "role_id": { + Description: "The ID of the role.", + Type: schema.TypeInt, + Required: true, + }, + "name": { + Description: "The name of the role.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "A short description about who this role is for or what permissions it grants.", + Type: schema.TypeString, + Computed: true, + }, + "source": { + Description: "Source answers the question, \"where did this role come from?\"", + Type: schema.TypeString, + Computed: true, + }, + "base_role": { + Description: "The system role from which this role inherits permissions.", + Type: schema.TypeString, + Computed: true, + }, + "permissions": { + Description: "A list of permissions included in this role.", + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + }, + } +} + +func dataSourceGithubOrganizationRoleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + ctx := context.Background() + orgName := meta.(*Owner).name + + roleId := int64(d.Get("role_id").(int)) + + role, _, err := client.Organizations.GetOrgRole(ctx, orgName, roleId) + if err != nil { + return err + } + + r := map[string]any{ + "role_id": role.GetID(), + "name": role.GetName(), + "description": role.GetDescription(), + "source": role.GetSource(), + "base_role": role.GetBaseRole(), + "permissions": role.Permissions, + } + + d.SetId(strconv.FormatInt(role.GetID(), 10)) + + for k, v := range r { + if err := d.Set(k, v); err != nil { + return err + } + } + + return nil +} diff --git a/github/data_source_github_organization_role_teams.go b/github/data_source_github_organization_role_teams.go new file mode 100644 index 0000000000..582c08cd18 --- /dev/null +++ b/github/data_source_github_organization_role_teams.go @@ -0,0 +1,90 @@ +package github + +import ( + "context" + "fmt" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubOrganizationRoleTeams() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGithubOrganizationRoleTeamsRead, + + Schema: map[string]*schema.Schema{ + "teams": { + Description: "List of teams assigned to the organization role.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "Unique identifier of the team.", + Type: schema.TypeInt, + Computed: true, + }, + "slug": { + Description: "Slug of the team name.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "Name of the team.", + Type: schema.TypeString, + Computed: true, + }, + "permission": { + Description: "Permission that the team will have for its repositories.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGithubOrganizationRoleTeamsRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + ctx := context.Background() + orgName := meta.(*Owner).name + + roleId := d.Get("role_id").(int64) + + allTeams := make([]any, 0) + + opts := &github.ListOptions{ + PerPage: maxPerPage, + } + + for { + teams, resp, err := client.Organizations.ListTeamsAssignedToOrgRole(ctx, orgName, roleId, opts) + if err != nil { + return err + } + + for _, team := range teams { + t := map[string]any{ + "id": team.GetID(), + "slug": team.GetSlug(), + "name": team.GetName(), + "permission": team.GetPermission(), + } + allTeams = append(allTeams, t) + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + d.SetId(fmt.Sprintf("%d", roleId)) + if err := d.Set("teams", allTeams); err != nil { + return fmt.Errorf("error setting teams: %s", err) + } + + return nil +} diff --git a/github/data_source_github_organization_role_teams_test.go b/github/data_source_github_organization_role_teams_test.go new file mode 100644 index 0000000000..d2e73c266e --- /dev/null +++ b/github/data_source_github_organization_role_teams_test.go @@ -0,0 +1 @@ +package github diff --git a/github/data_source_github_organization_role_test.go b/github/data_source_github_organization_role_test.go new file mode 100644 index 0000000000..9b044ec11d --- /dev/null +++ b/github/data_source_github_organization_role_test.go @@ -0,0 +1,35 @@ +package github + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceGithubOrganizationRole(t *testing.T) { + t.Run("get the organization role without error", func(t *testing.T) { + config := ` + data "github_organization_role" "test" { + role_id = 138 + } + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.github_organization_role.test", "role_id", "138"), + resource.TestCheckResourceAttr("data.github_organization_role.test", "name", "security_manager"), + resource.TestCheckResourceAttr("data.github_organization_role.test", "source", "Predefined"), + resource.TestCheckResourceAttr("data.github_organization_role.test", "base_role", "read"), + resource.TestCheckResourceAttrSet("data.github_organization_role.test", "description"), + resource.TestCheckResourceAttrSet("data.github_organization_role.test", "permissions.#"), + ), + }, + }, + }) + }) +} diff --git a/github/data_source_github_organization_role_users.go b/github/data_source_github_organization_role_users.go new file mode 100644 index 0000000000..70007c226a --- /dev/null +++ b/github/data_source_github_organization_role_users.go @@ -0,0 +1,84 @@ +package github + +import ( + "context" + "fmt" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubOrganizationRoleUsers() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGithubOrganizationRoleUsersRead, + + Schema: map[string]*schema.Schema{ + "users": { + Description: "List of users assigned to the organization role.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "Unique identifier of the user.", + Type: schema.TypeInt, + Computed: true, + }, + "login": { + Description: "Login for the user.", + Type: schema.TypeString, + Computed: true, + }, + "type": { + Description: "Determines if the user has a direct, indirect, or mixed relationship to a role.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGithubOrganizationRoleUsersRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + ctx := context.Background() + orgName := meta.(*Owner).name + + roleId := d.Get("role_id").(int64) + + allUsers := make([]any, 0) + + opts := &github.ListOptions{ + PerPage: maxPerPage, + } + + for { + users, resp, err := client.Organizations.ListUsersAssignedToOrgRole(ctx, orgName, roleId, opts) + if err != nil { + return err + } + + for _, user := range users { + u := map[string]any{ + "id": user.GetID(), + "login": user.GetLogin(), + "type": user.GetType(), + } + allUsers = append(allUsers, u) + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + d.SetId(fmt.Sprintf("%d", roleId)) + if err := d.Set("users", allUsers); err != nil { + return fmt.Errorf("error setting users: %s", err) + } + + return nil +} diff --git a/github/data_source_github_organization_role_users_test.go b/github/data_source_github_organization_role_users_test.go new file mode 100644 index 0000000000..d2e73c266e --- /dev/null +++ b/github/data_source_github_organization_role_users_test.go @@ -0,0 +1 @@ +package github diff --git a/github/data_source_github_organization_roles.go b/github/data_source_github_organization_roles.go new file mode 100644 index 0000000000..1618c52e79 --- /dev/null +++ b/github/data_source_github_organization_roles.go @@ -0,0 +1,88 @@ +package github + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubOrganizationRoles() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGithubOrganizationRolesRead, + + Schema: map[string]*schema.Schema{ + "roles": { + Description: "The list of organization roles available to the organization.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The unique identifier of the role.", + Type: schema.TypeInt, + Computed: true, + }, + "name": { + Description: "The name of the role.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "A short description about who this role is for or what permissions it grants.", + Type: schema.TypeString, + Computed: true, + }, + "source": { + Description: "Source answers the question, \"where did this role come from?\"", + Type: schema.TypeString, + Computed: true, + }, + "base_role": { + Description: "The system role from which this role inherits permissions.", + Type: schema.TypeString, + Computed: true, + }, + "permissions": { + Description: "A list of permissions included in this role.", + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGithubOrganizationRolesRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + ctx := context.Background() + orgName := meta.(*Owner).name + + ret, _, err := client.Organizations.ListRoles(ctx, orgName) + if err != nil { + return err + } + + allRoles := make([]any, ret.GetTotalCount()) + for _, role := range ret.CustomRepoRoles { + r := map[string]any{ + "id": role.GetID(), + "name": role.GetName(), + "description": role.GetDescription(), + "source": role.GetSource(), + "base_role": role.GetBaseRole(), + "permissions": role.Permissions, + } + allRoles = append(allRoles, r) + } + + d.SetId(fmt.Sprintf("%s/github-org-roles", orgName)) + if err := d.Set("roles", allRoles); err != nil { + return fmt.Errorf("error setting roles: %s", err) + } + + return nil +} diff --git a/github/data_source_github_organization_roles_test.go b/github/data_source_github_organization_roles_test.go new file mode 100644 index 0000000000..7a1c27bda4 --- /dev/null +++ b/github/data_source_github_organization_roles_test.go @@ -0,0 +1,28 @@ +package github + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceGithubOrganizationRoles(t *testing.T) { + t.Run("get the organization role without error", func(t *testing.T) { + config := ` + data "github_organization_roles" "test" {} + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, organization) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.github_organization_roles.test", "roles.#"), + ), + }, + }, + }) + }) +} diff --git a/github/provider.go b/github/provider.go index a9a04d0474..ca6fc75b01 100644 --- a/github/provider.go +++ b/github/provider.go @@ -160,8 +160,11 @@ func Provider() *schema.Provider { "github_organization_block": resourceOrganizationBlock(), "github_organization_custom_role": resourceGithubOrganizationCustomRole(), "github_organization_project": resourceGithubOrganizationProject(), - "github_organization_security_manager": resourceGithubOrganizationSecurityManager(), + "github_organization_role": resourceGithubOrganizationRole(), + "github_organization_role_team": resourceGithubOrganizationRoleTeam(), + "github_organization_role_user": resourceGithubOrganizationRoleUser(), "github_organization_ruleset": resourceGithubOrganizationRuleset(), + "github_organization_security_manager": resourceGithubOrganizationSecurityManager(), "github_organization_settings": resourceGithubOrganizationSettings(), "github_organization_webhook": resourceGithubOrganizationWebhook(), "github_project_card": resourceGithubProjectCard(), @@ -232,6 +235,10 @@ func Provider() *schema.Provider { "github_organization_custom_role": dataSourceGithubOrganizationCustomRole(), "github_organization_external_identities": dataSourceGithubOrganizationExternalIdentities(), "github_organization_ip_allow_list": dataSourceGithubOrganizationIpAllowList(), + "github_organization_role": dataSourceGithubOrganizationRole(), + "github_organization_role_teams": dataSourceGithubOrganizationRoleTeams(), + "github_organization_role_users": dataSourceGithubOrganizationRoleUsers(), + "github_organization_roles": dataSourceGithubOrganizationRoles(), "github_organization_team_sync_groups": dataSourceGithubOrganizationTeamSyncGroups(), "github_organization_teams": dataSourceGithubOrganizationTeams(), "github_organization_webhooks": dataSourceGithubOrganizationWebhooks(), diff --git a/github/resource_github_organization_role.go b/github/resource_github_organization_role.go new file mode 100644 index 0000000000..64a0f23287 --- /dev/null +++ b/github/resource_github_organization_role.go @@ -0,0 +1,182 @@ +package github + +import ( + "context" + "fmt" + "log" + "net/http" + "strconv" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubOrganizationRole() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubOrganizationRoleCreate, + Read: resourceGithubOrganizationRoleRead, + Update: resourceGithubOrganizationRoleUpdate, + Delete: resourceGithubOrganizationRoleDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "The organization custom org role to create.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "The description of the custom org role.", + Type: schema.TypeString, + Optional: true, + }, + "base_role": { + Description: "The base role for the custom org role.", + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validateValueFunc([]string{"read", "triage", "write", "maintain", "admin"}), + }, + "permissions": { + Description: "The permissions for the custom org role.", + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, // At least one permission should be passed. + }, + }, + } +} + +func resourceGithubOrganizationRoleCreate(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() + + permissions := d.Get("permissions").(*schema.Set).List() + permissionsStr := make([]string, len(permissions)) + for i, v := range permissions { + permissionsStr[i] = v.(string) + } + + role, _, err := client.Organizations.CreateCustomOrgRole(ctx, orgName, &github.CreateOrUpdateOrgRoleOptions{ + Name: github.String(d.Get("name").(string)), + Description: github.String(d.Get("description").(string)), + BaseRole: github.String(d.Get("base_role").(string)), + Permissions: permissionsStr, + }) + if err != nil { + return fmt.Errorf("error creating GitHub custom organization role (%s/%s): %s", orgName, d.Get("name").(string), err) + } + + d.SetId(fmt.Sprint(role.GetID())) + return resourceGithubOrganizationRoleRead(d, meta) +} + +func resourceGithubOrganizationRoleRead(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 + + roleId, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return err + } + + role, _, err := client.Organizations.GetOrgRole(ctx, orgName, roleId) + if err != nil { + if ghErr, ok := err.(*github.ErrorResponse); ok { + if ghErr.Response.StatusCode == http.StatusNotFound { + log.Printf("[WARN] GitHub custom organization role (%s/%d) not found, removing from state", orgName, roleId) + d.SetId("") + return nil + } + } + return err + } + + if err = d.Set("name", role.Name); err != nil { + return err + } + if err = d.Set("description", role.Description); err != nil { + return err + } + if err = d.Set("base_role", role.BaseRole); err != nil { + return err + } + if err = d.Set("permissions", role.Permissions); err != nil { + return err + } + + return nil +} + +func resourceGithubOrganizationRoleUpdate(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 + + roleId, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return err + } + + permissions := d.Get("permissions").(*schema.Set).List() + permissionsStr := make([]string, len(permissions)) + for i, v := range permissions { + permissionsStr[i] = v.(string) + } + + update := &github.CreateOrUpdateOrgRoleOptions{ + Name: github.String(d.Get("name").(string)), + Description: github.String(d.Get("description").(string)), + BaseRole: github.String(d.Get("base_role").(string)), + Permissions: permissionsStr, + } + + _, _, err = client.Organizations.UpdateCustomOrgRole(ctx, orgName, roleId, update) + if err != nil { + return fmt.Errorf("error updating GitHub custom organization role (%s/%s): %s", orgName, d.Get("name").(string), err) + } + + return resourceGithubOrganizationRoleRead(d, meta) +} + +func resourceGithubOrganizationRoleDelete(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 + + roleId, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return err + } + + _, err = client.Organizations.DeleteCustomOrgRole(ctx, orgName, roleId) + if err != nil { + return fmt.Errorf("Error deleting GitHub custom organization role %s (%d): %s", orgName, roleId, err) + } + + return nil +} diff --git a/github/resource_github_organization_role_team.go b/github/resource_github_organization_role_team.go new file mode 100644 index 0000000000..fb7aee77ba --- /dev/null +++ b/github/resource_github_organization_role_team.go @@ -0,0 +1,136 @@ +package github + +import ( + "context" + "log" + "strconv" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubOrganizationRoleTeam() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubOrganizationRoleTeamCreate, + Read: resourceGithubOrganizationRoleTeamRead, + Delete: resourceGithubOrganizationRoleTeamDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "role_id": { + Description: "The unique identifier of the role.", + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "team_slug": { + Description: "The slug of the team name.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceGithubOrganizationRoleTeamCreate(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 + + roleId := d.Get("role_id").(int64) + teamSlug := d.Get("team_slug").(string) + + _, err = client.Organizations.AssignOrgRoleToTeam(ctx, orgName, teamSlug, roleId) + if err != nil { + return err + } + + d.SetId(buildTwoPartID(strconv.FormatInt(roleId, 10), teamSlug)) + + return resourceGithubOrganizationRoleTeamRead(d, meta) +} + +func resourceGithubOrganizationRoleTeamRead(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 + + roleIdString, teamSlug, err := parseTwoPartID(d.Id(), "role_id", "team_slug") + if err != nil { + return err + } + roleId, err := strconv.ParseInt(roleIdString, 10, 64) + if err != nil { + return err + } + + opts := &github.ListOptions{ + PerPage: maxPerPage, + } + + var team *github.Team + for { + teams, resp, err := client.Organizations.ListTeamsAssignedToOrgRole(ctx, orgName, roleId, opts) + if err != nil { + return err + } + + for _, t := range teams { + if t.GetSlug() == teamSlug { + team = t + break + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + if team == nil { + log.Printf("[INFO] Removing organization role team (%d:%s) from state because it no longer exists in GitHub", roleId, teamSlug) + d.SetId("") + return nil + } + + if err = d.Set("role_id", roleId); err != nil { + return err + } + + if err = d.Set("team_slug", teamSlug); err != nil { + return err + } + + return nil +} + +func resourceGithubOrganizationRoleTeamDelete(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 + + roleId := d.Get("role_id").(int64) + teamSlug := d.Get("team_slug").(string) + + _, err = client.Organizations.RemoveOrgRoleFromTeam(ctx, orgName, teamSlug, roleId) + return err +} diff --git a/github/resource_github_organization_role_team_test.go b/github/resource_github_organization_role_team_test.go new file mode 100644 index 0000000000..d2e73c266e --- /dev/null +++ b/github/resource_github_organization_role_team_test.go @@ -0,0 +1 @@ +package github diff --git a/github/resource_github_organization_role_test.go b/github/resource_github_organization_role_test.go new file mode 100644 index 0000000000..1105e4ccb1 --- /dev/null +++ b/github/resource_github_organization_role_test.go @@ -0,0 +1,73 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubOrganizationRole(t *testing.T) { + t.Run("can create an organization org role without erroring", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + name := fmt.Sprintf("tf-acc-org-role-%s", randomID) + description := "This is a test org role." + baseRole := "write" + permission0 := "read_organization_actions_usage_metrics" + config := fmt.Sprintf(` + resource "github_organization_role" "test" { + name = "%s" + description = "%s" + base_role = "%s" + permissions = [ + "%s" + ] + } + `, name, description, baseRole, permission0) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, enterprise) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("github_organization_role.test", "id"), + resource.TestCheckResourceAttr("github_organization_role.test", "name", name), + resource.TestCheckResourceAttr("github_organization_role.test", "description", description), + resource.TestCheckResourceAttr("github_organization_role.test", "base_role", baseRole), + resource.TestCheckResourceAttrSet("github_organization_role.test", "permissions.#"), + resource.TestCheckResourceAttr("github_organization_role.test", "permissions.0", permission0), + ), + }, + }, + }) + }) + + t.Run("can create an minimal organization org role without erroring", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + name := fmt.Sprintf("tf-acc-org-role-%s", randomID) + config := fmt.Sprintf(` + resource "github_organization_role" "test" { + name = "%s" + } + `, name) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, enterprise) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("github_organization_role.test", "id"), + resource.TestCheckResourceAttr("github_organization_role.test", "name", name), + resource.TestCheckResourceAttrSet("github_organization_role.test", "base_role"), + resource.TestCheckResourceAttrSet("github_organization_role.test", "permissions.#"), + ), + }, + }, + }) + }) +} diff --git a/github/resource_github_organization_role_user.go b/github/resource_github_organization_role_user.go new file mode 100644 index 0000000000..58ea7efb3d --- /dev/null +++ b/github/resource_github_organization_role_user.go @@ -0,0 +1,136 @@ +package github + +import ( + "context" + "log" + "strconv" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubOrganizationRoleUser() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubOrganizationRoleUserCreate, + Read: resourceGithubOrganizationRoleUserRead, + Delete: resourceGithubOrganizationRoleUserDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "role_id": { + Description: "The unique identifier of the role.", + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "username": { + Description: "The handle for the GitHub user account.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceGithubOrganizationRoleUserCreate(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 + + roleId := d.Get("role_id").(int64) + username := d.Get("username").(string) + + _, err = client.Organizations.AssignOrgRoleToUser(ctx, orgName, username, roleId) + if err != nil { + return err + } + + d.SetId(buildTwoPartID(strconv.FormatInt(roleId, 10), username)) + + return resourceGithubOrganizationRoleUserRead(d, meta) +} + +func resourceGithubOrganizationRoleUserRead(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 + + roleIdString, username, err := parseTwoPartID(d.Id(), "role_id", "username") + if err != nil { + return err + } + roleId, err := strconv.ParseInt(roleIdString, 10, 64) + if err != nil { + return err + } + + opts := &github.ListOptions{ + PerPage: maxPerPage, + } + + var user *github.User + for { + users, resp, err := client.Organizations.ListUsersAssignedToOrgRole(ctx, orgName, roleId, opts) + if err != nil { + return err + } + + for _, u := range users { + if u.GetLogin() == username { + user = u + break + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + if user == nil { + log.Printf("[INFO] Removing organization role User (%d:%s) from state because it no longer exists in GitHub", roleId, username) + d.SetId("") + return nil + } + + if err = d.Set("role_id", roleId); err != nil { + return err + } + + if err = d.Set("username", username); err != nil { + return err + } + + return nil +} + +func resourceGithubOrganizationRoleUserDelete(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 + + roleId := d.Get("role_id").(int64) + username := d.Get("username").(string) + + _, err = client.Organizations.RemoveOrgRoleFromUser(ctx, orgName, username, roleId) + return err +} diff --git a/github/resource_github_organization_role_user_test.go b/github/resource_github_organization_role_user_test.go new file mode 100644 index 0000000000..d2e73c266e --- /dev/null +++ b/github/resource_github_organization_role_user_test.go @@ -0,0 +1 @@ +package github diff --git a/github/resource_github_organization_security_manager_test.go b/github/resource_github_organization_security_manager_test.go index 070ad9ae4d..5fec6a7486 100644 --- a/github/resource_github_organization_security_manager_test.go +++ b/github/resource_github_organization_security_manager_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccGithubOrganizationSecurityManagers(t *testing.T) { +func TestAccGithubOrganizationSecurityManager(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) t.Run("adds team as security manager", func(t *testing.T) {