class Kitchen::Transport::Sftp::Connection
Public Class Methods
new(config, options, &block)
click to toggle source
Calls superclass method
# File lib/kitchen/transport/sftp.rb, line 61 def initialize(config, options, &block) @config = config super(options, &block) end
Public Instance Methods
close()
click to toggle source
Wrap Ssh::Connection#close to also shut down the SFTP connection.
Calls superclass method
# File lib/kitchen/transport/sftp.rb, line 67 def close if @sftp_session logger.debug("[SFTP] closing connection to #{self}") begin sftp_session.close_channel rescue Net::SSH::Disconnect # Welp, we tried. rescue IOError # Can happen with net-ssh 4.x, no idea why. # See https://github.com/net-ssh/net-ssh/pull/493 end end ensure @sftp_session = nil # Make sure we can turn down the session even if closing the channels # fails in the middle because of a remote disconnect. saved_session = @session begin super rescue Net::SSH::Disconnect # Boooo zlib warnings. saved_session.transport.close if saved_session end end
upload(locals, remote)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 92 def upload(locals, remote) Array(locals).each do |local| full_remote = File.join(remote, File.basename(local)) options = { recursive: File.directory?(local), purge: File.basename(local) != 'cache', } recursive = File.directory?(local) time = Benchmark.realtime do sftp_upload!(local, full_remote, options) end logger.info("[SFTP] Time taken to upload #{local} to #{self}:#{full_remote}: %.2f sec" % time) end end
Private Instance Methods
add_xfer(xfer)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 246 def add_xfer(xfer) sftp_xfers << xfer sftp_loop end
copy_checksums_script!()
click to toggle source
Upload the checksum script if needed.
@return [void]
# File lib/kitchen/transport/sftp.rb, line 190 def copy_checksums_script! # Fast path because upload itself is called multiple times. return if @checksums_copied # Only try to transfer the script if it isn't present. a stat takes about # 1/3rd the time of the transfer, so worst case here is still okay. sftp_session.upload!(CHECKSUMS_PATH, CHECKSUMS_REMOTE_PATH) unless safe_stat(CHECKSUMS_REMOTE_PATH) @checksums_copied = true end
execute_with_exit_code(command)
click to toggle source
Bug fix for session.loop never terminating if there is an SFTP conn active since as far as it is concerned there is still active stuff. This function is Copyright Fletcher Nichol Tracked in github.com/test-kitchen/test-kitchen/pull/724
# File lib/kitchen/transport/sftp.rb, line 136 def execute_with_exit_code(command) exit_code = nil closed = false session.open_channel do |channel| channel.request_pty channel.exec(command) do |_ch, _success| channel.on_data do |_ch, data| logger << data end channel.on_extended_data do |_ch, _type, data| logger << data end channel.on_request("exit-status") do |_ch, data| exit_code = data.read_long end channel.on_close do |ch| # This block is new. closed = true end end end session.loop { exit_code.nil? && !closed } # THERE IS A CHANGE ON THIS LINE, PAY ATTENTION!!!!!! exit_code end
files_to_upload(checksums, local, recursive)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 199 def files_to_upload(checksums, local, recursive) glob_path = if recursive File.join(local, '**', '*') else local end pending = [] Dir.glob(glob_path, File::FNM_PATHNAME | File::FNM_DOTMATCH).each do |path| next unless File.file?(path) rel_path = path[local.length..-1] remote_hash = checksums.delete(rel_path) pending << rel_path unless remote_hash && remote_hash == Digest::SHA1.file(path).hexdigest end pending end
purge_files(checksums, remote)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 232 def purge_files(checksums, remote) checksums.each do |key, value| # Check if the file was uploaded in #upload_file. if value != true logger.debug("[SFTP] Removing #{remote}#{key}") add_xfer(sftp_session.remove("#{remote}#{key}")) end end end
safe_stat(path)
click to toggle source
Return if the path exists (because net::sftp uses exceptions for that and it makes code gross) and also raise an exception if the path is a symlink.
@param path [String] Remote path to check. @return [Boolean]
# File lib/kitchen/transport/sftp.rb, line 179 def safe_stat(path) stat = sftp_session.lstat!(path) raise "#{path} is a symlink, possible security threat, bailing out" if stat.symlink? true rescue Net::SFTP::StatusException false end
sftp_loop(n_xfers=MAX_TRANSFERS)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 251 def sftp_loop(n_xfers=MAX_TRANSFERS) sftp_session.loop do sftp_xfers.delete_if {|x| !(x.is_a?(Net::SFTP::Request) ? x.pending? : x.active?) } # Purge any completed operations, which has two different APIs for some reason sftp_xfers.length > n_xfers # Run until we have fewer than max end end
sftp_session()
click to toggle source
Create the SFTP session and block until it is ready.
@return [Net::SFTP::Session]
# File lib/kitchen/transport/sftp.rb, line 169 def sftp_session @sftp_session ||= session.sftp end
sftp_upload!(local, remote, recursive: true, purge: true)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 109 def sftp_upload!(local, remote, recursive: true, purge: true) # Fast path check, if the remote path doesn't exist at all we just run a direct transfer unless safe_stat(remote) logger.debug("[SFTP] Fast path upload from #{local} to #{remote}") sftp_session.mkdir!(remote) if recursive sftp_session.upload!(local, remote, requests: MAX_TRANSFERS) return end # Get checksums for existing files on the remote side. logger.debug("[SFTP] Slow path upload from #{local} to #{remote}") copy_checksums_script! checksum_cmd = "#{@config[:ruby_path]} #{CHECKSUMS_REMOTE_PATH} #{remote}" logger.debug("[SFTP] Running #{checksum_cmd}") checksums = JSON.parse(session.exec!(checksum_cmd)) # Sync files that have changed. files_to_upload(checksums, local, recursive).each do |rel_path| upload_file(checksums, local, remote, rel_path) end purge_files(checksums, remote) if purge # Wait until all xfers are complete. sftp_loop(0) end
sftp_xfers()
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 242 def sftp_xfers @sftp_xfers ||= [] end
upload_file(checksums, local, remote, rel_path)
click to toggle source
# File lib/kitchen/transport/sftp.rb, line 215 def upload_file(checksums, local, remote, rel_path) parts = rel_path.split('/') parts.pop # Drop the filename since we are only checking dirs parts_to_check = [] until parts.empty? parts_to_check << parts.shift path_to_check = parts_to_check.join('/') unless checksums[path_to_check] logger.debug("[SFTP] Creating directory #{remote}#{path_to_check}") add_xfer(sftp_session.mkdir("#{remote}#{path_to_check}")) checksums[path_to_check] = true end end logger.debug("[SFTP] Uploading #{local}#{rel_path} to #{remote}#{rel_path}") add_xfer(sftp_session.upload("#{local}#{rel_path}", "#{remote}#{rel_path}")) end