lib/io_streams/paths/sftp.rb in iostreams-1.6.2 vs lib/io_streams/paths/sftp.rb in iostreams-1.7.0
- old
+ new
@@ -45,13 +45,27 @@
# Name of user to login with.
#
# password: [String]
# Password for the user.
#
- # **ssh_options
- # Any other options supported by ssh_config.
- # `man ssh_config` to see all available options.
+ # ssh_options: [Hash]
+ # - IdentityKey [String]
+ # The identity key that this client should use to talk to this host.
+ # Under the covers this value is written to a file and then the file name is passed as `IdentityFile`
+ # - HostKey [String]
+ # The expected SSH Host key that is presented by the remote host.
+ # Instead of storing the host key in the `known_hosts` file, it can be supplied explicity
+ # using this option.
+ # Under the covers this value is written to a file and then the file name is passed as `UserKnownHostsFile`
+ # Notes:
+ # - It must contain the entire line that would be stored in `known_hosts`,
+ # including the hostname, ip address, key type and key value. This value is written as-is into a
+ # "known_hosts" like file and then passed into sftp using the `UserKnownHostsFile` option.
+ # - The easiest way to generate the required is to use `ssh-keyscan` and then supply that value in this field.
+ # For example: `ssh-keyscan hostname`
+ # - Any other options supported by ssh_config.
+ # `man ssh_config` to see all available options.
#
# Examples:
#
# # Display the contents of a remote file
# IOStreams.path("sftp://test.com/path/file_name.csv", username: "jack", password: "OpenSesame").reader do |io|
@@ -166,91 +180,105 @@
# Use sftp and sshpass executables to download to a local file
def sftp_download(remote_file_name, local_file_name)
with_sftp_args do |args|
Open3.popen2e(*args) do |writer, reader, waith_thr|
- begin
- # Give time for remote sftp server to get ready to accept the password.
- sleep self.class.before_password_wait_seconds
+ # Give time for remote sftp server to get ready to accept the password.
+ sleep self.class.before_password_wait_seconds
- writer.puts password
+ writer.puts password
- # Give time for password to be processed and stdin to be passed to sftp process.
- sleep self.class.sshpass_wait_seconds
+ # Give time for password to be processed and stdin to be passed to sftp process.
+ sleep self.class.sshpass_wait_seconds
- writer.puts "get #{remote_file_name} #{local_file_name}"
- writer.puts "bye"
- writer.close
- out = reader.read.chomp
- unless waith_thr.value.success?
- raise(
- Errors::CommunicationsFailure,
- "Download failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}"
- )
- end
-
- out
- rescue Errno::EPIPE
- out = begin
- reader.read.chomp
- rescue StandardError
- nil
- end
+ writer.puts "get #{remote_file_name} #{local_file_name}"
+ writer.puts "bye"
+ writer.close
+ out = reader.read.chomp
+ unless waith_thr.value.success?
raise(
Errors::CommunicationsFailure,
"Download failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}"
)
end
+
+ out
+ rescue Errno::EPIPE
+ out = begin
+ reader.read.chomp
+ rescue StandardError
+ nil
+ end
+ raise(
+ Errors::CommunicationsFailure,
+ "Download failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}"
+ )
end
end
end
def sftp_upload(local_file_name, remote_file_name)
with_sftp_args do |args|
Open3.popen2e(*args) do |writer, reader, waith_thr|
- begin
- writer.puts(password) if password
- # Give time for password to be processed and stdin to be passed to sftp process.
- sleep self.class.sshpass_wait_seconds
- writer.puts "put #{local_file_name.inspect} #{remote_file_name.inspect}"
- writer.puts "bye"
- writer.close
- out = reader.read.chomp
- unless waith_thr.value.success?
- raise(
- Errors::CommunicationsFailure,
- "Upload failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}"
- )
- end
-
- out
- rescue Errno::EPIPE
- out = begin
- reader.read.chomp
- rescue StandardError
- nil
- end
+ writer.puts(password) if password
+ # Give time for password to be processed and stdin to be passed to sftp process.
+ sleep self.class.sshpass_wait_seconds
+ writer.puts "put #{local_file_name.inspect} #{remote_file_name.inspect}"
+ writer.puts "bye"
+ writer.close
+ out = reader.read.chomp
+ unless waith_thr.value.success?
raise(
Errors::CommunicationsFailure,
"Upload failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}"
)
end
+
+ out
+ rescue Errno::EPIPE
+ out = begin
+ reader.read.chomp
+ rescue StandardError
+ nil
+ end
+ raise(
+ Errors::CommunicationsFailure,
+ "Upload failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}"
+ )
end
end
end
def with_sftp_args
- return yield sftp_args(ssh_options) unless ssh_options.key?("IdentityKey")
+ return yield sftp_args(ssh_options) if !ssh_options.key?("IdentityKey") && !ssh_options.key?("HostKey")
+ with_identity_key(ssh_options.dup) do |options|
+ with_host_key(options) do |options2|
+ yield sftp_args(options2)
+ end
+ end
+ end
+
+ def with_identity_key(options)
+ return yield options unless ssh_options.key?("IdentityKey")
+
+ with_temp_file(options, "IdentityFile", options.delete("IdentityKey")) { yield options }
+ end
+
+ def with_host_key(options)
+ return yield options unless ssh_options.key?("HostKey")
+
+ with_temp_file(options, "UserKnownHostsFile", options.delete("HostKey")) { yield options }
+ end
+
+ def with_temp_file(options, option, value)
Utils.temp_file_name("iostreams-sftp-args", "key") do |file_name|
- options = ssh_options.dup
- key = options.delete("IdentityKey")
# sftp requires that private key is only readable by the current user
- ::File.open(file_name, "wb", 0o600) { |io| io.write(key) }
+ ::File.open(file_name, "wb", 0o600) { |io| io.write(value) }
- options["IdentityFile"] = file_name
- yield sftp_args(options)
+ options[option] = file_name
+ yield options
end
end
def sftp_args(ssh_options)
args = [self.class.sshpass_bin, self.class.sftp_bin]
@@ -275,10 +303,10 @@
args << "#{username}@#{hostname}"
args
end
def build_ssh_options
- options = ssh_options.dup
+ options = ssh_options.dup
options[:logger] ||= logger if defined?(SemanticLogger)
options[:port] ||= port
options[:max_pkt_size] ||= 65_536
options[:password] ||= @password
options