net-ssh-simple 1.3.2 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +0 -14
- data/lib/net/ssh/simple/core.rb +52 -23
- data/lib/net/ssh/simple/version.rb +1 -1
- data/spec/net-ssh-simple.rb +68 -5
- metadata +17 -17
data/README.rdoc
CHANGED
@@ -57,21 +57,7 @@ Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
|
|
57
57
|
s.scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
|
58
58
|
s.close
|
59
59
|
|
60
|
-
=== Handling a Timeout
|
61
60
|
|
62
|
-
require 'net/ssh/simple'
|
63
|
-
|
64
|
-
# Hint: Set timeout=0 to disable, default is 60
|
65
|
-
begin
|
66
|
-
Net::SSH::Simple.sync({:timeout => 5}) do
|
67
|
-
ssh('example1.com', 'sleep 1') # I will (probably) succeed!
|
68
|
-
ssh('example2.com', 'sleep 60') # I will fail :(
|
69
|
-
end
|
70
|
-
rescue Net::SSH::Simple::Error => e
|
71
|
-
puts e.result.timed_out #=> true
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
61
|
== Documentation
|
76
62
|
|
77
63
|
See {Net::SSH::Simple}[http://rubydoc.info/gems/net-ssh-simple/Net/SSH/Simple] for more examples and full API.
|
data/lib/net/ssh/simple/core.rb
CHANGED
@@ -157,20 +157,6 @@ module Net
|
|
157
157
|
# end
|
158
158
|
#
|
159
159
|
# @example
|
160
|
-
# # Timeout handling
|
161
|
-
# #
|
162
|
-
# # Note: The timeout applies to each command independently.
|
163
|
-
# # Hint: Set timeout=0 to disable, default is 60
|
164
|
-
# begin
|
165
|
-
# Net::SSH::Simple.sync({:timeout => 5}) do
|
166
|
-
# ssh('example1.com', 'sleep 1') # I will (probably) succeed!
|
167
|
-
# ssh('example2.com', 'sleep 60') # I will fail :(
|
168
|
-
# end
|
169
|
-
# rescue Net::SSH::Simple::Error => e
|
170
|
-
# puts e.result.timed_out #=> true
|
171
|
-
# end
|
172
|
-
#
|
173
|
-
# @example
|
174
160
|
# # Using the SCP progress callback
|
175
161
|
# Net::SSH::Simple.sync do
|
176
162
|
# scp_ul 'example1.com', '/tmp/local_foo', '/tmp/remote_bar' do |sent, total|
|
@@ -459,7 +445,17 @@ module Net
|
|
459
445
|
# the max number of packets to process before rekeying
|
460
446
|
#
|
461
447
|
# @option opts [Integer] :timeout (60)
|
462
|
-
#
|
448
|
+
# maximum idle time before a connection will time out (0 = disable).
|
449
|
+
#
|
450
|
+
# @option opts [Integer] :operation_timeout (3600)
|
451
|
+
# maximum time before aborting an operation (0 = disable).
|
452
|
+
# you may use this to guard against run-away processes.
|
453
|
+
#
|
454
|
+
# @option opts [Integer] :close_timeout (5)
|
455
|
+
# grace-period on close before the connection will be terminated forcefully
|
456
|
+
# (0 = terminate immediately).
|
457
|
+
#
|
458
|
+
# @option opts [String] :user
|
463
459
|
#
|
464
460
|
# @option opts [String] :user
|
465
461
|
# the username to log in as
|
@@ -482,26 +478,31 @@ module Net
|
|
482
478
|
with_session(host, opts) do |session|
|
483
479
|
@result = Result.new(
|
484
480
|
{ :op => :ssh, :host => host, :cmd => cmd, :start_at => Time.new,
|
485
|
-
:opts => opts, :stdout => '', :stderr => '',
|
481
|
+
:last_event_at => Time.new, :opts => opts, :stdout => '', :stderr => '',
|
482
|
+
:success => nil
|
486
483
|
} )
|
487
484
|
|
488
485
|
channel = session.open_channel do |chan|
|
489
486
|
chan.exec cmd do |ch, success|
|
490
487
|
@result[:success] = success
|
491
488
|
ch.on_data do |c, data|
|
489
|
+
@result[:last_event_at] = Time.new
|
492
490
|
r = block.call(:stdout, ch, data) if block
|
493
491
|
@result[:stdout] += data.to_s unless r == :no_append
|
494
492
|
end
|
495
493
|
ch.on_extended_data do |c, type, data|
|
494
|
+
@result[:last_event_at] = Time.new
|
496
495
|
r = block.call(:stderr, ch, data) if block
|
497
496
|
@result[:stderr] += data.to_s unless r == :no_append
|
498
497
|
end
|
499
498
|
ch.on_request('exit-status') do |c, data|
|
499
|
+
@result[:last_event_at] = Time.new
|
500
500
|
exit_code = data.read_long
|
501
501
|
block.call(:exit_code, ch, exit_code) if block
|
502
502
|
@result[:exit_code] = exit_code
|
503
503
|
end
|
504
504
|
ch.on_request('exit-signal') do |c, data|
|
505
|
+
@result[:last_event_at] = Time.new
|
505
506
|
exit_signal = data.read_string
|
506
507
|
r = block.call(:exit_signal, ch, exit_signal) if block
|
507
508
|
@result[:exit_signal] = exit_signal
|
@@ -513,7 +514,7 @@ module Net
|
|
513
514
|
block.call(:start, ch, nil) if block
|
514
515
|
end
|
515
516
|
end
|
516
|
-
channel
|
517
|
+
wait_for_channel session, channel, @result, opts[:timeout]
|
517
518
|
@result[:finish_at] = Time.new
|
518
519
|
block.call(:finish, channel, nil) if block
|
519
520
|
@result
|
@@ -561,18 +562,34 @@ module Net
|
|
561
562
|
#
|
562
563
|
def close
|
563
564
|
Thread.current[:ssh_simple_sessions].values.each do |session|
|
564
|
-
|
565
|
+
begin
|
566
|
+
Timeout.timeout(@opts[:close_timeout] || 5) { session.close }
|
567
|
+
rescue => e
|
568
|
+
begin
|
569
|
+
session.shutdown!
|
570
|
+
rescue
|
571
|
+
end
|
572
|
+
end
|
565
573
|
end
|
566
574
|
@result
|
567
575
|
end
|
568
576
|
|
577
|
+
|
569
578
|
private
|
570
|
-
|
579
|
+
EXTRA_OPTS = [:operation_timeout, :close_timeout]
|
580
|
+
|
581
|
+
def with_session(host, opts={}, &block)
|
582
|
+
opts[:timeout] ||= 60
|
583
|
+
opts[:timeout] = 2**32 if opts[:timeout] == 0
|
584
|
+
opts[:operation_timeout] ||= 3600
|
585
|
+
opts[:operation_timeout] = 2**32 if opts[:operation_timeout] == 0
|
586
|
+
opts[:close_timeout] ||= 5
|
571
587
|
begin
|
572
|
-
|
588
|
+
net_ssh_opts = opts.reject{|k,v| EXTRA_OPTS.include? k }
|
589
|
+
Timeout.timeout(opts[:operation_timeout]) do
|
573
590
|
session = Thread.current[:ssh_simple_sessions][host.hash] \
|
574
591
|
= Thread.current[:ssh_simple_sessions][host.hash] \
|
575
|
-
|| Net::SSH.start(*[host, opts[:user],
|
592
|
+
|| Net::SSH.start(*[host, opts[:user], net_ssh_opts])
|
576
593
|
block.call(session)
|
577
594
|
end
|
578
595
|
rescue => e
|
@@ -585,10 +602,20 @@ module Net
|
|
585
602
|
end
|
586
603
|
end
|
587
604
|
|
605
|
+
def wait_for_channel(session, channel, result, timeout)
|
606
|
+
session.loop(1) do
|
607
|
+
if timeout < Time.now - result[:last_event_at]
|
608
|
+
raise Timeout::Error, 'idle timeout'
|
609
|
+
end
|
610
|
+
channel.active?
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
588
614
|
def scp(mode, host, src, dst, opts={}, &block)
|
589
615
|
@result = Result.new(
|
590
616
|
{ :op => :scp, :host => host, :opts => opts, :cmd => :scp_dl,
|
591
|
-
:
|
617
|
+
:last_event_at => Time.new, :start_at => Time.new,
|
618
|
+
:src => src, :dst => dst, :success => false
|
592
619
|
} )
|
593
620
|
with_session(host, opts) do |session|
|
594
621
|
lt = 0
|
@@ -596,9 +623,10 @@ module Net
|
|
596
623
|
@result[:name] ||= name
|
597
624
|
@result[:total] ||= total
|
598
625
|
@result[:sent] = sent
|
626
|
+
@result[:last_event_at] = Time.new
|
599
627
|
block.call(sent, total) unless block.nil?
|
600
628
|
end
|
601
|
-
channel
|
629
|
+
wait_for_channel session, channel, @result, opts[:timeout]
|
602
630
|
@result[:finish_at] = Time.new
|
603
631
|
@result[:success] = @result[:sent] == @result[:total]
|
604
632
|
@result
|
@@ -634,6 +662,7 @@ module Net
|
|
634
662
|
# @attr [String] cmd Shell command (SSH only)
|
635
663
|
# @attr [Time] start_at Operation start timestamp
|
636
664
|
# @attr [Time] finish_at Operation finish timestamp
|
665
|
+
# @attr [Time] last_event_at Timestamp of last activity
|
637
666
|
# @attr [Boolean] timed_out Set to true if the operation timed out
|
638
667
|
# @attr [String] stdout Output to stdout (SSH only)
|
639
668
|
# @attr [String] stderr Output to stderr (SSH only)
|
data/spec/net-ssh-simple.rb
CHANGED
@@ -42,10 +42,57 @@ describe Net::SSH::Simple do
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
it "enforces timeout" do
|
45
|
+
it "enforces idle timeout" do
|
46
46
|
raised = false
|
47
47
|
begin
|
48
48
|
r = Net::SSH::Simple.ssh('localhost', 'sleep 60', {:timeout => 1})
|
49
|
+
rescue => e
|
50
|
+
raised = true
|
51
|
+
e.to_s.should match /^idle timeout @ .*/
|
52
|
+
e.result.op == :ssh
|
53
|
+
e.result.timed_out.should == true
|
54
|
+
end
|
55
|
+
raised.should == true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "enforces operation timeout on ssh" do
|
59
|
+
raised = false
|
60
|
+
begin
|
61
|
+
r = Net::SSH::Simple.ssh('localhost', 'while true; do echo "buh"; sleep 1; done', {:operation_timeout => 2})
|
62
|
+
rescue => e
|
63
|
+
raised = true
|
64
|
+
e.to_s.should match /^execution expired @ .*/
|
65
|
+
e.result.op == :ssh
|
66
|
+
e.result.timed_out.should == true
|
67
|
+
end
|
68
|
+
raised.should == true
|
69
|
+
end
|
70
|
+
|
71
|
+
it "enforces operation timeout on scp_ul" do
|
72
|
+
raised = false
|
73
|
+
begin
|
74
|
+
r = Net::SSH::Simple.scp_ul('localhost', '/tmp/ssh_test_in0',
|
75
|
+
'/tmp/ssh_test_out0', {:operation_timeout=>1}) \
|
76
|
+
do |sent,total|
|
77
|
+
sleep 5
|
78
|
+
end
|
79
|
+
rescue => e
|
80
|
+
raised = true
|
81
|
+
e.to_s.should match /^execution expired @ .*/
|
82
|
+
e.result.op == :ssh
|
83
|
+
e.result.timed_out.should == true
|
84
|
+
end
|
85
|
+
raised.should == true
|
86
|
+
end
|
87
|
+
|
88
|
+
it "enforces operation timeout on scp_dl" do
|
89
|
+
raised = false
|
90
|
+
begin
|
91
|
+
r = Net::SSH::Simple.scp_dl('localhost', '/tmp/ssh_test_in0',
|
92
|
+
'/tmp/ssh_test_out0', {:operation_timeout=>1}) \
|
93
|
+
do |sent,total|
|
94
|
+
sleep 5
|
95
|
+
end
|
49
96
|
rescue => e
|
50
97
|
raised = true
|
51
98
|
e.to_s.should match /^execution expired @ .*/
|
@@ -59,6 +106,10 @@ describe Net::SSH::Simple do
|
|
59
106
|
Net::SSH::Simple.ssh('localhost', 'sleep 2', {:timeout => 0})
|
60
107
|
end
|
61
108
|
|
109
|
+
it "interprets operation_timeout=0 as no timeout" do
|
110
|
+
Net::SSH::Simple.ssh('localhost', 'sleep 2', {:operation_timeout => 0})
|
111
|
+
end
|
112
|
+
|
62
113
|
it "fails gently" do
|
63
114
|
raised = false
|
64
115
|
begin
|
@@ -182,6 +233,13 @@ describe Net::SSH::Simple do
|
|
182
233
|
it "returns a result" do
|
183
234
|
Net::SSH::Simple.sync do
|
184
235
|
ssh('localhost', 'true').success.should == true
|
236
|
+
# see coverage-report to see if session#shutdown! was exercised
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
it "force closes" do
|
241
|
+
Net::SSH::Simple.sync({:close_timeout => true}) do
|
242
|
+
ssh('localhost', 'true').success.should == true
|
185
243
|
end
|
186
244
|
end
|
187
245
|
|
@@ -317,7 +375,7 @@ describe Net::SSH::Simple do
|
|
317
375
|
end
|
318
376
|
|
319
377
|
it "handles signals" do
|
320
|
-
victim = Net::SSH::Simple.async do
|
378
|
+
victim = Net::SSH::Simple.async({:timeout => 10}) do
|
321
379
|
begin
|
322
380
|
ssh('localhost', 'sleep 1020304157')
|
323
381
|
rescue => e
|
@@ -325,12 +383,14 @@ describe Net::SSH::Simple do
|
|
325
383
|
end
|
326
384
|
end
|
327
385
|
|
328
|
-
killer = Net::SSH::Simple.async do
|
386
|
+
killer = Net::SSH::Simple.async({:operation_timeout => 5}) do
|
387
|
+
sleep 1 while 0 != ssh('localhost', "pgrep -f 'sleep 1020304157'").exit_code
|
329
388
|
ssh('localhost', "pkill -f 'sleep 1020304157'")
|
330
389
|
end
|
331
390
|
|
332
391
|
k = killer.value
|
333
392
|
k.success.should == true
|
393
|
+
k.exit_code.should == 0
|
334
394
|
|
335
395
|
v = victim.value
|
336
396
|
v.to_s.should match /Killed by SIGTERM @ .*/
|
@@ -610,7 +670,8 @@ describe Net::SSH::Simple do
|
|
610
670
|
end
|
611
671
|
end
|
612
672
|
|
613
|
-
killer = Net::SSH::Simple.async do
|
673
|
+
killer = Net::SSH::Simple.async({:operation_timeout => 5}) do
|
674
|
+
sleep 1 while 0 != ssh('localhost', "pgrep -f 'sleep 1020304157'").exit_code
|
614
675
|
ssh('localhost', "pkill -f 'sleep 1020304157'")
|
615
676
|
end
|
616
677
|
|
@@ -649,10 +710,12 @@ describe Net::SSH::Simple do
|
|
649
710
|
end
|
650
711
|
end
|
651
712
|
|
652
|
-
killer = Net::SSH::Simple.async do
|
713
|
+
killer = Net::SSH::Simple.async({:operation_timeout => 5}) do
|
714
|
+
sleep 1 while 0 != ssh('localhost', "pgrep -f 'sleep 1020304157'").exit_code
|
653
715
|
ssh('localhost', "pkill -f 'sleep 1020304157'")
|
654
716
|
end
|
655
717
|
|
718
|
+
|
656
719
|
k = killer.value
|
657
720
|
k.success.should == true
|
658
721
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-ssh-simple
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-10-
|
12
|
+
date: 2011-10-28 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
16
|
-
requirement: &
|
16
|
+
requirement: &9197060 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 2.1.4
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *9197060
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: net-scp
|
27
|
-
requirement: &
|
27
|
+
requirement: &9194900 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.0.4
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *9194900
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: blockenspiel
|
38
|
-
requirement: &
|
38
|
+
requirement: &9193640 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 0.4.3
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *9193640
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: hashie
|
49
|
-
requirement: &
|
49
|
+
requirement: &9192620 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.1.0
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *9192620
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rake
|
60
|
-
requirement: &
|
60
|
+
requirement: &9191660 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 0.9.2.2
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *9191660
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &9180700 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *9180700
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: cover_me
|
82
|
-
requirement: &
|
82
|
+
requirement: &9179600 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *9179600
|
91
91
|
description: Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
|
92
92
|
email:
|
93
93
|
- moe@busyloop.net
|
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
124
|
version: '0'
|
125
125
|
segments:
|
126
126
|
- 0
|
127
|
-
hash:
|
127
|
+
hash: 2108785634158212741
|
128
128
|
requirements: []
|
129
129
|
rubyforge_project:
|
130
130
|
rubygems_version: 1.8.10
|