Created
May 17, 2013 00:54
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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