Skip to content

Commit

Permalink
Merge pull request #74 from mercari/dylan/result-label-set
Browse files Browse the repository at this point in the history
  • Loading branch information
b4b4r07 authored Jun 2, 2020
2 parents 42a3e56 + e64cff0 commit c8c8bae
Show file tree
Hide file tree
Showing 13 changed files with 421 additions and 59 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ terraform:
# ...
```

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.
You can also let tfnotify add a label to PRs depending on the `terraform plan` output result. Currently, this feature is for Github labels only.

```yaml
---
Expand All @@ -174,8 +174,14 @@ terraform:
<pre><code>{{ .Body }}
</pre></code></details>
when_add_or_update_only:
label: "add-or-update"
when_destroy:
label: "destroy"
when_no_changes:
label: "no-changes"
when_plan_error:
label: "error"
# ...
```

Expand Down
21 changes: 17 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,34 @@ type Fmt struct {

// Plan is a terraform plan config
type Plan struct {
Template string `yaml:"template"`
WhenDestroy WhenDestroy `yaml:"when_destroy,omitempty"`
WhenNoChanges WhenNoChanges `yaml:"when_no_changes,omitempty"`
Template string `yaml:"template"`
WhenAddOrUpdateOnly WhenAddOrUpdateOnly `yaml:"when_add_or_update_only,omitempty"`
WhenDestroy WhenDestroy `yaml:"when_destroy,omitempty"`
WhenNoChanges WhenNoChanges `yaml:"when_no_changes,omitempty"`
WhenPlanError WhenPlanError `yaml:"when_plan_error,omitempty"`
}

// WhenAddOrUpdateOnly is a configuration to notify the plan result contains new or updated in place resources
type WhenAddOrUpdateOnly struct {
Label string `yaml:"label,omitempty"`
}

// WhenDestroy is a configuration to notify the plan result contains destroy operation
type WhenDestroy struct {
Template string `yaml:"template"`
Label string `yaml:"label,omitempty"`
Template string `yaml:"template,omitempty"`
}

// WhenNoChange is a configuration to add a label when the plan result contains no change
type WhenNoChanges struct {
Label string `yaml:"label,omitempty"`
}

// WhenPlanError is a configuration to notify the plan result returns an error
type WhenPlanError struct {
Label string `yaml:"label,omitempty"`
}

// Apply is a terraform apply config
type Apply struct {
Template string `yaml:"template"`
Expand Down
11 changes: 9 additions & 2 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestLoadFile(t *testing.T) {
ok: true,
},
{
file: "../example-with-destroy-and-no-changes.tfnotify.yaml",
file: "../example-with-destroy-and-result-labels.tfnotify.yaml",
cfg: Config{
CI: "circleci",
Notifier: Notifier{
Expand Down Expand Up @@ -93,9 +93,16 @@ func TestLoadFile(t *testing.T) {
},
Plan: Plan{
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",
WhenAddOrUpdateOnly: WhenAddOrUpdateOnly{
Label: "add-or-update",
},
WhenDestroy: WhenDestroy{
Label: "destroy",
Template: "## :warning: WARNING: Resource Deletion will happen :warning:\n\nThis plan contains **resource deletion**. Please check the plan result very carefully!\n",
},
WhenPlanError: WhenPlanError{
Label: "error",
},
WhenNoChanges: WhenNoChanges{
Label: "no-changes",
},
Expand All @@ -105,7 +112,7 @@ func TestLoadFile(t *testing.T) {
},
UseRawOutput: false,
},
path: "../example-with-destroy-and-no-changes.tfnotify.yaml",
path: "../example-with-destroy-and-result-labels.tfnotify.yaml",
},
ok: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ terraform:
<pre><code>{{ .Body }}
</pre></code></details>
when_no_changes:
label: "no-changes"
when_add_or_update_only:
label: "add-or-update"
when_destroy:
label: "destroy"
template: |
## :warning: WARNING: Resource Deletion will happen :warning:
This plan contains **resource deletion**. Please check the plan result very carefully!
when_no_changes:
label: "no-changes"
when_plan_error:
label: "error"
7 changes: 6 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ func (t *tfnotify) Run() error {
Template: t.template,
DestroyWarningTemplate: t.destroyWarningTemplate,
WarnDestroy: t.warnDestroy,
NoChangesLabel: t.config.Terraform.Plan.WhenNoChanges.Label,
ResultLabels: github.ResultLabels{
AddOrUpdateLabel: t.config.Terraform.Plan.WhenAddOrUpdateOnly.Label,
DestroyLabel: t.config.Terraform.Plan.WhenDestroy.Label,
NoChangesLabel: t.config.Terraform.Plan.WhenNoChanges.Label,
PlanErrorLabel: t.config.Terraform.Plan.WhenPlanError.Label,
},
})
if err != nil {
return err
Expand Down
29 changes: 27 additions & 2 deletions notifier/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ type Config struct {
// DestroyWarningTemplate is used only for additional warning
// the plan result contains destroy operation
DestroyWarningTemplate terraform.Template
// NoChangesLabel is a label to add to PRs when terraform output contains no changes
NoChangesLabel string
// ResultLabels is a set of labels to apply depending on the plan result
ResultLabels ResultLabels
}

// PullRequest represents GitHub Pull Request metadata
Expand Down Expand Up @@ -117,3 +117,28 @@ func NewClient(cfg Config) (*Client, error) {
func (pr *PullRequest) IsNumber() bool {
return pr.Number != 0
}

// ResultLabels represents the labels to add to the PR depending on the plan result
type ResultLabels struct {
AddOrUpdateLabel string
DestroyLabel string
NoChangesLabel string
PlanErrorLabel string
}

// HasAnyLabelDefined returns true if any of the internal labels are set
func (r *ResultLabels) HasAnyLabelDefined() bool {
return r.AddOrUpdateLabel != "" || r.DestroyLabel != "" || r.NoChangesLabel != "" || r.PlanErrorLabel != ""
}

// IsResultLabel returns true if a label matches any of the internal labels
func (r *ResultLabels) IsResultLabel(label string) bool {
switch label {
case "":
return false
case r.AddOrUpdateLabel, r.DestroyLabel, r.NoChangesLabel, r.PlanErrorLabel:
return true
default:
return false
}
}
103 changes: 103 additions & 0 deletions notifier/github/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,106 @@ func TestIsNumber(t *testing.T) {
}
}
}

func TestHasAnyLabelDefined(t *testing.T) {
testCases := []struct {
rl ResultLabels
want bool
}{
{
rl: ResultLabels{
AddOrUpdateLabel: "add-or-update",
DestroyLabel: "destroy",
NoChangesLabel: "no-changes",
PlanErrorLabel: "error",
},
want: true,
},
{
rl: ResultLabels{
AddOrUpdateLabel: "add-or-update",
DestroyLabel: "destroy",
NoChangesLabel: "",
PlanErrorLabel: "error",
},
want: true,
},
{
rl: ResultLabels{
AddOrUpdateLabel: "",
DestroyLabel: "",
NoChangesLabel: "",
PlanErrorLabel: "",
},
want: false,
},
{
rl: ResultLabels{},
want: false,
},
}
for _, testCase := range testCases {
if testCase.rl.HasAnyLabelDefined() != testCase.want {
t.Errorf("got %v but want %v", testCase.rl.HasAnyLabelDefined(), testCase.want)
}
}
}

func TestIsResultLabels(t *testing.T) {
testCases := []struct {
rl ResultLabels
label string
want bool
}{
{
rl: ResultLabels{
AddOrUpdateLabel: "add-or-update",
DestroyLabel: "destroy",
NoChangesLabel: "no-changes",
PlanErrorLabel: "error",
},
label: "add-or-update",
want: true,
},
{
rl: ResultLabels{
AddOrUpdateLabel: "add-or-update",
DestroyLabel: "destroy",
NoChangesLabel: "no-changes",
PlanErrorLabel: "error",
},
label: "my-label",
want: false,
},
{
rl: ResultLabels{
AddOrUpdateLabel: "add-or-update",
DestroyLabel: "destroy",
NoChangesLabel: "no-changes",
PlanErrorLabel: "error",
},
label: "",
want: false,
},
{
rl: ResultLabels{
AddOrUpdateLabel: "",
DestroyLabel: "",
NoChangesLabel: "no-changes",
PlanErrorLabel: "",
},
label: "",
want: false,
},
{
rl: ResultLabels{},
label: "",
want: false,
},
}
for _, testCase := range testCases {
if testCase.rl.IsResultLabel(testCase.label) != testCase.want {
t.Errorf("got %v but want %v", testCase.rl.IsResultLabel(testCase.label), testCase.want)
}
}
}
8 changes: 7 additions & 1 deletion notifier/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type API interface {
IssuesCreateComment(ctx context.Context, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
IssuesDeleteComment(ctx context.Context, commentID int64) (*github.Response, error)
IssuesListLabels(ctx context.Context, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error)
IssuesListComments(ctx context.Context, number int, opt *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)
IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error)
IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error)
Expand Down Expand Up @@ -44,7 +45,12 @@ func (g *GitHub) IssuesAddLabels(ctx context.Context, number int, labels []strin
return g.Client.Issues.AddLabelsToIssue(ctx, g.owner, g.repo, number, labels)
}

// IssuesAddLabels is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.RemoveLabelForIssue
// IssuesListLabels is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.ListLabelsByIssue
func (g *GitHub) IssuesListLabels(ctx context.Context, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error) {
return g.Client.Issues.ListLabelsByIssue(ctx, g.owner, g.repo, number, opt)
}

// IssuesRemoveLabel is a wrapper of https://godoc.org/github.com/google/go-github/github#IssuesService.RemoveLabelForIssue
func (g *GitHub) IssuesRemoveLabel(ctx context.Context, number int, label string) (*github.Response, error) {
return g.Client.Issues.RemoveLabelForIssue(ctx, g.owner, g.repo, number, label)
}
Expand Down
18 changes: 18 additions & 0 deletions notifier/github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type fakeAPI struct {
FakeIssuesCreateComment func(ctx context.Context, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error)
FakeIssuesDeleteComment func(ctx context.Context, commentID int64) (*github.Response, error)
FakeIssuesListComments func(ctx context.Context, number int, opt *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)
FakeIssuesListLabels func(ctx context.Context, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error)
FakeIssuesAddLabels func(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error)
FakeIssuesRemoveLabel func(ctx context.Context, number int, label string) (*github.Response, error)
FakeRepositoriesCreateComment func(ctx context.Context, sha string, comment *github.RepositoryComment) (*github.RepositoryComment, *github.Response, error)
Expand All @@ -31,6 +32,10 @@ func (g *fakeAPI) IssuesListComments(ctx context.Context, number int, opt *githu
return g.FakeIssuesListComments(ctx, number, opt)
}

func (g *fakeAPI) IssuesListLabels(ctx context.Context, number int, opt *github.ListOptions) ([]*github.Label, *github.Response, error) {
return g.FakeIssuesListLabels(ctx, number, opt)
}

func (g *fakeAPI) IssuesAddLabels(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
return g.FakeIssuesAddLabels(ctx, number, labels)
}
Expand Down Expand Up @@ -76,6 +81,19 @@ func newFakeAPI() fakeAPI {
}
return comments, nil, nil
},
FakeIssuesListLabels: func(ctx context.Context, number int, opts *github.ListOptions) ([]*github.Label, *github.Response, error) {
labels := []*github.Label{
&github.Label{
ID: github.Int64(371748792),
Name: github.String("label 1"),
},
&github.Label{
ID: github.Int64(371765743),
Name: github.String("label 2"),
},
}
return labels, nil, nil
},
FakeIssuesAddLabels: func(ctx context.Context, number int, labels []string) ([]*github.Label, *github.Response, error) {
return nil, nil, nil
},
Expand Down
Loading

0 comments on commit c8c8bae

Please sign in to comment.