はじめに
弊社では社内コミュニケーションツールとしてMicrosoft Teamsを利用しています。
日々アップデートしている様子もあり、リリース当初からするとSlackとの差をあまり感じなくなってきました(遊び心はSlackに軍配が上がりますね👌)
本記事ではCapistranoによるデプロイ実行時に、Teamsのチャネルに向けて作業開始の通知を送る方法についてまとめてみました。
開発環境
開発環境は以下のとおりです。
- Ruby 2.4.2
- Ruby on Rails 5.2
- Capistrano 3.11.0
Incoming Webhookコネクタの導入
まずはTeamsのチャネルに対してコネクタの追加を行い、Incoming Webhookの通知先リソース名を取得します。
チャネル名右側の [...] から「コネクタ」を選択します。
Incoming Webhook項目の「構成」を選択します。
名前(あとでTeams上で表示されます)を入力し、「作成」を選択します。
リソース名をクリップボードにコピーします。
上記手順によって取得したリソースが通知先になります。
通知用のGem
今回Incoming Webhookへ向けて通知を送る際に、こちらのGemを利用させて頂きました。
Capistranoのデプロイタスクに通知処理が組み込まれる、大変便利なGemです(ありがとうございます🙏)。
configに記載する内容はそれほど多くないため、容易に導入できると思います。
しかしそのままでは…
今回導入するIncoming Webhookコネクタに対してメッセージを送る場合は、少しだけ手を加える必要があります。
通常Incoming WebhookへメッセージをPOSTする際は、 payload
をキーとしてデータを送信する必要がありますが、
Incoming Webhookコネクタに対してメッセージをPOSTする場合、payload
キーを指定せずに直接JSON形式のデータを送信する必要があります。
詳しくは下記をご覧ください。
Gemの拡張手段
方法としては以下の手段が考えられます。
- 公開されているリポジトリから、ディレクトリ・ファイル構造をそのまま移植してコード修正
- Rubyのクラス拡張によるコード修正
- RubyのRefinementを使用したクラス拡張によるコード修正
1は流石に気が咎めるので却下ですね。
2でそのままモンキーパッチをする勇気はないので、3を選択しました。
追加したコード
lib/capistrano/tasks/webhook.rake
まずは lib/capistrano/tasks/webhook.rake
ファイルを作成します。
# Capistrano::Hook::Webのパッチモジュール module CapistranoHookWebRefine refine Capistrano::Hook::Web do def post(params) http.start do req = Net::HTTP::Post.new(uri.path) req['Content-Type'] = 'application/json' req.body = params.to_json res = http.request(req) res end end end end
上記コードの追加により、該当クラスのメソッドを再定義しています。
今回は post
メソッドの内容を書き換えて、パラメータのHashデータをJSONに変換してbodyに渡すように修正しました。
payload
キーの存在は、ここで忘れてしまいましょう。
# 独自に定義したタスクを優先させるため、gem側のcapistranoタスクを削除 Rake::Task['webhook:post:starting'].clear namespace :webhook do namespace :post do using CapistranoHookWebRefine # gemのwebhookのままだと、usingで呼び出したモジュールが優先されず、 # 拡張していない Capistrano::Hook::Web が使用されるので、別名で定義 def web_hook(url, payload) return if url.nil? || payload.nil? || payload.empty? info "POST #{url} payload='#{payload}'" result = Capistrano::Hook::Web.client(url).post(payload) message = "HTTP #{result.code} #{result.message} body='#{result.body}'; " if result.is_a?(Net::HTTPSuccess) info message else error message end end desc 'Post a starting message if :webhook_url and :webhook_starting_payload are present' task :starting do run_locally do url = fetch(:webhook_url) payload = fetch(:webhook_starting_payload) web_hook(url, payload) end end before 'deploy:starting', 'webhook:post:starting' end end
上記はGemで提供されているrake taskの定義を改変したものです。
もしかすると、web_hook
を定義せずにもう少しスマートにできるのかもしれませんが…。
config/deploy/production.rb
# TeamsのIncoming Webhookコネクタへの通知設定 base_paylod_params = { '@context' => 'http://schema.org/extensions', '@type' => 'MessageCard', themeColor: '0072c6', title: 'Release Production', } set :webhook_url, {取得したIncoming Webhookの通知先リソース} set :webhook_starting_payload, base_paylod_params.merge(text: 'Now, deploying...')
Incoming Webhookへ渡すパラメータの指定は以下を参考にしつつ、:webhook_url
には取得した通知先リソースを指定します。
Part 2: Working With Office 365 Groups And Connectors – Microsoft MVP Award Program Blog
デプロイの実行結果
普段どおり、bundle exec cap production deploy
を実行すると…。
無事メッセージが通知されました👌
課題
目標は達成できたものの、経緯を把握していない人がコードを見た時に負債だと感じる面が否めません。
Gemの構造が難しいものではないため、コードを参考にしつつ自前のGemを作成して組み込む、というのが個人的には最適解だと思いました。
いずれ時間ができた時にトライしてみようと思います。