net-ssh-simple 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"