はじめに
職場では、社内サーバーにgitbucketを立てており、slackによりメンバー間のコミュニケーションを取っています。
以下のような流れでレビューを実施しているのですが、「プルリクエストへのコメントにmentionがつかない」ので、
誰がプルリクエストへコメントしたかどうかが判断しにくいと、チームから意見がでました。
そこでrustを使用して、プルリクエストへコメントをmention付きでslackへ通知するプログラムを作成しました。
- プルリクエストを作成
- コードレビューを実施し、gitbucketでコメントを記載
- slackへ2で記載したコメントを通知(slack-github連携)
- ここでコメントは指定したチャンネルに通知されるが、mentionがつかない!
インストール
Macではhomebrewでインストールします。
brew install rust
Slack Token生成
rustプログラムからslackへ通知できるよう、アプリケーションを作成し、tokenを生成します。
slack app(チーム名の下矢印クリック) → Customize Slack → API(左サイドメニュー)→ Your Apps(右上メニュー)
Legacy Tokenを使用するので、ここから取得。
docker環境構築
Docker上で動作させる為、Dockerfileを作成します。
FROM ubuntu:16.04
RUN apt-get -y update
RUN apt-get install -y vim curl less
RUN apt-get install -y build-essential
RUN apt-get install -y libtool
RUN apt-get install -y zlib1g-dev
RUN apt-get install -y openssl libssl-dev libcrypto++-dev
RUN apt-get install -y sudo
RUN apt-get install -y pkg-config
RUN curl -sSf https://static.rust-lang.org/rustup.sh | sh
RUN mkdir -p /var/slack
WORKDIR /var/slack
ADD ./source/Cargo.toml /var/slack
ADD ./source/Cargo.lock /var/slack
ADD ./source/src/ /var/slack/src
ADD ./source/target/ /var/slack/target
RUN cargo build --release
CMD cargo run --release
ironサーバー構築
gitbucketからの通知を受け付けるために、ironサーバーを構築します。
Cargoを使用して、新しいプロジェクトを作成します(参考:cargoの使い方)。
cargo new notification
Cargo.tomlを修正し、iron等必要なcrateをインストールします。
[package]
name = "slack"
version = "0.1.0"
authors = ["developer"]
[dependencies]
iron = "*"
params = "*"
slack_api = "0.17.0"
reqwest = "*"
crate名 | 説明 | 参考 |
---|---|---|
params | リクエストパラメータをパースするためのironプラグイン | https://github.com/iron/params |
slack_api | Slackへ通知を送るcrate | https://github.com/slack-rs/slack-rs-api |
reqwest | HTTPクライアント(?) | https://github.com/seanmonstar/reqwest |
Slack通知処理
rustからSlackへ通知します。
流れ的には、
- gitbucketのプルリクエストへコメント記載
- gitbucketからslackへWebohook機能を使用して、ironサーバーへコメント情報をJSONで通知
- ironサーバーでJSONをパースし、特定チャンネルへmention付きでコメント通知
以下、コードになります。
gitbucketユーザーとslackユーザーが異なるので、紐付けるため「create_table」でテーブルを作成しています。
各種tokenやユーザー名/チャンネル名は、任意のものを指定してください。
extern crate iron;
extern crate params;
extern crate slack_api as slack;
extern crate reqwest;
use iron::prelude::*;
use iron::status;
use std::collections::HashMap;
use std::default::Default;
static TOKEN: &'static str = [slack legacy token];
// git/slackアカウント構造体.
struct Account {
git_name: String, // gitbucket上のユーザー名.
slack_name: String // slackユーザー名.
}
impl Account {
fn new(git: &str, slack: &str) -> Account {
Account{ git_name: git.to_string(), slack_name: slack.to_string() }
}
}
// slack-gitテーブル作成.
fn create_table() -> HashMap<i32, Account> {
// gitbucketユーザーからslackユーザーを検索できるよう、ここでテーブルを作成しています。
let mut git_slack_hash = HashMap::<i32, Account>::new();
git_slack_hash.insert(1, Account::new("gitbucket_test", "slack_test"));
git_slack_hash
}
// search slack user info.
fn get_slack_userid(_u: &Account) -> Result<String, String> {
let client = reqwest::Client::new().unwrap();
let request = slack::rtm::StartRequest::default();
let response = slack::rtm::start(&client, TOKEN, &request);
if let Ok(response) = response {
if let Some(users) = response.users {
// search user id.
let account = users.iter()
.filter(|u| u.name.clone().unwrap() == _u.slack_name)
.next()
.unwrap();
return Ok(account.clone().id.unwrap());
}
}
Err("Failed".to_string())
}
// send message to slack.
fn send_to_slack(id: &str, mention: &str, msg: &str) -> Result<String, String> {
// send to slack.
let req_info = slack::chat::PostMessageRequest{
channel: [チャンネル名],
text: &format!("<@{}|{}>\n{}", id, mention, msg),
username: Some([ユーザー名]),
parse: None,
link_names: None,
attachments: None,
unfurl_links: None,
as_user: None,
icon_emoji: None,
icon_url: None,
reply_broadcast: None,
thread_ts: None,
unfurl_media: None
};
let client = reqwest::Client::new().unwrap();
match slack::chat::post_message(&client, TOKEN, &req_info) {
Ok(_) => return Ok("Send Success".to_string()),
Err(_) => return Err("Send Error".to_string())
};
}
// main function.
fn main() {
//-------------------------------.
// slackへ通知を流す.
//-------------------------------.
fn notification(req: &mut Request) -> IronResult<Response> {
// slack hash table from gitbucket account.
let table = create_table();
use params::{Params, Value};
let data = req.get_ref::<Params>().unwrap();
// gitユーザー名取得.
let _name = match data.find(&["issue", "user", "login"]) {
Some(&Value::String(ref name)) => name,
_ => ""
};
// gitユーザ名からslackユーザー名検索.
let user = table.iter()
.filter(|&(_, val)| &val.git_name == _name)
.next()
.unwrap();
// コメント取得.
let msg = match data.find(&["comment", "body"]) {
Some(&Value::String(ref body)) => body,
_ => ""
};
// send to slack.
if let Ok(id) = get_slack_userid(&user.1) {
match send_to_slack(&id, &user.1.slack_name, &msg) {
Ok(_) => return Ok(Response::with((status::Ok, "Success"))),
Err(_) => return Ok(Response::with((status::Ok, "Success")))
};
}
else {
return Ok(Response::with((status::Ok, "Success")))
}
}
Iron::new(notification).http("0.0.0.0:8080").unwrap();
}
ironサーバー起動
docker build && docker up
にて、ironサーバーを起動します。
また、gitbucketサーバーからironサーバーへ通知がいくように設定を行います。
(gitbucketのwebhook設定は割愛します)
まとめ
ひとまずはgitbucketから受け取った情報をrustにてパースし、slackへ通知することができました。
ただし、unwrap
で直接取り出していたりとまだまだレベルが低いので、エラーハンドリングをもう少し勉強したいと思います。
課題
このプログラムで、githubからslackへmention付きでコメントが通知されるようになりましたが、プルリクエストを作成した人がコメントすると、その人自身にmentionがついてしまいます。
例えば、Aさんがプルリクエストを作成しコメントを行うと、Aさん自身にmentionが付き、コメントが記載されます。
このあたりを、例えば「プルリクエストを許可する人」対してmentionがつくよう、改良していきたいと思います(できるのかな?)。