Capistrano 2.0 and Tagged Releases
A few weeks ago Anthony setup http://vintagejesus.net. I was going to setup yet another deployment bash script on the server, but not all of our applications are deployed with a bash script. I stepped back and decided it was time to bring some order to the chaos of our current deployment strategy. Currently I use a lot of ‘organic’ bash scripts that deploy off of trunk, something I’ve never been a fan of. Mark enlightened me to use tagging to deploy apps. We use Capistrano to deploy some rails apps, so I figured I would set it up to deploy using tags. I was surprised I couldn’t find a plugin that I liked so I set off on building my own recipe.
The basic idea was that I wanted to run something like this:
cap deploy
The script should prompt for a tag, and if none is given prompt for a
tag name to apply to trunk and then deploy from the new tag.
As a bonus it makes rolling back a revision pretty easy. You can just select the previous tag, or use the built in:
cap deploy:rollback
To do this in Capistrano I created a set_tag recipe that lists the
recent tags and prompts to use one of them or create a new tag from
trunk. This updates the base_repository variable. I also had to
overwrite the default update_code action to call set_tag. (You may want to copy and paste it out since the formating gets a little hairy)
require 'rexml/document' #gem install rexml
set :application, "yourappname"
set :base_repository, "http://your.repository/#{application}"
set :repository, "#{base_repository}/trunk" # default, but is overwritten by set_tag
role :app, "some.prod.server", :primary => true
set :deploy_to, "/some/server/path"
set :use_sudo, false
namespace :deploy do
task :set_tag do
# get the release path
depend :local,:command,"svn"
show_recent_tags("#{base_repository}/tags")
tag = ::Capistrano::CLI.ui.ask("which tag (blank for trunk): ")
if tag == ""
new_tag = ::Capistrano::CLI.ui.ask("deploy from trunk, but you have to give it a tag: ")
raise Capistrano::Error, "have to tag a new release" if new_tag == ""
#raise error if there are invalid characters
raise Capistrano::Error, "invalid characters in tag" if new_tag =~ /[|]|\?|:|\/|\*|\"|\'||\||(\s)/
cmd = "svn copy quiet message \"cap tagged release\" #{base_repository}/trunk #{base_repository}/tags/#{new_tag}"
puts " * locally executing \"#{cmd}\""
system cmd
tag = new_tag
end
set :repository, "#{base_repository}/tags/#{tag}"
puts "repository: #{repository}"
end
# Overwrite update_code task to add call to set_tag
task :update_code, :except => { :no_release => true } do
set_tag
on_rollback { run "rm -rf #{release_path}; true" }
strategy.deploy!
finalize_update
end
def show_recent_tags(repo)
hsh = Hash.new
cmd = "svn ls --xml #{repo}"
puts " >> Listing tags: "
pipe = IO.popen cmd
result = pipe.read
pipe.close
entries = REXML::Document.new(result).root.elements['list']
entries.elements.each do |e|
# set hash key to release id and value to: name by: commit.author
hsh[e.elements['commit'].attribute('revision').to_s] = e.elements['name'].text + " \tby: " + e.elements['commit'].elements['author'].text
end
sorted_arr = hsh.sort {|a,b| a[0].to_i <=> b[0].to_i}
if sorted_arr.size > 5
sorted_arr.to_a[-5, 5].each {|t| puts "#{t[1]} \trev: #{t[0]} "}
else
sorted_arr.to_a.each {|t| puts "#{t[1]} \trev: #{t[0]} "}
end
end
end
Lets take a look at it in action:
cap deploy
>> Listing tags:
cap_test_0.0 by: sfaxon rev:163
cap_test_0.1 by: sfaxon rev: 167
which tag (blank for trunk): [type a tag listed above or hit return]
by just pressing return it asks:
deploying from trunk, but you have to give it a tag: [cap_test_0.2]
Next time I’ll show how we took this and applied it to other applications.
Update: direct link to the code above.





Codex Content