Skip to content

Commit 3fef38e

Browse files
committed
refactor: migrate resource_github_issue_label to context-aware CRUD and tflog
1 parent 9870fc6 commit 3fef38e

1 file changed

Lines changed: 178 additions & 107 deletions

File tree

github/resource_github_issue_label.go

Lines changed: 178 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@ package github
33
import (
44
"context"
55
"errors"
6-
"log"
76
"net/http"
87

98
"github.com/google/go-github/v88/github"
9+
"github.com/hashicorp/terraform-plugin-log/tflog"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1011
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1112
)
1213

1314
func resourceGithubIssueLabel() *schema.Resource {
1415
return &schema.Resource{
15-
Create: resourceGithubIssueLabelCreateOrUpdate,
16-
Read: resourceGithubIssueLabelRead,
17-
Update: resourceGithubIssueLabelCreateOrUpdate,
18-
Delete: resourceGithubIssueLabelDelete,
16+
CreateContext: resourceGithubIssueLabelCreate,
17+
ReadContext: resourceGithubIssueLabelRead,
18+
UpdateContext: resourceGithubIssueLabelUpdate,
19+
DeleteContext: resourceGithubIssueLabelDelete,
1920
Importer: &schema.ResourceImporter{
20-
StateContext: schema.ImportStatePassthroughContext,
21+
StateContext: resourceGithubIssueLabelImport,
2122
},
2223

2324
Schema: map[string]*schema.Schema{
@@ -60,150 +61,220 @@ func resourceGithubIssueLabel() *schema.Resource {
6061
}
6162
}
6263

63-
// resourceGithubIssueLabelCreateOrUpdate idempotently creates or updates an
64-
// issue label. Issue labels are keyed off of their "name", so pre-existing
65-
// issue labels result in a 422 HTTP error if they exist outside of Terraform.
66-
// Normally this would not be an issue, except new repositories are created with
67-
// a "default" set of labels, and those labels easily conflict with custom ones.
68-
//
69-
// This function will first check if the label exists, and then issue an update,
70-
// otherwise it will create. This is also advantageous in that we get to use the
71-
// same function for two schema funcs.
72-
73-
func resourceGithubIssueLabelCreateOrUpdate(d *schema.ResourceData, meta any) error {
74-
client := meta.(*Owner).v3client
75-
orgName := meta.(*Owner).name
76-
repoName := d.Get("repository").(string)
77-
name := d.Get("name").(string)
78-
color := d.Get("color").(string)
64+
// resourceGithubIssueLabelCreate creates an issue label.
65+
func resourceGithubIssueLabelCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
66+
meta, diags := getIssueLabelOwner(m)
67+
if diags.HasError() {
68+
return diags
69+
}
70+
client := meta.v3client
71+
orgName := meta.name
72+
repoName, diags := getIssueLabelStringAttribute(d, "repository")
73+
if diags.HasError() {
74+
return diags
75+
}
76+
name, diags := getIssueLabelStringAttribute(d, "name")
77+
if diags.HasError() {
78+
return diags
79+
}
80+
color, diags := getIssueLabelStringAttribute(d, "color")
81+
if diags.HasError() {
82+
return diags
83+
}
7984

8085
label := &github.Label{
8186
Name: new(name),
8287
Color: new(color),
8388
}
84-
ctx := context.Background()
85-
if !d.IsNewResource() {
86-
ctx = context.WithValue(ctx, ctxId, d.Id())
87-
}
88-
89-
// Pull out the original name. If we already have a resource, this is the
90-
// parsed ID. If not, it's the value given to the resource.
91-
var originalName string
92-
if d.Id() == "" {
93-
originalName = name
94-
} else {
95-
var err error
96-
_, originalName, err = parseID2(d.Id())
97-
if err != nil {
98-
return err
89+
90+
if v, ok := d.GetOk("description"); ok {
91+
description, ok := v.(string)
92+
if !ok {
93+
return diag.Errorf(`expected "description" to be string`)
9994
}
95+
label.Description = &description
10096
}
10197

102-
existing, resp, err := client.Issues.GetLabel(ctx,
103-
orgName, repoName, originalName)
104-
if err != nil && resp.StatusCode != http.StatusNotFound {
105-
return err
98+
githubLabel, resp, err := client.Issues.CreateLabel(ctx, orgName, repoName, label)
99+
if err != nil {
100+
return diag.FromErr(err)
106101
}
107-
108-
if existing != nil {
109-
label.Description = new(d.Get("description").(string))
110-
111-
// Pull out the original name. If we already have a resource, this is the
112-
// parsed ID. If not, it's the value given to the resource.
113-
var originalName string
114-
if d.Id() == "" {
115-
originalName = name
116-
} else {
117-
var err error
118-
_, originalName, err = parseID2(d.Id())
119-
if err != nil {
120-
return err
121-
}
122-
}
123-
124-
_, _, err := client.Issues.EditLabel(ctx,
125-
orgName, repoName, originalName, label)
126-
if err != nil {
127-
return err
128-
}
129-
} else {
130-
if v, ok := d.GetOk("description"); ok {
131-
label.Description = new(v.(string))
132-
}
133-
134-
_, _, err := client.Issues.CreateLabel(ctx,
135-
orgName, repoName, label)
136-
if err != nil {
137-
return err
138-
}
102+
id, err := buildID(repoName, name)
103+
if err != nil {
104+
return diag.FromErr(err)
105+
}
106+
d.SetId(id)
107+
if err := d.Set("url", githubLabel.GetURL()); err != nil {
108+
return diag.FromErr(err)
139109
}
140110

141-
d.SetId(buildTwoPartID(repoName, name))
142-
143-
return resourceGithubIssueLabelRead(d, meta)
111+
if err := d.Set("etag", resp.Header.Get("ETag")); err != nil {
112+
return diag.FromErr(err)
113+
}
114+
return nil
144115
}
145116

146-
func resourceGithubIssueLabelRead(d *schema.ResourceData, meta any) error {
147-
client := meta.(*Owner).v3client
148-
repoName, name, err := parseID2(d.Id())
149-
if err != nil {
150-
return err
117+
func resourceGithubIssueLabelRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
118+
meta, diags := getIssueLabelOwner(m)
119+
if diags.HasError() {
120+
return diags
121+
}
122+
client := meta.v3client
123+
repoName, diags := getIssueLabelStringAttribute(d, "repository")
124+
if diags.HasError() {
125+
return diags
126+
}
127+
name, diags := getIssueLabelStringAttribute(d, "name")
128+
if diags.HasError() {
129+
return diags
151130
}
152131

153-
orgName := meta.(*Owner).name
154-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
132+
orgName := meta.name
155133
if !d.IsNewResource() {
156-
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
134+
etag, diags := getIssueLabelStringAttribute(d, "etag")
135+
if diags.HasError() {
136+
return diags
137+
}
138+
ctx = context.WithValue(ctx, ctxEtag, etag)
157139
}
158140

159-
githubLabel, resp, err := client.Issues.GetLabel(ctx,
160-
orgName, repoName, name)
141+
githubLabel, resp, err := client.Issues.GetLabel(ctx, orgName, repoName, name)
161142
if err != nil {
162143
var ghErr *github.ErrorResponse
163144
if errors.As(err, &ghErr) {
164145
if ghErr.Response.StatusCode == http.StatusNotModified {
165146
return nil
166147
}
167148
if ghErr.Response.StatusCode == http.StatusNotFound {
168-
log.Printf("[INFO] Removing label %s (%s/%s) from state because it no longer exists in GitHub",
169-
name, orgName, repoName)
149+
tflog.Info(ctx, "Removing label from state because it no longer exists in GitHub", map[string]any{"name": name, "org_name": orgName, "repo_name": repoName})
170150
d.SetId("")
171151
return nil
172152
}
173153
}
174-
return err
154+
return diag.FromErr(err)
175155
}
176156

177157
if err = d.Set("etag", resp.Header.Get("ETag")); err != nil {
178-
return err
158+
return diag.FromErr(err)
179159
}
180-
if err = d.Set("repository", repoName); err != nil {
181-
return err
160+
if err = d.Set("url", githubLabel.GetURL()); err != nil {
161+
return diag.FromErr(err)
162+
}
163+
164+
return nil
165+
}
166+
167+
func resourceGithubIssueLabelUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
168+
meta, diags := getIssueLabelOwner(m)
169+
if diags.HasError() {
170+
return diags
182171
}
183-
if err = d.Set("name", name); err != nil {
184-
return err
172+
client := meta.v3client
173+
orgName := meta.name
174+
repoName, diags := getIssueLabelStringAttribute(d, "repository")
175+
if diags.HasError() {
176+
return diags
185177
}
186-
if err = d.Set("color", githubLabel.GetColor()); err != nil {
187-
return err
178+
name, diags := getIssueLabelStringAttribute(d, "name")
179+
if diags.HasError() {
180+
return diags
188181
}
189-
if err = d.Set("description", githubLabel.GetDescription()); err != nil {
190-
return err
182+
color, diags := getIssueLabelStringAttribute(d, "color")
183+
if diags.HasError() {
184+
return diags
191185
}
192-
if err = d.Set("url", githubLabel.GetURL()); err != nil {
193-
return err
186+
187+
originalName := name
188+
if d.HasChange("name") {
189+
oldName, _ := d.GetChange("name")
190+
oldNameString, ok := oldName.(string)
191+
if !ok {
192+
return diag.Errorf(`expected old "name" to be string`)
193+
}
194+
originalName = oldNameString
195+
}
196+
label := &github.Label{
197+
Name: new(name),
198+
Color: new(color),
199+
}
200+
if v, ok := d.GetOk("description"); ok {
201+
description, ok := v.(string)
202+
if !ok {
203+
return diag.Errorf(`expected "description" to be string`)
204+
}
205+
label.Description = &description
206+
}
207+
githubLabel, resp, err := client.Issues.EditLabel(ctx, orgName, repoName, originalName, label)
208+
if err != nil {
209+
return diag.FromErr(err)
210+
}
211+
id, err := buildID(repoName, name)
212+
if err != nil {
213+
return diag.FromErr(err)
214+
}
215+
d.SetId(id)
216+
217+
if err := d.Set("url", githubLabel.GetURL()); err != nil {
218+
return diag.FromErr(err)
219+
}
220+
221+
if err := d.Set("etag", resp.Header.Get("ETag")); err != nil {
222+
return diag.FromErr(err)
194223
}
195224

196225
return nil
197226
}
198227

199-
func resourceGithubIssueLabelDelete(d *schema.ResourceData, meta any) error {
200-
client := meta.(*Owner).v3client
228+
func resourceGithubIssueLabelDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
229+
meta, diags := getIssueLabelOwner(m)
230+
if diags.HasError() {
231+
return diags
232+
}
233+
client := meta.v3client
201234

202-
orgName := meta.(*Owner).name
203-
repoName := d.Get("repository").(string)
204-
name := d.Get("name").(string)
205-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
235+
orgName := meta.name
236+
repoName, diags := getIssueLabelStringAttribute(d, "repository")
237+
if diags.HasError() {
238+
return diags
239+
}
240+
name, diags := getIssueLabelStringAttribute(d, "name")
241+
if diags.HasError() {
242+
return diags
243+
}
206244

207245
_, err := client.Issues.DeleteLabel(ctx, orgName, repoName, name)
208-
return handleArchivedRepoDelete(err, "issue label", name, orgName, repoName)
246+
return diag.FromErr(handleArchivedRepoDelete(err, "issue label", name, orgName, repoName))
247+
}
248+
249+
func resourceGithubIssueLabelImport(_ context.Context, d *schema.ResourceData, _ any) ([]*schema.ResourceData, error) {
250+
repoName, name, err := parseID2(d.Id())
251+
if err != nil {
252+
return nil, err
253+
}
254+
255+
if err := d.Set("repository", repoName); err != nil {
256+
return nil, err
257+
}
258+
259+
if err := d.Set("name", name); err != nil {
260+
return nil, err
261+
}
262+
263+
return []*schema.ResourceData{d}, nil
264+
}
265+
266+
func getIssueLabelOwner(m any) (*Owner, diag.Diagnostics) {
267+
meta, ok := m.(*Owner)
268+
if !ok {
269+
return nil, diag.Errorf("expected *Owner, got %T", m)
270+
}
271+
return meta, nil
272+
}
273+
274+
func getIssueLabelStringAttribute(d *schema.ResourceData, key string) (string, diag.Diagnostics) {
275+
value, ok := d.Get(key).(string)
276+
if !ok {
277+
return "", diag.Errorf("expected %q to be string", key)
278+
}
279+
return value, nil
209280
}

0 commit comments

Comments
 (0)