Skip to content

Commit 42a3e56

Browse files
authored
Merge pull request #72 from mercari/dylan/no-change-label
2 parents 7a2ba7c + 3c91c66 commit 42a3e56

File tree

12 files changed

+152
-32
lines changed

12 files changed

+152
-32
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,30 @@ terraform:
155155
# ...
156156
```
157157

158+
You can also let tfnotify add a label to PRs whose `terraform plan` output result in no change to the current infrastructure. Currently, this feature is for Github labels only.
159+
160+
```yaml
161+
---
162+
# ...
163+
terraform:
164+
# ...
165+
plan:
166+
template: |
167+
{{ .Title }} <sup>[CI link]( {{ .Link }} )</sup>
168+
{{ .Message }}
169+
{{if .Result}}
170+
<pre><code>{{ .Result }}
171+
</pre></code>
172+
{{end}}
173+
<details><summary>Details (Click me)</summary>
174+
175+
<pre><code>{{ .Body }}
176+
</pre></code></details>
177+
when_no_changes:
178+
label: "no-changes"
179+
# ...
180+
```
181+
158182
Sometimes you may want not to HTML-escape Terraform command outputs.
159183
For example, when you use code block to print command output, it's better to use raw characters instead of character references (e.g. `-/+` -> `-/&#43;`, `"` -> `&#34;`).
160184

config/config.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,21 @@ type Fmt struct {
8181

8282
// Plan is a terraform plan config
8383
type Plan struct {
84-
Template string `yaml:"template"`
85-
WhenDestroy WhenDestroy `yaml:"when_destroy,omitempty"`
84+
Template string `yaml:"template"`
85+
WhenDestroy WhenDestroy `yaml:"when_destroy,omitempty"`
86+
WhenNoChanges WhenNoChanges `yaml:"when_no_changes,omitempty"`
8687
}
8788

8889
// WhenDestroy is a configuration to notify the plan result contains destroy operation
8990
type WhenDestroy struct {
9091
Template string `yaml:"template"`
9192
}
9293

94+
// WhenNoChange is a configuration to add a label when the plan result contains no change
95+
type WhenNoChanges struct {
96+
Label string `yaml:"label,omitempty"`
97+
}
98+
9399
// Apply is a terraform apply config
94100
type Apply struct {
95101
Template string `yaml:"template"`

config/config_test.go

Lines changed: 5 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.tfnotify.yaml",
66+
file: "../example-with-destroy-and-no-changes.tfnotify.yaml",
6767
cfg: Config{
6868
CI: "circleci",
6969
Notifier: Notifier{
@@ -96,13 +96,16 @@ func TestLoadFile(t *testing.T) {
9696
WhenDestroy: WhenDestroy{
9797
Template: "## :warning: WARNING: Resource Deletion will happen :warning:\n\nThis plan contains **resource deletion**. Please check the plan result very carefully!\n",
9898
},
99+
WhenNoChanges: WhenNoChanges{
100+
Label: "no-changes",
101+
},
99102
},
100103
Apply: Apply{
101104
Template: "",
102105
},
103106
UseRawOutput: false,
104107
},
105-
path: "../example-with-destroy.tfnotify.yaml",
108+
path: "../example-with-destroy-and-no-changes.tfnotify.yaml",
106109
},
107110
ok: true,
108111
},

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ terraform:
1818
1919
<pre><code>{{ .Body }}
2020
</pre></code></details>
21+
when_no_changes:
22+
label: "no-changes"
2123
when_destroy:
2224
template: |
2325
## :warning: WARNING: Resource Deletion will happen :warning:

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ 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,
118119
})
119120
if err != nil {
120121
return err

notifier/github/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +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
5153
}
5254

5355
// PullRequest represents GitHub Pull Request metadata

notifier/github/github.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ 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)
1313
IssuesListComments(ctx context.Context, number int, opt *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)
14+
IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error)
15+
IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error)
1416
RepositoriesCreateComment(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error)
1517
RepositoriesListCommits(ctx context.Context, opt *github.CommitsListOptions) ([]*github.RepositoryCommit, *github.Response, error)
1618
RepositoriesGetCommit(ctx context.Context, sha string) (*github.RepositoryCommit, *github.Response, error)
@@ -37,6 +39,16 @@ func (g *GitHub) IssuesListComments(ctx context.Context, number int, opt *github
3739
return g.Client.Issues.ListComments(ctx, g.owner, g.repo, number, opt)
3840
}
3941

42+
// IssuesAddLabels is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.AddLabelsToIssue
43+
func (g *GitHub) IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
44+
return g.Client.Issues.AddLabelsToIssue(ctx, g.owner, g.repo, number, labels)
45+
}
46+
47+
// IssuesAddLabels is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.RemoveLabelForIssue
48+
func (g *GitHub) IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error) {
49+
return g.Client.Issues.RemoveLabelForIssue(ctx, g.owner, g.repo, number, label)
50+
}
51+
4052
// RepositoriesCreateComment is a wrapper of https://godoc.org/github.com/google/go-github/github#RepositoriesService.CreateComment
4153
func (g *GitHub) RepositoriesCreateComment(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error) {
4254
return g.Client.Repositories.CreateComment(ctx, g.owner, g.repo, sha, comment)

notifier/github/github_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ 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+
FakeIssuesAddLabels func(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error)
16+
FakeIssuesRemoveLabel func(ctx context.Context, number int, label string) (*github.Response, error)
1517
FakeRepositoriesCreateComment func(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error)
1618
FakeRepositoriesListCommits func(ctx context.Context, opt *github.CommitsListOptions) ([]*github.RepositoryCommit, *github.Response, error)
1719
FakeRepositoriesGetCommit func(ctx context.Context, sha string) (*github.RepositoryCommit, *github.Response, error)
@@ -29,6 +31,14 @@ func (g *fakeAPI) IssuesListComments(ctx context.Context, number int, opt *githu
2931
return g.FakeIssuesListComments(ctx, number, opt)
3032
}
3133

34+
func (g *fakeAPI) IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
35+
return g.FakeIssuesAddLabels(ctx, number, labels)
36+
}
37+
38+
func (g *fakeAPI) IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error) {
39+
return g.FakeIssuesRemoveLabel(ctx, number, label)
40+
}
41+
3242
func (g *fakeAPI) RepositoriesCreateComment(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error) {
3343
return g.FakeRepositoriesCreateComment(ctx, sha, comment)
3444
}
@@ -66,6 +76,12 @@ func newFakeAPI() fakeAPI {
6676
}
6777
return comments, nil, nil
6878
},
79+
FakeIssuesAddLabels: func(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
80+
return nil, nil, nil
81+
},
82+
FakeIssuesRemoveLabel: func(ctx context.Context, number int, label string) (*github.Response, error) {
83+
return nil, nil
84+
},
6985
FakeRepositoriesCreateComment: func(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error) {
7086
return &github.RepositoryComment{
7187
ID: github.Int64(28427394),

notifier/github/notify.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package github
22

33
import (
4+
"context"
45
"github.com/mercari/tfnotify/terraform"
6+
"net/http"
57
)
68

79
// NotifyService handles communication with the notification related
@@ -30,6 +32,29 @@ func (g *NotifyService) Notify(body string) (exit int, err error) {
3032
return result.ExitCode, err
3133
}
3234
}
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 {
44+
return result.ExitCode, err
45+
}
46+
47+
if result.HasNoChanges {
48+
_, _, err = g.client.API.IssuesAddLabels(
49+
context.Background(),
50+
cfg.PR.Number,
51+
[]string{cfg.NoChangesLabel},
52+
)
53+
if err != nil {
54+
return result.ExitCode, err
55+
}
56+
}
57+
}
3358
}
3459

3560
template.SetValue(terraform.CommonTemplate{

notifier/github/notify_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,26 @@ func TestNotifyNotify(t *testing.T) {
124124
ok: true,
125125
exitCode: 0,
126126
},
127+
{
128+
// valid with no changes
129+
// TODO(drlau): check that the label was actually added
130+
config: Config{
131+
Token: "token",
132+
Owner: "owner",
133+
Repo: "repo",
134+
PR: PullRequest{
135+
Revision: "",
136+
Number: 1,
137+
Message: "message",
138+
},
139+
Parser: terraform.NewPlanParser(),
140+
Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate),
141+
NoChangesLabel: "terraform/no-changes",
142+
},
143+
body: "No changes. Infrastructure is up-to-date.",
144+
ok: true,
145+
exitCode: 0,
146+
},
127147
{
128148
// valid, contains destroy, but not to notify
129149
config: Config{

0 commit comments

Comments
 (0)