Daily Code Reading #20 – Capistrano recipes – deploy

I’m finishing up my code reading of Capistrano‘s recipes with the recipe that is run most often, deploy.

The Code and Review

deploy

1
2
3
4
5
6
namespace :deploy do
  task :default do
    update
    restart
  end
end

The deploy recipe itself is simple and just runs deploy:update and deploy:restart.

deploy:update

1
2
3
4
5
6
7
8
namespace :deploy do
  task :update do
    transaction do
      update_code
      symlink
    end
  end
end

deploy:update uses a transaction to make sure both deploy:update_code and deploy:symlink run successfully.

deploy:update_code

1
2
3
4
5
6
7
namespace :deploy do
  task :update_code, :except => { :no_release => true } do
    on_rollback { run "rm -rf #{release_path}; true" }
    strategy.deploy!
    finalize_update
  end
end

Now deploy:update_code starts to do some interesting work. First it defines an on_rollback so Capistrano knows how to revert the code updates. Next it calls Capistrano::Deploy::Strategy#deploy! to do the code update. This class provides a standard interface for how each of the different :deploy_via options work on the remote servers. Finally, deploy:finalize_update is called.

deploy:finalize_update

1
2
3
4
5
6
7
8
9
namespace :deploy do
  task :finalize_update, :except => { :no_release => true } do
    run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)
 
    # mkdir -p is making sure that the directories are there for some SCM's that don't
    # save empty folders
    run < { "TZ" => "UTC" }
    end
end

deploy:finalize_update does a few housecleaning tasks to make sure all of the files are setup correctly:

  1. Changes the release files to be group writable if the configuration option for :group_writable is set.
  2. Removes the log, system, and tmp/pids directories, which should be symlinks.
  3. Creates a public and tmp directory in the latest release.
  4. Symlinks the shared log, system, and pids directories into the latest release.
  5. Finally, it updates the timestamps of the public assets. This is done so when Rails links to assets they will have a newer url.

Next deploy:finalize_update and deploy:update_code return control back to deploy:update which runs deploy:symlink next.

deploy:symlink

1
2
3
4
5
6
7
8
9
10
11
12
namespace :deploy do
  task :symlink, :except => { :no_release => true } do
    on_rollback do
      if previous_release
        run "rm -f #{current_path}; ln -s #{previous_release} #{current_path}; true"
      else
        logger.important "no previous release to rollback to, rollback of symlink skipped"
      end
    end
 
    run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
  end

deploy:symlink is pretty simple:

  1. It defines an on_rollback to revert errors.
  2. It removes the current path (remember that it’s just a symlink to the deployed release directory).
  3. It creates a symlink from the latest release (being deployed) to the current path, thus “moving” the symlink target.

Now deploy:update is done so it returns back to deploy which now runs deploy:restart.

deploy:restart

1
2
3
4
5
6
namespace :deploy do
  task :restart, :roles => :app, :except => { :no_release => true } do
    warn "[DEPRECATED] `deploy:restart` is going to be changed to Passenger mod_rails' method after 2.5.9 - see http://is.gd/2BPeA"
    try_runner "#{current_path}/script/process/reaper"
  end
end

The try_runner method is just a simple wrapper that will try to run a command using sudo but as a different user. In this case, Capistrano is running Rails’ reaper script, which is supposed to restart the application servers. I haven’t used reaper scripts for a long time, I think pre Rails 1.0 days. Instead I just override deploy:restart with my own configuration.

1
2
3
4
5
6
7
8
# Example deploy:restart for Passenger
#
namespace :deploy do
  desc "Restart Application"
  task :restart, :roles => :app do
    run "touch #{current_path}/tmp/restart.txt"
  end
end

This concludes my code reading on the Capistrano codebase. I looked at how the cap command is translated into the recipes and then examined a few of the common Capistrano recipes and what commands they run.

Next week, I’ll be starting to read through a new project. Which one would you be interested in: