net-ssh-simple 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .yardoc
6
+ coverage.data
7
+ coverage
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -0,0 +1,44 @@
1
+ = Net::SSH::Simple
2
+
3
+ Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
4
+
5
+ == Installation
6
+
7
+ gem install net-ssh-simple
8
+
9
+ == Examples
10
+
11
+ === Block Syntax (synchronous)
12
+ Net::SSH::Simple.sync do
13
+ r = ssh 'example1.com', 'echo "Hello World."'
14
+ puts r.stdout #=> "Hello World."
15
+
16
+ scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
17
+ scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
18
+ end
19
+
20
+ === Block Syntax (asynchronous)
21
+ a = Net::SSH::Simple.async do
22
+ ssh 'example1.com', 'echo "Hello World."'
23
+ scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
24
+ scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
25
+ end
26
+ b = Net::SSH::Simple.async do
27
+ ssh 'example4.com', 'echo "Hello World."'
28
+ scp_ul 'example5.com', '/tmp/local_foo', '/tmp/remote_bar'
29
+ scp_dl 'example6.com', '/tmp/remote_foo', '/tmp/local_bar'
30
+ end
31
+ a.value # Wait for thread A to finish and capture result
32
+ b.value # Wait for thread B to finish and capture result
33
+
34
+ === Using an instance
35
+ s = Net::SSH::Simple.new
36
+ s.ssh 'example1.com', 'echo "Hello World."'
37
+ s.scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
38
+ s.scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
39
+ s.close
40
+
41
+ == Documentation
42
+
43
+ See {Net::SSH::Simple}[http://rubydoc.info/gems/net-ssh-simple/Net/SSH/Simple] for more examples and full API.
44
+
@@ -0,0 +1,25 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'bundler/setup'
3
+ Bundler::GemHelper.install_tasks
4
+ require 'rspec/core/rake_task'
5
+
6
+ task :default => :test
7
+
8
+ RSpec::Core::RakeTask.new("test:spec") do |t|
9
+ t.pattern = 'spec/*.rb'
10
+ t.rcov = false
11
+ #t.rspec_opts = '-b -c -f progress'
12
+ t.rspec_opts = '-b -c -f documentation'
13
+ end
14
+
15
+ namespace :test do
16
+ task :coverage do
17
+ require 'cover_me'
18
+ CoverMe.complete!
19
+ end
20
+ end
21
+
22
+ desc 'Run full test suite'
23
+ task :test => [ 'test:spec', 'test:coverage' ]
24
+
25
+
@@ -0,0 +1,7 @@
1
+ require 'net/ssh/simple/version'
2
+ require 'blockenspiel'
3
+ require 'hashie'
4
+ require 'net/ssh'
5
+ require 'net/scp'
6
+ require 'net/ssh/simple/core'
7
+
@@ -0,0 +1,502 @@
1
+ module Net
2
+ module SSH
3
+ # Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
4
+ #
5
+ # @example
6
+ # # Block Syntax (synchronous)
7
+ # Net::SSH::Simple.sync do
8
+ # ssh 'example1.com', 'echo "Hello World."'
9
+ # scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
10
+ # scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
11
+ # end
12
+ #
13
+ # @example
14
+ # # Block Syntax (asynchronous)
15
+ # a = Net::SSH::Simple.async do
16
+ # ssh 'example1.com', 'echo "Hello World."'
17
+ # scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
18
+ # scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
19
+ # end
20
+ # b = Net::SSH::Simple.async do
21
+ # ssh 'example4.com', 'echo "Hello World."'
22
+ # scp_ul 'example5.com', '/tmp/local_foo', '/tmp/remote_bar'
23
+ # scp_dl 'example6.com', '/tmp/remote_foo', '/tmp/local_bar'
24
+ # end
25
+ # a.value # Wait for thread A to finish and capture result
26
+ # b.value # Wait for thread B to finish and capture result
27
+ #
28
+ # @example
29
+ # # Using an instance
30
+ # s = Net::SSH::Simple.new
31
+ # s.ssh 'example1.com', 'echo "Hello World."'
32
+ # s.scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
33
+ # s.scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
34
+ # s.close
35
+ #
36
+ # @example
37
+ # # Using no instance
38
+ # # Note: This will create a new connection for each operation!
39
+ # # Use instance- or block-syntax for better performance.
40
+ # Net::SSH::Simple.ssh 'example1.com', 'echo "Hello World."'
41
+ # Net::SSH::Simple.scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
42
+ # Net::SSH::Simple.scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
43
+ #
44
+ # @example
45
+ # # Error Handling with Block Syntax (synchronous)
46
+ # begin
47
+ # Net::SSH::Simple.sync do
48
+ # r = ssh 'example1.com', 'echo "Hello World."'
49
+ # if r.success and r.stdout == 'Hello World.'
50
+ # puts "Success! I Helloed World."
51
+ # end
52
+ #
53
+ # scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
54
+ # if r.success and r.sent == r.total
55
+ # puts "Success! Uploaded #{r.sent} of #{r.total} bytes."
56
+ # end
57
+ #
58
+ # scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
59
+ # if r.success and r.sent == r.total
60
+ # puts "Success! Downloaded #{r.sent} of #{r.total} bytes."
61
+ # end
62
+ # end
63
+ # rescue Net::SSH::Simple::Error => e
64
+ # puts "Something bad happened!"
65
+ # puts e # Human readable error
66
+ # puts e.wrapped # Original Exception from Net::SSH
67
+ # puts e.context # Config that triggered the error
68
+ # end
69
+ #
70
+ # @example
71
+ # # Error Handling with Block Syntax (asynchronous)
72
+ # #
73
+ # # Exceptions are thrown inside your thread.
74
+ # # You are free to handle them or pass them outwards.
75
+ # #
76
+ #
77
+ # a = Net::SSH::Simple.async do
78
+ # begin
79
+ # ssh 'example1.com', 'echo "Hello World."'
80
+ # scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
81
+ # scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
82
+ # rescue Net::SSH::Result => e
83
+ # # return our exception to the parent thread
84
+ # e
85
+ # end
86
+ # end
87
+ # r = a.value # Wait for thread to finish and capture result
88
+ #
89
+ # unless r.is_a? Net::SSH::Result
90
+ # puts "Something bad happened!"
91
+ # puts r
92
+ # end
93
+ #
94
+ # @example
95
+ # # Error Handling with an instance
96
+ # s = Net::SSH::Simple.new
97
+ # begin
98
+ # r = s.ssh 'example1.com', 'echo "Hello World."'
99
+ # if r.success and r.stdout == 'Hello World.'
100
+ # puts "Success! I Helloed World."
101
+ # end
102
+ #
103
+ # r = s.scp_ul 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
104
+ # if r.success and r.sent == r.total
105
+ # puts "Success! Uploaded #{r.sent} of #{r.total} bytes."
106
+ # end
107
+ #
108
+ # r = s.scp_dl 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
109
+ # if r.success and r.sent == r.total
110
+ # puts "Success! Downloaded #{r.sent} of #{r.total} bytes."
111
+ # end
112
+ # rescue Net::SSH::Simple::Error => e
113
+ # puts "Something bad happened!"
114
+ # puts e # Human readable error
115
+ # puts e.wrapped # Original Exception from Net::SSH
116
+ # puts e.context # Config that triggered the error
117
+ # ensure
118
+ # s.close # don't forget the clean up!
119
+ # end
120
+ #
121
+ # @example
122
+ # # Parametrizing Net::SSH
123
+ # Net::SSH::Simple.sync do
124
+ # ssh('example1.com', 'echo "Hello World."',
125
+ # {:user => 'tom', :password => 'jerry', :port => 1234})
126
+ # end
127
+ #
128
+ # @example
129
+ # # Using the SCP progress callback
130
+ # Net::SSH::Simple.sync do
131
+ # scp_ul 'example1.com', '/tmp/local_foo', '/tmp/remote_bar' do |sent, total|
132
+ # puts "Bytes uploaded: #{sent} of #{total}"
133
+ # end
134
+ # end
135
+ #
136
+ # @author moe@busyloop.net
137
+ #
138
+ class Simple
139
+ include Blockenspiel::DSL
140
+
141
+ #
142
+ # Result of a Net::SSH::Simple::Operation.
143
+ #
144
+ # @return [Net::SSH::Simple::Result] Result of the current operation
145
+ attr_reader :result
146
+
147
+ #
148
+ # Perform ssh command on a remote host and capture the result.
149
+ # This will create a new connection for each invocation.
150
+ #
151
+ # @example
152
+ # Net::SSH::Simple.ssh('localhost', 'echo Hello').class #=> Net::SSH::Simple::Result
153
+ #
154
+ # @example
155
+ # Net::SSH::Simple.ssh('localhost', 'echo Hello').stdout #=> "Hello"
156
+ #
157
+ # @param (see Net::SSH::Simple#ssh)
158
+ # @raise [Net::SSH::Simple::Error]
159
+ # @return [Net::SSH::Simple::Result] Result
160
+ def self.ssh(*args)
161
+ s = self.new
162
+ r = s.ssh(*args)
163
+ s.close
164
+ r
165
+ end
166
+
167
+ #
168
+ # SCP upload to a remote host.
169
+ # This will create a new connection for each invocation.
170
+ #
171
+ # @example
172
+ # # SCP Upload
173
+ # Net::SSH::Simple.scp_ul('localhost', '/tmp/local_foo', '/tmp/remote_bar')
174
+ #
175
+ # @example
176
+ # # Pass a block to monitor progress
177
+ # Net::SSH::Simple.scp_ul('localhost', '/tmp/local_foo', '/tmp/remote_bar') do |sent, total|
178
+ # puts "Bytes uploaded: #{sent} of #{total}"
179
+ # end
180
+ #
181
+ # @param (see Net::SSH::Simple#scp_ul)
182
+ # @raise [Net::SSH::Simple::Error]
183
+ # @return [Net::SSH::Simple::Result] Result
184
+ def self.scp_ul(*args, &block)
185
+ s = self.new
186
+ r = s.scp_ul(*args, &block)
187
+ s.close
188
+ r
189
+ end
190
+
191
+ #
192
+ # SCP download from a remote host.
193
+ # This will create a new connection for each invocation.
194
+ #
195
+ # @example
196
+ # # SCP Download
197
+ # Net::SSH::Simple.scp_dl('localhost', '/tmp/remote_foo', '/tmp/local_bar')
198
+ #
199
+ # @example
200
+ # # Pass a block to monitor progress
201
+ # Net::SSH::Simple.scp_dl('localhost', '/tmp/remote_foo', '/tmp/local_bar') do |sent, total|
202
+ # puts "Bytes downloaded: #{sent} of #{total}"
203
+ # end
204
+ #
205
+ # @param (see Net::SSH::Simple#scp_dl)
206
+ # @raise [Net::SSH::Simple::Error]
207
+ # @return [Net::SSH::Simple::Result] Result
208
+ def self.scp_dl(*args, &block)
209
+ s = self.new
210
+ r = s.scp_dl(*args, &block)
211
+ s.close
212
+ r
213
+ end
214
+
215
+ #
216
+ # SCP upload to a remote host.
217
+ # The underlying Net::SSH::Simple instance will re-use
218
+ # existing connections for optimal performance.
219
+ #
220
+ # @param [String] host Destination hostname or ip-address
221
+ # @param [String] cmd Shell command to execute
222
+ # @param opts (see Net::SSH::Simple#ssh)
223
+ # @param [Block] block Progress callback (optional)
224
+ # @return [Net::SSH::Simple::Result] Result
225
+ #
226
+ def scp_ul(host, src, dst, opts={}, &block)
227
+ scp(:upload, host, src, dst, opts, &block)
228
+ end
229
+
230
+ #
231
+ # SCP download from a remote host.
232
+ # The underlying Net::SSH::Simple instance will re-use
233
+ # existing connections for optimal performance.
234
+ #
235
+ # @param [String] host Destination hostname or ip-address
236
+ # @param [String] cmd Shell command to execute
237
+ # @param [Hash] opts Parameters for the underlying Net::SSH
238
+ # @param [Block] block Progress callback (optional)
239
+ # @return [Net::SSH::Simple::Result] Result
240
+ # @see Net::SSH::Simple#scp_ul
241
+ #
242
+ def scp_dl(host, src, dst, opts={}, &block)
243
+ scp(:download, host, src, dst, opts, &block)
244
+ end
245
+
246
+ #
247
+ # Perform SSH operation on a remote host and capture the result.
248
+ # The underlying Net::SSH::Simple instance will re-use
249
+ # existing connections for optimal performance.
250
+ #
251
+ # @return [Net::SSH::Simple::Result] Result
252
+ # @param [String] host Destination hostname or ip-address
253
+ # @param [String] cmd Shell command to execute
254
+ # @param [Hash] opts Parameters for the underlying Net::SSH
255
+ # @option opts [Array] :auth_methods
256
+ # an array of authentication methods to try
257
+ #
258
+ # @option opts [String] :compression
259
+ # the compression algorithm to use,
260
+ # or true to use whatever is supported.
261
+ #
262
+ # @option opts [Number] :compression_level
263
+ # the compression level to use when sending data
264
+ #
265
+ # @option opts [String/boolean] :opts (true)
266
+ # set to true to load the default OpenSSH opts files
267
+ # (~/.ssh/opts, /etc/ssh_opts), or to false to not load them,
268
+ # or to a file-name (or array of file-names) to load those
269
+ # specific configuration files.
270
+ #
271
+ # @option opts [Array] :encryption
272
+ # the encryption cipher (or ciphers) to use
273
+ #
274
+ # @option opts [boolean] :forward_agent
275
+ # set to true if you want the SSH agent connection to be forwarded
276
+ #
277
+ # @option opts [String/Array] :global_known_hosts_file
278
+ # (['/etc/ssh/known_hosts','/etc/ssh/known_hosts2'])
279
+ # the location of the global known hosts file.
280
+ # Set to an array if you want to specify multiple
281
+ # global known hosts files.
282
+ #
283
+ # @option opts [String/Array] :hmac
284
+ # the hmac algorithm (or algorithms) to use
285
+ #
286
+ # @option opts [String] :host_key
287
+ # the host key algorithm (or algorithms) to use
288
+ #
289
+ # @option opts [String] :host_key_alias
290
+ # the host name to use when looking up or adding a host to a known_hosts dictionary file
291
+ #
292
+ # @option opts [String] :host_name
293
+ # the real host name or IP to log into. This is used instead of the host parameter,
294
+ # and is primarily only useful when specified in an SSH configuration file.
295
+ # It lets you specify an alias, similarly to adding an entry in /etc/hosts but
296
+ # without needing to modify /etc/hosts.
297
+ #
298
+ # @option opts [String/Array] :kex
299
+ # the key exchange algorithm (or algorithms) to use
300
+ #
301
+ # @option opts [Array] :keys
302
+ # an array of file names of private keys to use for publickey and hostbased authentication
303
+ #
304
+ # @option opts [Array] :key_data
305
+ # an array of strings, with each element of the array being a raw private key in PEM format.
306
+ #
307
+ # @option opts [boolean] :keys_only
308
+ # set to true to use only private keys from keys and key_data parameters, even if
309
+ # ssh-agent offers more identities. This option is intended for situations where
310
+ # ssh-agent offers many different identites.
311
+ #
312
+ # @option opts [Logger] :logger
313
+ # the logger instance to use when logging
314
+ #
315
+ # @option opts [boolean/:very] :paranoid
316
+ # either true, false, or :very, specifying how strict host-key verification should be
317
+ #
318
+ # @option opts [String] :passphrase (nil)
319
+ # the passphrase to use when loading a private key (default is nil, for no passphrase)
320
+ #
321
+ # @option opts [String] :password
322
+ # the password to use to login
323
+ #
324
+ # @option opts [Integer] :port
325
+ # the port to use when connecting to the remote host
326
+ #
327
+ # @option opts [Hash] :properties
328
+ # a hash of key/value pairs to add to the new connection’s properties
329
+ # (see Net::SSH::Connection::Session#properties)
330
+ #
331
+ # @option opts [String] :proxy
332
+ # a proxy instance (see Proxy) to use when connecting
333
+ #
334
+ # @option opts [Integer] :rekey_blocks_limit
335
+ # the max number of blocks to process before rekeying
336
+ #
337
+ # @option opts [Integer] :rekey_limit
338
+ # the max number of bytes to process before rekeying
339
+ #
340
+ # @option opts [Integer] :rekey_packet_limit
341
+ # the max number of packets to process before rekeying
342
+ #
343
+ # @option opts [Integer] :timeout
344
+ # how long to wait for the initial connection to be made
345
+ #
346
+ # @option opts [String] :user
347
+ # the username to log in as
348
+ #
349
+ # @option opts [String/Array] :user_known_hosts_file
350
+ # (['~/.ssh/known_hosts, ~/.ssh/known_hosts2'])
351
+ # the location of the user known hosts file. Set to an array to specify multiple
352
+ # user known hosts files.
353
+ #
354
+ # @option opts [Symbol] :verbose
355
+ # how verbose to be (Logger verbosity constants, Logger::DEBUG is very verbose,
356
+ # Logger::FATAL is all but silent). Logger::FATAL is the default. The symbols
357
+ # :debug, :info, :warn, :error, and :fatal are also supported and are translated
358
+ # to the corresponding Logger constant.
359
+ #
360
+ # @see http://net-ssh.github.com/ssh/v2/api/classes/Net/SSH/Config.html
361
+ # Net::SSH documentation for the 'opts'-hash
362
+ def ssh(host, cmd, opts={})
363
+ with_session(host, opts) do |session|
364
+ @result = Result.new(
365
+ { :host => host, :cmd => cmd, :start_at => Time.new,
366
+ :stdout => '' , :stderr => '' , :success => nil
367
+ } )
368
+
369
+ channel = session.open_channel do |chan|
370
+ chan.exec cmd do |ch, success|
371
+ @result[:success] = success
372
+ ch.on_data do |c, data|
373
+ @result[:stdout] += data.to_s
374
+ end
375
+ ch.on_extended_data do |c, type, data|
376
+ @result[:stderr] += data.to_s
377
+ end
378
+ ch.on_request('exit-status') do |c, data|
379
+ @result[:exit_code] = data.read_long
380
+ end
381
+ ch.on_request('exit-signal') do |c, data|
382
+ @result[:exit_signal] = data.read_string
383
+ @result[:success] = false
384
+ raise "Killed by SIG#{@result[:exit_signal]}"
385
+ end
386
+ end
387
+ end
388
+ channel.wait
389
+ @result[:finish_at] = Time.new
390
+ @result
391
+ end
392
+ end
393
+
394
+ dsl_methods false
395
+
396
+ def initialize()
397
+ @sessions = {}
398
+ @result = Result.new
399
+ end
400
+
401
+ #
402
+ # Spawn a Thread to perform a sequence of ssh/scp operations.
403
+ #
404
+ # @param [Block] block
405
+ # @return [Thread] Thread executing the SSH-Block.
406
+ #
407
+ def self.async(&block)
408
+ Thread.new do
409
+ self.sync(&block)
410
+ end
411
+ end
412
+
413
+ #
414
+ # Perform a sequence of ssh/scp operations.
415
+ #
416
+ # @return [Net::SSH::Simple::Result] Result
417
+ #
418
+ def self.sync(&block)
419
+ b = self.new
420
+ r = Blockenspiel.invoke(block, b)
421
+ b.close
422
+ r
423
+ end
424
+
425
+ #
426
+ # Close and cleanup.
427
+ #
428
+ # @return [Net::SSH::Simple::Result] Result
429
+ #
430
+ def close
431
+ @sessions.values.each do |session|
432
+ session.close
433
+ end
434
+ @result
435
+ end
436
+
437
+ private
438
+ def with_session(host, opts, &block)
439
+ begin
440
+ session = @sessions[host.hash] = @sessions[host.hash] ||\
441
+ Net::SSH.start(*[host, opts[:user], opts])
442
+ block.call(session)
443
+ rescue => e
444
+ opts[:password].gsub!(/./,'*') if opts.include? :password
445
+ @result[:exception] = e
446
+ @result[:context] = [host,opts]
447
+ raise Net::SSH::Simple::Error, [e, [host,opts]]
448
+ end
449
+ end
450
+
451
+ def scp(mode, host, src, dst, opts={}, &block)
452
+ @result = Result.new(
453
+ { :host => host, :cmd => :scp_dl, :start_at => Time.new,
454
+ :src => src , :dst => dst , :success => false
455
+ } )
456
+ with_session(host, opts) do |session|
457
+ lt = 0
458
+ channel = session.scp.send(mode, src, dst) do |ch, name, sent, total|
459
+ @result[:name] ||= name
460
+ @result[:total] ||= total
461
+ @result[:sent] = sent
462
+ block.call(sent, total) unless block.nil?
463
+ end
464
+ channel.wait
465
+ @result[:finish_at] = Time.new
466
+ @result[:success] = @result[:sent] == @result[:total]
467
+ @result
468
+ end
469
+ end
470
+
471
+ #
472
+ # Encapsulates any Errors that may occur
473
+ # during a Net::SSH::Simple operation.
474
+ #
475
+ class Error < RuntimeError
476
+ # Reference to the underlying Net::SSH Exception
477
+ attr_reader :wrapped
478
+ # The opts-hash of the operation that triggered the Error
479
+ attr_reader :context
480
+
481
+ def initialize(msg, e=$!)
482
+ super(msg)
483
+ @wrapped = e
484
+ @context = msg[1]
485
+ end
486
+
487
+ def to_s
488
+ "#{super[0]} @ #{super[1]}"
489
+ end
490
+ end
491
+
492
+ #
493
+ # Result of a Net::SSH::Simple operation.
494
+ #
495
+ # This Mash contains various information that may
496
+ # be relevant to your interests.
497
+ #
498
+ class Result < Hashie::Mash; end
499
+ end
500
+ end
501
+ end
502
+
@@ -0,0 +1,7 @@
1
+ module Net
2
+ module SSH
3
+ class Simple
4
+ VERSION = "0.9.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "net/ssh/simple/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "net-ssh-simple"
7
+ s.version = Net::SSH::Simple::VERSION
8
+ s.authors = ["Moe"]
9
+ s.email = ["moe@busyloop.net"]
10
+ s.homepage = "https://github.com/busyloop/net-ssh-simple"
11
+ s.description = %q{Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.}
12
+ s.summary = %q{SSH without the headache}
13
+
14
+ s.rubyforge_project = "net-ssh-simple"
15
+
16
+ s.add_dependency "net-ssh", "~> 2.1.4"
17
+ s.add_dependency "net-scp", "~> 1.0.4"
18
+ s.add_dependency "blockenspiel", "~> 0.4.3"
19
+ s.add_dependency "hashie", "~> 1.1.0"
20
+
21
+ s.add_development_dependency "rake"
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "cover_me"
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
+ s.require_paths = ["lib"]
29
+ end
@@ -0,0 +1,275 @@
1
+ require 'cover_me'
2
+ require 'net/ssh/simple'
3
+ require 'digest/md5'
4
+ require 'securerandom'
5
+
6
+ #
7
+ # In order to run this test-suite you must
8
+ # have ssh-access to localhost.
9
+ #
10
+ # 1. Add your own ssh-key to authorized_keys:
11
+ # cat >>~/.ssh/authorized_keys ~/.ssh/id_rsa.pub
12
+ #
13
+ # 2. Add something like this to ~/.ssh/config:
14
+ #
15
+ # Host localhost
16
+ # User my_name
17
+ # Port 22
18
+ #
19
+ # The test-suite will (over)write the following files on localhost:
20
+ # /tmp/ssh_test_in{0,1,2,3,4}
21
+ # /tmp/ssh_test_out{0,1,2,3,4}
22
+ #
23
+
24
+ describe Net::SSH::Simple do
25
+ describe "singleton" do
26
+ before :each do
27
+ (0..4).each do |i|
28
+ begin
29
+ File.unlink(File.join('/tmp', "ssh_test_in#{i}"))
30
+ rescue; end
31
+ begin
32
+ File.unlink(File.join('/tmp', "ssh_test_out#{i}"))
33
+ rescue; end
34
+ File.open(File.join('/tmp', "ssh_test_in#{i}"), 'w') do |fd|
35
+ fd.write(SecureRandom.random_bytes(1024+SecureRandom.random_number(8192)))
36
+ end
37
+ end
38
+ end
39
+
40
+ it "fails gently" do
41
+ lambda {
42
+ Net::SSH::Simple.ssh('localhost', 'true', {:port => 0})
43
+ }.should raise_error(Net::SSH::Simple::Error)
44
+
45
+ begin
46
+ Net::SSH::Simple.ssh('localhost', 'true', {:port => 0})
47
+ rescue => e
48
+ e.to_s.should == 'Connection refused - connect(2) @ ["localhost", {:port=>0}]'
49
+ end
50
+ end
51
+
52
+ it "returns a result" do
53
+ Net::SSH::Simple.ssh('localhost', 'true').success.should == true
54
+ end
55
+
56
+ it "recognizes exit-codes" do
57
+ Net::SSH::Simple.ssh('localhost', 'true').exit_code.should == 0
58
+ Net::SSH::Simple.ssh('localhost', 'false').exit_code.should == 1
59
+ end
60
+
61
+ it "reads stdout" do
62
+ Net::SSH::Simple.ssh('localhost', 'echo hello').stdout.should == "hello\n"
63
+ long = Net::SSH::Simple.ssh('localhost', 'seq 1 100000').stdout
64
+ Digest::MD5.hexdigest(long).should == 'dea9193b768319cbb4ff1a137ac03113'
65
+ end
66
+
67
+ it "reads stderr" do
68
+ Net::SSH::Simple.ssh('localhost', 'echo hello 1>&2').stderr.should == "hello\n"
69
+ long = Net::SSH::Simple.ssh('localhost', 'seq 1 100000 1>&2').stderr
70
+ Digest::MD5.hexdigest(long).should == 'dea9193b768319cbb4ff1a137ac03113'
71
+ end
72
+
73
+ it "uploads via scp" do
74
+ mockback = mock(:progress_callback)
75
+ mockback.should_receive(:ping).at_least(:once)
76
+ r = Net::SSH::Simple.scp_ul('localhost', '/tmp/ssh_test_in0', '/tmp/ssh_test_out0') do |sent,total|
77
+ mockback.ping
78
+ end
79
+ r.success.should == true
80
+ Digest::MD5.file('/tmp/ssh_test_in0').should == Digest::MD5.file('/tmp/ssh_test_out0')
81
+ end
82
+
83
+ it "downloads via scp" do
84
+ mockback = mock(:progress_callback)
85
+ mockback.should_receive(:ping).at_least(:once)
86
+ r = Net::SSH::Simple.scp_dl('localhost', '/tmp/ssh_test_in0', '/tmp/ssh_test_out0') do |sent,total|
87
+ mockback.ping
88
+ end
89
+ r.success.should == true
90
+ Digest::MD5.file('/tmp/ssh_test_in0').should == Digest::MD5.file('/tmp/ssh_test_out0')
91
+ end
92
+ end
93
+
94
+ describe "instance" do
95
+ before :each do
96
+ @s = Net::SSH::Simple.new
97
+
98
+ (0..4).each do |i|
99
+ begin
100
+ File.unlink(File.join('/tmp', "ssh_test_in#{i}"))
101
+ rescue; end
102
+ begin
103
+ File.unlink(File.join('/tmp', "ssh_test_out#{i}"))
104
+ rescue; end
105
+ File.open(File.join('/tmp', "ssh_test_in#{i}"), 'w') do |fd|
106
+ fd.write(SecureRandom.random_bytes(1024+SecureRandom.random_number(8192)))
107
+ end
108
+ end
109
+ end
110
+
111
+ after :each do
112
+ @s.close
113
+ end
114
+
115
+ it "returns a result" do
116
+ @s.ssh('localhost', 'true').success.should == true
117
+ end
118
+
119
+ it "recognizes exit-codes" do
120
+ @s.ssh('localhost', 'true').exit_code.should == 0
121
+ @s.ssh('localhost', 'false').exit_code.should == 1
122
+ end
123
+
124
+ it "reads stdout" do
125
+ @s.ssh('localhost', 'echo hello').stdout.should == "hello\n"
126
+ long = @s.ssh('localhost', 'seq 1 100000').stdout
127
+ Digest::MD5.hexdigest(long).should == 'dea9193b768319cbb4ff1a137ac03113'
128
+ end
129
+
130
+ it "reads stderr" do
131
+ @s.ssh('localhost', 'echo hello 1>&2').stderr.should == "hello\n"
132
+ long = @s.ssh('localhost', 'seq 1 100000 1>&2').stderr
133
+ Digest::MD5.hexdigest(long).should == 'dea9193b768319cbb4ff1a137ac03113'
134
+ end
135
+
136
+ it "uploads via scp" do
137
+ mockback = mock(:progress_callback)
138
+ mockback.should_receive(:ping).at_least(:once)
139
+ r = @s.scp_ul('localhost', '/tmp/ssh_test_in0', '/tmp/ssh_test_out0') do |sent,total|
140
+ mockback.ping
141
+ end
142
+ r.success.should == true
143
+ Digest::MD5.file('/tmp/ssh_test_in0').should == Digest::MD5.file('/tmp/ssh_test_out0')
144
+ end
145
+
146
+ it "downloads via scp" do
147
+ mockback = mock(:progress_callback)
148
+ mockback.should_receive(:ping).at_least(:once)
149
+ r = @s.scp_dl('localhost', '/tmp/ssh_test_in0', '/tmp/ssh_test_out0') do |sent,total|
150
+ mockback.ping
151
+ end
152
+ r.success.should == true
153
+ Digest::MD5.file('/tmp/ssh_test_in0').should == Digest::MD5.file('/tmp/ssh_test_out0')
154
+ end
155
+ end
156
+
157
+ describe "synchronous block syntax" do
158
+ it "returns a result" do
159
+ Net::SSH::Simple.sync do
160
+ ssh('localhost', 'true').success.should == true
161
+ end
162
+ end
163
+
164
+ it "recognizes exit-codes" do
165
+ Net::SSH::Simple.sync do
166
+ ssh('localhost', 'true').exit_code.should == 0
167
+ ssh('localhost', 'false').exit_code.should == 1
168
+ end
169
+ end
170
+
171
+ it "reads stdout" do
172
+ Net::SSH::Simple.sync do
173
+ ssh('localhost', 'echo hello').stdout.should == "hello\n"
174
+ long = ssh('localhost', 'seq 1 100000').stdout
175
+ Digest::MD5.hexdigest(long).should == 'dea9193b768319cbb4ff1a137ac03113'
176
+ end
177
+ end
178
+
179
+ it "reads stderr" do
180
+ Net::SSH::Simple.sync do
181
+ ssh('localhost', 'echo hello 1>&2').stderr.should == "hello\n"
182
+ long = ssh('localhost', 'seq 1 100000 1>&2').stderr
183
+ Digest::MD5.hexdigest(long).should == 'dea9193b768319cbb4ff1a137ac03113'
184
+ end
185
+ end
186
+
187
+ it "uploads via scp" do
188
+ Net::SSH::Simple.sync do
189
+ mockback = mock(:progress_callback)
190
+ mockback.should_receive(:ping).at_least(:once)
191
+ r = scp_ul('localhost', '/tmp/ssh_test_in0', '/tmp/ssh_test_out0') do |sent,total|
192
+ mockback.ping
193
+ end
194
+ r.success.should == true
195
+ Digest::MD5.file('/tmp/ssh_test_in0').should == Digest::MD5.file('/tmp/ssh_test_out0')
196
+ end
197
+ end
198
+
199
+ it "downloads via scp" do
200
+ Net::SSH::Simple.sync do
201
+ mockback = mock(:progress_callback)
202
+ mockback.should_receive(:ping).at_least(:once)
203
+ r = scp_dl('localhost', '/tmp/ssh_test_in0', '/tmp/ssh_test_out0') do |sent,total|
204
+ mockback.ping
205
+ end
206
+ r.success.should == true
207
+ Digest::MD5.file('/tmp/ssh_test_in0').should == Digest::MD5.file('/tmp/ssh_test_out0')
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "asynchronous block syntax" do
213
+ before :each do
214
+ (0..4).each do |i|
215
+ begin
216
+ File.unlink(File.join('/tmp', "ssh_test_in#{i}"))
217
+ rescue; end
218
+ begin
219
+ File.unlink(File.join('/tmp', "ssh_test_out#{i}"))
220
+ rescue; end
221
+ File.open(File.join('/tmp', "ssh_test_in#{i}"), 'w') do |fd|
222
+ fd.write(SecureRandom.random_bytes(1024+SecureRandom.random_number(8192)))
223
+ end
224
+ end
225
+ end
226
+
227
+ it "copes with a little concurrency" do
228
+ t = []
229
+ (0..4).each do |i|
230
+ t[i] = Net::SSH::Simple.async do
231
+ mockback = mock(:progress_callback)
232
+ mockback.should_receive(:ping).at_least(:once)
233
+ r = nil
234
+ if 0 == i % 2
235
+ r = scp_dl('localhost', "/tmp/ssh_test_in#{i}", "/tmp/ssh_test_out#{i}") do |sent,total|
236
+ mockback.ping
237
+ end
238
+ else
239
+ r = scp_ul('localhost', "/tmp/ssh_test_in#{i}", "/tmp/ssh_test_out#{i}") do |sent,total|
240
+ mockback.ping
241
+ end
242
+ end
243
+ r.success.should == true
244
+ ssh('localhost', "echo hello #{i}")
245
+ end
246
+ end
247
+
248
+ (0..4).each do |i|
249
+ r = t[i].value
250
+ r.stdout.should == "hello #{i}\n"
251
+ Digest::MD5.file("/tmp/ssh_test_in#{i}").should == Digest::MD5.file("/tmp/ssh_test_out#{i}")
252
+ end
253
+ end
254
+
255
+ it "handles signals" do
256
+ victim = Net::SSH::Simple.async do
257
+ begin
258
+ ssh('localhost', 'sleep 1020304157')
259
+ rescue => e
260
+ e
261
+ end
262
+ end
263
+
264
+ killer = Net::SSH::Simple.async do
265
+ ssh('localhost', "pkill -f 'sleep 1020304157'")
266
+ end
267
+
268
+ k = killer.value
269
+ k.success.should == true
270
+
271
+ v = victim.value
272
+ v.to_s.should == 'Killed by SIGTERM @ ["localhost", {}]'
273
+ end
274
+ end
275
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-ssh-simple
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 9
8
+ - 0
9
+ version: 0.9.0
10
+ platform: ruby
11
+ authors:
12
+ - Moe
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-10-22 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: net-ssh
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 1
30
+ - 4
31
+ version: 2.1.4
32
+ type: :runtime
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: net-scp
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 1
44
+ - 0
45
+ - 4
46
+ version: 1.0.4
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: blockenspiel
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ - 4
60
+ - 3
61
+ version: 0.4.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: hashie
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ~>
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 1
74
+ - 1
75
+ - 0
76
+ version: 1.1.0
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: rake
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *id005
93
+ - !ruby/object:Gem::Dependency
94
+ name: rspec
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: *id006
106
+ - !ruby/object:Gem::Dependency
107
+ name: cover_me
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: *id007
119
+ description: Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.
120
+ email:
121
+ - moe@busyloop.net
122
+ executables: []
123
+
124
+ extensions: []
125
+
126
+ extra_rdoc_files: []
127
+
128
+ files:
129
+ - .gitignore
130
+ - Gemfile
131
+ - README.rdoc
132
+ - Rakefile
133
+ - lib/net/ssh/simple.rb
134
+ - lib/net/ssh/simple/core.rb
135
+ - lib/net/ssh/simple/version.rb
136
+ - net-ssh-simple.gemspec
137
+ - spec/net-ssh-simple.rb
138
+ has_rdoc: true
139
+ homepage: https://github.com/busyloop/net-ssh-simple
140
+ licenses: []
141
+
142
+ post_install_message:
143
+ rdoc_options: []
144
+
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ hash: 1030525555759223160
153
+ segments:
154
+ - 0
155
+ version: "0"
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ hash: 1030525555759223160
162
+ segments:
163
+ - 0
164
+ version: "0"
165
+ requirements: []
166
+
167
+ rubyforge_project: net-ssh-simple
168
+ rubygems_version: 1.3.7
169
+ signing_key:
170
+ specification_version: 3
171
+ summary: SSH without the headache
172
+ test_files: []
173
+