Skip to content

Commit

Permalink
feat: implement github_issue_labels resource
Browse files Browse the repository at this point in the history
  • Loading branch information
nickfloyd authored Nov 29, 2023
2 parents 46650cb + 908d43c commit 19b29af
Show file tree
Hide file tree
Showing 5 changed files with 437 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 @@ -127,6 +127,7 @@ func Provider() terraform.ResourceProvider {
"github_emu_group_mapping": resourceGithubEMUGroupMapping(),
"github_issue": resourceGithubIssue(),
"github_issue_label": resourceGithubIssueLabel(),
"github_issue_labels": resourceGithubIssueLabels(),
"github_membership": resourceGithubMembership(),
"github_organization_block": resourceOrganizationBlock(),
"github_organization_custom_role": resourceGithubOrganizationCustomRole(),
Expand Down
247 changes: 247 additions & 0 deletions github/resource_github_issue_labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package github

import (
"context"
"log"
"strings"

"github.com/google/go-github/v52/github"

Check failure on line 8 in github/resource_github_issue_labels.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

cannot find module providing package github.com/google/go-github/v52/github: import lookup disabled by -mod=vendor
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceGithubIssueLabels() *schema.Resource {
return &schema.Resource{
Create: resourceGithubIssueLabelsCreateOrUpdate,
Read: resourceGithubIssueLabelsRead,
Update: resourceGithubIssueLabelsCreateOrUpdate,
Delete: resourceGithubIssueLabelsDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The GitHub repository.",
},
"label": {
Type: schema.TypeSet,
Optional: true,
Description: "List of labels",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the label.",
},
"color": {
Type: schema.TypeString,
Required: true,
Description: "A 6 character hex code, without the leading '#', identifying the color of the label.",
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: "A short description of the label.",
},
"url": {
Type: schema.TypeString,
Computed: true,
Description: "The URL to the issue label.",
},
},
},
},
},
}
}

func resourceGithubIssueLabelsRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

owner := meta.(*Owner).name
repository := d.Id()

log.Printf("[DEBUG] Reading GitHub issue labels for %s/%s", owner, repository)

ctx := context.WithValue(context.Background(), ctxId, repository)

options := &github.ListOptions{
PerPage: maxPerPage,
}

labels := make([]map[string]interface{}, 0)

for {
ls, resp, err := client.Issues.ListLabels(ctx, owner, repository, options)
if err != nil {
return err
}
for _, l := range ls {
labels = append(labels, map[string]interface{}{
"name": l.GetName(),
"color": l.GetColor(),
"description": l.GetDescription(),
"url": l.GetURL(),
})
}

if resp.NextPage == 0 {
break
}
options.Page = resp.NextPage
}

log.Printf("[DEBUG] Found %d GitHub issue labels for %s/%s", len(labels), owner, repository)
log.Printf("[DEBUG] Labels: %v", labels)

err := d.Set("repository", repository)
if err != nil {
return err
}

err = d.Set("label", labels)
if err != nil {
return err
}

return nil
}

func resourceGithubIssueLabelsCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

owner := meta.(*Owner).name
repository := d.Get("repository").(string)
ctx := context.WithValue(context.Background(), ctxId, repository)

o, n := d.GetChange("label")

log.Printf("[DEBUG] Updating GitHub issue labels for %s/%s", owner, repository)
log.Printf("[DEBUG] Old labels: %v", o)
log.Printf("[DEBUG] New labels: %v", n)

oMap := make(map[string]map[string]interface{})
nMap := make(map[string]map[string]interface{})
for _, raw := range o.(*schema.Set).List() {
m := raw.(map[string]interface{})
name := strings.ToLower(m["name"].(string))
oMap[name] = m
}
for _, raw := range n.(*schema.Set).List() {
m := raw.(map[string]interface{})
name := strings.ToLower(m["name"].(string))
nMap[name] = m
}

labels := make([]map[string]interface{}, 0)

// create
for name, n := range nMap {
if _, ok := oMap[name]; !ok {
log.Printf("[DEBUG] Creating GitHub issue label %s/%s/%s", owner, repository, name)

label, _, err := client.Issues.CreateLabel(ctx, owner, repository, &github.Label{
Name: github.String(n["name"].(string)),
Color: github.String(n["color"].(string)),
Description: github.String(n["description"].(string)),
})
if err != nil {
return err
}

labels = append(labels, map[string]interface{}{
"name": label.GetName(),
"color": label.GetColor(),
"description": label.GetDescription(),
"url": label.GetURL(),
})
}
}

// delete
for name, o := range oMap {
if _, ok := nMap[name]; !ok {
log.Printf("[DEBUG] Deleting GitHub issue label %s/%s/%s", owner, repository, name)

_, err := client.Issues.DeleteLabel(ctx, owner, repository, o["name"].(string))
if err != nil {
return err
}
}
}

// update
for name, n := range nMap {
if o, ok := oMap[name]; ok {
if o["name"] != n["name"] || o["color"] != n["color"] || o["description"] != n["description"] {
log.Printf("[DEBUG] Updating GitHub issue label %s/%s/%s", owner, repository, name)

label, _, err := client.Issues.EditLabel(ctx, owner, repository, name, &github.Label{
Name: github.String(n["name"].(string)),
Color: github.String(n["color"].(string)),
Description: github.String(n["description"].(string)),
})
if err != nil {
return err
}

labels = append(labels, map[string]interface{}{
"name": label.GetName(),
"color": label.GetColor(),
"description": label.GetDescription(),
"url": label.GetURL(),
})
} else {
labels = append(labels, o)
}
}
}

d.SetId(repository)

err := d.Set("label", labels)
if err != nil {
return err
}

return nil
}

func resourceGithubIssueLabelsDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

owner := meta.(*Owner).name
repository := d.Get("repository").(string)
ctx := context.WithValue(context.Background(), ctxId, repository)

labels := d.Get("label").(*schema.Set).List()

log.Printf("[DEBUG] Deleting GitHub issue labels for %s/%s", owner, repository)
log.Printf("[DEBUG] Labels: %v", labels)

// delete
for _, raw := range labels {
label := raw.(map[string]interface{})
name := label["name"].(string)

log.Printf("[DEBUG] Deleting GitHub issue label %s/%s/%s", owner, repository, name)

_, err := client.Issues.DeleteLabel(ctx, owner, repository, name)
if err != nil {
return err
}
}

d.SetId(repository)

err := d.Set("label", make([]map[string]interface{}, 0))
if err != nil {
return err
}

return nil
}
126 changes: 126 additions & 0 deletions github/resource_github_issue_labels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package github

import (
"fmt"
"testing"

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

func TestAccGithubIssueLabels(t *testing.T) {
t.Run("authoritatively overtakes existing labels", func(t *testing.T) {
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
empty := []map[string]interface{}{}

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// 0. Check if some labels already exist (indicated by non-empty plan)
{
Config: testAccGithubIssueLabelsConfig(randomID, empty),
ExpectNonEmptyPlan: true,
},
// 1. Check if all the labels are destroyed when the resource is added
{
Config: testAccGithubIssueLabelsConfig(randomID, empty),
Check: resource.TestCheckResourceAttr("github_issue_labels.test", "label.#", "0"),
},
// 2. Check if a label can be created
{
Config: testAccGithubIssueLabelsConfig(randomID, append(empty, map[string]interface{}{
"name": "foo",
"color": "000000",
"description": "foo",
})),
Check: resource.TestCheckResourceAttr("github_issue_labels.test", "label.#", "1"),
},
// 3. Check if a label can be recreated
{
Config: testAccGithubIssueLabelsConfig(randomID, append(empty, map[string]interface{}{
"name": "Foo",
"color": "000000",
"description": "foo",
})),
Check: resource.TestCheckResourceAttr("github_issue_labels.test", "label.#", "1"),
},
// 4. Check if multiple labels can be created
{
Config: testAccGithubIssueLabelsConfig(randomID, append(empty,
map[string]interface{}{
"name": "Foo",
"color": "000000",
"description": "foo",
},
map[string]interface{}{
"name": "bar",
"color": "000000",
"description": "bar",
}, map[string]interface{}{
"name": "baz",
"color": "000000",
"description": "baz",
})),
Check: resource.TestCheckResourceAttr("github_issue_labels.test", "label.#", "3"),
},
// 5. Check if labels can be destroyed
{
Config: testAccGithubIssueLabelsConfig(randomID, nil),
},
// 6. Check if labels were actually destroyed
{
Config: testAccGithubIssueLabelsConfig(randomID, empty),
Check: resource.TestCheckResourceAttr("github_issue_labels.test", "label.#", "0"),
},
},
})
}

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) {
testCase(t, individual)
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})
})
}

func testAccGithubIssueLabelsConfig(randomId string, labels []map[string]interface{}) string {
resource := ""
if labels != nil {
dynamic := ""
for _, label := range labels {
dynamic += fmt.Sprintf(`
label {
name = "%s"
color = "%s"
description = "%s"
}
`, label["name"], label["color"], label["description"])
}

resource = fmt.Sprintf(`
resource "github_issue_labels" "test" {
repository = github_repository.test.id
%s
}
`, dynamic)
}

return fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
%s
`, randomId, resource)
}
Loading

0 comments on commit 19b29af

Please sign in to comment.