net-ssh-simple 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,10 @@ module Net
5
5
  # @example
6
6
  # # Block Syntax (synchronous)
7
7
  # Net::SSH::Simple.sync do
8
- # ssh 'example1.com', 'echo "Hello World."'
8
+ # r = ssh 'example1.com', 'echo "Hello World."'
9
+ # puts r.stdout #=> "Hello World."
10
+ # puts r.exit_code #=> 0
11
+ #
9
12
  # scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
10
13
  # scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
11
14
  # end
@@ -133,6 +136,72 @@ module Net
133
136
  # end
134
137
  # end
135
138
  #
139
+ # @example
140
+ # #
141
+ # # Here be dragons: Using the event-API for a stdin->stdout pipeline
142
+ # #
143
+ # Net::SSH::Simple.sync do
144
+ # # open a shell
145
+ # r = ssh('localhost', '/bin/sh') do |e,c,d|
146
+ # # e = :start, :stdout, :stderr, :exit_code, :exit_signal or :finish
147
+ # # c = our Net::SSH::Connection::Channel instance
148
+ # # d = data for this event
149
+ # case e
150
+ # # :start is triggered exactly once per connection
151
+ # when :start
152
+ # # we can send data using Channel#send_data
153
+ # c.send_data("echo 'hello stdout'\n")
154
+ # c.send_data("echo 'hello stderr' 1>&2\n")
155
+ # # don't forget to eof when done feeding!
156
+ # c.eof!
157
+ #
158
+ # # :stdout is triggered when there's stdout data from remote.
159
+ # # by default the data is also appended to result[:stdout].
160
+ # # you may return :no_append as seen below to avoid that.
161
+ # when :stdout
162
+ # # read the input line-wise (it *will* arrive fragmented!)
163
+ # (@buf ||= '') << d
164
+ # while line = @buf.slice!(/(.*)\r?\n/)
165
+ # puts line #=> "hello stdout"
166
+ # end
167
+ # :no_append
168
+ #
169
+ # # :stderr is triggered when there's stderr data from remote.
170
+ # # by default the data is also appended to result[:stderr].
171
+ # # you may return :no_append as seen below to avoid that.
172
+ # when :stderr
173
+ # # read the input line-wise (it *will* arrive fragmented!)
174
+ # (@buf ||= '') << d
175
+ # while line = @buf.slice!(/(.*)\r?\n/)
176
+ # puts line #=> "hello stderr"
177
+ # end
178
+ # :no_append
179
+ #
180
+ # # :exit_code is triggered when the remote process exits normally.
181
+ # # it does *not* trigger when the remote process exits by signal!
182
+ # when :exit_code
183
+ # puts d #=> 0
184
+ #
185
+ # # :exit_signal is triggered when the remote is killed by signal.
186
+ # # this would normally raise a Net::SSH::Simple::Error but
187
+ # # we suppress that here by returning :no_raise
188
+ # when :exit_signal
189
+ # puts d # won't fire in this example, could be "TERM"
190
+ # :no_raise
191
+ #
192
+ # # :finish triggers after :exit_code when the command exits normally.
193
+ # # it does *not* trigger when the remote process exits by signal!
194
+ # when :finish
195
+ # puts "we are finished!"
196
+ # end
197
+ # end
198
+ # end
199
+ #
200
+ # # Our Result has been populated normally, except for
201
+ # # :stdout and :stdin (because we used :no_append).
202
+ # puts r #=> Net::SSH::Simple::Result
203
+ #
204
+ #
136
205
  # @author moe@busyloop.net
137
206
  #
138
207
  class Simple
@@ -221,7 +290,7 @@ module Net
221
290
  # @param [String] host Destination hostname or ip-address
222
291
  # @param [String] cmd Shell command to execute
223
292
  # @param opts (see Net::SSH::Simple#ssh)
224
- # @param [Block] block Progress callback (optional)
293
+ # @param [Block] &block Progress callback (optional)
225
294
  # @return [Net::SSH::Simple::Result] Result
226
295
  #
227
296
  def scp_ul(host, src, dst, opts={}, &block)
@@ -236,7 +305,7 @@ module Net
236
305
  # @param [String] host Destination hostname or ip-address
237
306
  # @param [String] cmd Shell command to execute
238
307
  # @param [Hash] opts Parameters for the underlying Net::SSH
239
- # @param [Block] block Progress callback (optional)
308
+ # @param [Block] &block Progress callback (optional)
240
309
  # @return [Net::SSH::Simple::Result] Result
241
310
  # @see Net::SSH::Simple#scp_ul
242
311
  #
@@ -253,6 +322,7 @@ module Net
253
322
  # @param [String] host Destination hostname or ip-address
254
323
  # @param [String] cmd Shell command to execute
255
324
  # @param [Hash] opts Parameters for the underlying Net::SSH
325
+ # @param [Block] &block Use the event-API (see example above)
256
326
  # @option opts [Array] :auth_methods
257
327
  # an array of authentication methods to try
258
328
  #
@@ -360,7 +430,7 @@ module Net
360
430
  #
361
431
  # @see http://net-ssh.github.com/ssh/v2/api/classes/Net/SSH/Config.html
362
432
  # Net::SSH documentation for the 'opts'-hash
363
- def ssh(host, cmd, opts={})
433
+ def ssh(host, cmd, opts={}, &block)
364
434
  with_session(host, opts) do |session|
365
435
  @result = Result.new(
366
436
  { :host => host, :cmd => cmd, :start_at => Time.new,
@@ -371,23 +441,33 @@ module Net
371
441
  chan.exec cmd do |ch, success|
372
442
  @result[:success] = success
373
443
  ch.on_data do |c, data|
374
- @result[:stdout] += data.to_s
444
+ r = block.call(:stdout, ch, data) if block
445
+ @result[:stdout] += data.to_s unless r == :no_append
375
446
  end
376
447
  ch.on_extended_data do |c, type, data|
377
- @result[:stderr] += data.to_s
448
+ r = block.call(:stderr, ch, data) if block
449
+ @result[:stderr] += data.to_s unless r == :no_append
378
450
  end
379
451
  ch.on_request('exit-status') do |c, data|
380
- @result[:exit_code] = data.read_long
452
+ exit_code = data.read_long
453
+ block.call(:exit_code, ch, exit_code) if block
454
+ @result[:exit_code] = exit_code
381
455
  end
382
456
  ch.on_request('exit-signal') do |c, data|
383
- @result[:exit_signal] = data.read_string
457
+ exit_signal = data.read_string
458
+ r = block.call(:exit_signal, ch, exit_signal) if block
459
+ @result[:exit_signal] = exit_signal
384
460
  @result[:success] = false
385
- raise "Killed by SIG#{@result[:exit_signal]}"
461
+ unless r == :no_raise
462
+ raise "Killed by SIG#{@result[:exit_signal]}"
463
+ end
386
464
  end
465
+ block.call(:start, ch, nil) if block
387
466
  end
388
467
  end
389
468
  channel.wait
390
469
  @result[:finish_at] = Time.new
470
+ block.call(:finish, channel, nil) if block
391
471
  @result
392
472
  end
393
473
  end
@@ -1,7 +1,7 @@
1
1
  module Net
2
2
  module SSH
3
3
  class Simple
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.0"
5
5
  end
6
6
  end
7
7
  end
@@ -272,4 +272,215 @@ describe Net::SSH::Simple do
272
272
  v.to_s.should == 'Killed by SIGTERM @ ["localhost", {}]'
273
273
  end
274
274
  end
275
+
276
+ describe "event api" do
277
+ it "handles long stdin->stdout pipe" do
278
+ mockie = mock(:callbacks)
279
+ mockie.should_receive(:start).once.ordered
280
+ mockie.should_receive(:exit_code).once.ordered
281
+ mockie.should_receive(:finish).once.ordered
282
+ mockie.should_not_receive(:exit_signal)
283
+
284
+ stdout_copy = ''
285
+ a = Net::SSH::Simple.sync do
286
+ i = 0
287
+ r = ssh('localhost', 'sed "s/0/X/g"') do |e,c,d|
288
+ case e
289
+ when :start
290
+ mockie.start()
291
+ (0..16384).each do |i|
292
+ c.send_data("foobar #{i}\n")
293
+ end
294
+ c.eof!
295
+ when :stdout
296
+ stdout_copy << d
297
+ (@buf ||= '') << d
298
+ while line = @buf.slice!(/(.*)\r?\n/)
299
+ line.chop.should == "foobar #{i}".gsub('0','X')
300
+ i += 1
301
+ end
302
+ when :exit_code
303
+ mockie.exit_code()
304
+ when :exit_signal
305
+ mockie.exit_signal()
306
+ when :finish
307
+ mockie.finish()
308
+ end
309
+ end
310
+ r.stdout.should == stdout_copy
311
+ r.stderr.should == ''
312
+ end
313
+ end
314
+
315
+ it "handles intermingled stdout/stderr" do
316
+ mockie = mock(:callbacks)
317
+ mockie.should_receive(:start).once.ordered
318
+ mockie.should_receive(:exit_code).once.ordered
319
+ mockie.should_receive(:finish).once.ordered
320
+ mockie.should_not_receive(:exit_signal)
321
+ a = Net::SSH::Simple.sync do
322
+ stdout_c = 0
323
+ stderr_c = 0
324
+ stdout_copy = ''
325
+ stderr_copy = ''
326
+ r = ssh('localhost', '/bin/sh') do |e,c,d|
327
+ case e
328
+ when :start
329
+ mockie.start()
330
+ (1..420).each do |i|
331
+ c.send_data("echo 'hello stderr' 1>&2\n")
332
+ c.send_data("echo 'hello stdout'\n")
333
+ c.send_data("echo 'HELLO STDERR' 1>&2\n")
334
+ c.send_data("echo 'HELLO STDOUT'\n")
335
+ end
336
+ c.eof!
337
+ when :stdout
338
+ stdout_copy << d
339
+ (@buf ||= '') << d
340
+ while line = @buf.slice!(/(.*)\r?\n/)
341
+ oddeven = stdout_c % 2
342
+ case oddeven
343
+ when 0
344
+ line.chop.should == "hello stdout"
345
+ else
346
+ line.chop.should == "HELLO STDOUT"
347
+ end
348
+ stdout_c += 1
349
+ end
350
+ when :stderr
351
+ stderr_copy << d
352
+ (@buf ||= '') << d
353
+ while line = @buf.slice!(/(.*)\r?\n/)
354
+ oddeven = stderr_c % 2
355
+ case oddeven
356
+ when 0
357
+ line.chop.should == "hello stderr"
358
+ else
359
+ line.chop.should == "HELLO STDERR"
360
+ end
361
+ stderr_c += 1
362
+ end
363
+ when :exit_code
364
+ mockie.exit_code()
365
+
366
+ when :exit_signal
367
+ mockie.exit_signal()
368
+
369
+ when :finish
370
+ stdout_c.should == 840
371
+ stderr_c.should == 840
372
+ mockie.finish()
373
+ end
374
+ end
375
+ # result should be populated
376
+ r.stdout.should == stdout_copy
377
+ r.stderr.should == stderr_copy
378
+ r.exit_code.should == 0
379
+ end
380
+ end
381
+
382
+ it "handles signals" do
383
+ mockie = mock(:callbacks)
384
+ mockie.should_receive(:start).once.ordered
385
+ mockie.should_not_receive(:exit_code)
386
+ mockie.should_receive(:exit_signal).once
387
+ mockie.should_not_receive(:finish)
388
+
389
+ victim = Net::SSH::Simple.async do
390
+ begin
391
+ ssh('localhost', 'sleep 1020304157') do |e,c,d|
392
+ case e
393
+ when :start
394
+ mockie.start()
395
+ when :exit_signal
396
+ d.should == 'TERM'
397
+ mockie.exit_signal()
398
+ when :exit_code
399
+ mockie.exit_code()
400
+ when :finish
401
+ mockie.finish()
402
+ end
403
+ end
404
+ rescue => e
405
+ e
406
+ end
407
+ end
408
+
409
+ killer = Net::SSH::Simple.async do
410
+ ssh('localhost', "pkill -f 'sleep 1020304157'")
411
+ end
412
+
413
+ k = killer.value
414
+ k.success.should == true
415
+
416
+ v = victim.value
417
+ v.to_s.should == 'Killed by SIGTERM @ ["localhost", {}]'
418
+ end
419
+
420
+ it "handles signals (:no_raise)" do
421
+ mockie = mock(:callbacks)
422
+ mockie.should_receive(:start).once.ordered
423
+ mockie.should_not_receive(:exit_code)
424
+ mockie.should_receive(:exit_signal).once
425
+ mockie.should_receive(:finish).once.ordered
426
+
427
+ victim = Net::SSH::Simple.async do
428
+ begin
429
+ ssh('localhost', 'sleep 1020304157') do |e,c,d|
430
+ case e
431
+ when :start
432
+ mockie.start()
433
+ when :exit_signal
434
+ d.should == 'TERM'
435
+ mockie.exit_signal()
436
+ :no_raise
437
+ when :exit_code
438
+ mockie.exit_code()
439
+ when :finish
440
+ mockie.finish()
441
+ end
442
+ end
443
+ rescue => e
444
+ e
445
+ end
446
+ end
447
+
448
+ killer = Net::SSH::Simple.async do
449
+ ssh('localhost', "pkill -f 'sleep 1020304157'")
450
+ end
451
+
452
+ k = killer.value
453
+ k.success.should == true
454
+
455
+ v = victim.value
456
+ v.success.should == false
457
+ v.exit_signal.should == 'TERM'
458
+ end
459
+
460
+ it "respects :no_append" do
461
+ r = Net::SSH::Simple.sync do
462
+ stdout_c = 0
463
+ stderr_c = 0
464
+ stdout_copy = ''
465
+ stderr_copy = ''
466
+ ssh('localhost', '/bin/sh') do |e,c,d|
467
+ case e
468
+ when :start
469
+ c.send_data("echo 'hello stderr' 1>&2\n")
470
+ c.send_data("echo 'hello stdout'\n")
471
+ c.send_data("echo 'HELLO STDERR' 1>&2\n")
472
+ c.send_data("echo 'HELLO STDOUT'\n")
473
+ c.eof!
474
+ when :stdout
475
+ :no_append
476
+ when :stderr
477
+ :no_append
478
+ end
479
+ end
480
+ end
481
+ r.stdout.should == ''
482
+ r.stderr.should == ''
483
+ end
484
+
485
+ end
275
486
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 0
8
7
  - 1
9
- version: 1.0.1
8
+ - 0
9
+ version: 1.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Moe
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-10-23 00:00:00 +02:00
17
+ date: 2011-10-25 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -149,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
- hash: 4169602504577549835
152
+ hash: -3127829972184651328
153
153
  segments:
154
154
  - 0
155
155
  version: "0"
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
158
  requirements:
159
159
  - - ">="
160
160
  - !ruby/object:Gem::Version
161
- hash: 4169602504577549835
161
+ hash: -3127829972184651328
162
162
  segments:
163
163
  - 0
164
164
  version: "0"