HomeRumblingsSoftwareTravelingArchivesAbout
February 2008 Archives

Capistrano: Programmatically adjusting task execution based on roles

Capistrano started as the Rails deployment tool. Really impressive stuff, like automatic rollback of all software to the previous version if anything went wrong (with a good use of svn) and several limitations (too Rails-centric, too SVN-centric). Capistrano 2 (currently at 2.1.0) went back to the basics, split the Rails stuff and defined a very nice subset for doing remote operations on many servers.
As always, my use case deviates from the normal.

The idea is to be able to say

cap deploy:some_package DEPLOY_TO=some_node

and have Capistrano assemble and configure the package, copy it and install it.
Lets build some vocabulary first:

A simplified scenario would be an application package A and a database package D.
They can be deployed on separate servers (two nodes each with the parameters for the corresponding package) or on the same server (a single node with the parameters for both packages).

Now supposed we have sold our super duper application to 2 clients, each of them has setup 2 servers and they wait for us to deploy.

The simple way would be to define the server to deploy to on the command line (i.e. using capistrano variables ‘cap -s’), or edit the capfile everytime and assign the correct ip address to each role.
The more releases – and clients – you get, the more “mühsam” this is (mühsam means laborious in german). In other words, the process won’t scale.
Given the definition above, the address of the server should be part of the node information.

Luckily, Capistrano uses lazy evaluation for most of it’s functionality, meaning values are calculated when they are needed and not when the script loads.
This means that we can assign a task to a role and define/modify the role just before the first remote operation is executed


task :example, roles=>:important do
role :important, “important@server”
run(“start important example”)
end

Caveat: Calling role does not replace the values for the role, it just adds new values, so the following
	
role :important, “important@server”
task :example, roles=>:important do
role :important, “important@other.server”
run(“start important example”)
end

means that the ‘important example’ runs on important@other.server and important@server.

Posted by Vassilis Rizopoulos on Feb 15, 2008