平間ソンでできたこと1つ目。
背景
- 開発チームはVSTSを使っている
- ちょっとした連絡にはRocket.Chatを使っている
- VSTSでプルリクエストを作った後、いちいち手動でRocket.Chatに連絡しているので、面倒
- Rocket.Chatのサーバは 社内イントラ にある
やりたいこと
- VSTSでプルリクエストを作ったら、自動でRocket.Chatにメッセージを投げるようにしたい
実現案
a. VSTSのRoom使う (Service Hooks)
多分一番素直なやり方。今回は使えないけど。
連携方法
Roomを開いて、Manage Events -> Pull requestsを選択。
出てくるダイアログで、プロジェクトとリポジトリ、変更した人(チャットメンバーまたは誰でも)を選べば通知ができる。(チェックボックスをONにするのを忘れずに)
詳しくは https://msdn.microsoft.com/en-us/library/vs/alm/work/productivity/collaborate-in-a-team-room あたりを見ればわかる。
対応しているイベント
作成と、ステータス変更。タイトルなどの修正は対応してない。
連携イメージ

b. Service Hooksを使う
Service Hooksについて
Service Hooksを使うと、外部ツールと連携ができる。
https://www.visualstudio.com/en-us/get-started/integrate/service-hooks/webhooks-and-vso-vs
一覧がどこにあるのかわからなかった。
https://www.visualstudio.com/en-us/integrate/explore/explore-vso-vsi.aspx で紹介されているアイコンでなんとなくわかるのでよし。

Rocket.Chatとの連携
Rocket.ChatのIncomming WebHookはSlackと互換性があるので、Service Hooksの設定で連携先にSlackを選択、URLにRocket.ChatのIncomming WebHookのURLを設定すれば良い。
ただし、https必須。
Rocket.Chatをhttpsで
独自ドメインも証明書も持ってないので、AzureのWeb Appsでも使おうかと思ったけど、はまりそうだったので、Herokuを利用。Herokuだと本家リポジトリにあるリンクから一発でデプロイできる。
https://github.com/RocketChat/Rocket.Chat
でも、ここまでするなら素直にSlack使ったほうがいいよなぁ...と思ったことは内緒にしておく。
連携方法
Rocket.Chat
管理 -> サービス連携 -> 新しいサービス連携 -> Incomming WebHookで作成。
作成したら、Webhook URLに表示されているURLをメモしておく。
プロジェクトの設定 -> Service Hooks -> "+"ボタンでHookを追加する。
- Serivice: Slack
- Trigger: Pull request created
- Action:
- Slack Webhook URL: (Rocket.Chatで作成したWebhookのURLを設定)
Trigger "Pull request updated" についても同じようにHookを作る。

対応しているイベント
Roomと同じ。
作成と、ステータス変更。タイトルなどの修正は対応してない。
連携イメージ

URLのリンクがうまくいってないのは、SlackとRocket.Chatで書式が違うから。(だと思う)
Incommig WebHookの設定にScriptという項目があるので、そこでごにょごにょすれば直るかもしれない?
背景の Rocket.Chatのサーバは社内イントラにある のおかげで、a,b案は使えない。
ので、結局ここに落ち着くはず。
HubotでVSTSのREST APIを叩き、拾ってきた情報をRocket.Chatに流す。
HubotとRocket.Chatの連携
Rocket.ChatもRocket.Chat連携用のHubotもコンテナが用意されてるのでそれを使うと楽。
RocketChat/hubot-rocketchat: Rocket.Chat Hubot adapter
Pull Requestに関しては以下のページを参照。
Git Pull Requests | REST API Reference for Visual Studio Team Services and Team Foundation Server
Basicのみ対応。
GitHubにあるRailsアプリをVSTSでビルドして、Azureにデプロイする with Docker - cougerの日記 を見て、Alternate authentication credentials を有効にする。
Personal Access Tokenに対応しているので、そちらを使おう。ScopeはCode(Read)のみでOKっぽい。
指定する場合はこんな感じになる。ユーザ名はなんでもOK、パスワードにPersonal Access Tokenを指定すれば良いようだ。
http://[なんでもOK]:[Personal Access Token]@hogefuga.visualstudio.com/....
Personal Access Tokenの有効期間は180日なのでご注意をば。設定で1年間にすることも可能。
Personal Access Token、有効期間について @kkamegawa さんにアドバイスもらいました。ありがとーございます。
できたもの
だんだん疲れてきたので、できたものをペタペタ。
最初はActiveなものを全部出して、その後は、新規作成したものだけ表示。
vsts show pr [Active|Completed|Abandoned] のコマンドをhubotに投げると指定したステータスのプルリクを全部表示。
あと、チャーハン。
ごちゃごちゃしているのはなんとかならないものかなぁ。
Personal Access Tokenを使うように変更。
cronJob = require('cron').CronJob
startDate = new Date
vstsAccount = "[VSTSアカウント名]"
vstsProject = "[VSTSプロジェクト名]"
vstsRepository = "[VSTSリポジトリ名]"
vstsPersonalAccessToken = "[VSTS Personal Access Token]"
vstsRemoteUrlBase4PR = "https://#{vstsAccount}.visualstudio.com/DefaultCollection/#{vstsProject}/_git/#{vstsRepository}/pullrequest"
vstsRestApiUrlBase4PR = "https://hubotvstsnotifier:#{vstsPersonalAccessToken}@#{vstsAccount}.visualstudio.com/defaultcollection/#{vstsProject}/_apis/git/repositories/#{vstsRepository}/pullrequests\?api-version\=1.0"
module.exports = (robot) ->
createRestApiUrl4PR = (status, top) ->
url = vstsRestApiUrlBase4PR
if status?
url = "#{url}\&status\=#{status}"
if top?
url = "#{url}\&$top=#{top}"
url
searchPullRequests = (status, top, callBack) ->
url = createRestApiUrl4PR status, top
robot.http(url)
.get() (err, res, body) ->
responseJson = JSON.parse body
callBack responseJson.value
createPRMessage = (pullReq) ->
prUrl = "#{vstsRemoteUrlBase4PR}/#{pullReq.pullRequestId}"
"[#{pullReq.pullRequestId}](#{prUrl}) #{pullReq.title} (#{pullReq.sourceRefName} -> #{pullReq.targetRefName})"
showPullRequests = (status, top) ->
callBack = (pullReqs) ->
prMsgs = for pullReq in pullReqs
createPRMessage pullReq
if prMsgs.length is 0
msg = "@all: プルリクエストはなかったよ"
else
msg = "@all: プルリクエストが#{prMsgs.length}個あるよ\n#{prMsgs.join('\n')}"
robot.send {room: 'general'}, msg
searchPullRequests status, top, callBack
showRecentPullRequests = (status, top) ->
callBack = (pullReqs) ->
prMsgs = for pullReq in pullReqs
creationDate = new Date pullReq.creationDate
continue if startDate > creationDate
createPRMessage pullReq
startDate = new Date
if prMsgs.length is 0
return
msg = "@all: 新しいプルリクエストが#{prMsgs.length}個あるよ\n#{prMsgs.join('\n')}"
robot.send {room: 'general'}, msg
searchPullRequests status, top, callBack
robot.respond /vsts show pr (.*)/i, (msg) ->
showPullRequests msg.match[1], null
robot.respond /チャーハン/, (msg) ->
msg.send """
```` <- 本当は3個
チャーハン作るよ!!
∧_∧
(`・ω・) 。・゚・⌒)
/ o━ヽニニフ))
しーJ
```` <- 本当は3個
"""
showPullRequests "Active"
new cronJob('*/30 * * * * *', () ->
showRecentPullRequests "Active"
).start()
実行環境
カレントディレクトリにscriptsフォルダを作成して、上記のCoffee Scriptを配置したあと docker-compose up -d でRocket.ChatとHubotが立ち上がり、VSTSの連携が始まります。
フォルダ構成
+ root
+ bot
+ Dockerfile
+ scrpts
+ hubot-vsts-notifier.coffee
+ docker-compose.yml
docker-compose.yml
chat:
image: rocketchat/rocket.chat
environment:
- MONGO_URL=mongodb://db/rocketchat
ports:
- "3000:3000"
links:
- db
db:
image: mongo
ports:
- 27017
bot:
build: ./bot
links:
- chat
environment:
ROCKETCHAT_URL: http://chat:3000
ROCKETCHAT_ROOM: ''
LISTEN_ON_ALL_PUBLIC: true
ROCKETCHAT_USER: [RocketChat認証ユーザ名]
ROCKETCHAT_PASSWORD: [RocketChat認証パスワード]
ROCKETCHAT_AUTH: password
BOT_NAME: bot
volumes:
- ./scripts:/home/hubot/scripts
Dockerfile
FROM rocketchat/hubot-rocketchat
RUN npm install cron time
連携イメージ
