forked from integrations/terraform-provider-github
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for organization security managers (integrations#1371)
* add org security managers resource * log error on too many security managers * add default state import by team id * add security manager resource docs * Add missing error check Co-authored-by: Keegan Campbell <me@kfcampbell.com>
- Loading branch information
Showing
5 changed files
with
362 additions
and
0 deletions.
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
151 changes: 151 additions & 0 deletions
151
github/resource_github_organization_security_manager.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,151 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"net/http" | ||
"strconv" | ||
|
||
"github.com/google/go-github/v48/github" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
) | ||
|
||
func resourceGithubOrganizationSecurityManager() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceGithubOrganizationSecurityManagerCreate, | ||
Read: resourceGithubOrganizationSecurityManagerRead, | ||
Update: resourceGithubOrganizationSecurityManagerUpdate, | ||
Delete: resourceGithubOrganizationSecurityManagerDelete, | ||
Importer: &schema.ResourceImporter{ | ||
State: schema.ImportStatePassthrough, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"team_slug": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceGithubOrganizationSecurityManagerCreate(d *schema.ResourceData, meta interface{}) error { | ||
err := checkOrganization(meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
orgName := meta.(*Owner).name | ||
teamSlug := d.Get("team_slug").(string) | ||
|
||
client := meta.(*Owner).v3client | ||
ctx := context.Background() | ||
|
||
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) | ||
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 | ||
} | ||
|
||
d.SetId(strconv.FormatInt(team.GetID(), 10)) | ||
|
||
return resourceGithubOrganizationSecurityManagerRead(d, meta) | ||
} | ||
|
||
func resourceGithubOrganizationSecurityManagerRead(d *schema.ResourceData, meta interface{}) error { | ||
err := checkOrganization(meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
orgName := meta.(*Owner).name | ||
teamId, err := strconv.ParseInt(d.Id(), 10, 64) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
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) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var team *github.Team | ||
for _, t := range teams { | ||
if t.GetID() == teamId { | ||
team = t | ||
break | ||
} | ||
} | ||
|
||
if team == 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 | ||
} | ||
|
||
d.Set("team_slug", team.GetSlug()) | ||
|
||
return nil | ||
} | ||
|
||
func resourceGithubOrganizationSecurityManagerUpdate(d *schema.ResourceData, meta interface{}) error { | ||
err := checkOrganization(meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
orgId := meta.(*Owner).id | ||
orgName := meta.(*Owner).name | ||
teamId, err := strconv.ParseInt(d.Id(), 10, 64) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
client := meta.(*Owner).v3client | ||
ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
|
||
team, _, err := client.Teams.GetTeamByID(ctx, orgId, teamId) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Adding the same team is a no-op. | ||
_, err = client.Organizations.AddSecurityManagerTeam(ctx, orgName, team.GetSlug()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return resourceGithubOrganizationSecurityManagerRead(d, meta) | ||
} | ||
|
||
func resourceGithubOrganizationSecurityManagerDelete(d *schema.ResourceData, meta interface{}) error { | ||
err := checkOrganization(meta) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
orgName := meta.(*Owner).name | ||
teamSlug := d.Get("team_slug").(string) | ||
|
||
client := meta.(*Owner).v3client | ||
ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
|
||
_, err = client.Organizations.RemoveSecurityManagerTeam(ctx, orgName, teamSlug) | ||
return err | ||
} |
172 changes: 172 additions & 0 deletions
172
github/resource_github_organization_security_manager_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,172 @@ | ||
package github | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest" | ||
"github.com/hashicorp/terraform-plugin-sdk/helper/resource" | ||
) | ||
|
||
func TestAccGithubOrganizationSecurityManagers(t *testing.T) { | ||
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) | ||
|
||
t.Run("adds team as security manager", func(t *testing.T) { | ||
config := fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-%s" | ||
} | ||
resource "github_organization_security_manager" "test" { | ||
team_slug = github_team.test.slug | ||
} | ||
`, randomID) | ||
|
||
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: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrPair("github_team.test", "ID", "github_organization_security_manager.test", "ID"), | ||
resource.TestCheckResourceAttrPair("github_team.test", "slug", "github_organization_security_manager.test", "team_slug"), | ||
resource.TestCheckResourceAttr("github_organization_security_manager.test", "team_slug", fmt.Sprintf("tf-acc-%s", randomID)), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
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) | ||
}) | ||
}) | ||
|
||
t.Run("handles team name changes", func(t *testing.T) { | ||
config := fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-%s" | ||
} | ||
resource "github_organization_security_manager" "test" { | ||
team_slug = github_team.test.slug | ||
} | ||
`, randomID) | ||
|
||
configUpdated := fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-updated-%s" | ||
} | ||
resource "github_organization_security_manager" "test" { | ||
team_slug = github_team.test.slug | ||
} | ||
`, randomID) | ||
|
||
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: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrPair("github_team.test", "ID", "github_organization_security_manager.test", "ID"), | ||
resource.TestCheckResourceAttrPair("github_team.test", "slug", "github_organization_security_manager.test", "team_slug"), | ||
resource.TestCheckResourceAttr("github_organization_security_manager.test", "team_slug", fmt.Sprintf("tf-acc-%s", randomID)), | ||
), | ||
}, | ||
{ | ||
Config: configUpdated, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrPair("github_team.test", "ID", "github_organization_security_manager.test", "ID"), | ||
resource.TestCheckResourceAttrPair("github_team.test", "slug", "github_organization_security_manager.test", "team_slug"), | ||
resource.TestCheckResourceAttr("github_organization_security_manager.test", "team_slug", fmt.Sprintf("tf-acc-updated-%s", randomID)), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
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) | ||
}) | ||
}) | ||
|
||
t.Run("handles team name changes", func(t *testing.T) { | ||
config := fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-%s" | ||
} | ||
resource "github_organization_security_manager" "test" { | ||
team_slug = github_team.test.slug | ||
} | ||
`, randomID) | ||
|
||
configUpdated := fmt.Sprintf(` | ||
resource "github_team" "test" { | ||
name = "tf-acc-updated-%s" | ||
} | ||
resource "github_organization_security_manager" "test" { | ||
team_slug = github_team.test.slug | ||
} | ||
`, randomID) | ||
|
||
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: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrPair("github_team.test", "ID", "github_organization_security_manager.test", "ID"), | ||
resource.TestCheckResourceAttrPair("github_team.test", "slug", "github_organization_security_manager.test", "team_slug"), | ||
resource.TestCheckResourceAttr("github_organization_security_manager.test", "team_slug", fmt.Sprintf("tf-acc-%s", randomID)), | ||
), | ||
}, | ||
{ | ||
Config: configUpdated, | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttrPair("github_team.test", "ID", "github_organization_security_manager.test", "ID"), | ||
resource.TestCheckResourceAttrPair("github_team.test", "slug", "github_organization_security_manager.test", "team_slug"), | ||
resource.TestCheckResourceAttr("github_organization_security_manager.test", "team_slug", fmt.Sprintf("tf-acc-updated-%s", randomID)), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
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) | ||
}) | ||
}) | ||
} |
35 changes: 35 additions & 0 deletions
35
website/docs/r/organization_security_manager.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,35 @@ | ||
--- | ||
layout: "github" | ||
page_title: "GitHub: github_organization_security_manager" | ||
description: |- | ||
Manages the Security manager teams for a GitHub Organization. | ||
--- | ||
|
||
# github_organization_security_manager | ||
|
||
## Example Usage | ||
|
||
```hcl | ||
resource "github_team" "some_team" { | ||
name = "SomeTeam" | ||
description = "Some cool team" | ||
} | ||
resource "github_organization_security_manager" "some_team" { | ||
team_slug = github_team.some_team.slug | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `team_slug` - (Required) The slug of the team to manage. | ||
|
||
## Import | ||
|
||
GitHub Security Manager Teams can be imported using the GitHub team ID e.g. | ||
|
||
``` | ||
$ terraform import github_organization_security_manager.core 1234567 | ||
``` |
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