Skip to content
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

Update organization security manager resource to use operations that are not deprecated #2533

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reviewers:

Without the trailing slash, this command creates a binary called bin in the ~/go/ directory. With the trailing slash, the command correctly creates a binary called terraform-provider-github in the ~/go/bin/ directory.


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.

Expand All @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions examples/organization_security_manager/README.md
Original file line number Diff line number Diff line change
@@ -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}"
```
8 changes: 8 additions & 0 deletions examples/organization_security_manager/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 4 additions & 0 deletions examples/organization_security_manager/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "github_security_managers_team" {
description = "The organization security managers team"
value = github_organization_security_manager.security_managers
}
12 changes: 12 additions & 0 deletions examples/organization_security_manager/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
provider "github" {
owner = var.owner
token = var.github_token
}

terraform {
required_providers {
github = {
source = "integrations/github"
}
}
}
14 changes: 14 additions & 0 deletions examples/organization_security_manager/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}
77 changes: 57 additions & 20 deletions github/resource_github_organization_security_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package github

import (
"context"
"errors"
"log"
"net/http"
"strconv"

"github.com/google/go-github/v66/github"
Expand All @@ -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 {
Expand All @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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
}