Skip to content

Commit f25aea2

Browse files
committed
add labels for other terraform results
1 parent 42a3e56 commit f25aea2

File tree

12 files changed

+390
-25
lines changed

12 files changed

+390
-25
lines changed

config/config.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,26 @@ type Fmt struct {
8282
// Plan is a terraform plan config
8383
type Plan struct {
8484
Template string `yaml:"template"`
85+
WhenChanges WhenChanges `yaml:"when_changes,omitempty"`
8586
WhenDestroy WhenDestroy `yaml:"when_destroy,omitempty"`
87+
WhenError WhenError `yaml:"when_error,omitempty"`
8688
WhenNoChanges WhenNoChanges `yaml:"when_no_changes,omitempty"`
8789
}
8890

91+
// WhenChanges is a configuration to notify the plan result contains new or updated in place resources
92+
type WhenChanges struct {
93+
Label string `yaml:"label,omitempty"`
94+
}
95+
8996
// WhenDestroy is a configuration to notify the plan result contains destroy operation
9097
type WhenDestroy struct {
91-
Template string `yaml:"template"`
98+
Label string `yaml:"label,omitempty"`
99+
Template string `yaml:"template,omitempty"`
100+
}
101+
102+
// WhenError is a configuration to notify the plan result contains new or updated in place resources
103+
type WhenError struct {
104+
Label string `yaml:"label,omitempty"`
92105
}
93106

94107
// WhenNoChange is a configuration to add a label when the plan result contains no change

config/config_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func TestLoadFile(t *testing.T) {
6363
ok: true,
6464
},
6565
{
66-
file: "../example-with-destroy-and-no-changes.tfnotify.yaml",
66+
file: "../example-with-destroy-and-result-labels.tfnotify.yaml",
6767
cfg: Config{
6868
CI: "circleci",
6969
Notifier: Notifier{
@@ -93,9 +93,16 @@ func TestLoadFile(t *testing.T) {
9393
},
9494
Plan: Plan{
9595
Template: "{{ .Title }}\n{{ .Message }}\n{{if .Result}}\n<pre><code>{{ .Result }}\n</pre></code>\n{{end}}\n<details><summary>Details (Click me)</summary>\n\n<pre><code>{{ .Body }}\n</pre></code></details>\n",
96+
WhenChanges: WhenChanges{
97+
Label: "changes",
98+
},
9699
WhenDestroy: WhenDestroy{
100+
Label: "destroy",
97101
Template: "## :warning: WARNING: Resource Deletion will happen :warning:\n\nThis plan contains **resource deletion**. Please check the plan result very carefully!\n",
98102
},
103+
WhenError: WhenError{
104+
Label: "error",
105+
},
99106
WhenNoChanges: WhenNoChanges{
100107
Label: "no-changes",
101108
},
@@ -105,7 +112,7 @@ func TestLoadFile(t *testing.T) {
105112
},
106113
UseRawOutput: false,
107114
},
108-
path: "../example-with-destroy-and-no-changes.tfnotify.yaml",
115+
path: "../example-with-destroy-and-result-labels.tfnotify.yaml",
109116
},
110117
ok: true,
111118
},

example-with-destroy-and-no-changes.tfnotify.yaml renamed to example-with-destroy-and-result-labels.tfnotify.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ terraform:
1818
1919
<pre><code>{{ .Body }}
2020
</pre></code></details>
21-
when_no_changes:
22-
label: "no-changes"
21+
when_changes:
22+
label: "changes"
2323
when_destroy:
24+
label: "destroy"
2425
template: |
2526
## :warning: WARNING: Resource Deletion will happen :warning:
2627
2728
This plan contains **resource deletion**. Please check the plan result very carefully!
29+
when_error:
30+
label: "error"
31+
when_no_changes:
32+
label: "no-changes"

main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,12 @@ func (t *tfnotify) Run() error {
115115
Template: t.template,
116116
DestroyWarningTemplate: t.destroyWarningTemplate,
117117
WarnDestroy: t.warnDestroy,
118-
NoChangesLabel: t.config.Terraform.Plan.WhenNoChanges.Label,
118+
ResultLabels: github.ResultLabels{
119+
ChangesLabel: t.config.Terraform.Plan.WhenChanges.Label,
120+
DestroyLabel: t.config.Terraform.Plan.WhenDestroy.Label,
121+
ErrorLabel: t.config.Terraform.Plan.WhenError.Label,
122+
NoChangesLabel: t.config.Terraform.Plan.WhenNoChanges.Label,
123+
},
119124
})
120125
if err != nil {
121126
return err

notifier/github/client.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ type Config struct {
4848
// DestroyWarningTemplate is used only for additional warning
4949
// the plan result contains destroy operation
5050
DestroyWarningTemplate terraform.Template
51-
// NoChangesLabel is a label to add to PRs when terraform output contains no changes
52-
NoChangesLabel string
51+
// ResultLabels is a set of labels to apply depending on the plan result
52+
ResultLabels ResultLabels
5353
}
5454

5555
// PullRequest represents GitHub Pull Request metadata
@@ -117,3 +117,45 @@ func NewClient(cfg Config) (*Client, error) {
117117
func (pr *PullRequest) IsNumber() bool {
118118
return pr.Number != 0
119119
}
120+
121+
// ResultLabels represents the labels to add to the PR depending on the plan result
122+
type ResultLabels struct {
123+
ChangesLabel string
124+
DestroyLabel string
125+
ErrorLabel string
126+
NoChangesLabel string
127+
}
128+
129+
// HasAnyLabelDefined returns true if any of the internal labels are set
130+
func (r *ResultLabels) HasAnyLabelDefined() bool {
131+
return r.ChangesLabel != "" || r.DestroyLabel != "" || r.ErrorLabel != "" || r.NoChangesLabel != ""
132+
}
133+
134+
// ToStringSlice returns all the defined labels as a string slice
135+
func (r *ResultLabels) ToStringSlice() []string {
136+
var result []string
137+
if r.ChangesLabel != "" {
138+
result = append(result, r.ChangesLabel)
139+
}
140+
if r.DestroyLabel != "" {
141+
result = append(result, r.DestroyLabel)
142+
}
143+
if r.ErrorLabel != "" {
144+
result = append(result, r.ErrorLabel)
145+
}
146+
if r.NoChangesLabel != "" {
147+
result = append(result, r.NoChangesLabel)
148+
}
149+
return result
150+
}
151+
152+
func (r *ResultLabels) IsResultLabel(label string) bool {
153+
switch label {
154+
case "":
155+
return false
156+
case r.ChangesLabel, r.DestroyLabel, r.ErrorLabel, r.NoChangesLabel:
157+
return true
158+
default:
159+
return false
160+
}
161+
}

notifier/github/client_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,97 @@ func TestIsNumber(t *testing.T) {
179179
}
180180
}
181181
}
182+
183+
func TestHasAnyLabelDefined(t *testing.T) {
184+
testCases := []struct {
185+
rl ResultLabels
186+
want bool
187+
}{
188+
{
189+
rl: ResultLabels{
190+
ChangesLabel: "changes",
191+
DestroyLabel: "destroy",
192+
ErrorLabel: "error",
193+
NoChangesLabel: "no-changes",
194+
},
195+
want: true,
196+
},
197+
{
198+
rl: ResultLabels{
199+
ChangesLabel: "changes",
200+
DestroyLabel: "destroy",
201+
ErrorLabel: "error",
202+
NoChangesLabel: "",
203+
},
204+
want: true,
205+
},
206+
{
207+
rl: ResultLabels{
208+
ChangesLabel: "",
209+
DestroyLabel: "",
210+
ErrorLabel: "",
211+
NoChangesLabel: "",
212+
},
213+
want: false,
214+
},
215+
}
216+
for _, testCase := range testCases {
217+
if testCase.rl.HasAnyLabelDefined() != testCase.want {
218+
t.Errorf("got %v but want %v", testCase.rl.HasAnyLabelDefined(), testCase.want)
219+
}
220+
}
221+
}
222+
223+
func TestIsResultLabels(t *testing.T) {
224+
testCases := []struct {
225+
rl ResultLabels
226+
label string
227+
want bool
228+
}{
229+
{
230+
rl: ResultLabels{
231+
ChangesLabel: "changes",
232+
DestroyLabel: "destroy",
233+
ErrorLabel: "error",
234+
NoChangesLabel: "no-changes",
235+
},
236+
label: "changes",
237+
want: true,
238+
},
239+
{
240+
rl: ResultLabels{
241+
ChangesLabel: "changes",
242+
DestroyLabel: "destroy",
243+
ErrorLabel: "error",
244+
NoChangesLabel: "no-changes",
245+
},
246+
label: "my-label",
247+
want: false,
248+
},
249+
{
250+
rl: ResultLabels{
251+
ChangesLabel: "changes",
252+
DestroyLabel: "destroy",
253+
ErrorLabel: "error",
254+
NoChangesLabel: "no-changes",
255+
},
256+
label: "",
257+
want: false,
258+
},
259+
{
260+
rl: ResultLabels{
261+
ChangesLabel: "",
262+
DestroyLabel: "",
263+
ErrorLabel: "",
264+
NoChangesLabel: "no-changes",
265+
},
266+
label: "",
267+
want: false,
268+
},
269+
}
270+
for _, testCase := range testCases {
271+
if testCase.rl.IsResultLabel(testCase.label) != testCase.want {
272+
t.Errorf("got %v but want %v", testCase.rl.IsResultLabel(testCase.label), testCase.want)
273+
}
274+
}
275+
}

notifier/github/github.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type API interface {
1111
IssuesCreateComment(ctx context.Context, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
1212
IssuesDeleteComment(ctx context.Context, commentID int64) (*github.Response, error)
13+
IssuesListLabels(ctx context.Context, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error)
1314
IssuesListComments(ctx context.Context, number int, opt *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)
1415
IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error)
1516
IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error)
@@ -44,7 +45,12 @@ func (g *GitHub) IssuesAddLabels(ctx context.Context, number int, labels []strin
4445
return g.Client.Issues.AddLabelsToIssue(ctx, g.owner, g.repo, number, labels)
4546
}
4647

47-
// IssuesAddLabels is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.RemoveLabelForIssue
48+
// IssuesListLabels is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.ListLabelsByIssue
49+
func (g *GitHub) IssuesListLabels(ctx context.Context, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error) {
50+
return g.Client.Issues.ListLabelsByIssue(ctx, g.owner, g.repo, number, opt)
51+
}
52+
53+
// IssuesRemoveLabel is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.RemoveLabelForIssue
4854
func (g *GitHub) IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error) {
4955
return g.Client.Issues.RemoveLabelForIssue(ctx, g.owner, g.repo, number, label)
5056
}

notifier/github/github_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type fakeAPI struct {
1212
FakeIssuesCreateComment func(ctx context.Context, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
1313
FakeIssuesDeleteComment func(ctx context.Context, commentID int64) (*github.Response, error)
1414
FakeIssuesListComments func(ctx context.Context, number int, opt *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)
15+
FakeIssuesListLabels func(ctx context.Context, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error)
1516
FakeIssuesAddLabels func(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error)
1617
FakeIssuesRemoveLabel func(ctx context.Context, number int, label string) (*github.Response, error)
1718
FakeRepositoriesCreateComment func(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error)
@@ -31,6 +32,10 @@ func (g *fakeAPI) IssuesListComments(ctx context.Context, number int, opt *githu
3132
return g.FakeIssuesListComments(ctx, number, opt)
3233
}
3334

35+
func (g *fakeAPI) IssuesListLabels(ctx context.Context, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error) {
36+
return g.FakeIssuesListLabels(ctx, number, opt)
37+
}
38+
3439
func (g *fakeAPI) IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
3540
return g.FakeIssuesAddLabels(ctx, number, labels)
3641
}
@@ -76,6 +81,20 @@ func newFakeAPI() fakeAPI {
7681
}
7782
return comments, nil, nil
7883
},
84+
FakeIssuesListLabels: func(ctx context.Context, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error) {
85+
var labels []*github.Label
86+
labels = []*github.Label{
87+
&github.Label{
88+
ID: github.Int64(371748792),
89+
Name: github.String("label 1"),
90+
},
91+
&github.Label{
92+
ID: github.Int64(371765743),
93+
Name: github.String("label 2"),
94+
},
95+
}
96+
return labels, nil, nil
97+
},
7998
FakeIssuesAddLabels: func(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
8099
return nil, nil, nil
81100
},

notifier/github/notify.go

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,28 @@ func (g *NotifyService) Notify(body string) (exit int, err error) {
3232
return result.ExitCode, err
3333
}
3434
}
35-
if cfg.PR.IsNumber() && cfg.NoChangesLabel != "" {
36-
// Always attempt to remove the label first so that an IssueLabeled event is created
37-
resp, err := g.client.API.IssuesRemoveLabel(
38-
context.Background(),
39-
cfg.PR.Number,
40-
cfg.NoChangesLabel,
41-
)
42-
// Ignore 404 errors, which are from the PR not having the label
43-
if err != nil && resp.StatusCode != http.StatusNotFound {
35+
if cfg.PR.IsNumber() && cfg.ResultLabels.HasAnyLabelDefined() {
36+
err = g.removeResultLabels()
37+
if err != nil {
4438
return result.ExitCode, err
4539
}
40+
var labelToAdd string
41+
42+
if result.HasChanges {
43+
labelToAdd = cfg.ResultLabels.ChangesLabel
44+
} else if result.HasDestroy {
45+
labelToAdd = cfg.ResultLabels.DestroyLabel
46+
} else if result.HasPlanError {
47+
labelToAdd = cfg.ResultLabels.ErrorLabel
48+
} else if result.HasNoChanges {
49+
labelToAdd = cfg.ResultLabels.NoChangesLabel
50+
}
4651

47-
if result.HasNoChanges {
52+
if labelToAdd != "" {
4853
_, _, err = g.client.API.IssuesAddLabels(
4954
context.Background(),
5055
cfg.PR.Number,
51-
[]string{cfg.NoChangesLabel},
56+
[]string{labelToAdd},
5257
)
5358
if err != nil {
5459
return result.ExitCode, err
@@ -118,3 +123,24 @@ func (g *NotifyService) notifyDestoryWarning(body string, result terraform.Parse
118123
Revision: cfg.PR.Revision,
119124
})
120125
}
126+
127+
func (g *NotifyService) removeResultLabels() error {
128+
cfg := g.client.Config
129+
labels, _, err := g.client.API.IssuesListLabels(context.Background(), cfg.PR.Number, nil)
130+
if err != nil {
131+
return err
132+
}
133+
134+
for _, l := range labels {
135+
labelText := l.GetName()
136+
if cfg.ResultLabels.IsResultLabel(labelText) {
137+
resp, err := g.client.API.IssuesRemoveLabel(context.Background(), cfg.PR.Number, labelText)
138+
// Ignore 404 errors, which are from the PR not having the label
139+
if err != nil && resp.StatusCode != http.StatusNotFound {
140+
return err
141+
}
142+
}
143+
}
144+
145+
return nil
146+
}

notifier/github/notify_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,14 @@ func TestNotifyNotify(t *testing.T) {
136136
Number: 1,
137137
Message: "message",
138138
},
139-
Parser: terraform.NewPlanParser(),
140-
Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate),
141-
NoChangesLabel: "terraform/no-changes",
139+
Parser: terraform.NewPlanParser(),
140+
Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate),
141+
ResultLabels: ResultLabels{
142+
ChangesLabel: "changes",
143+
DestroyLabel: "destroy",
144+
ErrorLabel: "error",
145+
NoChangesLabel: "no-changes",
146+
},
142147
},
143148
body: "No changes. Infrastructure is up-to-date.",
144149
ok: true,

0 commit comments

Comments
 (0)