TES Blog

株式会社テクニカルエンジニアリングサポートに所属する社員が、自身が携わるテクノロジーやイベントに関する情報を発信しています。

Capistranoデプロイ実行時に、Microsoft TeamsのIncoming Webhookコネクタに向けて(Gemを拡張しつつ)通知を送る

はじめに

弊社では社内コミュニケーションツールとしてMicrosoft Teamsを利用しています。
日々アップデートしている様子もあり、リリース当初からするとSlackとの差をあまり感じなくなってきました(遊び心はSlackに軍配が上がりますね👌)

本記事ではCapistranoによるデプロイ実行時に、Teamsのチャネルに向けて作業開始の通知を送る方法についてまとめてみました。

開発環境

開発環境は以下のとおりです。

  • Ruby 2.4.2
  • Ruby on Rails 5.2
  • Capistrano 3.11.0

Incoming Webhookコネクタの導入

まずはTeamsのチャネルに対してコネクタの追加を行い、Incoming Webhookの通知先リソース名を取得します。

  • チャネル名右側の [...] から「コネクタ」を選択します。

    f:id:t-miyahara11:20180725082429p:plain

  • Incoming Webhook項目の「構成」を選択します。

    f:id:t-miyahara11:20180725082615p:plain

  • 名前(あとでTeams上で表示されます)を入力し、「作成」を選択します。

    f:id:t-miyahara11:20180725082741p:plain

  • リソース名をクリップボードにコピーします。

    f:id:t-miyahara11:20180725083058p:plain

上記手順によって取得したリソースが通知先になります。

通知用のGem

今回Incoming Webhookへ向けて通知を送る際に、こちらのGemを利用させて頂きました。

github.com

Capistranoのデプロイタスクに通知処理が組み込まれる、大変便利なGemです(ありがとうございます🙏)。
configに記載する内容はそれほど多くないため、容易に導入できると思います。

しかしそのままでは…

今回導入するIncoming Webhookコネクタに対してメッセージを送る場合は、少しだけ手を加える必要があります。
通常Incoming WebhookへメッセージをPOSTする際は、 payload をキーとしてデータを送信する必要がありますが、
Incoming Webhookコネクタに対してメッセージをPOSTする場合、payload キーを指定せずに直接JSON形式のデータを送信する必要があります。
詳しくは下記をご覧ください。

docs.microsoft.com

Gemの拡張手段

方法としては以下の手段が考えられます。

  1. 公開されているリポジトリから、ディレクトリ・ファイル構造をそのまま移植してコード修正
  2. Rubyのクラス拡張によるコード修正
  3. 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を実行すると…。

f:id:t-miyahara11:20180818153224p:plain

無事メッセージが通知されました👌

課題

目標は達成できたものの、経緯を把握していない人がコードを見た時に負債だと感じる面が否めません。
Gemの構造が難しいものではないため、コードを参考にしつつ自前のGemを作成して組み込む、というのが個人的には最適解だと思いました。
いずれ時間ができた時にトライしてみようと思います。