From f8547f77ee8673f772dee4e20006912019c53235 Mon Sep 17 00:00:00 2001 From: Joshua French Date: Fri, 10 Jan 2025 22:41:56 +0000 Subject: [PATCH 1/3] Change how the organization security manager resource uses the GitHub client to use non-deprecated operations --- ...ce_github_organization_security_manager.go | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/github/resource_github_organization_security_manager.go b/github/resource_github_organization_security_manager.go index 54d86227f3..9e0af73de8 100644 --- a/github/resource_github_organization_security_manager.go +++ b/github/resource_github_organization_security_manager.go @@ -2,8 +2,8 @@ package github import ( "context" + "errors" "log" - "net/http" "strconv" "github.com/google/go-github/v66/github" @@ -30,6 +30,21 @@ func resourceGithubOrganizationSecurityManager() *schema.Resource { } } +func getSecurityManagerRole(client *github.Client, ctx context.Context, orgName string) (*github.CustomOrgRoles, error) { + roles, _, err := client.Organizations.ListRoles(ctx, orgName) + if err != nil { + return nil, err + } + + for _, role := range roles.CustomRepoRoles { + if *role.Name == "security_manager" { + return role, nil + } + } + + return nil, errors.New("security manager role not found") +} + func resourceGithubOrganizationSecurityManagerCreate(d *schema.ResourceData, meta interface{}) error { err := checkOrganization(meta) if err != nil { @@ -44,18 +59,16 @@ func resourceGithubOrganizationSecurityManagerCreate(d *schema.ResourceData, met team, _, err := client.Teams.GetTeamBySlug(ctx, orgName, teamSlug) if err != nil { - log.Printf("[INFO] Team %s/%s was not found in GitHub", orgName, teamSlug) return err } - _, err = client.Organizations.AddSecurityManagerTeam(ctx, orgName, teamSlug) + smRole, err := getSecurityManagerRole(client, ctx, orgName) + if err != nil { + return err + } + + _, err = client.Organizations.AssignOrgRoleToTeam(ctx, orgName, teamSlug, smRole.GetID()) if err != nil { - if ghErr, ok := err.(*github.ErrorResponse); ok { - if ghErr.Response.StatusCode == http.StatusConflict { - log.Printf("[WARN] Organization %s has reached the maximum number of security manager teams", orgName) - return nil - } - } return err } @@ -79,28 +92,42 @@ func resourceGithubOrganizationSecurityManagerRead(d *schema.ResourceData, meta client := meta.(*Owner).v3client ctx := context.WithValue(context.Background(), ctxId, d.Id()) - // There is no endpoint for getting a single security manager team, so get the list and filter. - // There is a maximum number of security manager teams (currently 10), so this should be fine. - teams, _, err := client.Organizations.ListSecurityManagerTeams(ctx, orgName) + smRole, err := getSecurityManagerRole(client, ctx, orgName) if err != nil { return err } - var team *github.Team - for _, t := range teams { - if t.GetID() == teamId { - team = t + // There is no endpoint for getting a single security manager team, so get the list and filter. + options := &github.ListOptions{PerPage: 100} + var smTeam *github.Team = nil + for { + smTeams, resp, err := client.Organizations.ListTeamsAssignedToOrgRole(ctx, orgName, smRole.GetID(), options) + if err != nil { + return err + } + + for _, t := range smTeams { + if t.GetID() == teamId { + smTeam = t + break + } + } + + // Break when we've found the team or there are no more pages. + if smTeam != nil || resp.NextPage == 0 { break } + + options.Page = resp.NextPage } - if team == nil { + if smTeam == nil { log.Printf("[WARN] Removing organization security manager team %s from state because it no longer exists in GitHub", d.Id()) d.SetId("") return nil } - if err = d.Set("team_slug", team.GetSlug()); err != nil { + if err = d.Set("team_slug", smTeam.GetSlug()); err != nil { return err } @@ -128,8 +155,13 @@ func resourceGithubOrganizationSecurityManagerUpdate(d *schema.ResourceData, met return err } + smRole, err := getSecurityManagerRole(client, ctx, orgName) + if err != nil { + return err + } + // Adding the same team is a no-op. - _, err = client.Organizations.AddSecurityManagerTeam(ctx, orgName, team.GetSlug()) + _, err = client.Organizations.AssignOrgRoleToTeam(ctx, orgName, team.GetSlug(), smRole.GetID()) if err != nil { return err } @@ -149,6 +181,11 @@ func resourceGithubOrganizationSecurityManagerDelete(d *schema.ResourceData, met client := meta.(*Owner).v3client ctx := context.WithValue(context.Background(), ctxId, d.Id()) - _, err = client.Organizations.RemoveSecurityManagerTeam(ctx, orgName, teamSlug) + smRole, err := getSecurityManagerRole(client, ctx, orgName) + if err != nil { + return err + } + + _, err = client.Organizations.RemoveOrgRoleFromTeam(ctx, orgName, teamSlug, smRole.GetID()) return err } From 4e548790e2cdd65955ddd5a84501bbda65b48b81 Mon Sep 17 00:00:00 2001 From: Joshua French Date: Sat, 11 Jan 2025 02:27:41 +0000 Subject: [PATCH 2/3] Add example for creating an organization security manager team --- .../organization_security_manager/README.md | 22 +++++++++++++++++++ .../organization_security_manager/main.tf | 8 +++++++ .../organization_security_manager/output.tf | 4 ++++ .../providers.tf | 12 ++++++++++ .../variables.tf | 14 ++++++++++++ 5 files changed, 60 insertions(+) create mode 100644 examples/organization_security_manager/README.md create mode 100644 examples/organization_security_manager/main.tf create mode 100644 examples/organization_security_manager/output.tf create mode 100644 examples/organization_security_manager/providers.tf create mode 100644 examples/organization_security_manager/variables.tf diff --git a/examples/organization_security_manager/README.md b/examples/organization_security_manager/README.md new file mode 100644 index 0000000000..249708bfb6 --- /dev/null +++ b/examples/organization_security_manager/README.md @@ -0,0 +1,22 @@ +# Organization Security Manager Example + +This example demonstrates creating an organization security manager team. + +It will: +- Create a team with the specified `team_name` in the specified `owner` organization +- Assign the organization security manager role to the team + +The GitHub token must have the `admin:org` scope. + +```console +export GITHUB_OWNER=my-organization +export GITHUB_TOKEN=ghp_### +export GITHUB_TEAM_NAME="My Security Manager Team" +``` + +```console +terraform apply \ + -var "owner=${GITHUB_OWNER}" \ + -var "github_token=${GITHUB_TOKEN}" \ + -var "team_name=${GITHUB_TEAM_NAME}" +``` diff --git a/examples/organization_security_manager/main.tf b/examples/organization_security_manager/main.tf new file mode 100644 index 0000000000..d7401daa65 --- /dev/null +++ b/examples/organization_security_manager/main.tf @@ -0,0 +1,8 @@ +resource "github_team" "security_managers" { + name = var.team_name + description = "A team of organization security managers" +} + +resource "github_organization_security_manager" "security_managers" { + team_slug = github_team.security_managers.slug +} diff --git a/examples/organization_security_manager/output.tf b/examples/organization_security_manager/output.tf new file mode 100644 index 0000000000..491c383b09 --- /dev/null +++ b/examples/organization_security_manager/output.tf @@ -0,0 +1,4 @@ +output "github_security_managers_team" { + description = "The organization security managers team" + value = github_organization_security_manager.security_managers +} diff --git a/examples/organization_security_manager/providers.tf b/examples/organization_security_manager/providers.tf new file mode 100644 index 0000000000..68338ea899 --- /dev/null +++ b/examples/organization_security_manager/providers.tf @@ -0,0 +1,12 @@ +provider "github" { + owner = var.owner + token = var.github_token +} + +terraform { + required_providers { + github = { + source = "integrations/github" + } + } +} diff --git a/examples/organization_security_manager/variables.tf b/examples/organization_security_manager/variables.tf new file mode 100644 index 0000000000..300e5456e6 --- /dev/null +++ b/examples/organization_security_manager/variables.tf @@ -0,0 +1,14 @@ +variable "github_token" { + description = "GitHub access token used to configure the provider" + type = string +} + +variable "owner" { + description = "GitHub owner used to configure the provider" + type = string +} + +variable "team_name" { + description = "The name to use for the GitHub team" + type = string +} From 9741187ce4da64f011fd70cb042d817ced7432cc Mon Sep 17 00:00:00 2001 From: Joshua French Date: Sat, 11 Jan 2025 03:03:10 +0000 Subject: [PATCH 3/3] Update contributing documentation to correct build command --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1526ef9a6..85d9a531ef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,7 +81,7 @@ Setting a `processId` of 0 allows a dropdown to select the process of the provid 0. Add a sleep call (e.g. `time.Sleep(10 * time.Second)`) in the [`func providerConfigure(p *schema.Provider) schema.ConfigureFunc`](https://github.com/integrations/terraform-provider-github/blob/cec7e175c50bb091feecdc96ba117067c35ee351/github/provider.go#L274C1-L274C64) before the immediate `return` call. This will allow time to connect the debugger while the provider is initializing, before any critical logic happens. -0. Build the terraform provider with debug flags enabled and copy it to the appropriate bin folder with a command like `go build -gcflags="all=-N -l" -o ~/go/bin`. +0. Build the terraform provider with debug flags enabled and copy it to the appropriate bin folder with a command like `go build -gcflags="all=-N -l" -o ~/go/bin/`. 0. Create or edit a `dev.tfrc` that points toward the newly-built binary, and export the `TF_CLI_CONFIG_FILE` variable to point to it. Further instructions on this process may be found in the [Building the provider](#using-a-local-version-of-the-provider) section. @@ -99,7 +99,7 @@ Manual testing should be performed on each PR opened in order to validate the pr Build the provider and specify the output directory: ```sh -$ go build -gcflags="all=-N -l" -o ~/go/bin +$ go build -gcflags="all=-N -l" -o ~/go/bin/ ``` This enables verifying your locally built provider using examples available in the `examples/` directory.