catalyst で作ったアプリを capistrano でデプロイ

capistrano インストール

gem install capistrano

Capfile を配置

capify コマンドで Capfile と config/deploy.rb を作ってくれるが、今回は自前で用意する。

好きなディレクトリに下記内容の Capfile ファイルを配置する。
catalyst.pl コマンドで作ったディレクトリの直下が解りやすいかも。

load 'conf/deploy'

これで、conf/deploy.rb を読み込んでくれる。

deploy.rb を配置

Capfile で指定した path に下記内容の deploy.rb ファイルを配置する。

require 'capistrano/recipes/deploy/scm'
require 'capistrano/recipes/deploy/strategy'

set :application, "AppName"
set :repository,  "http://domain/path/to/AppName/"

set :scm, :subversion
set :deploy_via, :checkout

set(:deploy_to) { "/path/to/#{application}" }
set(:revision)  { source.head }

set(:source)    { Capistrano::Deploy::SCM.new(scm, self) }
set(:real_revision) {
  source.local.query_revision(revision) { |cmd|
    with_env("LC_ALL", "C") {`#{cmd}`}
  }
}

set(:strategy) { Capistrano::Deploy::Strategy.new(deploy_via, self) }

set(:release_name) {
  set :deploy_timestamped, true; Time.now.utc.strftime("%Y%m%d%H%M%S")
}

set(:releases_path) { File.join(deploy_to, "releases") }
set(:current_path)  { File.join(deploy_to, "current") }
set(:release_path)  { File.join(releases_path, release_name) }

set(:releases)         { capture("ls -x #{releases_path}").split.sort }
set(:current_release)  { File.join(releases_path, releases.last) }
set(:previous_release) { File.join(releases_path, releases[-2]) }

set(:current_revision)  { capture("cat #{current_path}/REVISION").chomp }
set(:latest_revision)   { capture("cat #{current_release}/REVISION").chomp }
set(:previous_revision) { capture("cat #{previous_release}/REVISION").chomp}

set(:latest_release) {
  exists?(:deploy_timestamped) ? release_path : current_release
}

set(:run_method) { fetch(:use_sudo, true) ? :sudo : :run }

def with_env(name, value)
  saved, ENV[name] = ENV[name], value 
  yield
ensure
  ENV[name] = saved
end

role :servers, "domain name or ip address", "domain name or ip address"

namespace :deploy do
  desc "deploy."
  task :default do
    update
    restart
  end
  
  task :update do
    transaction do
      update_code
      symlink
    end
  end
  
  task :update_code, :except => { :no_release => true } do
    on_rollback { run "rm -rf #{release_path}; true" }
    strategy.deploy!
    finalize_update
  end

  task :finalize_update, :except => { :no_release => true } do
    stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
    asset_paths = %w(images css js).map { |p|
      "#{latest_release}/root/static/#{p}"
    }.join(" ")
    run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
  end

  task :symlink, :except => { :no_release => true } do
    on_rollback {
      run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true"
    }
    run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
  end

  task :restart do
    sudo "/usr/local/etc/rc.d/apache22 stop"
    sudo "/usr/local/etc/rc.d/apache22 start"
  end

  desc "rollback."
  task :rollback do
    rollback_code
    restart
  end

  task :rollback_code, :except => { :no_release => true } do
    if releases.length < 2
      abort "could not rollback the code because there is no prior release"
    else
      run "rm #{current_path}; ln -s #{previous_release} #{current_path} && rm -rf #{current_release}"
    end
  end

  desc "setup."
  task :setup, :except => { :no_release => true } do
    dirs = [deploy_to, releases_path]
    run "umask 02 && mkdir -p #{dirs.join(' ')}"
  end

  desc "cleanup."
  task :cleanup, :except => { :no_release => true } do
    count = fetch(:keep_releases, 5).to_i
    if count >= releases.length
      logger.important "no old releases to clean up"
    else
      logger.info "keeping #{count} of #{releases.length} deployed releases"
      directories = (releases - releases.last(count)).map { |release|
        File.join(releases_path, release) }.join(" ")
        invoke_command "rm -rf #{directories}", :via => run_method
    end
  end
end

capistrano/recipes/deploy.rb まる写し。

cap の引数 説明
deploy サーバにアプリを配置して、apache を再起動
deploy:rollback リリースを一世代戻して、apache を再起動
deploy:setup サーバに配置先のディレクトリを作成する
deploy:cleanup 五世代前のリリースを削除する

role を app と web に分けたら実用に近づきそう。

古い subversion を使っていると、svn info を実行する際にエラーが出るので注意。