くりにっき

フルスタックキュアエンジニアです

ChatWorkMentionTaskを作った #megurorb

Meguro.rb#12 で趣味アプリについてのLTしたので資料を上げておきます

megurorb.connpass.com

スライド版

sue445.github.io

エントリ版

ChatWorkMentionTaskとは

f:id:sue445:20180226235644p:plain

動作風景

  • 自分宛にきたメンションが自動的に別部屋にタスク化され、一覧表示される
  • 重要なものや後で対応必要なものだけ残して、それ以外のタスクを完了にする運用

f:id:sue445:20180226235658p:plain

所感

  • 半月くらい人柱運用してるけど日々の仕事がむっちゃ捗ってる!!!
  • ぶっちゃげChatWork本体に欲しい機能なんだが、3年前から要望は出ている がいまだに実装されていないので諦めて自分で作った

技術的なこと

仕組み

  1. ChatWorkMentionTaskにChatWorkのアカウントでOAuth認証
  2. ChatWorkの自分のアカウントにChatWorkMentionTaskのwebhookを登録
  3. メンションがきたらwebhookが飛んできてタスク化される

頑張ったこと

ChatWorkのAPIのリフレッシュトークンの有効期限は2週間なので、リフレッシュトークンが切れる3日前にリマインド用にタスクを作るようにした

f:id:sue445:20180226235725p:plain

webhookの params[:body] にチャット本文が入っていて、それがログに出るのが嫌だったのでパスワード同様フィルタリングするようにした。

f:id:sue445:20180226235744p:plain

f:id:sue445:20180226235756p:plain

https://github.com/sue445/chatwork_mention_task/commit/ec3e2583044e2c132ef1de9ef0c656f1e74dcac1

副産物の紹介

  • omniauth-chatwork 💎
  • chatwork 💎
  • chatwork_webhook_verify 💎
  • dockerfile-heroku-cli 🐳
  • dockerhub-slack-webhook 🐳

omniauth-chatwork 💎

https://github.com/sue445/omniauth-chatwork

実際の設定

Railsだとこんな風に書いておくだけでいい感じにChatWorkのOAuth認証が使えるようになる

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :chatwork, ENV["CHATWORK_CLIENT_ID"], ENV["CHATWORK_CLIENT_SECRET"], scope: ["users.profile.me:read", "rooms.tasks:write", "rooms.info:read"]
end

https://github.com/sue445/chatwork_mention_task/blob/10dd0c197060fbc62016fe33b60a2089dfb74261/config/initializers/omniauth.rb

chatwork 💎

https://github.com/asonas/chatwork-ruby

やったこと

f:id:sue445:20180226235815p:plain

頑張ったこと

実際のテストコード

  • stub_chatwork_requestit_behaves_like :a_chatwork_api でramlを利用
  • APIクライアントではテストデータの生成が面倒なのでそこを公式が提供しているデータを使って自動化できて便利
describe ".create", type: :api do
  subject { ChatWork::Message.create(room_id: room_id, body: body, &block) }

  let(:room_id) { 123 }
  let(:body)    { "Hello ChatWork!" }

  before do
    stub_chatwork_request(:post, "/rooms/#{room_id}/messages", "/rooms/{room_id}/messages")
  end

  it_behaves_like :a_chatwork_api, :post, "/rooms/{room_id}/messages"
end

https://github.com/asonas/chatwork-ruby/blob/v0.8.0/spec/lib/chatwork/message_spec.rb#L24-L35

chatwork_webhook_verify 💎

# app/controllers/webhook_controller.rb
class WebhookController < ApplicationController
  before_action :verify_chatwork_webhook_signature!
end

リクエストごとにtokenを変えて検証する必要がある場合はこんな感じ

class WebhookController < ApplicationController
  before_action :set_user
  before_action :verify_signature!

  private

    def verify_signature!
      return unless Global.app.verify_signature?

      verify_chatwork_webhook_signature!(@user.webhook_token)
    end
end

https://github.com/sue445/chatwork_mention_task/blob/10dd0c197060fbc62016fe33b60a2089dfb74261/app/controllers/webhook_controller.rb

dockerfile-heroku-cli 🐳

  • https://github.com/sue445/dockerfile-heroku-cli
  • https://hub.docker.com/r/sue445/heroku-cli/
  • CircleCIからherokuにデプロイする時に毎回 heroku/cli をダウンロードしたくなかったのでインストール済のイメージを作った
  • alpineに必要最低限のパッケージやバイナリしか入れてないので47MBしかない超軽量イメージ
    • Docker Hubを検索してると似たようなイメージを作ってる人はたくさんいるけど、自分が見た範囲では sue445/heroku-cli が最軽量

頑張ったこと

常に最新のheroku/cliをDockerイメージで使いたかったので、heroku/cliの更新を自動検知してDockerイメージを自動ビルドする仕組を作った

  1. CircleCIのスケジューラが週1で起動
  2. heroku cliのバージョンが上がっていればCircleCIがファイルをコミットしてGitHubにpush
  3. GitHubにpushされればDocker Hubの automated builds でビルドがされる

https://github.com/sue445/dockerfile-heroku-cli/blob/fd4517b1b252e182d7b318fed74fe95fbe79e782/.circleci/config.yml#L64-L79

dockerhub-slack-webhook 🐳

  • https://github.com/sue445/dockerhub-slack-webhook
  • Docker Hubでビルドした後にSlackに通知するためのwebhook
  • ビルドしたイメージがいつビルド終わるか分からないので、Slackに手軽に通知できるようにしたかった

f:id:sue445:20180226235907p:plain

頑張ったこと

Deploy to Herokuボタンに対応してるので、Herokuのアカウントさえあればワンクリックでデプロイできる

f:id:sue445:20180226235917p:plain

まとめ

  • ChatWorkを使ってる場合はChatWorkMentionTaskは便利なので是非使ってください
  • RubyでChatWorkをハックする技術はだいたい制覇できた感あるw

2018/8/9 追記

ChatWorkのAPIのリフレッシュトークンの有効期限は2週間なので、リフレッシュトークンが切れる3日前にリマインド用にタスクを作るようにした

リフレッシュトークンの期限を無期限に設定できるようになったので上記処理は不要になりました

sue445.hatenablog.com

OSS雑メンテ #railsdm

Rails Developers Meetup 2017 というイベントで「OSS雑メンテ」という発表をしたのでスライドをシェアさせていただきます

スライド

発表中の反応

他の人のスライド

他の人のスライドは下記をご覧ください

railsdm.github.io

chatwork-rubyのメンテナになった

注意:アドベントカレンダーとは全く関係ないエントリです

github.com

https://rubygems.org/gems/chatwork

chatwork-rubyにPRを送りまくってたら気づいたらメンテナになっていました

他にも Jenkins ChatWork Plugin のメンテナにもなったし*1omniauth-chatwork も作ってるので、日本におけるChatWork APIのヘビーユーザと言っても過言ではなさそう(過言)

chatwork-rubyの方は権限周りの動作確認も兼ねてOAuth対応版をリリースしておきました

今後のこと

プライベートが色々ごたついているので、落ち着いたらテスト周りの整備をしつつ対応してないエンドポイントの対応をしておこうと思ってます。

SAML連携したChatWorkアカウントでOAuthログインする方法

先日のブログの続編です

sue445.hatenablog.com

うちの会社ではChatWorkをSAML連携させて利用しています。

SAML連携したChatWorkアカウントだと普通のやり方ではOAuth認証できません。一応できるんですがやり方が難しすぎて初見だとエラーが出てハマるので現時点でのやり方を書いておきます。*1

やり方

  1. https://www.chatwork.com/login.php 経由でログインしてブラウザに予めChatWorkのセッションを作っておく。*2
  2. コンセント画面(omniauth-chatworkでは/auth/chatworkでリダイレクトした先の画面)では普通のログインボタンではなく、新規登録ボタンの真下の「KDDI ChatWorkをご利用の方はこちら」のリンク先からでログイン

このログイン画面だと普通赤いログインボタンを押すやろ・・・

f:id:sue445:20171116174813p:plain

omniauth-chatwork実装時にリファレンス通りにやってもOAuthログインできなくて、ChatWorkのサポートに問い合わせてメールで何往復かしてようやく上記の手順が分かりました。

一応断片的にはドキュメントには書いてあるんですが、非常に分かりづらいのでサポートに改善要望を投げています。

*1:最後に書いてるけど改善要望は投げているので今後仕様が変わる可能性はある

*2: http://developer.chatwork.com/ja/oauth.html の「OAuth2専用ログイン画面ではSAML認証には対応しておりません。通常のログイン画面を経由してSAML認証でログインした後、再度コンセント画面にアクセスしてください。」参照

omniauth-chatworkを作った

github.com

なんのgem?

最近ChatWorkでOAuth認証に対応したのですが、それに対応したomniauthのproviderです

使い方

OAuthクライアントの登録

まず最初に下記を参考にOAuthクライアントを作ってください

developer.chatwork.com

blog-ja.chatwork.com

この時リダイレクトURIhttps://〜/outh/chatwork の形式にしてください。

f:id:sue445:20171116165200p:plain

https://127.0.0.1:4567/ も登録しているのはローカルでの確認用です。

余談ですが2週間位前に https://localhost:4567/ で登録しようとすると「https://example.com形式で登録してください」的なエラーが出たのでその時は https://127.0.0.1:4567で登録して回避しました。

今確認したら https://localhost:4567/で登録できるようになっていたので今はどっちでもいいと思います。

omniauth-chatworkをproviderとして組み込む

あとは omniauth-facebookomniauth-twitter と同様に下記のような感じでproviderを登録してください

use OmniAuth::Builder do
  provider :chatwork, ENV["CLIENT_ID"], ENV["CLIENT_SECRET"]

  # scopeを明示的に指定したい場合はこっち
  provider :chatwork, ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], scope: ["users.all:read", "rooms.all:read_write", "contacts.all:read_write"]
end

確認してないけどRailsなら config/initializers/omniauth.rb をこんな風に書いておけばたぶん動きます。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :chatwork, ENV["CLIENT_ID"], ENV["CLIENT_SECRET"]
end

技術的なこと

ローカルでのSSL確認時はwebrick使うのが便利だった

http://developer.chatwork.com/ja/oauth.html にも書いてますがリダイレクトURIhttpsである必要があります。これは当然ローカルでの確認時にもhttpsが要求されます

ググるHTTPS証明書を作成してローカルで起動したApacheに喰わせて云々っていう記事が出ますが、Rubyならwebrickで下記のような設定を書いておけば起動時にオレオレ証明書を動的生成してくれるのでそんな心配はありません

set :server_settings,
    SSLEnable: true,
    SSLCertName: [["CN", WEBrick::Utils.getservername]]

https://github.com/sue445/omniauth-chatwork/blob/v0.1.0/spec/dummy/app.rb

実際ローカルでOAuth認証確認する時はomniauth-chatworkのリポジトリをcloneして bundle exec ruby spec/dummy/app.rb で動きます

参考資料

omniauth provider作成時に参考にしたもの

今までOAuth2のクライアントライブラリ使わずにゴリゴリ自前実装してきたので、omniauthのルールに則ってgemを作るってのが新鮮でした

追伸

SAML連携してるChatWorkアカウントだとOAuth認証が非常に分かりづらいのでそれはまた後日書きます

-> 書いた

sue445.hatenablog.com

wercker_build_triggerを作った

github.com

wercker_build_triggerについて

Wercker のビルドを外部から手軽に実行するためのツールです

モチベーション

僕は下記のように週1回Travis CIで定期ビルドを実行しています

sue445.hatenablog.com

WerckerでCI回してるリポジトリでも定期ビルドをやりたかったのですが、WerckerではTravis CIのCron jobsのような機能がなかったのでcrontabから実行しやすくするためにツールを作りました。

使い方

golang製なので https://github.com/sue445/wercker_build_trigger/releases からバイナリを落としてきて適当な場所に置いてください。

設定ファイルはこんな感じ

pipelines:
  - application_path: "wercker/docs"
    pipeline_name: "build"
    branch: "master"
  - application_path: "sue445/wercker_build_trigger"

application_pathは必須。pipeline_namebranchは省略可。pipeline_name省略時はbuildが、branch省略時はmaster が使われます

crontabにこんな感じに書いておけば毎週日曜の3:00に実行されます。

0 3 * * 0 /path/to/wercker_build_trigger --config /path/to/wercker_build_trigger.yml --token xxxxxxx

tokenは https://app.wercker.com/profile/tokens で生成したやつを貼り付けてください。

導入事例

実際に使ってる設定ファイル。

pipelines:
  - application_path: "sue445/itamae-plugin-recipe-omori_gohan"
  - application_path: "sue445/itamae-plugin-recipe-tmux"
  - application_path: "sue445/itamae-plugin-recipe-tig"
  - application_path: "sue445/itamae-plugin-recipe-git_now"
  - application_path: "sue445/itamae-plugin-resource-encrypted_remote_file"
  - application_path: "sue445/itamae-plugin-recipe-consul"
  - application_path: "sue445/capistrano-itamae"
  - application_path: "sue445/wercker_build_trigger"

wercker_build_trigger自身の定期ビルドもwercker_build_triggerで行っています。(ややこしい)

実行結果

[application_path:sue445/itamae-plugin-recipe-omori_gohan][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54714816100010bd46f
[application_path:sue445/itamae-plugin-recipe-tmux][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54814816100010bd477
[application_path:sue445/itamae-plugin-recipe-tig][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a5492535040001c453c2
[application_path:sue445/itamae-plugin-recipe-git_now][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54a8e888f0001b7de36
[application_path:sue445/itamae-plugin-resource-encrypted_remote_file][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54c8e888f0001b7de3e
[application_path:sue445/itamae-plugin-recipe-consul][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54d14816100010bd47f
[application_path:sue445/capistrano-itamae][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54e8e888f0001b7de46
[application_path:sue445/wercker_build_trigger][pipeline_name:build][branch:master] Triggered pipeline: https://app.wercker.com/api/v3/runs/59d9a54f2535040001c453ca

ビルド結果はSlackに流すようにしてるのでこんな風に通知されます

f:id:sue445:20171008131301p:plain

DigitalOceanで使えるimageを一覧で取得する

tl;dr;

https://gist.github.com/sue445/dd1ab749f9cf364777acbe66d34576e4

前置き

itamaeプラグインのCIを回してたら DigitalOceanで「centos-7-0-x64なんてイメージはないぞ!」ってエラーになりました

+ vagrant up centos70 --provider=digital_ocean
Bringing machine 'centos70' up with 'digital_ocean' provider...
==> centos70: Using existing SSH key: wercker-itamae-plugin-recipe-consul
There was an issue with the request made to the DigitalOcean
API at:

Path: /v2/droplets
URI Params: {:size=>"512MB", :region=>"nyc3", :image=>"centos-7-0-x64", :name=>"itamae-consul-centos70-59d3b5938e888f0001b27d75", :ssh_keys=>[1874082], :private_networking=>false, :ipv6=>false}

The response status from the API was:

Status: 422
Response: {"id"=>"unprocessable_entity", "message"=>"You specified an invalid image for Droplet creation."}

Vagrabtfileだと下記のように provider.image で設定してたのですが、どうも消えた模様。

  config.vm.define :centos70 do |c|
    c.vm.box = "centos/7"
    c.vm.provider :digital_ocean do |provider, override|
      provider.image = "centos-7-0-x64"
    end
    c.vm.hostname  = 'itamae-consul-centos70'
    c.vm.hostname  += "-#{ENV['WERCKER_RUN_ID']}" if ENV['WERCKER_RUN_ID']
  end

実際に使えるイメージ一覧を自分のgistには乗っけてはいたんですが、ググれる場所にあった方が便利なのでブログに記載

使い方

はじめに https://cloud.digitalocean.com/settings/api/tokensAPIトークンを取得

f:id:sue445:20171004203142p:plain

あとはこんな感じに実行したらjsonがとれます。下記のslugをVagrantfileに渡してやればよさそう

$ export DIGITALOCEAN_ACCESS_TOKEN="xxxxxxxxxxxxxxx"
$ curl -s "https://api.digitalocean.com/v2/images?filter=global&per_page=100" -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" | jq .

{
  "images": [
    {
      "id": 27983450,
      "name": "1520.4.0 (beta)",
      "distribution": "CoreOS",
      "slug": "coreos-beta",
      "public": true,
      "regions": [
        "nyc1",
        "sfo1",
        "nyc2",
        "ams2",
        "sgp1",
        "lon1",
        "nyc3",
        "ams3",
        "fra1",
        "tor1",
        "sfo2",
        "blr1"
      ],

slugで検索したい場合はこんな感じ

$ curl -s "https://api.digitalocean.com/v2/images?filter=global&per_page=100" -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" | jq -r ".images[].slug" | grep centos
centos-6-5-x32
centos-6-5-x64
centos-6-x32
centos-6-x64
centos-7-x64

jsonはここに置いてるのでページ内検索でも探せます

https://gist.github.com/sue445/dd1ab749f9cf364777acbe66d34576e4