ChatOps 実現のために外部コマンド実行したい
基本的に ruboty になにかタスクを追加するためには gem を作らないといけないわけです(よね?)。
私としてはロボットにデプロイやらなんやらやらせたいわけです。となると、ansible 叩いたり YAML 作ったり dhcpd.conf 更新したり、とにかくいろいろやらせたいわけで、それをすべて ruboty の gem にするのは、ちょっとキツすぎる。ruboty-serverspec とか名前だけで楽しそうですが。。。
最初は ruboty-aun を使わせてもらおうと思ったのですが、ちょっと思ってるのと違うなーというのと、勉強も兼ねて自分で作ってみることにしました。
ということで、その作った ruboty-exec_command の説明です。
gem 'ruboty-exec_command'
と書けまして
#!/bin/bash
if [ "$1" == "-h" ]; then
echo "Say hello to something"
exit
fi
echo "Hello! $*"
と置きます。
その心は!
$ bundle exec ruboty
Type `exit` or `quit` to end the session.
> ruboty help
ruboty /example hello/i - Say hello to something
ruboty /help( me)?\z/i - Show this help message
ruboty /ping\z/i - Return PONG to PING
ruboty /who am i\?/i - Answer who you are
> ruboty example hello world
Hello!
というように、commands の下のパスをハンドラとして登録し、それを実行した結果を取得します。いまのところ引数を取ってませんが・・・
デフォルトではカレントディレクトリを探しますが、${RUBOTY_ROOT}/commands
に実行ファイルを置くことも出来ます。
と、説明はこんなところで、以下体験記(2)です。
gem 工作体験記
なにぶん初めてなのでいろいろハマりましたが・・今後も役に立ちそうなところをピックアップしてみました。
仕様は以下のとおり。
- commands/ ディレクトリ以下の実行ファイルのパス名をハンドラとして登録
- つまり commands/hello/world がある場合
@ruboty hello world
でこのファイルを実行する
ひな形は ruboty-gen で作成。こういうコマンドがあるのはとってもありがたい。先人たちに感謝です。
$ ruboty-gen g exec_command command
せっかくなので、guard も追加。
spec.add_development_dependency "guard-rspec"
spec.add_development_dependency "growl"
$ guard init rspec
$ guard
lib しか見ないので Guardfile はシンプル。
guard :rspec, cmd: 'bundle exec rspec' do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
end
ハンドラの rspec は ruboty のコードをパク、、じゃなくて参考にしながら以下のようなものを書いてみました。(let(:to)
など一部省略)
let(:said) do
"@ruboty example hello"
end
let(:replied) do
"hello!"
end
describe "#command_handler" do
it "run example command" do
robot.should_receive(:say).with(
body: replied,
from: to,
to: from,
original: {
body: said,
from: from,
robot: robot,
to: to,
},
)
robot.receive(body: said, from: from, to: to)
end
end
ハンドラ書くのが一番大変だったわけで
ハンドラを書くわけですよ。
動かないわけですよ。
print デバッグするわけです。
なにも間違ってない。
guard が rspec 実行しますわな。
うん!動かない!
いろいろ悩んだですが、フックにハットが要らないということがわかりました。なんという初歩的ミス!
最初こう書いたわけです。
on /^hello world/i, name: "command_handler", description "hello world"
どーにもひっかからないなーと思ったらハットが要らなかったという。ただそれだけなんですが、悩んでしまいました。ハットがなくてもメッセージの先頭にマッチするみたいです。先頭以外をフックしたい場合は .*
とかつければいいんでしょうか。
最終的には実行ファイルを処理する Command クラスを作ったので、こんな風になりました。
Ruboty::ExecCommand::Command.all.each do |e|
on /#{e.command}/i, name: "command_handler", description: e.help
end
続いて、アクションを書くのも、また躓いてました。そう簡単にはいかない。
アクションに渡されるメッセージは、"@ruboty hello world"
とメンション含め全部渡されるわけですね。私としては、/path/to/hello/world を生成したいので @ruboty
が不要なわけです。単に
message.body.sub(ロボット名, '')
すればいいんですが、ロボット名がわからない。
ruboty のコードを読みながらしばらく頭をひねった結果、Ruboty::Action に prefix_pattern という素敵な関数が用意されてまして、これをそのまま使うことになりました。Ruboty::Messsage もくまなく読んで(print デバッグして)ロボット名も取れることがわかりました。
odule Ruboty
module Extender
module Actions
class Command < Ruboty::Actions::Base
def robot_name
Ruboty::Action.prefix_pattern(message.original[:robot].name)
end
def command_body
message.body.sub(robot_name,'')
end
出来上がったコードがなんとも Command だらけで、シンボルネームの管理としてどうなのかと不安になりつつ・・・今回はここらで終わりにしたいと思います。
過去記事は以下のとおりです。