Skip to content

Commit

Permalink
Add Project Card Support (#460)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy Udit authored Nov 23, 2020
1 parent 35b1a36 commit e9dc4e7
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 0 deletions.
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func Provider() terraform.ResourceProvider {
"github_organization_block": resourceOrganizationBlock(),
"github_organization_project": resourceGithubOrganizationProject(),
"github_organization_webhook": resourceGithubOrganizationWebhook(),
"github_project_card": resourceGithubProjectCard(),
"github_project_column": resourceGithubProjectColumn(),
"github_repository_collaborator": resourceGithubRepositoryCollaborator(),
"github_repository_deploy_key": resourceGithubRepositoryDeployKey(),
Expand Down
160 changes: 160 additions & 0 deletions github/resource_github_project_card.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package github

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

"github.com/google/go-github/v32/github"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceGithubProjectCard() *schema.Resource {
return &schema.Resource{
Create: resourceGithubProjectCardCreate,
Read: resourceGithubProjectCardRead,
Update: resourceGithubProjectCardUpdate,
Delete: resourceGithubProjectCardDelete,
Importer: &schema.ResourceImporter{
State: resourceGithubProjectCardImport,
},
Schema: map[string]*schema.Schema{
"column_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"note": {
Type: schema.TypeString,
Required: true,
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
"card_id": {
Type: schema.TypeInt,
Computed: true,
},
},
}
}

func resourceGithubProjectCardCreate(d *schema.ResourceData, meta interface{}) error {
err := checkOrganization(meta)
if err != nil {
return err
}

columnIDStr := d.Get("column_id").(string)
columnID, err := strconv.ParseInt(columnIDStr, 10, 64)
if err != nil {
return unconvertibleIdErr(columnIDStr, err)
}

log.Printf("[DEBUG] Creating project card note in column ID: %d", columnID)
client := meta.(*Owner).v3client
options := github.ProjectCardOptions{Note: d.Get("note").(string)}
ctx := context.Background()
card, _, err := client.Projects.CreateProjectCard(ctx, columnID, &options)
if err != nil {
return err
}

d.Set("card_id", card.GetID())
d.SetId(card.GetNodeID())

return resourceGithubProjectCardRead(d, meta)
}

func resourceGithubProjectCardRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
nodeID := d.Id()
cardID := d.Get("card_id").(int)
ctx := context.WithValue(context.Background(), ctxId, d.Id())
if !d.IsNewResource() {
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
}

log.Printf("[DEBUG] Reading project card: %s", nodeID)
card, _, err := client.Projects.GetProjectCard(ctx, int64(cardID))
if err != nil {
if err, ok := err.(*github.ErrorResponse); ok {
if err.Response.StatusCode == http.StatusNotFound {
log.Printf("[WARN] Removing project card %s from state because it no longer exists in GitHub", d.Id())
d.SetId("")
return nil
}
}
return err
}

// FIXME: Remove URL parsing if a better option becomes available
columnURL := card.GetColumnURL()
columnIDStr := strings.TrimPrefix(columnURL, client.BaseURL.String()+`projects/columns/`)
if err != nil {
return unconvertibleIdErr(columnIDStr, err)
}

d.Set("note", card.GetNote())
d.Set("column_id", columnIDStr)
d.Set("card_id", card.GetID())

return nil
}

func resourceGithubProjectCardUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
cardID := d.Get("card_id").(int)

log.Printf("[DEBUG] Updating project Card: %s", d.Id())
options := github.ProjectCardOptions{
Note: d.Get("note").(string),
}
ctx := context.WithValue(context.Background(), ctxId, d.Id())
_, _, err := client.Projects.UpdateProjectCard(ctx, int64(cardID), &options)
if err != nil {
return err
}

return resourceGithubProjectCardRead(d, meta)
}

func resourceGithubProjectCardDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.WithValue(context.Background(), ctxId, d.Id())

log.Printf("[DEBUG] Deleting project Card: %s", d.Id())
cardID := d.Get("card_id").(int)
_, err := client.Projects.DeleteProjectCard(ctx, int64(cardID))
if err != nil {
return err
}

return nil
}

func resourceGithubProjectCardImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {

cardIDStr := d.Id()
cardID, err := strconv.ParseInt(cardIDStr, 10, 64)
if err != nil {
return []*schema.ResourceData{d}, unconvertibleIdErr(cardIDStr, err)
}

log.Printf("[DEBUG] Importing project card with card ID: %d", cardID)
client := meta.(*Owner).v3client
ctx := context.Background()
card, _, err := client.Projects.GetProjectCard(ctx, cardID)
if card == nil || err != nil {
return []*schema.ResourceData{d}, err
}

d.SetId(card.GetNodeID())
d.Set("card_id", cardID)

return []*schema.ResourceData{d}, nil

}
68 changes: 68 additions & 0 deletions github/resource_github_project_card_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package github

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccGithubProjectCard(t *testing.T) {

randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)

t.Run("creates a project card", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_organization_project" "project" {
name = "tf-acc-%s"
body = "This is an organization project."
}
resource "github_project_column" "column" {
project_id = github_organization_project.project.id
name = "Backlog"
}
resource "github_project_card" "card" {
column_id = github_project_column.column.column_id
note = "## Unaccepted 👇"
}
`, randomID)

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(
"github_project_card.card", "note",
),
)

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: check,
},
},
})
}

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)
})

})
}
7 changes: 7 additions & 0 deletions github/resource_github_project_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ func resourceGithubProjectColumn() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"column_id": {
Type: schema.TypeInt,
Computed: true,
},
"etag": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -67,7 +71,9 @@ func resourceGithubProjectColumnCreate(d *schema.ResourceData, meta interface{})
if err != nil {
return err
}

d.SetId(strconv.FormatInt(column.GetID(), 10))
d.Set("column_id", column.GetID())

return resourceGithubProjectColumnRead(d, meta)
}
Expand Down Expand Up @@ -102,6 +108,7 @@ func resourceGithubProjectColumnRead(d *schema.ResourceData, meta interface{}) e

d.Set("name", column.GetName())
d.Set("project_id", projectID)
d.Set("column_id", column.GetID())
return nil
}

Expand Down
45 changes: 45 additions & 0 deletions website/docs/r/project_card.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
layout: "github"
page_title: "GitHub: github_project_card"
description: |-
Creates and manages project cards for GitHub projects
---

# github_project_card

This resource allows you to create and manage cards for GitHub projects.

## Example Usage

```hcl
resource "github_organization_project" "project" {
name = "An Organization Project"
body = "This is an organization project."
}
resource "github_project_column" "column" {
project_id = github_organization_project.project.id
name = "Backlog"
}
resource "github_project_card" "card" {
column_id = github_project_column.column.column_id
note = "## Unaccepted 👇"
}
```

## Argument Reference

The following arguments are supported:

* `column_id` - (Required) The ID of the card.

* `note` - (Required) The note contents of the card. Markdown supported.

## Import

A GitHub Project Card can be imported using its [Card ID](https://developer.github.com/v3/projects/cards/#get-a-project-card):

```
$ terraform import github_project_card.card 01234567
```
3 changes: 3 additions & 0 deletions website/github.erb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
<li>
<a href="/docs/providers/github/r/organization_webhook.html">github_organization_webhook</a>
</li>
<li>
<a href="/docs/providers/github/r/project_card.html">github_project_card</a>
</li>
<li>
<a href="/docs/providers/github/r/project_column.html">github_project_column</a>
</li>
Expand Down

0 comments on commit e9dc4e7

Please sign in to comment.