aboutsummaryrefslogtreecommitdiff
path: root/vendor/rsync_command/lib
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/rsync_command/lib')
-rw-r--r--vendor/rsync_command/lib/rsync_command.rb96
-rw-r--r--vendor/rsync_command/lib/rsync_command/ssh_options.rb159
-rw-r--r--vendor/rsync_command/lib/rsync_command/thread_pool.rb36
-rw-r--r--vendor/rsync_command/lib/rsync_command/version.rb3
4 files changed, 294 insertions, 0 deletions
diff --git a/vendor/rsync_command/lib/rsync_command.rb b/vendor/rsync_command/lib/rsync_command.rb
new file mode 100644
index 0000000..39e5945
--- /dev/null
+++ b/vendor/rsync_command/lib/rsync_command.rb
@@ -0,0 +1,96 @@
+require "rsync_command/version"
+require "rsync_command/ssh_options"
+require "rsync_command/thread_pool"
+
+require 'monitor'
+
+class RsyncCommand
+ attr_accessor :failures, :logger
+
+ def initialize(options={})
+ @options = options.dup
+ @logger = @options.delete(:logger)
+ @flags = @options.delete(:flags)
+ @failures = []
+ @failures.extend(MonitorMixin)
+ end
+
+ #
+ # takes an Enumerable and iterates each item in the list in parallel.
+ #
+ def asynchronously(array, &block)
+ pool = ThreadPool.new
+ array.each do |item|
+ pool.schedule(item, &block)
+ end
+ pool.shutdown
+ end
+
+ #
+ # runs rsync, recording failures
+ #
+ def exec(src, dest, options={})
+ @failures.synchronize do
+ @failures.clear
+ end
+ rsync_cmd = command(src, dest, options)
+ if options[:chdir]
+ rsync_cmd = "cd '#{options[:chdir]}'; #{rsync_cmd}"
+ end
+ @logger.debug rsync_cmd if @logger
+ ok = system(rsync_cmd)
+ unless ok
+ @failures.synchronize do
+ @failures << {:source => src, :dest => dest, :options => options.dup}
+ end
+ end
+ end
+
+ #
+ # returns true if last exec returned a failure
+ #
+ def failed?
+ @failures && @failures.any?
+ end
+
+ #
+ # build rsync command
+ #
+ def command(src, dest, options={})
+ src = remote_address(src)
+ dest = remote_address(dest)
+ options = @options.merge(options)
+ flags = []
+ flags << @flags if @flags
+ flags << options[:flags] if options.has_key?(:flags)
+ flags << '--delete' if options[:delete]
+ flags << includes(options[:includes]) if options.has_key?(:includes)
+ flags << excludes(options[:excludes]) if options.has_key?(:excludes)
+ flags << SshOptions.new(options[:ssh]).to_flags if options.has_key?(:ssh)
+ "rsync #{flags.compact.join(' ')} #{src} #{dest}"
+ end
+
+ private
+
+ #
+ # Creates an rsync location if the +address+ is a hash with keys :user, :host, and :path
+ # (each component is optional). If +address+ is a string, we just pass it through.
+ #
+ def remote_address(address)
+ if address.is_a? String
+ address # assume it is already formatted.
+ elsif address.is_a? Hash
+ [[address[:user], address[:host]].compact.join('@'), address[:path]].compact.join(':')
+ end
+ end
+
+ def excludes(patterns)
+ [patterns].flatten.compact.map { |p| "--exclude='#{p}'" }
+ end
+
+ def includes(patterns)
+ [patterns].flatten.compact.map { |p| "--include='#{p}'" }
+ end
+
+end
+
diff --git a/vendor/rsync_command/lib/rsync_command/ssh_options.rb b/vendor/rsync_command/lib/rsync_command/ssh_options.rb
new file mode 100644
index 0000000..494ec9d
--- /dev/null
+++ b/vendor/rsync_command/lib/rsync_command/ssh_options.rb
@@ -0,0 +1,159 @@
+#
+# Converts capistrano-style ssh configuration (which uses Net::SSH) into a OpenSSH command line flags suitable for rsync.
+#
+# For a list of the options normally support by Net::SSH (and thus Capistrano), see
+# http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
+#
+# Also, to see how Net::SSH does the opposite of the conversion we are doing here, check out:
+# https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/config.rb
+#
+# API mismatch:
+#
+# * many OpenSSH options not supported
+# * some options only make sense for Net::SSH
+# * compression: for Net::SSH, this option is supposed to accept true, false, or algorithm. OpenSSH accepts 'yes' or 'no'
+#
+class RsyncCommand
+ class SshOptions
+
+ def initialize(options={})
+ @options = parse_options(options)
+ end
+
+ def to_flags
+ if @options.empty?
+ nil
+ else
+ %[-e "ssh #{@options.join(' ')}"]
+ end
+ end
+
+ private
+
+ def parse_options(options)
+ options.map do |key, value|
+ next unless value
+ # Convert Net::SSH options into OpenSSH options.
+ case key
+ when :auth_methods then opt_auth_methods(value)
+ when :bind_address then opt('BindAddress', value)
+ when :compression then opt('Compression', value ? 'yes' : 'no')
+ when :compression_level then opt('CompressionLevel', value.to_i)
+ when :config then "-F '#{value}'"
+ when :encryption then opt('Ciphers', [value].flatten.join(','))
+ when :forward_agent then opt('ForwardAgent', value)
+ when :global_known_hosts_file then opt('GlobalKnownHostsFile', value)
+ when :hmac then opt('MACs', [value].flatten.join(','))
+ when :host_key then opt('HostKeyAlgorithms', [value].flatten.join(','))
+ when :host_key_alias then opt('HostKeyAlias', value)
+ when :host_name then opt('HostName', value)
+ when :kex then opt('KexAlgorithms', [value].flatten.join(','))
+ when :key_data then nil # not supported
+ when :keys then [value].flatten.select { |k| File.exist?(k) }.map { |k| "-i '#{k}'" }
+ when :keys_only then opt('IdentitiesOnly', value ? 'yes' : 'no')
+ when :languages then nil # not applicable
+ when :logger then nil # not applicable
+ when :paranoid then opt('StrictHostKeyChecking', value ? 'yes' : 'no')
+ when :passphrase then nil # not supported
+ when :password then nil # not supported
+ when :port then "-p #{value.to_i}"
+ when :properties then nil # not applicable
+ when :proxy then nil # not applicable
+ when :rekey_blocks_limit then nil # not supported
+ when :rekey_limit then opt('RekeyLimit', reverse_interpret_size(value))
+ when :rekey_packet_limit then nil # not supported
+ when :timeout then opt('ConnectTimeout', value.to_i)
+ when :user then "-l #{value}"
+ when :user_known_hosts_file then multi_opt('UserKnownHostsFile', value)
+ when :verbose then opt('LogLevel', interpret_log_level(value))
+ end
+ end.compact
+ end
+
+ private
+
+ def opt(option_name, option_value)
+ "-o #{option_name}='#{option_value}'"
+ end
+
+ def multi_opt(option_name, option_values)
+ [option_values].flatten.map do |value|
+ opt(option_name, value)
+ end.join(' ')
+ end
+
+ #
+ # In OpenSSH, password and pubkey default to 'yes', hostbased defaults to 'no'.
+ # Regardless, if :auth_method is configured, then we explicitly set the auth method.
+ #
+ def opt_auth_methods(value)
+ value = [value].flatten
+ opts = []
+ if value.any?
+ if value.include? 'password'
+ opts << opt('PasswordAuthentication', 'yes')
+ else
+ opts << opt('PasswordAuthentication', 'no')
+ end
+ if value.include? 'publickey'
+ opts << opt('PubkeyAuthentication', 'yes')
+ else
+ opts << opt('PubkeyAuthentication', 'no')
+ end
+ if value.include? 'hostbased'
+ opts << opt('HostbasedAuthentication', 'yes')
+ else
+ opts << opt('HostbasedAuthentication', 'no')
+ end
+ end
+ if opts.any?
+ return opts.join(' ')
+ else
+ nil
+ end
+ end
+
+ #
+ # Converts the given integer size in bytes into a string with 'K', 'M', 'G' suffix, as appropriate.
+ #
+ # reverse of interpret_size in https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/config.rb
+ #
+ def reverse_interpret_size(size)
+ size = size.to_i
+ if size < 1024
+ "#{size}"
+ elsif size < 1024 * 1024
+ "#{size/1024}K"
+ elsif size < 1024 * 1024 * 1024
+ "#{size/(1024*1024)}M"
+ else
+ "#{size/(1024*1024*1024)}G"
+ end
+ end
+
+ def interpret_log_level(level)
+ if level.is_a? Symbol
+ case level
+ when :debug then "DEBUG"
+ when :info then "INFO"
+ when :warn then "ERROR"
+ when :error then "ERROR"
+ when :fatal then "FATAL"
+ else "INFO"
+ end
+ elsif level.is_a?(Integer) && defined?(Logger)
+ case level
+ when Logger::DEBUG then "DEBUG"
+ when Logger::INFO then "INFO"
+ when Logger::WARN then "ERROR"
+ when Logger::ERROR then "ERROR"
+ when Logger::FATAL then "FATAL"
+ else "INFO"
+ end
+ else
+ "INFO"
+ end
+ end
+
+ end
+end
diff --git a/vendor/rsync_command/lib/rsync_command/thread_pool.rb b/vendor/rsync_command/lib/rsync_command/thread_pool.rb
new file mode 100644
index 0000000..c788ee2
--- /dev/null
+++ b/vendor/rsync_command/lib/rsync_command/thread_pool.rb
@@ -0,0 +1,36 @@
+require 'thread'
+
+class RsyncCommand
+ class ThreadPool
+ class << self
+ attr_accessor :default_size
+ end
+
+ def initialize(size=nil)
+ @size = size || ThreadPool.default_size || 10
+ @jobs = Queue.new
+ @retvals = []
+ @pool = Array.new(@size) do |i|
+ Thread.new do
+ Thread.current[:id] = i
+ catch(:exit) do
+ loop do
+ job, args = @jobs.pop
+ @retvals << job.call(*args)
+ end
+ end
+ end
+ end
+ end
+ def schedule(*args, &block)
+ @jobs << [block, args]
+ end
+ def shutdown
+ @size.times do
+ schedule { throw :exit }
+ end
+ @pool.map(&:join)
+ @retvals
+ end
+ end
+end
diff --git a/vendor/rsync_command/lib/rsync_command/version.rb b/vendor/rsync_command/lib/rsync_command/version.rb
new file mode 100644
index 0000000..654a308
--- /dev/null
+++ b/vendor/rsync_command/lib/rsync_command/version.rb
@@ -0,0 +1,3 @@
+class RsyncCommand
+ VERSION = "0.0.1"
+end