Skip to content

Commit

Permalink
Improved locking: use raise instead of abort, create lock before upda…
Browse files Browse the repository at this point in the history
…te_code
  • Loading branch information
giosh94mhz committed May 26, 2014
1 parent a977c17 commit 47cc237
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 8 deletions.
4 changes: 4 additions & 0 deletions README.rdoc
Expand Up @@ -15,6 +15,10 @@ Include in your Capfile
Define the name of your lock file in your deploy.rb
set :lockfile, "my.lock" # defaults to "cap.lock"

The `lock:check` task is executed automatically before `deploy:update_code` (i.e. before deploy).
If you have overridden the default deploy task, you may need to add the task with:
before "deploy", "lock:check"

If the lockfile becomes stale (because you interrupted the deployment with CTRL-C for example)
cap lock:release # or "cap production lock:release" with multistage

Expand Down
17 changes: 12 additions & 5 deletions lib/caplock.rb
Expand Up @@ -2,7 +2,9 @@

module Capistrano
module Caplock


class LockedDeployError < RuntimeError ; end

# Returns Boolean indicating the result of +filetest+ on +full_path+ on the server, evaluated by shell on
# the server (usually bash or something roughly compatible).
def remote_filetest_passes?(filetest, full_path)
Expand Down Expand Up @@ -35,12 +37,16 @@ def remote_file_differs?(full_path, content)
def self.load_into(configuration)
configuration.load do
set :lockfile, "cap.lock"


# internal
set :keep_lock, false

namespace :lock do
desc "check lock"
task :check, :roles => :app do
if caplock.remote_file_exists?("#{deploy_to}/#{lockfile}")
abort "\n\n\n\e[0;31m A Deployment is already in progress\n Remove #{deploy_to}/#{lockfile} to unlock \e[0m\n\n\n"
keep_lock = true
raise LockedDeployError, "\n\n\n\e[0;31m A Deployment is already in progress\n Remove #{deploy_to}/#{lockfile} to unlock \e[0m\n\n\n"
end
end

Expand All @@ -49,16 +55,17 @@ def self.load_into(configuration)
timestamp = Time.now.strftime("%m/%d/%Y %H:%M:%S %Z")
lock_message = "Deploy started at #{timestamp} in progress"
put lock_message, "#{deploy_to}/#{lockfile}", :mode => 0644
on_rollback { find_and_execute_task("lock:release") }
end

desc "release lock"
task :release, :roles => :app do
run "rm -f #{deploy_to}/#{lockfile}"
run "rm -f #{deploy_to}/#{lockfile}" unless keep_lock
end
end

# Deployment
before "deploy", "lock:check"
before "deploy:update_code", "lock:check"
after "lock:check", "lock:create"
after "deploy", "lock:release"

Expand Down
57 changes: 54 additions & 3 deletions test/test_caplock.rb
@@ -1,13 +1,44 @@
require 'helper'

class StubLogger
def close ; end
def log(level, message, line_prefix=nil) ; end
def important(message, line_prefix=nil) ; end
def info(message, line_prefix=nil) ; end
def debug(message, line_prefix=nil) ; end
def trace(message, line_prefix=nil); end
def format(message, color, style, nl = "\n") ; end
end

class TestCaplock < Test::Unit::TestCase
class TestCaplockError < RuntimeError ; end

def setup
@update_code_raise = false;
@config = Capistrano::Configuration.new
@config.load do
namespace :deploy do
task :default do
transaction do
update_code
end
end
task :update_code do
raise TestCaplockError, "update_code simulated exception" if caplock_update_code_raise
end
end
end
@config.logger = StubLogger.new
#@config.logger = Capistrano::Logger.new :level => Capistrano::Logger::MAX_LEVEL
Capistrano::Caplock.load_into(@config)
@config.set :deploy_to, "/tmp"
@config.set :caplock_update_code_raise, false
@config.role :app, "localhost"

# ensure clean test
File.unlink("/tmp/cap.lock") if File.exists?("/tmp/cap.lock")
end

should "use default lockfile name 'cap.lock'" do
assert_equal @config.lockfile, 'cap.lock'
end
Expand All @@ -33,15 +64,35 @@ def setup
assert_nil @config.lock.check
end

# FIXME: need to find a way to test 'abort'
should "check for lock and abort" do
assert_nil @config.lock.create
#assert_raises SystemExit, @config.lock.check
assert_raises(Capistrano::Caplock::LockedDeployError) { @config.lock.check }
end

should "check if remote file exists" do
@config.set :lockfile, 'test.lock'
assert_nil @config.lock.create
assert @config.caplock.remote_file_exists?("/tmp/test.lock")
end

should "remove lock on rollback" do
@config.set :caplock_update_code_raise, true
assert_raises(TestCaplockError) { @config.deploy.default }
assert_nil @config.lock.check
end

should "not remove lock owned by other process" do
File.open("/tmp/cap.lock", "w") { |file| file.write("Simulate cap.lock from another deploy process") }
assert File.exists?("/tmp/cap.lock")
assert_raises(Capistrano::Caplock::LockedDeployError) { @config.deploy.default }
# rollback should not have removed the file, so it is still locked
assert_raises(Capistrano::Caplock::LockedDeployError) { @config.lock.check }
end

should "remove lock owned by other process on manual release" do
File.open("/tmp/cap.lock", "w") { |file| file.write("Simulate cap.lock from another deploy process") }
assert File.exists?("/tmp/cap.lock")
assert_nil @config.lock.release
assert_nil @config.lock.check
end
end

0 comments on commit 47cc237

Please sign in to comment.