From b7fb4c8aec0f03f8b25508def1f66714878726bd Mon Sep 17 00:00:00 2001 From: Shi Han NG Date: Tue, 7 Aug 2018 14:05:59 +0900 Subject: [PATCH 1/6] r/github_repository_project: first commit --- github/provider.go | 1 + github/resource_github_repository_project.go | 122 +++++++++++++++ ...resource_github_repository_project_test.go | 141 ++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 github/resource_github_repository_project.go create mode 100644 github/resource_github_repository_project_test.go diff --git a/github/provider.go b/github/provider.go index 7934c5d467..e36ca22b4c 100644 --- a/github/provider.go +++ b/github/provider.go @@ -46,6 +46,7 @@ func Provider() terraform.ResourceProvider { "github_repository": resourceGithubRepository(), "github_repository_collaborator": resourceGithubRepositoryCollaborator(), "github_repository_deploy_key": resourceGithubRepositoryDeployKey(), + "github_repository_project": resourceGithubRepositoryProject(), "github_repository_webhook": resourceGithubRepositoryWebhook(), "github_team": resourceGithubTeam(), "github_team_membership": resourceGithubTeamMembership(), diff --git a/github/resource_github_repository_project.go b/github/resource_github_repository_project.go new file mode 100644 index 0000000000..62c59756b4 --- /dev/null +++ b/github/resource_github_repository_project.go @@ -0,0 +1,122 @@ +package github + +import ( + "context" + "fmt" + "strconv" + + "github.com/google/go-github/github" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceGithubRepositoryProject() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubRepositoryProjectCreate, + Read: resourceGithubRepositoryProjectRead, + Update: resourceGithubRepositoryProjectUpdate, + Delete: resourceGithubRepositoryProjectDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "repository": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "body": { + Type: schema.TypeString, + Optional: true, + }, + "url": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceGithubRepositoryProjectCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + o := meta.(*Organization).name + n := d.Get("name").(string) + b := d.Get("body").(string) + r := d.Get("repository").(string) + + options := github.ProjectOptions{ + Name: n, + Body: b, + } + + project, _, err := client.Repositories.CreateProject(context.TODO(), o, r, &options) + if err != nil { + return err + } + d.SetId(strconv.FormatInt(*project.ID, 10)) + + return resourceGithubRepositoryProjectRead(d, meta) +} + +func resourceGithubRepositoryProjectRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + o := meta.(*Organization).name + + projectID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return err + } + + project, resp, err := client.Projects.GetProject(context.TODO(), projectID) + if err != nil { + if resp != nil && resp.StatusCode == 404 { + d.SetId("") + return nil + } + return err + } + + d.Set("name", project.GetName()) + d.Set("body", project.GetBody()) + d.Set("url", fmt.Sprintf("https://github.com/%s/%s/projects/%d", o, d.Get("repository"), project.GetNumber())) + + return nil +} + +func resourceGithubRepositoryProjectUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + n := d.Get("name").(string) + b := d.Get("body").(string) + + options := github.ProjectOptions{ + Name: n, + Body: b, + } + + projectID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return err + } + + if _, _, err := client.Projects.UpdateProject(context.TODO(), projectID, &options); err != nil { + return err + } + + return resourceGithubRepositoryProjectRead(d, meta) +} + +func resourceGithubRepositoryProjectDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Organization).client + + projectID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return err + } + + _, err = client.Projects.DeleteProject(context.TODO(), projectID) + return err +} diff --git a/github/resource_github_repository_project_test.go b/github/resource_github_repository_project_test.go new file mode 100644 index 0000000000..f1c3d7b189 --- /dev/null +++ b/github/resource_github_repository_project_test.go @@ -0,0 +1,141 @@ +package github + +import ( + "context" + "fmt" + "path" + "strconv" + "strings" + "testing" + + "github.com/google/go-github/github" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccGithubRepositoryProject_basic(t *testing.T) { + randRepoName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + var project github.Project + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGithubRepositoryProjectDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryProjectConfig(randRepoName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryProjectExists("github_repository_project.test", &project), + testAccCheckGithubRepositoryProjectAttributes(&project, &testAccGithubRepositoryProjectExpectedAttributes{ + Name: "test-project", + Repository: randRepoName, + Body: "this is a test project", + }), + ), + }, + }, + }) +} + +func testAccGithubRepositoryProjectDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*Organization).client + + for _, rs := range s.RootModule().Resources { + if rs.Type != "github_repository_project" { + continue + } + + projectID, err := strconv.ParseInt(rs.Primary.ID, 10, 64) + if err != nil { + return err + } + + project, res, err := conn.Projects.GetProject(context.TODO(), projectID) + if err == nil { + if project != nil && + project.GetID() == projectID { + return fmt.Errorf("Repository project still exists") + } + } + if res.StatusCode != 404 { + return err + } + return nil + } + return nil +} + +func testAccCheckGithubRepositoryProjectExists(n string, project *github.Project) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not Found: %s", n) + } + + projectID, err := strconv.ParseInt(rs.Primary.ID, 10, 64) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*Organization).client + gotProject, _, err := conn.Projects.GetProject(context.TODO(), projectID) + if err != nil { + return err + } + *project = *gotProject + return nil + } +} + +type testAccGithubRepositoryProjectExpectedAttributes struct { + Name string + Repository string + Body string +} + +func testAccCheckGithubRepositoryProjectAttributes(project *github.Project, want *testAccGithubRepositoryProjectExpectedAttributes) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if *project.Name != want.Name { + return fmt.Errorf("got project %q; want %q", *project.Name, want.Name) + } + if got := path.Base(project.GetOwnerURL()); got != want.Repository { + return fmt.Errorf("got project %q; want %q", got, want.Repository) + } + if *project.Body != want.Body { + return fmt.Errorf("got project n%q; want %q", *project.Body, want.Body) + } + if !strings.HasPrefix(*project.URL, "https://") { + return fmt.Errorf("got http URL %q; want to start with 'https://'", *project.URL) + } + + return nil + } +} + +func testAccGithubRepositoryProjectConfig(repoName string) string { + return fmt.Sprintf(` +resource "github_repository" "foo" { + name = "%[1]s" + description = "Terraform acceptance tests" + homepage_url = "http://example.com/" + + # So that acceptance tests can be run in a github organization + # with no billing + private = false + + has_projects = true + has_issues = true + has_wiki = true + has_downloads = true +} + +resource "github_repository_project" "test" { + depends_on = ["github_repository.foo"] + + name = "test-project" + repository = "%[1]s" + body = "this is a test project" +}`, repoName) +} From 7da0e51bffda4e6b958af5465f5ac8327a492918 Mon Sep 17 00:00:00 2001 From: Shi Han NG Date: Wed, 8 Aug 2018 22:01:14 +0900 Subject: [PATCH 2/6] r/github_repository_project: add docs --- .../docs/r/repository_project.html.markdown | 43 +++++++++++++++++++ website/github.erb | 3 ++ 2 files changed, 46 insertions(+) create mode 100644 website/docs/r/repository_project.html.markdown diff --git a/website/docs/r/repository_project.html.markdown b/website/docs/r/repository_project.html.markdown new file mode 100644 index 0000000000..637eac644f --- /dev/null +++ b/website/docs/r/repository_project.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_project" +sidebar_current: "docs-github-resource-repository-project" +description: |- + Creates and manages projects for Github repositories +--- + +# github_repository_project + +This resource allows you to create and manage projects for Github repository. + +## Example Usage + +```hcl +resource "github_repository" "example" { + name = "example" + description = "My awesome codebase" + has_projects = true +} + +resource "github_repository_project" "project" { + name = "A Repository Project" + repository = "${github_repository.example.name}" + body = "This is a repository project." +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the project. + +* `repository` - (Required) The repository of the project. + +* `body` - (Optional) The body of the project. + +## Attributes Reference + +The following additional attributes are exported: + +* `url` - URL of the project diff --git a/website/github.erb b/website/github.erb index 434428fb5c..dfde2967ca 100644 --- a/website/github.erb +++ b/website/github.erb @@ -67,6 +67,9 @@ > github_organization_project + > + github_organization_project + From d3099f15bc688357c024ff73be4988e2cbe88b91 Mon Sep 17 00:00:00 2001 From: Shi Han NG Date: Thu, 9 Aug 2018 23:34:48 +0900 Subject: [PATCH 3/6] inline struct fields for readability --- github/resource_github_repository_project.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/github/resource_github_repository_project.go b/github/resource_github_repository_project.go index 62c59756b4..b7162ff0ed 100644 --- a/github/resource_github_repository_project.go +++ b/github/resource_github_repository_project.go @@ -48,10 +48,7 @@ func resourceGithubRepositoryProjectCreate(d *schema.ResourceData, meta interfac b := d.Get("body").(string) r := d.Get("repository").(string) - options := github.ProjectOptions{ - Name: n, - Body: b, - } + options := github.ProjectOptions{Name: n, Body: b} project, _, err := client.Repositories.CreateProject(context.TODO(), o, r, &options) if err != nil { @@ -92,10 +89,7 @@ func resourceGithubRepositoryProjectUpdate(d *schema.ResourceData, meta interfac n := d.Get("name").(string) b := d.Get("body").(string) - options := github.ProjectOptions{ - Name: n, - Body: b, - } + options := github.ProjectOptions{Name: n, Body: b} projectID, err := strconv.ParseInt(d.Id(), 10, 64) if err != nil { From dd354510f0dd2a158bc31ee9757fbc58163175e4 Mon Sep 17 00:00:00 2001 From: Shi Han NG Date: Fri, 10 Aug 2018 00:03:37 +0900 Subject: [PATCH 4/6] repository_project: fix import and add test --- github/resource_github_repository_project.go | 11 +++++++++- ...resource_github_repository_project_test.go | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/github/resource_github_repository_project.go b/github/resource_github_repository_project.go index b7162ff0ed..4813201b01 100644 --- a/github/resource_github_repository_project.go +++ b/github/resource_github_repository_project.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/google/go-github/github" "github.com/hashicorp/terraform/helper/schema" @@ -16,7 +17,15 @@ func resourceGithubRepositoryProject() *schema.Resource { Update: resourceGithubRepositoryProjectUpdate, Delete: resourceGithubRepositoryProjectDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid ID specified. Supplied ID must be written as /") + } + d.Set("repository", parts[0]) + d.SetId(parts[1]) + return []*schema.ResourceData{d}, nil + }, }, Schema: map[string]*schema.Schema{ diff --git a/github/resource_github_repository_project_test.go b/github/resource_github_repository_project_test.go index f1c3d7b189..793e54a8dc 100644 --- a/github/resource_github_repository_project_test.go +++ b/github/resource_github_repository_project_test.go @@ -38,6 +38,27 @@ func TestAccGithubRepositoryProject_basic(t *testing.T) { }) } +func TestAccGithubRepositoryProject_importBasic(t *testing.T) { + randRepoName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGithubRepositoryProjectDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryProjectConfig(randRepoName), + }, + { + ResourceName: "github_repository_project.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdPrefix: randRepoName + `/`, + }, + }, + }) +} + func testAccGithubRepositoryProjectDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*Organization).client From a19a9dc6a7bc72d9b91260416c07a0a44e66b1b5 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 10 Aug 2018 06:07:01 +0100 Subject: [PATCH 5/6] Clean up var names & fix typo --- github/resource_github_repository_project.go | 30 ++++++++++++-------- website/github.erb | 6 ++-- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/github/resource_github_repository_project.go b/github/resource_github_repository_project.go index 4813201b01..17b2bbb3ad 100644 --- a/github/resource_github_repository_project.go +++ b/github/resource_github_repository_project.go @@ -52,14 +52,17 @@ func resourceGithubRepositoryProject() *schema.Resource { func resourceGithubRepositoryProjectCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*Organization).client - o := meta.(*Organization).name - n := d.Get("name").(string) - b := d.Get("body").(string) - r := d.Get("repository").(string) - options := github.ProjectOptions{Name: n, Body: b} + options := github.ProjectOptions{ + Name: d.Get("name").(string), + Body: d.Get("body").(string), + } - project, _, err := client.Repositories.CreateProject(context.TODO(), o, r, &options) + project, _, err := client.Repositories.CreateProject(context.TODO(), + meta.(*Organization).name, + d.Get("repository").(string), + &options, + ) if err != nil { return err } @@ -70,7 +73,7 @@ func resourceGithubRepositoryProjectCreate(d *schema.ResourceData, meta interfac func resourceGithubRepositoryProjectRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*Organization).client - o := meta.(*Organization).name + orgName := meta.(*Organization).name projectID, err := strconv.ParseInt(d.Id(), 10, 64) if err != nil { @@ -88,24 +91,27 @@ func resourceGithubRepositoryProjectRead(d *schema.ResourceData, meta interface{ d.Set("name", project.GetName()) d.Set("body", project.GetBody()) - d.Set("url", fmt.Sprintf("https://github.com/%s/%s/projects/%d", o, d.Get("repository"), project.GetNumber())) + d.Set("url", fmt.Sprintf("https://github.com/%s/%s/projects/%d", + orgName, d.Get("repository"), project.GetNumber())) return nil } func resourceGithubRepositoryProjectUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*Organization).client - n := d.Get("name").(string) - b := d.Get("body").(string) - options := github.ProjectOptions{Name: n, Body: b} + options := github.ProjectOptions{ + Name: d.Get("name").(string), + Body: d.Get("body").(string), + } projectID, err := strconv.ParseInt(d.Id(), 10, 64) if err != nil { return err } - if _, _, err := client.Projects.UpdateProject(context.TODO(), projectID, &options); err != nil { + _, _, err = client.Projects.UpdateProject(context.TODO(), projectID, &options) + if err != nil { return err } diff --git a/website/github.erb b/website/github.erb index dfde2967ca..6be46ed5ac 100644 --- a/website/github.erb +++ b/website/github.erb @@ -49,6 +49,9 @@ > github_repository_deploy_key + > + github_repository_project + > github_repository_webhook @@ -67,9 +70,6 @@ > github_organization_project - > - github_organization_project - From 038316a7e3dff35e3ebf1e32b4d1ab9ac9e624c1 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 10 Aug 2018 06:10:20 +0100 Subject: [PATCH 6/6] Improve error messages --- github/resource_github_repository_project.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/github/resource_github_repository_project.go b/github/resource_github_repository_project.go index 17b2bbb3ad..302ac8617b 100644 --- a/github/resource_github_repository_project.go +++ b/github/resource_github_repository_project.go @@ -77,7 +77,7 @@ func resourceGithubRepositoryProjectRead(d *schema.ResourceData, meta interface{ projectID, err := strconv.ParseInt(d.Id(), 10, 64) if err != nil { - return err + return unconvertibleIdErr(d.Id(), err) } project, resp, err := client.Projects.GetProject(context.TODO(), projectID) @@ -107,7 +107,7 @@ func resourceGithubRepositoryProjectUpdate(d *schema.ResourceData, meta interfac projectID, err := strconv.ParseInt(d.Id(), 10, 64) if err != nil { - return err + return unconvertibleIdErr(d.Id(), err) } _, _, err = client.Projects.UpdateProject(context.TODO(), projectID, &options) @@ -123,7 +123,7 @@ func resourceGithubRepositoryProjectDelete(d *schema.ResourceData, meta interfac projectID, err := strconv.ParseInt(d.Id(), 10, 64) if err != nil { - return err + return unconvertibleIdErr(d.Id(), err) } _, err = client.Projects.DeleteProject(context.TODO(), projectID)