Skip to content

Instantly share code, notes, and snippets.

@yanatan16
Created May 17, 2013 00:54
Show Gist options
  • Save yanatan16/5596269 to your computer and use it in GitHub Desktop.
Save yanatan16/5596269 to your computer and use it in GitHub Desktop.
Copy and Rsync Strategy for capistrano. This may not work immediately, as I had to strip out some of my app-specific code.
require 'capistrano/recipes/deploy/strategy/base'
require 'fileutils'
require 'tempfile' # Dir.tmpdir
# Copy with Rsync capability.
class CopyRsync < Capistrano::Deploy::Strategy::Base
# Obtains a copy of the source code locally (via the #command method),
# compresses it to a single file, copies that file to all target
# servers, and uncompresses it on each of them into the deployment
# directory.
def deploy!
# Don't build twice
if not deployed_previously?
run_copy_cache_strategy
create_revision_file
deployed_previously
end
distribute!
end
# Distributes the file to the remote servers
def distribute!
run "if [ -d #{repository_cache} ]; then ls >/dev/null; else " +
"mkdir #{repository_cache} && chmod a+w #{repository_cache}; fi"
servers.each do |host|
rsync "#{destination}/ #{user}@#{host}:#{repository_cache}"
end
run "chown -R Administrators #{repository_cache}"
end
def build directory
execute "running build script on #{directory}" do
Dir.chdir(directory) { system(build_script) }
end if build_script and not exists?(:nobuild)
end
def check!
super.check do |d|
d.local.command(source.local.command) if source.local.command
d.local.command(compress(nil, nil).first)
d.remote.command(decompress(nil).first)
end
end
# Returns the location of the local copy cache, if the strategy should
# use a local cache + copy instead of a new checkout/export every
# time. Returns +nil+ unless :copy_cache has been set. If :copy_cache
# is +true+, a default cache location will be returned.
def copy_cache
@copy_cache ||= configuration[:copy_cache] == true ?
File.expand_path(configuration[:application], Dir.tmpdir), acquire_lock) :
File.expand_path(configuration[:copy_cache], Dir.pwd)
end
def release_cache
@copy_cache.release
end
private
def queue_unlock
task = task_call_frames[0].task.fully_qualified_name
after task, "deploy:unlock"
end
def servers
find_servers(current_task.options)
end
def do_run_locally(command)
logger.info "Running #{command}"
logger.info `#{command}`
end
def rsync(args)
if copy_exclude.empty?
exclusions = ""
else
exclusions = copy_exclude.map { |e| "--exclude=\"#{e}\"" }.join(' ')
end
opts = "-rtz --perms --delete"
do_run_locally "rsync #{opts} #{exclusions} #{args}"
end
def deployed_previously
@prevdep = true
end
def deployed_previously?
@prevdep || false
end
def run_copy_cache_strategy
copy_repository_to_local_cache
build copy_cache
end
def execute description, &block
logger.debug description
handle_system_errors &block
end
def handle_system_errors &block
block.call
raise_command_failed if last_command_failed?
end
def refresh_local_cache
execute "refreshing local cache to revision #{revision} at #{copy_cache}" do
system(source.sync(revision, copy_cache))
end
end
def create_local_cache
execute "preparing local cache at #{copy_cache}" do
system(source.checkout(revision, copy_cache))
end
end
def raise_command_failed
raise Capistrano::Error, "shell command failed with return code #{$?}"
end
def last_command_failed?
$? != 0
end
def copy_files files
files.each { |name| process_file(name) }
end
def process_file name
send "copy_#{filetype(name)}", name
end
def filetype name
filetype = File.ftype name
filetype = "file" unless ["link", "directory"].include? filetype
filetype
end
def copy_link name
FileUtils.ln_s(File.readlink(name), File.join(destination, name))
end
def copy_directory name
FileUtils.mkdir(File.join(destination, name))
copy_files(queue_files(name))
end
def copy_file name
FileUtils.ln(name, File.join(destination, name))
end
def queue_files directory=nil
Dir.glob(pattern_for(directory), File::FNM_DOTMATCH).reject! { |file| excluded_files_contain? file }
end
def pattern_for directory
!directory.nil? ? "#{directory}/*" : "*"
end
def excluded_files_contain? file
copy_exclude.any? { |p| File.fnmatch(p, file) } or [ ".", ".."].include? File.basename(file)
end
def copy_repository_to_server
execute "getting (via #{copy_strategy}) revision #{revision} to #{destination}" do
copy_repository_via_strategy
end
end
def copy_repository_via_strategy
system(command)
end
def remove_excluded_files
logger.debug "processing exclusions..."
copy_exclude.each do |pattern|
delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH)
# avoid the /.. trap that deletes the parent directories
delete_list.delete_if { |dir| dir =~ /\/\.\.$/ }
FileUtils.rm_rf(delete_list.compact)
end
end
def create_revision_file
File.open(File.join(destination, "REVISION"), "w") { |f|
f.puts(revision)
}
end
def copy_repository_to_local_cache
return refresh_local_cache if File.exists?(copy_cache)
create_local_cache
end
def build_script
configuration[:build_script]
end
# Specify patterns to exclude from the copy. This is only valid
# when using a local cache.
def copy_exclude
@copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
end
# Returns the basename of the release_path, which will be used to
# name the local copy and archive file.
def destination
@destination ||= copy_cache
end
# Returns the value of the :copy_strategy variable, defaulting to
# :checkout if it has not been set.
def copy_strategy
@copy_strategy ||= configuration.fetch(:copy_strategy, :checkout)
end
# Should return the command(s) necessary to obtain the source code
# locally.
def command
@command ||= case copy_strategy
when :checkout
source.checkout(revision, destination)
when :export
source.export(revision, destination)
end
end
# Returns the name of the file that the source code will be
# compressed to.
# The directory to which the copy should be checked out
def copy_dir
@copy_dir ||= File.expand_path(configuration[:copy_dir] || Dir.tmpdir, Dir.pwd)
end
def repository_cache
configuration[:deploy_to]
end
def copy_exclude
@copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment