CircleCIのビルド結果をSlackのダイレクトメッセージに送信する
2016年 10月 21日
ニュースチームでは、チャットツールとしてSlackを使用しています。CIツールとして新たにCircleCIを導入することを検討していた際に、Slackとの連携について調査しました。
今回やりたかったのは、git push時にCircleCIのビルドを行い、Slackのchannelにビルド結果を送信し、ビルドが失敗していたときのみpushした人のslackbotにダイレクトメッセージを送信することです。
ビルド失敗通知の取得やダイレクトメッセージの送信はSlackAppのOutgoing WebHooksとIncoming WebHooksを使って、Google Apps Script(GAS)で実装します。
処理の流れ
①git pushを行い、CircleCIのビルドが走る②CircleCIのビルド結果がSlackのchannelに通知される
③Outgoing WebHooksが通知を感知してGASが実行される(doPostメソッドが走る)
④GASでSlackの通知内容を確認する
⑤ビルド失敗の通知であれば、Incoming WebHooksでダイレクトメッセージを送信する
特定のchannelにビルド結果の通知を送るのは、CircleCIとSlackのWebの設定ですぐできるかと思うので割愛します。以下のようなビルド結果がchannelに送られてきます。


画像のようにCircleCIのビルド結果はfallback形式で送信されているため、Outgoing WebHooksではTrigger Word(s)が取得できません。
そこで、Trigger Word(s)を空にしてビルド結果が送られるchannelに何か通知される度にOutgoing WebHooksからGASのdoPostメソッドが走り、channelの過去ログ1件を取得、ビルド失敗通知であれば対象者のslackbotに送信するようにしました。
URLはGASの匿名での実行を公開しているリンクを入力

var API_TOKEN = PropertiesService.getScriptProperties().getProperty('SLACK_ACCESS_TOKEN');
function doPost(e) {
// Outgoing WebHooksのToken
var verify_token = "xxxxxxxxxxxxxxxx";
if (verify_token != e.parameter.token) {
throw new Error("invalid token.");
}
// slackのログを取得するchannelのID
var chId = e.parameter.channel_id;
var message = getSlackChannelHistory(chId);
}
function getSlackChannelHistory(chId) {
var params = [];
params['channel'] = chId;
params['count'] = 1;
var data = requestSlackApi('channels.history', params);
return data.messages[0];
}
function requestSlackApi(path, params) {
var queryParams = ['token=' + encodeURIComponent(API_TOKEN)];
for (var key in params) {
queryParams.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key]));
}
var url = 'https://slack.com/api/' + path + '?' + queryParams.join('&');
var res = UrlFetchApp.fetch(url);
var data = JSON.parse(res.getContentText());
if (data.error) {
throw "GET " + path + ": " + data.error;
}
return data;
}
また、対象者のslack名がわからないとダイレクトメッセージを送信できないので、git pushした名前とSlackの名前を紐付けるスプレッドシートを用意します。

function myFunction() {
var name = 'test_user';
var user = getSlackName(name);
Logger.log(user);
}
function getSlackName(name) {
var sheetUrl = "https://docs.google.com/spreadsheets/d/1GxZxxxxxxxxxxxxxE/edit#gid=0";
var sheetName = "シート1";
var book = SpreadsheetApp.openByUrl(sheetUrl);
var sheet = book.getSheetByName(sheetName);
var lastRow = sheet.getLastRow();
var user = '';
for (var rowIndex=2; rowIndex<=lastRow; rowIndex++) {
var gitName = sheet.getRange(rowIndex, 1).getValue();
var slackName = sheet.getRange(rowIndex, 2).getValue();
if (gitName == name) {
user = slackName;
break;
}
}
return user;
}
一致する紐付けがあったとき対象者宛にダイレクトメッセージを送信します。今回は一致しなければ特定のchannelへ通知するようにしました。

var reg = /^Failed:[\s|\S]*\(.*?\sby\s(.*)\)/;
function doPost(e) {
・・・
// スプレッドシートに名前がなかったときに通知するchannel名
var chName = "#xxxxx";
postMessage(message, chName);
}
function postMessage(message, chName) {
var bot_name = "circle ci bot";
var bot_icon = "https://a.slack-edge.com/bda7/plugins/circleci/assets/service_512.png";
var channel = chName;
//Incomig WebHooks の webhook url
var url = "https://hooks.slack.com/services/Txxxxxxxxx5/BxxxxxxB/aDxxxxxxxxxMY";
var method = "POST";
if (obj = message.attachments) {
var matches = obj[0].fallback.match(reg);
if (!matches) {
return;
}
var user = matches[1];
name = getSlackName(user);
if (name) {
user = name;
channel = user;
}
var attachments = obj;
var payload = {
"text" : user + " さん、buildに失敗しました。",
"channel" : channel,
"attachments": attachments,
"username" : bot_name,
"icon_url" : bot_icon,
"link_names" : 1
};
var option = {
"method" : method,
"payload" : JSON.stringify(payload)
};
UrlFetchApp.fetch(url, option);
}
}
以上です。
他にもGASとスプレッドシートを使用したら色々できそうなので、ぜひ試してみてください。
エンジニア募集
詳しくは、こちらの採用情報ページをご覧ください。