Really Simple Git Deployment with Vlad
Posted by kev Sun, 06 Apr 2008 00:13:00 GMT
Just drop this in your Rakefile. This is slightly modified from something I’m using in production.
Disect! Enjoy! Explanation (read: spoilers) after the jump.
##############################################################################
# Deploy
##############################################################################
begin
require 'rake_remote_task'
APP_NAME = "someapp"
DEPLOY_ROOT = "/usr/local/share/applications/#{APP_NAME}"
ON_DEPLOY_RESTART = ["someappd"]
role :app_server, "myserver.com"
def archive
commit = `git-rev-list --max-count=1 --abbrev=10 --abbrev-commit HEAD`.chomp
file = "#{APP_NAME}-#{commit}.tar.gz"
end
def restart_daemons
ON_DEPLOY_RESTART.each do |app|
run "sudo god restart #{app}"
end
end
namespace :deploy do
task :build do
sh "git archive --format=tar HEAD | gzip > #{archive}"
end
remote_task :push => :build do
rsync archive, "/tmp"
end
desc "Install a release from the latest commit"
remote_task :install => :push do
date_stamp = Time.now.strftime("%Y%m%d")
last_release = run("ls #{DEPLOY_ROOT}/rels | sort -r | head -n 1").chomp
if last_release =~ /#{date_stamp}\-(\d+)/
serial = $1.to_i + 1
else
serial = 0
end
rel = ("%d-%02d" % [date_stamp, serial])
rel_dir = "#{DEPLOY_ROOT}/rels/#{rel}"
run "sudo mkdir -p #{rel_dir}"
run "sudo tar -xzvf /tmp/#{archive} -C #{rel_dir} && rm -rf /tmp/#{archive}"
run "sudo ln -s -f -T #{rel_dir} #{DEPLOY_ROOT}/current"
restart_daemons
end
desc "Rollback to the previous release"
remote_task :rollback do
current_link = run("ls -alF #{DEPLOY_ROOT} | awk '/current -> .*/ { print $NF }'").chomp
current = File.basename(current_link)
releases = run("ls #{DEPLOY_ROOT}/rels | sort -r").split("\n")
previous = releases.find {|rel| current > rel}
raise "No previous release" if previous.nil?
run "sudo ln -s -f -T #{DEPLOY_ROOT}/rels/#{previous} #{DEPLOY_ROOT}/current"
restart_daemons
puts "Moved to #{previous}"
end
desc "Rollforward to the next release"
remote_task :rollforward do
current_link = run("ls -alF #{DEPLOY_ROOT} | awk '/current -> .*/ { print $NF }'").chomp
current = File.basename(current_link)
releases = run("ls #{DEPLOY_ROOT}/rels | sort -r").split("\n")
next_rel = releases.find {|rel| current < rel}
raise "No next release" if next_rel.nil?
run "sudo ln -s -f -T #{DEPLOY_ROOT}/rels/#{next_rel} #{DEPLOY_ROOT}/current"
restart_daemons
puts "Moved to #{next_rel}"
end
end
rescue LoadError => e
puts "NOTE: Install vlad to get Kevin's awesome deployment tasks"
endWait, what?
I wanted the simplest deploy/rollforward/rollback functionality possible. No packages. No pulling from version control. No magic.
When you run rake deploy:install:
- The
installtask calls thepushtask which calls thebuildtask. - The
buildtask just dumps a tarball (woooo git). pushuses vlad’s rsync helper which pushes the git tarball to the server.installassigns an easily understood version to the tarball (year, month, date, serial number), unpacks the archive and adjusts thecurrentsymlink.runis a helper provided by vlad to execute on the server duringremote_tasks.
rollforward and rollback use a little shell magic[1] (yes, that *is* awk being run from a shell remotely through rake) to figure out what the version to go to and remaps the symlink. run("ls -alF #{DEPLOY_ROOT} | awk '/current -> .*/ { print $NF }'").chomp roughly translates to: run ls remotely, and return the string of characters that follow ‘current ->’, which is the target of the symlink.
Cap is great, but feels to heavy handed for lots of things. I’ve found vlad makes an excellent library for remote tasks that may or may not involve actually deploying an app. In this case, I’m not even using the built in tasks, just creating my own with remote_task.


can I use it with Ror 1.2 ?
@Sebastian: I don’t see why not. You might need to tweak it slightly to restart the right services. If you’re doing Rails apps Vlad’s built in recipes may do the job for you as well.
Sorry but how is that simple? Here’s the capistrano version:
set :application, "website" set :scm, :git set :ssh_options, { :forward_agent => true } set :repository, "git@github.com:user/#{application}.git" set :repository_cache, "git-cache" set :deploy_via, :remote_cache role :web, "dev.server.com" role :app, "dev.server.com" role :db, "dev.server.com", :primary => true namespace :deploy do task :start, :roles => :app do deploy.mongrel.start end task :stop, :roles => :app do deploy.mongrel.stop end task :restart, :roles => :app, :except => { :no_release => true } do deploy.mongrel.restart end namespace :mongrel do [ :stop, :start, :restart ].each do |t| task t, :roles => :app do run "umask 02 && cd #{current_path} && mongrel_rails cluster::#{t}" end end end endHi Tim, The difference is in the magic. Mine doesn’t have any. Maybe I’m just a curmudgeon, but I like understanding what my code is doing. Your code looks like a rake task, but there’s actually lots more going on. That’s great. It’s really powerful and a good choice for many, but frankly I find it obscure and confusing.
I think that fact that my tasks are specifically defined as something other than a normal rake task is a feature. I think that the only code that’s being executed I can’t see are methods I’m explicitly calling is a feature. Most importantly, I think the fact that I’m doing deploy, but just as easily could be doing other remote tasks, clearly, without understanding how a framework works, is a feature. In a technical sense, mine is the less complex code, if not in the number of lines sense.
Just consider that I’ve replaced your framework with a couple of helpers and some version logic. After that, I can hide code however I’d like.