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

これであれば、どの行に絞りこまれても影響はありません。

まとめ

parallel を使って意図的に絞込みを掛けるときは、

  • role で絞り込んだり
  • server を使って定義したり

して、条件のミスヒットを避けるようにしましょう。

おかげで疲れました…。