-
Notifications
You must be signed in to change notification settings - Fork 764
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement github_issue_labels resource
- Loading branch information
Showing
5 changed files
with
437 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
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,247 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"strings" | ||
|
||
"github.com/google/go-github/v52/github" | ||
"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 | ||
} |
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,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) | ||
} |
Oops, something went wrong.