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
Read more...

Posted in ,  | 4 comments

Depth First Search

Posted by kev Tue, 27 Nov 2007 19:24:00 GMT

Because I hadn’t implemented DFS in Ruby before, and it’s just so damn easy.

Update: Phillip rightly pointed out in the comments that with the yield at the end, it’s actually post-order traversal, not depth first search per se.

class TreeNode
  attr_reader :name

  def initialize(name)
    @name = name
    @children = []
  end

  def add_node(node)
    @children << node
  end

  def each_depth_first
    @children.each do |child|
      child.each_depth_first do |c|
        yield c
      end
    end

    yield self
  end
end

# root  -  a  -  b
#   \       \ 
#    e - f   c  -  d
#     \
#      g

root = TreeNode.new("root")
root.add_node( a = TreeNode.new("a"))
a.add_node( b = TreeNode.new("b"))
a.add_node( c = TreeNode.new("c"))
c.add_node( d = TreeNode.new("d"))
root.add_node(e = TreeNode.new("e"))
e.add_node(f = TreeNode.new("f"))
e.add_node(g = TreeNode.new("g"))

root.each_depth_first do |child|
  puts child.name
end

# produces:
# b
# d
# c
# a
# f
# g
# e
# root

Posted in ,  | 3 comments

ForwardsToEnumerable

Posted by kev Tue, 30 Oct 2007 01:16:00 GMT

I haven’t yet decided if this is a good idea or not.

I’ll be at RubyConf this weekend. Say hello, if you get the urge.

require "test/unit"
require 'rubygems'
require 'mocha'
require 'stubba'

module ForwardsToEnumerable
  def self.included(klass)
    klass.extend(ClassMethods)
  end

  module ClassMethods
    def forward_to_enum(instance_var, *meths)
      meths.each do |meth|
        class_eval <<-METH
          def #{meth}(*args, &block)
            #{instance_var.to_s}.each do |i|
              i.send(:#{meth}, *args, &block)
            end
          end
        METH
      end
    end
  end

end

class ForwardsToArray
  include ForwardsToEnumerable
  forward_to_enum :@array, :foo, :bar, :baz

  def initialize(array)
    @array = array
  end
end

class TestForwardsToArray < Test::Unit::TestCase
  def test_forward_to_enum
    items = [mock(), mock(), mock()]
    items.each {|i| i.expects(:foo); i.expects(:bar); i.expects(:baz) }
    f = ForwardsToArray.new(items)
    f.foo
    f.bar
    f.baz
  end
end

Posted in ,  | no comments

RPM Version Comparison Revisited

Posted by kev Fri, 21 Sep 2007 20:21:00 GMT

# Equivalent to rpmvercmp in librpm, eccentricities and all
def <=>(other)
  return 0 if self.version == other.version and self.rel == other.rel

  versions = self.version.split(/[^[:alnum:]]/).push self.rel
  other_versions = other.version.split(/[^[:alnum:]]/).push other.rel

  return  1 if versions.size > other_versions.size
  return -1 if versions.size < other_versions.size

  versions.size.times do |n|
    if versions[n] =~ /[^\d]/ && other_versions[n] =~ /[^\d]/
      comparison = (versions[n] <=> other_versions[n])
    elsif versions[n] !~ /[^\d]/ && other_versions[n] !~ /[^\d]/
      comparison = (versions[n].to_i <=> other_versions[n].to_i)
    else
      comparison = -1
    end
    return comparison unless comparison.zero?
  end

  return 0 
end

Original version sort was here.

Posted in ,  | no comments

<3 rpm version comparison

Posted by kev Wed, 19 Sep 2007 21:19:00 GMT

/* take care of the case where the two version segments are */
/* different types: one numeric and one alpha */
if (one == str1) return -1; /* arbitrary */
if (two == str2) return -1;

– rpm/lib/misc.c

Posted in ,  | no comments

git-quickserver

Posted by kev Wed, 19 Sep 2007 07:35:00 GMT

/usr/bin/git-quickserver
#!/bin/sh

git daemon --verbose --reuseaddr --export-all --base-path='.'
sisyphus:~/code/god kev$ git quickserver
sisyphus:~ kev$ git clone git://localhost/ somethin
Initialized empty Git repository in /Users/kev/somethin/.git/
remote: Generating pack...
remote: Done counting 1469 objects.
remote: Deltifying 1469 objects...
 100% (1469/1remote: 469) done
Indexing 1469 objects...
remote: Total 1469 (delta 905), reused 1461 (delta 902)
 100% (1469/1469) done
Resolving 905 deltas...
 100% (905/905) done

(via KirinDave)

Posted in  | no comments

Growl-Based Bonjour Chat with Channels in 150 Lines

Posted by kev Sun, 06 May 2007 10:58:00 GMT

One of the many awesome things about working at Powerset is the guys I get to hack with. Tonight, my buddies Tom Preston-Werner, Chris Van Pelt, and I were feeling whimsical. Full source, with quicksilver hook and startup scripts can be found here, but this is the meat:

growl_handler.rb
require 'rubygems'
require 'ruby-growl'

module Jakl
  class GrowlHandler
    def initialize
      if `which growlnotify` =~ /^no .+ in/
        @strategy = :ruby
        @growl = Growl.new("localhost", "jakl", ["jakl_message"])
      else
        @strategy = :command
      end
    end

    def notify(group, name, message)
      case @strategy
        when :command
          img_path = File.join(File.dirname(__FILE__), '../../assets/jakl.png')
          `growlnotify -n jakl --image #{img_path} -m '#{message}' '#{name} (#{group})'`
        when :ruby
          @growl.notify("jakl_message", "#{name} (#{group})", message)
        else
          raise StandardError.new('Invalid strategy')
      end
    end
  end
end
client.rb
require 'rubygems'
require 'net/dns/mdns-sd'
require 'base64'

module Jakl
  class Client
    DNSSD = Net::DNS::MDNSSD

    @@debug = false

    def self.debug=(value)
      @@debug = value
    end

    def self.debug
      @@debug
    end

    def initialize(options={})
      default_options = {
        :default_recv => "jakl", 
        :timeout => 2, 
        :login => ENV['USER']
      }
      @options = default_options.merge(options)
    end

    def send(message, recv=nil)
      recv ||= @options[:default_recv]
      recv = recv.split(',').collect {|g| g.strip }
      puts "Sending: '#{message}' to '#{recv.join(',')}'" if @@debug

      find_recipients = DNSSD.resolve('jakl', '_jakl._tcp') do |r|
        puts "Found jakl service at #{r.target}" if @@debug
        recvs = r.text_record['recvs'].split(',').collect {|g| g.strip }
        puts "  responds to: #{recvs.join(', ')}"  if @@debug

        if (succ_recvs = recvs & recv).any?
          puts "Sending to: #{r.target}:#{r.port}" if @@debug

          # B64 Encoded NAME;GROUP;MESSAGE
          data = [@options[:login], succ_recvs.first, message].map do |s| 
            Base64.encode64(s)
          end.join(';')
          TCPSocket.new(r.target, r.port).send(data, 0)
        end
      end

      sleep @options[:timeout]
      find_recipients.stop
    end
  end
end
server.rb
require 'rubygems'
require 'eventmachine'
require 'net/dns/mdns-sd'
require 'base64'

$:.unshift File.dirname(__FILE__)
require 'growl_handler'

module JaklEventServer
  def receive_data(data)
    # B64 Encoded NAME;GROUP;MESSAGE
    name, recv, message = data.split(';').map {|s| Base64.decode64(s) }
    Jakl::GrowlHandler.new.notify(recv, name, message)
    $stderr.puts "Name: #{name}, Recipient: #{recv}, Message: #{message}" if Jakl::Server.debug
  end
end

module Jakl
  class Server
    DNSSD = Net::DNS::MDNSSD

    @@debug = false

    def self.debug=(value)
      @@debug = value
    end

    def self.debug
      @@debug
    end

    def initialize(options={})
      default_options = {
        :recvs => "jakl", 
        :timeout => 5, 
        :login => ENV['USER']
      }
      @options = default_options.merge(options)

      validate_login!
    end

    def start
      DNSSD.register('jakl', '_jakl._tcp', 'local', 4180, 
                             {'recvs' => @options[:recvs], 'login' => @options[:login]})

      EventMachine::run {
        EventMachine::start_server "0.0.0.0", 4180, JaklEventServer
        puts "Listening for howls on 4180"
      }
    end

    def validate_login!
      # Yeah, I know there's a race condition here. So it goes.
      name_validation = DNSSD.resolve('jakl', '_jakl._tcp') do |r|
        if [r.text_record['login'], 
            r.text_record['recvs'].split(',')].flatten.include? @options[:login]
          puts "The name #{@options[:login]} is already taken. Sorry :\\"
          exit 1
        end
      end

      # Add username to recvs
      @options[:recvs] = [@options[:recvs].split(',') << @options[:login]].join(',')

      # Wait for resolv thread to come back
      sleep 3
      name_validation.stop
    end
  end
end

Posted in ,  | no comments

Snippet: Shuffle an Array

Posted by kev Tue, 20 Mar 2007 07:47:00 GMT

class Array
  def shuffle
    sort { rand(3) - 1 }
  end
end

arr = (1..10).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr.shuffle
# => [1, 8, 6, 10, 9, 3, 7, 2, 5, 4]
arr.shuffle
# => [3, 7, 10, 4, 5, 8, 2, 6, 9, 1]

Posted in , ,  | 7 comments

Tormenting Your Tests with Heckle

Posted by kev Tue, 19 Dec 2006 09:24:00 GMT

Update: Ruby2Ruby is having gem propogation issues. Feel free to download the gem here directly and install via gem install ruby2ruby-1.1.2.gem.

Update 2: We’ve found a bug in the loading that causes problems when you supply a method to Heckle. A bug fix has been checked into the repo and we’re preparing a release. Look for 1.1.1 soonish.

Update 3: Ok, 1.1.1 is out the door. The gem server is syncing, so look for a new version this afternoon (12/20) with several bugs including the loading error fixed.

Yes, I know what you’re thinking. “Holy crap, Kevin posted for the first time in months! I thought he died, or got eaten by a corporate zombie, or set out on a epic adventure to find himself.” But hey, good things come to those who wait, right?

So, you’ve been waiting, and I’ve been writing Heckle. It’s a good thing.

Heckle is a mutation tester. It modifies your code and runs your tests to make sure they fail. The idea is that if code can be changed and your tests don’t notice, either that code isn’t being covered or it doesn’t do anything.

It’s a little weird, I know, but I like to think about it as pen-testing. It’s like hiring a white-hat hacker to try to break into your server and making sure you detect it. You learn the most by trying to break things and watching the outcome.

Anyway, Heckle was inspired by Jester, and Ryan Davis wrote a proof of concept at RubyConf. As he notes, I went a little nuts and much of the current implementation I rewrote that night or on the plane home.

You can install Heckle from Ruby Gems:

  gem install heckle --include-dependencies 

Let’s take the new toy out for a test drive.

Read more...

Posted in ,  | 10 comments

Colorize Your Tests

Posted by kev Tue, 22 Aug 2006 17:49:00 GMT

Are your test runs dull and listless? Do you miss the red/green bar of JUnit? Well, now you don’t have to! Between Pat Eyler’s RedGreen and Chris Wanstrath’s modifications you can have red, green and yellow tests. That doesn’t sound nearly as cool written, but take a look:

Colored Tests!

It makes me happy. Oh, and a 3 line addition to your ~/.autotest will make colorized autotest runs (pictured above).

Posted in , ,

Older posts: 1 2 3