Visual Studio Team Services と Rocket.Chat の連携 (プルリクエストだけ)
平間ソンでできたこと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をメモしておく。
VSTS
プロジェクトの設定 -> 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という項目があるので、そこでごにょごにょすれば直るかもしれない?
c. REST APIを使う
背景の 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
VSTSのREST API
Pull Requestに関しては以下のページを参照。 Git Pull Requests | REST API Reference for Visual Studio Team Services and Team Foundation Server
APIに必要な認証
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 さんにアドバイスもらいました。ありがとーございます。
@nobiinu_and personal access token じゃダメなんでしたっけ?
— kkamegawa (@kkamegawa) May 3, 2016
@nobiinu_and あとPATを使う場合最長180日だったかなので、気をつけてください
— kkamegawa (@kkamegawa) May 3, 2016
できたもの
だんだん疲れてきたので、できたものをペタペタ。
最初はActiveなものを全部出して、その後は、新規作成したものだけ表示。
vsts show pr [Active|Completed|Abandoned]
のコマンドをhubotに投げると指定したステータスのプルリクを全部表示。
あと、チャーハン。
ごちゃごちゃしているのはなんとかならないものかなぁ。
Hubotスクリプト
Personal Access Tokenを使うように変更。
# Description: # VSTSのPull Requestが作成されたらメッセージを投下します # # Configuration # vsts... の変数を適宜変更してください # # Commands: # hubot vsts show pr [Active|Completed|Abandoned] - 指定したステータスのPull Requestを全部表示 # hubot チャーハン - チャーハン作ります 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個 """ # 初回のみActiveなものを全部出す 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
連携イメージ