PullRequestをSlackから取得できるようにしてみた
MMMサーバサイドエンジニアの柳沼です。この間雪まつりに行きました。
MMMはメンバー数が多くないこともあり、ひとりが複数のプロジェクトに関わることが多くあります。
そのような状況では、自分に来ているプルリクエストを見落としがちで、たまに催促されることがあります。
あまり良くない状況なので、改善方法を考えてみました。
方針
- Slackのスラッシュコマンドを叩くと、レビュー待ちになっている && まだレビューしていない PRを一覧で取得できるようにする。
成果物
こちらのリポジトリに置いときました。
MMMにフィットするように作ってあるので、そのままは使えません。
スラッシュコマンドの作成
Go製です。
スラッシュコマンドをつくるのは始めてでしたが、公式を
参考に、簡単に作れました。
エンドポイントとポートを指定すればPostリクエストが飛んでくれるので、EC2などで作る場合はセキュリティグループの設定をいい感じにやってください。
GoでAPIクライアントを実装すること
APIクライアントの実装は、以下のように行います。
type client struct {
URL *url.URL
Org string
HTTPClient *http.Client
Authorization string
Logger *log.Logger
}
client構造体を定義し、
func newClient(org string, auth string) *client {
client := new(client)
u, _ := url.Parse("https://api.github.com")
client.URL = u
client.Org = org
client.HTTPClient = &http.Client{}
client.Authorization = auth
f, _ := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
client.Logger = log.New(f, "logger: ", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
return client
}
client構造体を生成する関数を定義します。
こちらの実装ではログ出力先をファイルにしていますが、引数で渡すようにすれば標準出力にもできます。
APIのバージョンなどが異なる場合は、中のURLを変えて、 clientV1
clientV2
などに構造体を分けるのがおすすめです。
そして、 newRequest
メソッドを定義します。
func (c *client) newRequest(method string, spath string, body io.Reader) (*http.Request, error) {
u := *c.URL
u.Path = path.Join(c.URL.Path, spath)
req, err := http.NewRequest(method, u.String(), body)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", c.Authorization)
return req, nil
}
最後に、 decodeBody
関数を作っておきます。
func decodeBody(resp *http.Response, out interface{}) error {
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
return decoder.Decode(out)
}
後は、実際のAPIに対応するメソッドを作ります。
例えば、GithubのプルリクエストのAPIを使うには、こんな感じにできます。
pullRequests
構造体は事前に作っておいてください。筆者はこちらをよく利用してます。
func (c *client) getPRs(repo string) (*pullRequests, error) {
spath := fmt.Sprintf("/repos/%v/%v/pulls", c.Org, repo)
req, _ := c.newRequest("GET", spath, nil)
res, err := c.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var prs pullRequests
if err := decodeBody(res, &prs); err != nil {
return nil, err
}
return &prs, err
}
これらを利用すると、以下のようにAPIが叩けます。
func main() {
var c *client
c = newClient(os.Getenv("PR_GITHUB_ORG"), os.Getenv("PR_GITHUB_TOKEN"))
prs, err := c.getPRs("example_repository_name")
c.log(err)
}
このやり方であれば、別のAPIを使うときも、メソッドを増やせば対応できるし、mainの記述が減ります。
*http.Client
オブジェクトを使いまわせるのもメリットです。
使ってみた様子
所感
複数のリポジトリを見に行かなくてよいのが楽です。
今までは github.com/pulls/mentionedを見たりしていたのですが、WIPのものや、
すでにレビュー済みのものも見えたりしていたので、使いやすいな〜と思っています。
社内でもけっこう使ってくれる人がいて嬉しいです。
スラッシュコマンドを自動実行する方法
普通にAPIからSlackにスラッシュコマンドを投稿しても、文字列としてポストされてしまいます。
これは、そもそもポスト先のURLが異なっているためです。
こんな感じでたたけることを確認しています。(デベロッパーツールから確認したエンドポイントなので、Slack側の問題で使えなくなる可能性があります。)
TEAM='team_name'
USER='user_name'
CHANNEL_ID='channel_id' # SlackのWeb版からアクセスした時のURL末尾の文字列
TOKEN='your_token'
curl "https://${TEAM}.slack.com/api/chat.command"
-H 'Content-Type: multipart/form-data; boundary=----{boundary_string}'
--data-binary $"------{boundary_string}
Content-Disposition: form-data; name='command'
/gp
------{boundary_string}
Content-Disposition: form-data; name='text'
${USER}
------{boundary_string}
Content-Disposition: form-data; name='channel'
${CHANNEL_ID}
------{boundary_string}
Content-Disposition: form-data; name='token'
${TOKEN}
------{boundary_string}--
"