From c196f66cf22caa8288980d8acdc906b5b3769dcd Mon Sep 17 00:00:00 2001 From: Issei Horie Date: Wed, 9 Jan 2019 01:07:43 +0900 Subject: [PATCH 1/3] Support Typetalk --- .codecov.yml | 1 + Gopkg.lock | 14 ++++++ README.md | 30 +++++++++++- config/config.go | 24 +++++++++- config/config_test.go | 39 +++++++++++++++ main.go | 15 ++++++ misc/images/3.png | Bin 0 -> 62929 bytes notifier/typetalk/client.go | 72 ++++++++++++++++++++++++++++ notifier/typetalk/client_test.go | 73 +++++++++++++++++++++++++++++ notifier/typetalk/notify.go | 44 +++++++++++++++++ notifier/typetalk/notify_test.go | 64 +++++++++++++++++++++++++ notifier/typetalk/typetalk.go | 24 ++++++++++ notifier/typetalk/typetalk_test.go | 17 +++++++ 13 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 misc/images/3.png create mode 100644 notifier/typetalk/client.go create mode 100644 notifier/typetalk/client_test.go create mode 100644 notifier/typetalk/notify.go create mode 100644 notifier/typetalk/notify_test.go create mode 100644 notifier/typetalk/typetalk.go create mode 100644 notifier/typetalk/typetalk_test.go diff --git a/.codecov.yml b/.codecov.yml index b52a5da..0d496e0 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -7,3 +7,4 @@ ignore: - "main.go" - "notifier/github/github.go" - "notifier/slack/slack.go" + - "notifier/typetalk/typetalk.go" diff --git a/Gopkg.lock b/Gopkg.lock index 1b23eca..8098e47 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -69,6 +69,18 @@ revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" version = "v0.0.4" +[[projects]] + digest = "1:f2506d323836b8fa307d3cdcadd5475015367b95f465a002a8a0a7d77b505b8b" + name = "github.com/nulab/go-typetalk" + packages = [ + "typetalk/internal", + "typetalk/shared", + "typetalk/v1", + ] + pruneopts = "NUT" + revision = "a2c8b0c4afa7b874f4d911a1e9199999bc15b2f4" + version = "v2.1.0" + [[projects]] digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" name = "github.com/pkg/errors" @@ -147,6 +159,8 @@ "github.com/lestrrat-go/slack", "github.com/lestrrat-go/slack/objects", "github.com/mattn/go-colorable", + "github.com/nulab/go-typetalk/typetalk/shared", + "github.com/nulab/go-typetalk/typetalk/v1", "github.com/urfave/cli", "golang.org/x/oauth2", "gopkg.in/yaml.v2", diff --git a/README.md b/README.md index 8c4d6f7..6bac63c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ You can do this by using this command. + + ## Installation Grab the binary from GitHub Releases (Recommended) @@ -60,7 +62,7 @@ For `plan` command, you also need to specify `plan` as the argument of tfnotify. When running tfnotify, you can specify the configuration path via `--config` option (if it's omitted, it defaults to `{.,}tfnotify.y{,a}ml`). -The example settings of GitHub, GitHub Enterprise and Slack are as follows. Incidentally, there is no need to replace TOKEN string such as `$GITHUB_TOKEN` with the actual token. Instead, it must be defined as environment variables in CI settings. +The example settings of GitHub and GitHub Enterprise, Slack, Typetalk are as follows. Incidentally, there is no need to replace TOKEN string such as `$GITHUB_TOKEN` with the actual token. Instead, it must be defined as environment variables in CI settings. [template](https://golang.org/pkg/text/template/) of Go can be used for `template`. The templates can be used in `tfnotify.yaml` are as follows: @@ -198,6 +200,32 @@ terraform: +
+For Typetalk + +```yaml +--- +ci: circleci +notifier: + typetalk: + token: $TYPETALK_TOKEN + topic_id: $TYPETALK_TOPIC_ID +terraform: + plan: + template: | + {{ .Message }} + {{if .Result}} + ``` + {{ .Result }} + ``` + {{end}} + ``` + {{ .Body }} + ``` +``` + +
+ ### Supported CI Currently, supported CI are here: diff --git a/config/config.go b/config/config.go index 11a257a..419a4b1 100644 --- a/config/config.go +++ b/config/config.go @@ -21,8 +21,9 @@ type Config struct { // Notifier is a notification notifier type Notifier struct { - Github GithubNotifier `yaml:"github"` - Slack SlackNotifier `yaml:"slack"` + Github GithubNotifier `yaml:"github"` + Slack SlackNotifier `yaml:"slack"` + Typetalk TypetalkNotifier `yaml:"typetalk"` } // GithubNotifier is a notifier for GitHub @@ -45,6 +46,12 @@ type SlackNotifier struct { Bot string `yaml:"bot"` } +// TypetalkNotifier is a notifier for Typetalk +type TypetalkNotifier struct { + Token string `yaml:"token"` + TopicID string `yaml:"topic_id"` +} + // Terraform represents terraform configurations type Terraform struct { Default Default `yaml:"default"` @@ -111,6 +118,11 @@ func (cfg *Config) Validation() error { return fmt.Errorf("slack channel id is missing") } } + if cfg.isDefinedTypetalk() { + if cfg.Notifier.Typetalk.TopicID == "" { + return fmt.Errorf("Typetalk topic id is missing") + } + } notifier := cfg.GetNotifierType() if notifier == "" { return fmt.Errorf("notifier is missing") @@ -128,6 +140,11 @@ func (cfg *Config) isDefinedSlack() bool { return cfg.Notifier.Slack != (SlackNotifier{}) } +func (cfg *Config) isDefinedTypetalk() bool { + // not empty + return cfg.Notifier.Typetalk != (TypetalkNotifier{}) +} + // GetNotifierType return notifier type described in Config func (cfg *Config) GetNotifierType() string { if cfg.isDefinedGithub() { @@ -136,6 +153,9 @@ func (cfg *Config) GetNotifierType() string { if cfg.isDefinedSlack() { return "slack" } + if cfg.isDefinedTypetalk() { + return "typetalk" + } return "" } diff --git a/config/config_test.go b/config/config_test.go index 7d73d39..fcd29b7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -37,6 +37,10 @@ func TestLoadFile(t *testing.T) { Channel: "", Bot: "", }, + Typetalk: TypetalkNotifier{ + Token: "", + TopicID: "", + }, }, Terraform: Terraform{ Default: Default{ @@ -73,6 +77,10 @@ func TestLoadFile(t *testing.T) { Channel: "", Bot: "", }, + Typetalk: TypetalkNotifier{ + Token: "", + TopicID: "", + }, }, Terraform: Terraform{ Default: Default{ @@ -186,6 +194,33 @@ notifier: slack: token: token channel: channel +`), + expected: "", + }, + { + contents: []byte(` +ci: circleci +notifier: + typetalk: +`), + expected: "notifier is missing", + }, + { + contents: []byte(` +ci: circleci +notifier: + typetalk: + token: token +`), + expected: "Typetalk topic id is missing", + }, + { + contents: []byte(` +ci: circleci +notifier: + typetalk: + token: token + topic_id: 12345 `), expected: "", }, @@ -221,6 +256,10 @@ func TestGetNotifierType(t *testing.T) { contents: []byte("repository:\n owner: a\n name: b\nci: circleci\nnotifier:\n slack:\n token: token\n"), expected: "slack", }, + { + contents: []byte("repository:\n owner: a\n name: b\nci: circleci\nnotifier:\n typetalk:\n token: token\n"), + expected: "typetalk", + }, } for _, testCase := range testCases { cfg, err := helperLoadConfig(testCase.contents) diff --git a/main.go b/main.go index f563198..4b2fca7 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "github.com/mercari/tfnotify/notifier" "github.com/mercari/tfnotify/notifier/github" "github.com/mercari/tfnotify/notifier/slack" + "github.com/mercari/tfnotify/notifier/typetalk" "github.com/mercari/tfnotify/terraform" "github.com/urfave/cli" @@ -100,6 +101,20 @@ func (t *tfnotify) Run() error { return err } notifier = client.Notify + case "typetalk": + client, err := typetalk.NewClient(typetalk.Config{ + Token: t.config.Notifier.Typetalk.Token, + TopicID: t.config.Notifier.Typetalk.TopicID, + Title: t.context.String("title"), + Message: t.context.String("message"), + CI: ci.URL, + Parser: t.parser, + Template: t.template, + }) + if err != nil { + return err + } + notifier = client.Notify case "": return fmt.Errorf("notifier is missing") default: diff --git a/misc/images/3.png b/misc/images/3.png new file mode 100644 index 0000000000000000000000000000000000000000..f16ce9a599da1d8baca30b00059e1ee0f92607d5 GIT binary patch literal 62929 zcmeFZXH=6>+AfSJA|PM`Y!pF7L5d0zs zgn)?D6r_gE7iZ?1ch-5~=UMCgn5;!s*!$V-?$^EdenK_W6zFMh(o#@R&?_mvdP6}$ zRYgHTDSY8PxrAZa?=A(!#rw9hvKmUVvK$&Nj#jqzmJ}3

ev@dRkq~X~56X(Swwi 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�( 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 LTFQlrj|2V}+P2LT literal 0 HcmV?d00001 diff --git a/notifier/typetalk/client.go b/notifier/typetalk/client.go new file mode 100644 index 0000000..778f962 --- /dev/null +++ b/notifier/typetalk/client.go @@ -0,0 +1,72 @@ +package typetalk + +import ( + "errors" + "os" + "strconv" + + "github.com/mercari/tfnotify/terraform" + typetalk "github.com/nulab/go-typetalk/typetalk/v1" +) + +// EnvToken is Typetalk API Token +const EnvToken = "TYPETALK_TOKEN" + +// Client represents Typetalk API client. +type Client struct { + *typetalk.Client + Config Config + common service + Notify *NotifyService + API API +} + +// Config is a configuration for Typetalk Client +type Config struct { + Token string + Title string + TopicID string + Message string + CI string + Parser terraform.Parser + Template terraform.Template +} + +type service struct { + client *Client +} + +// NewClient returns Client initialized with Config +func NewClient(cfg Config) (*Client, error) { + token := cfg.Token + if token == EnvToken { + token = os.Getenv(EnvToken) + } + if token == "" { + return &Client{}, errors.New("Typetalk token is missing") + } + + if cfg.TopicID == "" { + return &Client{}, errors.New("Typetalk topic ID is missing") + } + + topicID, err := strconv.Atoi(cfg.TopicID) + if err != nil { + return &Client{}, err + } + + client := typetalk.NewClient(nil) + client.SetTypetalkToken(token) + c := &Client{ + Config: cfg, + Client: client, + } + c.common.client = c + c.Notify = (*NotifyService)(&c.common) + c.API = &Typetalk{ + Client: client, + TopicID: topicID, + } + + return c, nil +} diff --git a/notifier/typetalk/client_test.go b/notifier/typetalk/client_test.go new file mode 100644 index 0000000..dced5cc --- /dev/null +++ b/notifier/typetalk/client_test.go @@ -0,0 +1,73 @@ +package typetalk + +import ( + "os" + "testing" +) + +func TestNewClient(t *testing.T) { + typetalkToken := os.Getenv(EnvToken) + defer func() { + os.Setenv(EnvToken, typetalkToken) + }() + os.Setenv(EnvToken, "") + + testCases := []struct { + config Config + envToken string + expect string + }{ + { + // specify directly + config: Config{Token: "abcdefg", TopicID: "12345"}, + envToken: "", + expect: "", + }, + { + // specify via env but not to be set env (part 1) + config: Config{Token: "TYPETALK_TOKEN", TopicID: "12345"}, + envToken: "", + expect: "Typetalk token is missing", + }, + { + // specify via env (part 1) + config: Config{Token: "TYPETALK_TOKEN", TopicID: "12345"}, + envToken: "abcdefg", + expect: "", + }, + { + // specify via env but not to be set env (part 2) + config: Config{Token: "$TYPETALK_TOKEN", TopicID: "12345"}, + envToken: "", + expect: "typetalk token is missing", + }, + { + // specify via env (part 2) + config: Config{Token: "$TYPETALK_TOKEN", TopicID: "12345"}, + envToken: "abcdefg", + expect: "", + }, + { + // no specification (part 1) + config: Config{TopicID: "12345"}, + envToken: "", + expect: "Typetalk token is missing", + }, + { + // no specification (part 2) + config: Config{TopicID: "12345"}, + envToken: "abcdefg", + expect: "Typetalk token is missing", + }, + } + for _, testCase := range testCases { + os.Setenv(EnvToken, testCase.envToken) + _, err := NewClient(testCase.config) + if err == nil { + continue + } + if err.Error() != testCase.expect { + t.Errorf("got %q but want %q", err.Error(), testCase.expect) + } + } +} diff --git a/notifier/typetalk/notify.go b/notifier/typetalk/notify.go new file mode 100644 index 0000000..e4663f2 --- /dev/null +++ b/notifier/typetalk/notify.go @@ -0,0 +1,44 @@ +package typetalk + +import ( + "context" + "errors" + + "github.com/mercari/tfnotify/terraform" +) + +// NotifyService handles notification process. +type NotifyService service + +// Notify posts message to Typetalk. +func (s *NotifyService) Notify(body string) (exit int, err error) { + cfg := s.client.Config + parser := s.client.Config.Parser + template := s.client.Config.Template + + if cfg.TopicID == "" { + return terraform.ExitFail, errors.New("topic id is required") + } + + result := parser.Parse(body) + if result.Error != nil { + return result.ExitCode, result.Error + } + if result.Result == "" { + return result.ExitCode, result.Error + } + + template.SetValue(terraform.CommonTemplate{ + Title: cfg.Title, + Message: cfg.Message, + Result: result.Result, + Body: body, + }) + text, err := template.Execute() + if err != nil { + return result.ExitCode, err + } + + _, _, err = s.client.API.ChatPostMessage(context.Background(), text) + return result.ExitCode, err +} diff --git a/notifier/typetalk/notify_test.go b/notifier/typetalk/notify_test.go new file mode 100644 index 0000000..20bba6f --- /dev/null +++ b/notifier/typetalk/notify_test.go @@ -0,0 +1,64 @@ +package typetalk + +import ( + "context" + "testing" + + "github.com/mercari/tfnotify/terraform" + typetalkShared "github.com/nulab/go-typetalk/typetalk/shared" + typetalk "github.com/nulab/go-typetalk/typetalk/v1" +) + +func TestNotify(t *testing.T) { + testCases := []struct { + config Config + body string + exitCode int + ok bool + }{ + { + config: Config{ + Token: "token", + TopicID: "12345", + Message: "", + Parser: terraform.NewPlanParser(), + Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + }, + body: "Plan: 1 to add", + exitCode: 0, + ok: true, + }, + { + config: Config{ + Token: "token", + TopicID: "12345", + Message: "", + Parser: terraform.NewPlanParser(), + Template: terraform.NewPlanTemplate(terraform.DefaultPlanTemplate), + }, + body: "BLUR BLUR BLUR", + exitCode: 1, + ok: false, + }, + } + fake := fakeAPI{ + FakeChatPostMessage: func(ctx context.Context, message string) (*typetalk.PostedMessageResult, *typetalkShared.Response, error) { + return nil, nil, nil + }, + } + + for _, testCase := range testCases { + client, err := NewClient(testCase.config) + if err != nil { + t.Fatal(err) + } + client.API = &fake + exitCode, err := client.Notify.Notify(testCase.body) + if (err == nil) != testCase.ok { + t.Errorf("got error %q", err) + } + if exitCode != testCase.exitCode { + t.Errorf("got %q but want %q", exitCode, testCase.exitCode) + } + } +} diff --git a/notifier/typetalk/typetalk.go b/notifier/typetalk/typetalk.go new file mode 100644 index 0000000..df96c2f --- /dev/null +++ b/notifier/typetalk/typetalk.go @@ -0,0 +1,24 @@ +package typetalk + +import ( + "context" + + typetalkShared "github.com/nulab/go-typetalk/typetalk/shared" + typetalk "github.com/nulab/go-typetalk/typetalk/v1" +) + +// API is Typetalk API interface +type API interface { + ChatPostMessage(ctx context.Context, message string) (*typetalk.PostedMessageResult, *typetalkShared.Response, error) +} + +// Typetalk represents the attribute information necessary for requesting Typetalk API +type Typetalk struct { + *typetalk.Client + TopicID int +} + +// ChatPostMessage is wrapper for https://godoc.org/github.com/nulab/go-typetalk/typetalk/v1#MessagesService.PostMessage +func (t *Typetalk) ChatPostMessage(ctx context.Context, message string) (*typetalk.PostedMessageResult, *typetalkShared.Response, error) { + return t.Client.Messages.PostMessage(ctx, t.TopicID, message, nil) +} diff --git a/notifier/typetalk/typetalk_test.go b/notifier/typetalk/typetalk_test.go new file mode 100644 index 0000000..501252c --- /dev/null +++ b/notifier/typetalk/typetalk_test.go @@ -0,0 +1,17 @@ +package typetalk + +import ( + "context" + + typetalkShared "github.com/nulab/go-typetalk/typetalk/shared" + typetalk "github.com/nulab/go-typetalk/typetalk/v1" +) + +type fakeAPI struct { + API + FakeChatPostMessage func(ctx context.Context, message string) (*typetalk.PostedMessageResult, *typetalkShared.Response, error) +} + +func (g *fakeAPI) ChatPostMessage(ctx context.Context, message string) (*typetalk.PostedMessageResult, *typetalkShared.Response, error) { + return g.FakeChatPostMessage(ctx, message) +} From 79fb37b02a611bc60e3e82e84a3b272c38845c07 Mon Sep 17 00:00:00 2001 From: Issei Horie Date: Wed, 16 Jan 2019 00:40:14 +0900 Subject: [PATCH 2/3] Add link to Typetalk --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bac63c..5907d72 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ For `plan` command, you also need to specify `plan` as the argument of tfnotify. When running tfnotify, you can specify the configuration path via `--config` option (if it's omitted, it defaults to `{.,}tfnotify.y{,a}ml`). -The example settings of GitHub and GitHub Enterprise, Slack, Typetalk are as follows. Incidentally, there is no need to replace TOKEN string such as `$GITHUB_TOKEN` with the actual token. Instead, it must be defined as environment variables in CI settings. +The example settings of GitHub and GitHub Enterprise, Slack, [Typetalk](https://www.typetalk.com/) are as follows. Incidentally, there is no need to replace TOKEN string such as `$GITHUB_TOKEN` with the actual token. Instead, it must be defined as environment variables in CI settings. [template](https://golang.org/pkg/text/template/) of Go can be used for `template`. The templates can be used in `tfnotify.yaml` are as follows: From 7a1347cfa6fd3e1fd70ecc986194161aa6864d18 Mon Sep 17 00:00:00 2001 From: Issei Horie Date: Wed, 16 Jan 2019 01:12:33 +0900 Subject: [PATCH 3/3] Use "$" as prefix --- notifier/typetalk/client.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/notifier/typetalk/client.go b/notifier/typetalk/client.go index 778f962..283cbf2 100644 --- a/notifier/typetalk/client.go +++ b/notifier/typetalk/client.go @@ -12,6 +12,9 @@ import ( // EnvToken is Typetalk API Token const EnvToken = "TYPETALK_TOKEN" +// EnvTopicID is Typetalk topic ID +const EnvTopicID = "TYPETALK_TOPIC_ID" + // Client represents Typetalk API client. type Client struct { *typetalk.Client @@ -38,7 +41,7 @@ type service struct { // NewClient returns Client initialized with Config func NewClient(cfg Config) (*Client, error) { - token := cfg.Token + token := os.ExpandEnv(cfg.Token) if token == EnvToken { token = os.Getenv(EnvToken) } @@ -46,13 +49,17 @@ func NewClient(cfg Config) (*Client, error) { return &Client{}, errors.New("Typetalk token is missing") } - if cfg.TopicID == "" { + topicIDString := os.ExpandEnv(cfg.TopicID) + if topicIDString == EnvTopicID { + topicIDString = os.Getenv(EnvTopicID) + } + if topicIDString == "" { return &Client{}, errors.New("Typetalk topic ID is missing") } - topicID, err := strconv.Atoi(cfg.TopicID) + topicID, err := strconv.Atoi(topicIDString) if err != nil { - return &Client{}, err + return &Client{}, errors.New("Typetalk topic ID is not numeric value") } client := typetalk.NewClient(nil)