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"
end

Wait, 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 install task calls the push task which calls the build task.
  • The build task just dumps a tarball (woooo git).
  • push uses vlad’s rsync helper which pushes the git tarball to the server.
  • install assigns an easily understood version to the tarball (year, month, date, serial number), unpacks the archive and adjusts the current symlink. run is a helper provided by vlad to execute on the server during remote_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.

Posted in ,  | 4 comments

Comments

  1. Avatar Sebastian said about 18 hours later:

    can I use it with Ror 1.2 ?

  2. Avatar Kevin Clark said about 22 hours later:

    @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.

  3. Avatar Tim Dysinger said 12 days later:

    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
    end
    
  4. Avatar Kevin Clark said 12 days later:

    Hi 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.

Comments are disabled