この記事は Livesense Advent Calendar 15日目です。
Hubot 使ってますか。便利で楽しい Hubot ですが、今回は Hubot を使って REPL 環境を bot 化するシステムを作ってみたいと思います。
Haskell の REPL である GHCi を bot 化して、Slack で遊んでいるところ
使うもの
- Hubot
- Node.js + express
- REPL の APIサーバを作る
- Docker
- REPL 環境をサンドボックス化する
- REPL
- 今回は Haskell GHCi を使います
構成
青い点線内が今回のシステムのスコープです。
Hubot
Hubot プラグインとして実装します。
module.exports = (robot) ->
robot.hear /^(haskell|hs\!)(\s|\n)+([^\s\n][\s\S]*)/i, (msg)->
script = msg.match[3].trim()
msg.http("http://localhost:3000/eval")
.query({expr: script})
.get() (err, res, body) ->
switch res.statusCode
when 200
msg.send "\`\`\`\n#{body.trim()}\n\`\`\`"
robot.hear でチャットルーム上のポストを全て拾い、文頭が haskell または hs! の場合に反応するように設定しています。コマンドを API サーバに投げて、レスポンスを整形し、チャットルームに投げます。
GHCi API
API: GET /eval?expr=[PARAM]
PARAM を GHCi で実行し、結果を返します。
GHCi on Docker
サーバ上でそのまま GHCi を実行するのは、セキュリティ上危険です。悪意あるユーザから、
- OSコマンドの不正な実行
- 外部ネットワークへの不正なアクセス
- サーバ資源の食いつぶし
- メモリ
- ディスク
のような攻撃を受ける可能性があります。
そこで、Docker を使って手っ取り早くサンドボックス環境を手に入れます。Docker を使うことで、ホスト OS との干渉がなく、メモリ容量や CPU が制限された状態でアプリケーションを実行することができます。詳しい話は Docker の公式ブログ記事: CONTAINERS & DOCKER: HOW SECURE ARE THEY? より。
ということで、API サーバにはあらかじめ Docker および haskell:latest の Docker Image をインストールしておきます。
$ docker run -i --net="none" -c 256 -m 512m haskell:latest ghci
でサンドボックス環境の GHCi プロセスが立ち上がります。
API サーバ
API サーバは起動と同時に、上述の GHCi on Docker プロセスを 1 つ立ち上げます。何らかの要因でプロセスが死ぬと、即座に新たな GHCi プロセスを立ち上げます。あとは飛んできたリクエストをパースして、GHCi プロセスに投げて、応答を返すだけです。
一番めんどくさいのはこの GHCi プロセスとやりとりする部分だったりします。GHCi プロセスはステートレスであり、INPUT と OUTPUT の間に対応関係がありません。一方で API サーバはリクエストに対して明確にレスポンスを対応付ける必要があります。このミスマッチを解消する処理を自前で実装する必要があるため、ソースコードが重くなりがちです。(良いアイデアがあれば教えて下さい。)
ソースコード
- Hubot 本体 + プラグイン: https://github.com/na-o-y/lucky_bot/blob/master/scripts/ghci.coffee
- GHCi API: https://github.com/na-o-y/nghci
GHCi はよく止まるため、チャットルームからリスタートできるようにしたりしています。
他の言語でも、Docker Image と API サーバだけ用意してやれば簡単に bot 化できます!