Capistrano の parallel の罠を踏み抜いた。
capistrano の parallel を使っていて罠にハマったのでご報告致します。
parallel おさらい
parallel は条件に基づいて、実行するコマンドを変化させるコマンドです。
たとえば、
task :setup do parallel do |session| session.when "in?(:app)", "echo this host is application role!" session.when "in?(:db)", "echo this host is db role!" sesion.else "echo this host is other role" end end
と書くと、ロールに基づいて echo する値を切り替えることができます。
僕がいままで使っていたのは、role 定義のときに
role :app, "web1.example.com", :route_type => :admin role :app, "web2.example.com", :route_type => :portal
のように属性を定義しておいて
task :setup_route do parallel do |session| session.when "options[:route_type] == :admin", setup_script_for(:admin) session.when "options[:route_type] == :portal", setup_script_for(:portal) sesion.else setup_script_for(:others) end end def setup_script_for(route_type) "cp route.rb.#{route_type} route.rb" end
こんなふうに属性によって実行するスクリプトを差し替えるようにしていました。
上記の setup_route タスクでは、:route_type の値によって、コピーする routes.rb が変化します。
ハマった罠
このやり方は大抵うまく動くのですが、あるホストが複数のロールを兼ねる場合に問題が起きます。
こんなケースです。
role :web, "localhost" role :app, "localhost", :route_type => :admin role :db, "localhost", :primary => true
開発環境などでは、ひとつのホストで幾つものロールを兼ねることになるので、こんな定義になります。
この状態でさっきの setup_route タスクを実行してみるとどうなるでしょう。
設定からは、routes.rb.admin がコピーされると予想されるのですが、実際には routes.rb.others がコピーされます。
なぜかを追ってみたところ、parallel で取得されるのサーバ定義情報は「絞り込み条件に一致する一番最初の定義」になっていました。
ここで言う「絞り込み条件」というのはタスク定義の絞り込み条件です。
setup_routes タスクは絞り込み条件を書いていないので、すべての定義が対象になります。
つまり、
role :web, "localhost"
の行です。
この定義には属性が指定されていないため、session.else で示されるコマンドが選択されて実行されていました。
うまくやる方法 (その1)
先ほど書いた「絞り込み条件」をただしく明示します。
ここでは app ロールのだけを対象に絞り込みたいので、タスク定義のところに :roles 区で条件を追加します。
task :setup_route, :roles => [:app] do parallel do |session| session.when "options[:route_type] == :admin", setup_script_for(:admin) session.when "options[:route_type] == :portal", setup_script_for(:portal) sesion.else setup_script_for(:others) end end
こうすると、「絞り込み条件に一致する一番最初の定義」は :app ロールの行になるので、意図通りに動かすことができます。
うまくやる方法 (その2)
もうひとつの方法は、定義の仕方を変えることです。
capistrano には role によるホストの定義の他に、もうひとつ server という定義方法があるのでこれを使います。
server "localhost", :web, :app, :db, :route_type => :admin, :primary => true
server による定義は、次のように展開されます。
role :web, "localhost", :route_type => :admin, :primary => true role :app, "localhost", :route_type => :admin, :primary => true role :db, "localhost", :route_type => :admin, :primary => true
これであれば、どの行に絞りこまれても影響はありません。