From 0681fd21a65dfe493816ef236e89be8947589cb1 Mon Sep 17 00:00:00 2001 From: Ryo Okubo Date: Tue, 12 Jun 2018 00:33:11 +0900 Subject: [PATCH] Support conditional notification based on matching parse exitcode --- README.md | 4 ++++ config/config.go | 18 +++++++++++++++--- config/config_test.go | 34 ++++++++++++++++++++++++++++++++++ error.go | 7 +++++++ error_test.go | 6 ++++++ main.go | 2 ++ notifier/github/client.go | 2 ++ notifier/github/notify.go | 6 ++++++ notifier/github/notify_test.go | 28 ++++++++++++++++++++++++++++ notifier/notifier.go | 8 ++++++++ notifier/slack/client.go | 4 +++- notifier/slack/notify.go | 8 +++++++- notifier/slack/notify_test.go | 21 ++++++++++++++++++++- 13 files changed, 142 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3554cf4..e9f5717 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ notifier: repository: owner: "mercari" name: "tfnotify" + filters: + parse_exit_code: 1 terraform: fmt: template: | @@ -133,6 +135,8 @@ notifier: token: $SLACK_TOKEN channel: $SLACK_CHANNEL_ID bot: $SLACK_BOT_NAME + filters: + parse_exit_code: 1 terraform: plan: template: | diff --git a/config/config.go b/config/config.go index 951ed76..0a497fa 100644 --- a/config/config.go +++ b/config/config.go @@ -29,6 +29,7 @@ type Notifier struct { type GithubNotifier struct { Token string `yaml:"token"` Repository Repository `yaml:"repository"` + Filters *Filters `yaml:"filters"` } // Repository represents a GitHub repository @@ -39,9 +40,15 @@ type Repository struct { // SlackNotifier is a notifier for Slack type SlackNotifier struct { - Token string `yaml:"token"` - Channel string `yaml:"channel"` - Bot string `yaml:"bot"` + Token string `yaml:"token"` + Channel string `yaml:"channel"` + Bot string `yaml:"bot"` + Filters *Filters `yaml:"filters"` +} + +// Filters is conditions for notification +type Filters struct { + ParseExitCode int `yaml:"parse_exit_code"` } // Terraform represents terraform configurations @@ -157,3 +164,8 @@ func (cfg *Config) Find(file string) (string, error) { } return "", errors.New("config for tfnotify is not found at all") } + +// Match returns terraform result matches conditions or not +func (filters *Filters) Match(exitCode int) bool { + return filters == nil || exitCode == filters.ParseExitCode +} diff --git a/config/config_test.go b/config/config_test.go index 803150c..1447d44 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -317,3 +317,37 @@ func TestFind(t *testing.T) { } } } + +func TestFiltersMatch(t *testing.T) { + testCases := []struct { + filters *Filters + exitCode int + expect bool + }{ + { + nil, + 1, + true, + }, + { + &Filters{ + ParseExitCode: 1, + }, + 1, + true, + }, + { + &Filters{ + ParseExitCode: 1, + }, + 0, + false, + }, + } + for _, testCase := range testCases { + actual := testCase.filters.Match(testCase.exitCode) + if actual != testCase.expect { + t.Errorf("got %t but want %t", actual, testCase.expect) + } + } +} diff --git a/error.go b/error.go index ab342f4..5ac1b8e 100644 --- a/error.go +++ b/error.go @@ -3,6 +3,8 @@ package main import ( "fmt" "os" + + "github.com/mercari/tfnotify/notifier" ) // Exit codes are int values for the exit code that shell interpreter can interpret @@ -57,6 +59,11 @@ func HandleExit(err error) int { return ExitCodeOK } + // Ignore nop + if err == notifier.ErrNop { + return ExitCodeOK + } + if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { if _, ok := exitErr.(ErrorFormatter); ok { diff --git a/error_test.go b/error_test.go index 67e28aa..2d687d7 100644 --- a/error_test.go +++ b/error_test.go @@ -3,6 +3,8 @@ package main import ( "errors" "testing" + + "github.com/mercari/tfnotify/notifier" ) func TestHandleError(t *testing.T) { @@ -34,6 +36,10 @@ func TestHandleError(t *testing.T) { err: nil, exitCode: 0, }, + { + err: notifier.ErrNop, + exitCode: 0, + }, } for _, testCase := range testCases { diff --git a/main.go b/main.go index e13c3e5..a85c31f 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,7 @@ func (t *tfnotify) Run() error { CI: ci.URL, Parser: t.parser, Template: t.template, + Filters: t.config.Notifier.Github.Filters, }) if err != nil { return err @@ -87,6 +88,7 @@ func (t *tfnotify) Run() error { CI: ci.URL, Parser: t.parser, Template: t.template, + Filters: t.config.Notifier.Slack.Filters, }) if err != nil { return err diff --git a/notifier/github/client.go b/notifier/github/client.go index d36ecde..26f9f67 100644 --- a/notifier/github/client.go +++ b/notifier/github/client.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/google/go-github/github" + "github.com/mercari/tfnotify/config" "github.com/mercari/tfnotify/terraform" "golang.org/x/oauth2" ) @@ -38,6 +39,7 @@ type Config struct { CI string Parser terraform.Parser Template terraform.Template + Filters *config.Filters } // PullRequest represents GitHub Pull Request metadata diff --git a/notifier/github/notify.go b/notifier/github/notify.go index ddaba77..679db58 100644 --- a/notifier/github/notify.go +++ b/notifier/github/notify.go @@ -1,6 +1,7 @@ package github import ( + "github.com/mercari/tfnotify/notifier" "github.com/mercari/tfnotify/terraform" ) @@ -13,6 +14,7 @@ func (g *NotifyService) Notify(body string) (exit int, err error) { cfg := g.client.Config parser := g.client.Config.Parser template := g.client.Config.Template + filters := g.client.Config.Filters result := parser.Parse(body) if result.Error != nil { @@ -22,6 +24,10 @@ func (g *NotifyService) Notify(body string) (exit int, err error) { return result.ExitCode, result.Error } + if !filters.Match(result.ExitCode) { + return result.ExitCode, notifier.ErrNop + } + template.SetValue(terraform.CommonTemplate{ Message: cfg.PR.Message, Result: result.Result, diff --git a/notifier/github/notify_test.go b/notifier/github/notify_test.go index 7f7bb32..600d888 100644 --- a/notifier/github/notify_test.go +++ b/notifier/github/notify_test.go @@ -3,6 +3,7 @@ package github import ( "testing" + "github.com/mercari/tfnotify/config" "github.com/mercari/tfnotify/terraform" ) @@ -26,6 +27,7 @@ func TestNotifyNotify(t *testing.T) { }, Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "body", ok: false, @@ -44,6 +46,7 @@ func TestNotifyNotify(t *testing.T) { }, Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "Plan: 1 to add", ok: false, @@ -62,6 +65,7 @@ func TestNotifyNotify(t *testing.T) { }, Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "Error: hoge", ok: true, @@ -80,6 +84,7 @@ func TestNotifyNotify(t *testing.T) { }, Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "Plan: 1 to add", ok: true, @@ -98,11 +103,33 @@ func TestNotifyNotify(t *testing.T) { }, Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "Plan: 1 to add", ok: true, exitCode: 0, }, + { + // valid, filter mismatch + config: Config{ + Token: "token", + Owner: "owner", + Repo: "repo", + PR: PullRequest{ + Revision: "", + Number: 1, + Message: "message", + }, + Parser: terraform.NewPlanParser(), + Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: &config.Filters{ + ParseExitCode: 1, + }, + }, + body: "Plan: 1 to add", // ParseExitCode is 0 + ok: false, // nop + exitCode: 0, + }, { // apply case config: Config{ @@ -116,6 +143,7 @@ func TestNotifyNotify(t *testing.T) { }, Parser: terraform.NewApplyParser(), Template: terraform.NewApplyTemplate(terraform.DefaultApplyTemplate), + Filters: nil, }, body: "Apply complete!", ok: true, diff --git a/notifier/notifier.go b/notifier/notifier.go index f6488ea..9a02e0d 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -1,5 +1,13 @@ package notifier +import ( + "errors" +) + +var ( + ErrNop = errors.New("notification wasn't operated") +) + // Notifier is a notification interface type Notifier interface { Notify(body string) (exit int, err error) diff --git a/notifier/slack/client.go b/notifier/slack/client.go index 6736feb..d2ebbba 100644 --- a/notifier/slack/client.go +++ b/notifier/slack/client.go @@ -5,8 +5,9 @@ import ( "os" "strings" - "github.com/mercari/tfnotify/terraform" "github.com/lestrrat-go/slack" + "github.com/mercari/tfnotify/config" + "github.com/mercari/tfnotify/terraform" ) // EnvToken is Slack API Token @@ -34,6 +35,7 @@ type Config struct { CI string Parser terraform.Parser Template terraform.Template + Filters *config.Filters } type service struct { diff --git a/notifier/slack/notify.go b/notifier/slack/notify.go index 5076b75..b388529 100644 --- a/notifier/slack/notify.go +++ b/notifier/slack/notify.go @@ -4,8 +4,9 @@ import ( "context" "errors" - "github.com/mercari/tfnotify/terraform" "github.com/lestrrat-go/slack/objects" + "github.com/mercari/tfnotify/notifier" + "github.com/mercari/tfnotify/terraform" ) // NotifyService handles communication with the notification related @@ -17,6 +18,7 @@ func (s *NotifyService) Notify(body string) (exit int, err error) { cfg := s.client.Config parser := s.client.Config.Parser template := s.client.Config.Template + filters := s.client.Config.Filters if cfg.Channel == "" { return terraform.ExitFail, errors.New("channel id is required") @@ -30,6 +32,10 @@ func (s *NotifyService) Notify(body string) (exit int, err error) { return result.ExitCode, result.Error } + if !filters.Match(result.ExitCode) { + return result.ExitCode, notifier.ErrNop + } + color := "warning" switch result.ExitCode { case terraform.ExitPass: diff --git a/notifier/slack/notify_test.go b/notifier/slack/notify_test.go index 1c49cc0..c850732 100644 --- a/notifier/slack/notify_test.go +++ b/notifier/slack/notify_test.go @@ -4,8 +4,9 @@ import ( "context" "testing" - "github.com/mercari/tfnotify/terraform" "github.com/lestrrat-go/slack/objects" + "github.com/mercari/tfnotify/config" + "github.com/mercari/tfnotify/terraform" ) func TestNotify(t *testing.T) { @@ -23,6 +24,7 @@ func TestNotify(t *testing.T) { Message: "", Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "Plan: 1 to add", exitCode: 0, @@ -36,11 +38,28 @@ func TestNotify(t *testing.T) { Message: "", Parser: terraform.NewPlanParser(), Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: nil, }, body: "Plan: 1 to add", exitCode: 1, ok: false, }, + { + config: Config{ + Token: "token", + Channel: "channel", + Botname: "botname", + Message: "", + Parser: terraform.NewPlanParser(), + Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + Filters: &config.Filters{ + ParseExitCode: 1, + }, + }, + body: "Plan: 1 to add", // ParseExitCode is 0 + exitCode: 0, + ok: false, // nop + }, } fake := fakeAPI{ FakeChatPostMessage: func(ctx context.Context, attachments []*objects.Attachment) (*objects.ChatResponse, error) {