From a1e92c6f7720e99c753146be9255dbcde6376a2c Mon Sep 17 00:00:00 2001 From: Michael Handler Date: Wed, 1 Jun 2016 17:06:35 -0700 Subject: [PATCH] +github_repository_collaborator (#6861) --- builtin/providers/github/provider.go | 9 +- builtin/providers/github/provider_test.go | 5 + ...resource_github_repository_collaborator.go | 99 +++++++++++++ ...rce_github_repository_collaborator_test.go | 135 ++++++++++++++++++ .../github/resource_github_team_repository.go | 23 --- .../resource_github_team_repository_test.go | 12 +- builtin/providers/github/util_permissions.go | 24 ++++ .../r/repository_collaborator.html.markdown | 45 ++++++ website/source/layouts/github.erb | 3 + 9 files changed, 322 insertions(+), 33 deletions(-) create mode 100644 builtin/providers/github/resource_github_repository_collaborator.go create mode 100644 builtin/providers/github/resource_github_repository_collaborator_test.go create mode 100644 builtin/providers/github/util_permissions.go create mode 100644 website/source/docs/providers/github/r/repository_collaborator.html.markdown diff --git a/builtin/providers/github/provider.go b/builtin/providers/github/provider.go index d512a46067..167b8759c3 100644 --- a/builtin/providers/github/provider.go +++ b/builtin/providers/github/provider.go @@ -32,10 +32,11 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "github_team": resourceGithubTeam(), - "github_team_membership": resourceGithubTeamMembership(), - "github_team_repository": resourceGithubTeamRepository(), - "github_membership": resourceGithubMembership(), + "github_team": resourceGithubTeam(), + "github_team_membership": resourceGithubTeamMembership(), + "github_team_repository": resourceGithubTeamRepository(), + "github_membership": resourceGithubMembership(), + "github_repository_collaborator": resourceGithubRepositoryCollaborator(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/github/provider_test.go b/builtin/providers/github/provider_test.go index ccaf84d268..07b2a79d65 100644 --- a/builtin/providers/github/provider_test.go +++ b/builtin/providers/github/provider_test.go @@ -8,6 +8,8 @@ import ( "github.com/hashicorp/terraform/terraform" ) +const testRepo string = "test-repo" + var testAccProviders map[string]terraform.ResourceProvider var testAccProvider *schema.Provider @@ -38,4 +40,7 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("GITHUB_TEST_USER"); v == "" { t.Fatal("GITHUB_TEST_USER must be set for acceptance tests") } + if v := os.Getenv("GITHUB_TEST_COLLABORATOR"); v == "" { + t.Fatal("GITHUB_TEST_COLLABORATOR must be set for acceptance tests") + } } diff --git a/builtin/providers/github/resource_github_repository_collaborator.go b/builtin/providers/github/resource_github_repository_collaborator.go new file mode 100644 index 0000000000..fdbca0cb6a --- /dev/null +++ b/builtin/providers/github/resource_github_repository_collaborator.go @@ -0,0 +1,99 @@ +package github + +import ( + "github.com/google/go-github/github" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceGithubRepositoryCollaborator() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubRepositoryCollaboratorCreate, + Read: resourceGithubRepositoryCollaboratorRead, + // editing repository collaborators are not supported by github api so forcing new on any changes + Delete: resourceGithubRepositoryCollaboratorDelete, + + Schema: map[string]*schema.Schema{ + "username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "repository": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "permission": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "push", + ValidateFunc: validateValueFunc([]string{"pull", "push", "admin"}), + }, + }, + } +} + +func resourceGithubRepositoryCollaboratorCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + u := d.Get("username").(string) + r := d.Get("repository").(string) + p := d.Get("permission").(string) + + _, err := client.Repositories.AddCollaborator(meta.(*Organization).name, r, u, + &github.RepositoryAddCollaboratorOptions{Permission: p}) + + if err != nil { + return err + } + + d.SetId(buildTwoPartID(&r, &u)) + + return resourceGithubRepositoryCollaboratorRead(d, meta) +} + +func resourceGithubRepositoryCollaboratorRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + u := d.Get("username").(string) + r := d.Get("repository").(string) + + isCollaborator, _, err := client.Repositories.IsCollaborator(meta.(*Organization).name, r, u) + + if !isCollaborator || err != nil { + d.SetId("") + return nil + } + + collaborators, _, err := client.Repositories.ListCollaborators(meta.(*Organization).name, r, + &github.ListOptions{}) + + if err != nil { + return err + } + + for _, c := range collaborators { + if *c.Login == u { + permName, err := getRepoPermission(c.Permissions) + + if err != nil { + return err + } + + d.Set("permission", permName) + + return nil + } + } + + return nil +} + +func resourceGithubRepositoryCollaboratorDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + u := d.Get("username").(string) + r := d.Get("repository").(string) + + _, err := client.Repositories.RemoveCollaborator(meta.(*Organization).name, r, u) + + return err +} diff --git a/builtin/providers/github/resource_github_repository_collaborator_test.go b/builtin/providers/github/resource_github_repository_collaborator_test.go new file mode 100644 index 0000000000..cecee96c0b --- /dev/null +++ b/builtin/providers/github/resource_github_repository_collaborator_test.go @@ -0,0 +1,135 @@ +package github + +import ( + "fmt" + "os" + "testing" + + "github.com/google/go-github/github" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +const expectedPermission string = "admin" + +func TestAccGithubRepositoryCollaborator_basic(t *testing.T) { + testCollaborator := os.Getenv("GITHUB_TEST_COLLABORATOR") + testAccGithubRepositoryCollaboratorConfig := fmt.Sprintf(` + resource "github_repository_collaborator" "test_repo_collaborator" { + repository = "%s" + username = "%s" + permission = "%s" + } + `, testRepo, testCollaborator, expectedPermission) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryCollaboratorDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccGithubRepositoryCollaboratorConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryCollaboratorExists("github_repository_collaborator.test_repo_collaborator"), + testAccCheckGithubRepositoryCollaboratorPermission("github_repository_collaborator.test_repo_collaborator"), + ), + }, + }, + }) +} + +func testAccCheckGithubRepositoryCollaboratorDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*Organization).client + + for _, rs := range s.RootModule().Resources { + if rs.Type != "github_repository_collaborator" { + continue + } + + o := testAccProvider.Meta().(*Organization).name + r, u := parseTwoPartID(rs.Primary.ID) + isCollaborator, _, err := conn.Repositories.IsCollaborator(o, r, u) + + if err != nil { + return err + } + + if isCollaborator { + return fmt.Errorf("Repository collaborator still exists") + } + + return nil + } + + return nil +} + +func testAccCheckGithubRepositoryCollaboratorExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not Found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No membership ID is set") + } + + conn := testAccProvider.Meta().(*Organization).client + o := testAccProvider.Meta().(*Organization).name + r, u := parseTwoPartID(rs.Primary.ID) + + isCollaborator, _, err := conn.Repositories.IsCollaborator(o, r, u) + + if err != nil { + return err + } + + if !isCollaborator { + return fmt.Errorf("Repository collaborator does not exist") + } + + return nil + } +} + +func testAccCheckGithubRepositoryCollaboratorPermission(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not Found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No membership ID is set") + } + + conn := testAccProvider.Meta().(*Organization).client + o := testAccProvider.Meta().(*Organization).name + r, u := parseTwoPartID(rs.Primary.ID) + + collaborators, _, err := conn.Repositories.ListCollaborators(o, r, &github.ListOptions{}) + + if err != nil { + return err + } + + for _, c := range collaborators { + if *c.Login == u { + permName, err := getRepoPermission(c.Permissions) + + if err != nil { + return err + } + + if permName != expectedPermission { + return fmt.Errorf("Expected permission %s on repository collaborator, actual permission %s", expectedPermission, permName) + } + + return nil + } + } + + return fmt.Errorf("Repository collaborator did not appear in list of collaborators on repository") + } +} diff --git a/builtin/providers/github/resource_github_team_repository.go b/builtin/providers/github/resource_github_team_repository.go index cc46f66ce8..b9e50a09b9 100644 --- a/builtin/providers/github/resource_github_team_repository.go +++ b/builtin/providers/github/resource_github_team_repository.go @@ -1,16 +1,10 @@ package github import ( - "errors" - "github.com/google/go-github/github" "github.com/hashicorp/terraform/helper/schema" ) -const pullPermission string = "pull" -const pushPermission string = "push" -const adminPermission string = "admin" - func resourceGithubTeamRepository() *schema.Resource { return &schema.Resource{ Create: resourceGithubTeamRepositoryCreate, @@ -110,20 +104,3 @@ func resourceGithubTeamRepositoryDelete(d *schema.ResourceData, meta interface{} return err } - -func getRepoPermission(p *map[string]bool) (string, error) { - - // Permissions are returned in this map format such that if you have a certain level - // of permission, all levels below are also true. For example, if a team has push - // permission, the map will be: {"pull": true, "push": true, "admin": false} - if (*p)[adminPermission] { - return adminPermission, nil - } else if (*p)[pushPermission] { - return pushPermission, nil - } else { - if (*p)[pullPermission] { - return pullPermission, nil - } - return "", errors.New("At least one permission expected from permissions map.") - } -} diff --git a/builtin/providers/github/resource_github_team_repository_test.go b/builtin/providers/github/resource_github_team_repository_test.go index c7a9b2c20a..fd90a8fc10 100644 --- a/builtin/providers/github/resource_github_team_repository_test.go +++ b/builtin/providers/github/resource_github_team_repository_test.go @@ -127,7 +127,7 @@ func testAccCheckGithubTeamRepositoryDestroy(s *terraform.State) error { return nil } -const testAccGithubTeamRepositoryConfig = ` +var testAccGithubTeamRepositoryConfig string = fmt.Sprintf(` resource "github_team" "test_team" { name = "foo" description = "Terraform acc test group" @@ -135,12 +135,12 @@ resource "github_team" "test_team" { resource "github_team_repository" "test_team_test_repo" { team_id = "${github_team.test_team.id}" - repository = "test-repo" + repository = "%s" permission = "pull" } -` +`, testRepo) -const testAccGithubTeamRepositoryUpdateConfig = ` +var testAccGithubTeamRepositoryUpdateConfig string = fmt.Sprintf(` resource "github_team" "test_team" { name = "foo" description = "Terraform acc test group" @@ -148,7 +148,7 @@ resource "github_team" "test_team" { resource "github_team_repository" "test_team_test_repo" { team_id = "${github_team.test_team.id}" - repository = "test-repo" + repository = "%s" permission = "push" } -` +`, testRepo) diff --git a/builtin/providers/github/util_permissions.go b/builtin/providers/github/util_permissions.go new file mode 100644 index 0000000000..43dd2744df --- /dev/null +++ b/builtin/providers/github/util_permissions.go @@ -0,0 +1,24 @@ +package github + +import "errors" + +const pullPermission string = "pull" +const pushPermission string = "push" +const adminPermission string = "admin" + +func getRepoPermission(p *map[string]bool) (string, error) { + + // Permissions are returned in this map format such that if you have a certain level + // of permission, all levels below are also true. For example, if a team has push + // permission, the map will be: {"pull": true, "push": true, "admin": false} + if (*p)[adminPermission] { + return adminPermission, nil + } else if (*p)[pushPermission] { + return pushPermission, nil + } else { + if (*p)[pullPermission] { + return pullPermission, nil + } + return "", errors.New("At least one permission expected from permissions map.") + } +} diff --git a/website/source/docs/providers/github/r/repository_collaborator.html.markdown b/website/source/docs/providers/github/r/repository_collaborator.html.markdown new file mode 100644 index 0000000000..e72c40d31b --- /dev/null +++ b/website/source/docs/providers/github/r/repository_collaborator.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_collaborator" +sidebar_current: "docs-github-resource-repository-collaborator" +description: |- + Provides a GitHub repository collaborator resource. +--- + +# github\_repository_collaborator + +Provides a GitHub repository collaborator resource. + +This resource allows you to add/remove collaborators from repositories in your +organization. Collaborators can have explicit (and differing levels of) read, +write, or administrator access to specific repositories in your organization, +without giving the user full organization membership. + +When applied, an invitation will be sent to the user to become a collaborator +on a repository. When destroyed, either the invitation will be cancelled or the +collaborator will be removed from the repository. + +Further documentation on GitHub collaborators: + +- [Adding outside collaborators to repositories in your organization](https://help.github.com/articles/adding-outside-collaborators-to-repositories-in-your-organization/) +- [Converting an organization member to an outside collaborator](https://help.github.com/articles/converting-an-organization-member-to-an-outside-collaborator/) + +## Example Usage + +``` +# Add a collaborator to a repository +resource "github_repository_collaborator" "a_repo_collaborator" { + repository = "our-cool-repo" + username = "SomeUser" + permission = "admin" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `repository` - (Required) The GitHub repository +* `username` - (Required) The user to add to the repository as a collaborator. +* `permission` - (Optional) The permission of the outside collaborator for the repository. + Must be one of `pull`, `push`, or `admin`. Defaults to `push`. diff --git a/website/source/layouts/github.erb b/website/source/layouts/github.erb index 54dbe960cf..4d0fdb2b12 100644 --- a/website/source/layouts/github.erb +++ b/website/source/layouts/github.erb @@ -16,6 +16,9 @@ > github_membership + > + github_repository_collaborator + > github_team