iostreams 1.0.0.beta3 → 1.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ecd282f3e3fb3dbbb1cc912458ad80f1286a4921bc36c1797d84ef3863a9b57
4
- data.tar.gz: 8f695a423c861f7899d0a20b0be370787486d83f2c2741c03a71fc801ae06e44
3
+ metadata.gz: 1f3b07c0b0634bbc94434c4fb9de6fe4a41de44c9a8a069e8e53922a80a1e560
4
+ data.tar.gz: d4d7cd4fbb3fedcdf8b6df763d8eace74a075c389895231171e5b41d5fefe975
5
5
  SHA512:
6
- metadata.gz: 2f46b8cfccf5b45099465ed4d9169a124fe427d55c0168d03c22fedb73e092d3e41e1f481c955276f04b02377041a9e277bd40c9732e6521cfd368afa7f6b935
7
- data.tar.gz: 18cc3db574ed6a61005f4743b74da70f787f740e039dda2127d62a02f150e1b90fc7555f6d64e0bb436258c8ef1d2bab64d7fb49615cb6262b1b5d41469e3211
6
+ metadata.gz: 2beb8a498df91a5b564858a1f7deb5bcf139a138ceb2373a8d42a0d7c0a60f913d7675a179e558136272eacb2b25d0194aee7cb482732440bda5da6502123fe7
7
+ data.tar.gz: a6891d61d2910ad1787213c5b0e8c1e5de3f9147c6ab43b267910878ecba8b97fbe753e643ceeaa369f7969b584780dc25a1137e0ba844e01d46fad1b6b97882
@@ -134,6 +134,9 @@ module IOStreams
134
134
  key = uri.path.sub(%r{\A/}, '')
135
135
  @client = client || ::Aws::S3::Client.new
136
136
  @options = args
137
+
138
+ URI.decode_www_form(uri.query).each { |key, value| @options[key] = value } if uri.query
139
+
137
140
  super(key)
138
141
  end
139
142
 
@@ -193,7 +196,7 @@ module IOStreams
193
196
  Utils.temp_file_name("iostreams_s3") do |file_name|
194
197
  read_file(file_name)
195
198
 
196
- ::File.open(file_name, 'rb') { |io| io.read }
199
+ ::File.open(file_name, 'rb', &block)
197
200
  end
198
201
  end
199
202
 
@@ -13,7 +13,7 @@ module IOStreams
13
13
  @sshpass_bin = 'sshpass'
14
14
  @sshpass_wait_seconds = 5
15
15
 
16
- attr_reader :hostname, :username, :ssh_options, :url
16
+ attr_reader :hostname, :username, :ssh_options, :url, :port
17
17
 
18
18
  # Stream to a remote file over sftp.
19
19
  #
@@ -58,13 +58,17 @@ module IOStreams
58
58
  uri = URI.parse(url)
59
59
  raise(ArgumentError, "Invalid URL. Required Format: 'sftp://<host_name>/<file_name>'") unless uri.scheme == 'sftp'
60
60
 
61
- @hostname = uri.hostname
62
- @mkdir = false
63
- @username = username || uri.user
64
- @url = url
65
- @password = password || uri.password
66
- @port = uri.port || 22
67
- @ssh_options = ssh_options
61
+ @hostname = uri.hostname
62
+ @mkdir = false
63
+ @username = username || uri.user
64
+ @url = url
65
+ @password = password || uri.password
66
+ @port = uri.port || 22
67
+ # Not Ruby 2.5 yet: transform_keys(&:to_s)
68
+ @ssh_options = {}
69
+ ssh_options.each_pair { |key, value| @ssh_options[key.to_s] = value }
70
+
71
+ URI.decode_www_form(uri.query).each { |key, value| @ssh_options[key] = value } if uri.query
68
72
 
69
73
  super(uri.path)
70
74
  end
@@ -152,37 +156,78 @@ module IOStreams
152
156
 
153
157
  # Use sftp and sshpass executables to download to a local file
154
158
  def sftp_download(remote_file_name, local_file_name)
155
- Open3.popen2e(*sftp_args) do |writer, reader, waith_thr|
156
- writer.puts password
157
- # Give time for password to be processed and stdin to be passed to sftp process.
158
- sleep self.class.sshpass_wait_seconds
159
- writer.puts "get #{remote_file_name} #{local_file_name}"
160
- writer.puts 'bye'
161
- writer.close
162
- out = reader.read.chomp
163
- raise(Errors::CommunicationsFailure, "Failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}") unless waith_thr.value.success?
164
- out
159
+ with_sftp_args do |args|
160
+ Open3.popen2e(*args) do |writer, reader, waith_thr|
161
+ begin
162
+ writer.puts password
163
+ # Give time for password to be processed and stdin to be passed to sftp process.
164
+ sleep self.class.sshpass_wait_seconds
165
+ writer.puts "get #{remote_file_name} #{local_file_name}"
166
+ writer.puts 'bye'
167
+ writer.close
168
+ out = reader.read.chomp
169
+ raise(Errors::CommunicationsFailure, "Download failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}") unless waith_thr.value.success?
170
+ out
171
+ rescue Errno::EPIPE
172
+ out = reader.read.chomp rescue nil
173
+ raise(Errors::CommunicationsFailure, "Download failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}")
174
+ end
175
+ end
165
176
  end
166
177
  end
167
178
 
168
179
  def sftp_upload(local_file_name, remote_file_name)
169
- Open3.popen2e(*sftp_args) do |writer, reader, waith_thr|
170
- writer.puts(password) if password
171
- # Give time for password to be processed and stdin to be passed to sftp process.
172
- sleep self.class.sshpass_wait_seconds
173
- writer.puts "put #{local_file_name.inspect} #{remote_file_name.inspect}"
174
- writer.puts 'bye'
175
- writer.close
176
- out = reader.read.chomp
177
- raise(Errors::CommunicationsFailure, "Failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}") unless waith_thr.value.success?
178
- out
180
+ with_sftp_args do |args|
181
+ Open3.popen2e(*args) do |writer, reader, waith_thr|
182
+ begin
183
+ writer.puts(password) if password
184
+ # Give time for password to be processed and stdin to be passed to sftp process.
185
+ sleep self.class.sshpass_wait_seconds
186
+ writer.puts "put #{local_file_name.inspect} #{remote_file_name.inspect}"
187
+ writer.puts 'bye'
188
+ writer.close
189
+ out = reader.read.chomp
190
+ raise(Errors::CommunicationsFailure, "Upload failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}") unless waith_thr.value.success?
191
+ out
192
+ rescue Errno::EPIPE
193
+ out = reader.read.chomp rescue nil
194
+ raise(Errors::CommunicationsFailure, "Upload failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}")
195
+ end
196
+ end
179
197
  end
180
198
  end
181
199
 
182
- def sftp_args
183
- args = [self.class.sshpass_bin, self.class.sftp_bin, '-oBatchMode=no']
184
- # Force it to use the password when supplied.
185
- args << "-oPubkeyAuthentication=no" if password
200
+ def with_sftp_args
201
+ return yield sftp_args(ssh_options) unless ssh_options.key?('IdentityKey')
202
+
203
+ Utils.temp_file_name('iostreams-sftp-args', 'key') do |file_name|
204
+ options = ssh_options.dup
205
+ key = options.delete('IdentityKey')
206
+ # sftp requires that private key is only readable by the current user
207
+ File.open(file_name, 'wb', 0600) { |io| io.write(key) }
208
+
209
+ options['IdentityFile'] = file_name
210
+ yield sftp_args(ssh_options)
211
+ end
212
+ end
213
+
214
+ def sftp_args(ssh_options)
215
+ args = [self.class.sshpass_bin, self.class.sftp_bin]
216
+ # Force sftp to use the password when supplied,
217
+ # and stop sftp from prompting for a password when none was supplied.
218
+ if password
219
+ args << "-oBatchMode=no"
220
+ args << "-oNumberOfPasswordPrompts=1"
221
+ args << "-oPubkeyAuthentication=no"
222
+ else
223
+ args << "-oBatchMode=yes"
224
+ args << "-oPasswordAuthentication=no"
225
+ end
226
+ args << "-oIdentitiesOnly=yes" if ssh_options.key?('IdentityFile')
227
+ # Default is ask, but this is non-interactive so make the default fail without asking.
228
+ args << "-oStrictHostKeyChecking=yes" unless ssh_options.key?('StrictHostKeyChecking')
229
+ args << "-oLogLevel=#{map_log_level}" unless ssh_options.key?('LogLevel')
230
+ args << "-oPort=#{port}" unless port == 22
186
231
  ssh_options.each_pair { |key, value| args << "-o#{key}=#{value}" }
187
232
  args << '-b'
188
233
  args << '-'
@@ -193,11 +238,24 @@ module IOStreams
193
238
  def build_ssh_options
194
239
  options = ssh_options.dup
195
240
  options[:logger] ||= self.logger if defined?(SemanticLogger)
196
- options[:port] ||= @port
241
+ options[:port] ||= port
197
242
  options[:max_pkt_size] ||= 65_536
198
243
  options[:password] ||= @password
199
244
  options
200
245
  end
246
+
247
+ def map_log_level
248
+ return "INFO" unless defined?(SemanticLogger)
249
+
250
+ case logger.level
251
+ when :trace
252
+ "DEBUG3"
253
+ when :warn
254
+ "ERROR"
255
+ else
256
+ logger.level.to_s
257
+ end
258
+ end
201
259
  end
202
260
  end
203
261
  end
@@ -275,7 +275,7 @@ module IOStreams
275
275
  # * Invalidated keys must be removed manually.
276
276
  def self.import(key:)
277
277
  version_check
278
- command = "#{executable} --import"
278
+ command = "#{executable} --batch --import"
279
279
 
280
280
  out, err, status = Open3.capture3(command, binmode: true, stdin_data: key)
281
281
  logger&.debug { "IOStreams::Pgp.import: #{command}\n#{err}#{out}" }
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = "1.0.0.beta3".freeze
2
+ VERSION = "1.0.0.beta4".freeze
3
3
  end
@@ -97,6 +97,14 @@ module Paths
97
97
  end
98
98
  end
99
99
 
100
+ describe '#each_line' do
101
+ it 'reads line by line' do
102
+ lines = []
103
+ existing_path.each_line {|line| lines << line}
104
+ assert_equal raw.lines.collect(&:chomp), lines
105
+ end
106
+ end
107
+
100
108
  describe '#each_child' do
101
109
  # TODO: case_sensitive: false, directories: false, hidden: false
102
110
  let(:abd_file_names) { %w[abd/test1.txt abd/test5.file abd/extra/file.csv] }
@@ -116,7 +124,8 @@ module Paths
116
124
 
117
125
  it 'existing file returns just the file itself' do
118
126
  # Glorified exists call
119
- assert_equal [each_root.join('readme').to_s], each_root.children("readme").collect(&:to_s)
127
+ existing_path
128
+ assert_equal root_path.join("test.txt").to_s, root_path.children("test.txt").first.to_s
120
129
  end
121
130
 
122
131
  it 'missing file does nothing' do
@@ -5,25 +5,21 @@ module Paths
5
5
  describe IOStreams::Paths::SFTP do
6
6
  before do
7
7
  unless ENV["SFTP_HOSTNAME"]
8
- skip "Supply environment variables to test SFTP paths: SFTP_HOSTNAME, SFTP_USERNAME, SFTP_PASSWORD, and optional SFTP_DIR"
8
+ skip "Supply environment variables to test SFTP paths: SFTP_HOSTNAME, SFTP_USERNAME, SFTP_PASSWORD, and optional SFTP_DIR, SFTP_IDENTITY_FILE"
9
9
  end
10
10
  end
11
11
 
12
12
  let(:host_name) { ENV["SFTP_HOSTNAME"] }
13
13
  let(:username) { ENV["SFTP_USERNAME"] }
14
14
  let(:password) { ENV["SFTP_PASSWORD"] }
15
- let(:ftp_dir) { ENV["SFTP_DIR"] || "iostreams_test"}
16
- let(:url) { File.join("sftp://", host_name, ftp_dir) }
15
+ let(:ftp_dir) { ENV["SFTP_DIR"] || "iostreams_test" }
17
16
 
18
- let :file_name do
19
- File.join(File.dirname(__FILE__), '..', 'files', 'text.txt')
20
- end
17
+ let(:url) { File.join("sftp://", host_name, ftp_dir) }
21
18
 
22
- let :raw do
23
- File.read(file_name)
24
- end
19
+ let(:file_name) { File.join(File.dirname(__FILE__), '..', 'files', 'text.txt') }
20
+ let(:raw) { File.read(file_name) }
25
21
 
26
- let(:root_path) { IOStreams::Paths::SFTP.new(url, username: username, password: password, ruby: false) }
22
+ let(:root_path) { IOStreams::Paths::SFTP.new(url, username: username, password: password) }
27
23
 
28
24
  let :existing_path do
29
25
  path = root_path.join('test.txt')
@@ -72,6 +68,31 @@ module Paths
72
68
  missing_path.write("Bad path")
73
69
  end
74
70
  end
71
+
72
+ describe 'use identity file instead of password' do
73
+ let :root_path do
74
+ IOStreams::Paths::SFTP.new(url, username: username, ssh_options: {'IdentityFile' => ENV["SFTP_IDENTITY_FILE"]} )
75
+ end
76
+
77
+ it 'writes' do
78
+ skip "No identity file env var set: SFTP_IDENTITY_FILE" unless ENV["SFTP_IDENTITY_FILE"]
79
+ assert_equal raw.size, write_path.writer { |io| io.write(raw) }
80
+ assert_equal raw, write_path.read
81
+ end
82
+ end
83
+
84
+ describe 'use identity key instead of password' do
85
+ let :root_path do
86
+ key = File.open(ENV["SFTP_IDENTITY_FILE"], 'rb', &:read)
87
+ IOStreams::Paths::SFTP.new(url, username: username, ssh_options: {'IdentityKey' => key})
88
+ end
89
+
90
+ it 'writes' do
91
+ skip "No identity file env var set: SFTP_IDENTITY_FILE" unless ENV["SFTP_IDENTITY_FILE"]
92
+ assert_equal raw.size, write_path.writer { |io| io.write(raw) }
93
+ assert_equal raw, write_path.read
94
+ end
95
+ end
75
96
  end
76
97
  end
77
98
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iostreams
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta3
4
+ version: 1.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-14 00:00:00.000000000 Z
11
+ date: 2019-11-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: