Class: Net::SSH::Simple

Inherits:
Object
  • Object
show all
Includes:
Blockenspiel::DSL
Defined in:
lib/net/ssh/simple.rb,
lib/net/ssh/simple.rb,
lib/net/ssh/simple/version.rb

Overview

Net::SSH::Simple is a simple wrapper around Net::SSH and Net::SCP.

Examples:

# Block Syntax (synchronous)
Net::SSH::Simple.sync do
  r = ssh 'example1.com', 'echo "Hello World."'
  puts r.stdout    #=> "Hello World."
  puts r.exit_code #=> 0

  scp_put 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
  scp_get 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
end
# Block Syntax (asynchronous)
t1 = Net::SSH::Simple.async do
  scp_put 'example1.com', '/tmp/local_foo', '/tmp/remote_bar'
  ssh    'example3.com', 'echo "Hello World A."'
end
t2 = Net::SSH::Simple.async do
  scp_get 'example6.com', '/tmp/remote_foo', '/tmp/local_bar'
  ssh    'example7.com', 'echo "Hello World B."'
end
r1 = t1.value # wait for t1 to finish and grab return value
r2 = t2.value # wait for t2 to finish and grab return value

puts r1.stdout #=> "Hello World A."
puts r2.stdout #=> "Hello World B."
# Using an instance
s = Net::SSH::Simple.new
s.ssh     'example1.com', 'echo "Hello World."'
s.scp_put 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
s.scp_get 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
s.close
# Using no instance
# Note: This will create a new connection for each operation!
#       Use instance- or block-syntax for better performance.
Net::SSH::Simple.ssh     'example1.com', 'echo "Hello World."'
Net::SSH::Simple.scp_put 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
Net::SSH::Simple.scp_get 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
# Error Handling with Block Syntax (synchronous)
begin
  Net::SSH::Simple.sync do
    r = ssh    'example1.com', 'echo "Hello World."'
    if r.success and r.stdout == 'Hello World.'
      puts "Success! I Helloed World."
    end

    r = scp_put 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
    if r.success and r.sent == r.total
      puts "Success! Uploaded #{r.sent} of #{r.total} bytes."
    end

    r = scp_get 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
    if r.success and r.sent == r.total
      puts "Success! Downloaded #{r.sent} of #{r.total} bytes."
    end
  end
rescue Net::SSH::Simple::Error => e
  puts "Something bad happened!"
  puts e          # Human readable error
  puts e.wrapped  # Original Exception
  puts e.result   # Net::SSH::Simple::Result
end
# Error Handling with Block Syntax (asynchronous)
#
# Exceptions are raised inside your thread.
# You are free to handle them or pass them outwards.

a = Net::SSH::Simple.async do
  begin
    ssh     'example1.com', 'echo "Hello World."'
    scp_put 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
    scp_get 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
  rescue Net::SSH::Simple::Error => e
    # return our exception to the parent thread
    e
  end
end
r = a.value # Wait for thread to finish and capture result

unless r.is_a? Net::SSH::Simple::Result
  puts "Something bad happened!"
  puts r
end
# Error Handling with an instance
s = Net::SSH::Simple.new
begin
  r = s.ssh    'example1.com', 'echo "Hello World."'
  if r.success and r.stdout == 'Hello World.'
    puts "Success! I Helloed World."
  end

  r = s.scp_put 'example2.com', '/tmp/local_foo', '/tmp/remote_bar'
  if r.success and r.sent == r.total
    puts "Success! Uploaded #{r.sent} of #{r.total} bytes."
  end

  r = s.scp_get 'example3.com', '/tmp/remote_foo', '/tmp/local_bar'
  if r.success and r.sent == r.total
    puts "Success! Downloaded #{r.sent} of #{r.total} bytes."
  end
rescue Net::SSH::Simple::Error => e
  puts "Something bad happened!"
  puts e          # Human readable error
  puts e.wrapped  # Original Exception
  puts e.result   # Net::SSH::Simple::Result (partial result)
ensure
  s.close # don't forget the clean up!
end
# Parameters
Net::SSH::Simple.sync do
  ssh('example1.com', 'echo "Hello World."',
      {:user => 'tom', :password => 'jerry', :port => 1234})
end

# Parameter inheritance
Net::SSH::Simple.sync({:user => 'tom', :port => 1234}) do
  # Both commands will inherit :user and :port
  ssh('example1.com', 'echo "Hello World."', {:password => 'jerry'})
  scp_put('example2.com', '/tmp/a', '/tmp/a', {:password => 's3cr3t'})
end
# Using the SCP progress callback
Net::SSH::Simple.sync do
  scp_put 'example1.com', '/tmp/local_foo', '/tmp/remote_bar' do |sent, total|
    puts "Bytes uploaded: #{sent} of #{total}"
  end
end
#
# Here be dragons: Using the event-API for a stdin->stdout pipeline
#
r = Net::SSH::Simple.sync do
  # open a shell
  ssh('localhost', '/bin/sh') do |e,c,d|
    # e = :start, :stdout, :stderr, :exit_code, :exit_signal or :finish
    # c = our Net::SSH::Connection::Channel instance
    # d = data for this event
    case e
      # :start is triggered exactly once per connection
      when :start
        # we can send data using Channel#send_data
        c.send_data("echo 'hello stdout'\n")
        c.send_data("echo 'hello stderr' 1>&2\n")
        # don't forget to eof when done feeding!
        c.eof!

      # :stdout is triggered when there's stdout data from remote.
      # by default the data is also appended to result[:stdout].
      # you may return :no_append as seen below to avoid that.
      when :stdout
        # read the input line-wise (it *will* arrive fragmented!)
        (@buf ||= '') << d
        while line = @buf.slice!(/(.*)\r?\n/)
          puts line #=> "hello stdout"
        end
        :no_append

      # :stderr is triggered when there's stderr data from remote.
      # by default the data is also appended to result[:stderr].
      # you may return :no_append as seen below to avoid that.
      when :stderr
        # read the input line-wise (it *will* arrive fragmented!)
        (@buf ||= '') << d
        while line = @buf.slice!(/(.*)\r?\n/)
          puts line #=> "hello stderr"
        end
        :no_append

      # :exit_code is triggered when the remote process exits normally.
      # it does *not* trigger when the remote process exits by signal!
      when :exit_code
        puts d #=> 0

      # :exit_signal is triggered when the remote is killed by signal.
      # this would normally raise a Net::SSH::Simple::Error but
      # we suppress that here by returning :no_raise
      when :exit_signal
        puts d  # won't fire in this example, could be "TERM"
        :no_raise

      # :finish triggers after :exit_code when the command exits normally.
      # it does *not* trigger when the remote process exits by signal!
      when :finish
        puts "we are finished!"
    end
  end
end

# Our Result has been populated normally, except for
# :stdout and :stdin (because we used :no_append).
puts r           #=> Net::SSH::Simple::Result
puts r.exit_code #=> 0
puts r.stdout    #=> ''
puts r.stderr    #=> ''

Author:

Defined Under Namespace

Classes: Error, Result

Constant Summary collapse

MAX_TIMEOUT =
is_64bit_platform ? 2**32 : 2**16
VERSION =
"1.7.3"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Simple

Returns a new instance of Simple.



538
539
540
541
542
# File 'lib/net/ssh/simple.rb', line 538

def initialize(opts={})
  @opts     = opts
  Thread.current[:ssh_simple_sessions] = {}
  @result   = Result.new
end

Instance Attribute Details

#resultNet::SSH::Simple::Result (readonly)

Result of the current Net::SSH::Simple::Operation.

Returns:



253
254
255
# File 'lib/net/ssh/simple.rb', line 253

def result
  @result
end

Class Method Details

.async(opts = {}, &block) ⇒ Thread

Spawn a Thread to perform a sequence of ssh/scp operations.

Parameters:

  • block (Block)
  • opts (Hash) (defaults to: {})

    SSH options

Returns:

  • (Thread)

    Thread executing the SSH-Block.



551
552
553
554
555
# File 'lib/net/ssh/simple.rb', line 551

def self.async(opts={}, &block)
  Thread.new do
    self.sync(opts, &block)
  end
end

.scp_get(*args, &block) ⇒ Net::SSH::Simple::Result

SCP download from a remote host. This will create a new connection for each invocation.

Examples:

# SCP Download
Net::SSH::Simple.scp_get('localhost', '/tmp/remote_foo', '/tmp/local_bar')
# Pass a block to monitor progress
Net::SSH::Simple.scp_get('localhost', '/tmp/remote_foo', '/tmp/local_bar') do |sent, total|
  puts "Bytes downloaded: #{sent} of #{total}"
end

Parameters:

  • host (String)

    Destination hostname or ip-address

  • src (String)

    Source path (on remote host)

  • dst (String)

    Destination path (on localhost)

  • block (Block)

    Progress callback (optional)

  • opts (Hash)

    SSH options

Returns:

Raises:



317
318
319
320
321
322
# File 'lib/net/ssh/simple.rb', line 317

def self.scp_get(*args, &block)
  s = self.new
  r = s.scp_get(*args, &block)
  s.close
  r
end

.scp_put(*args, &block) ⇒ Net::SSH::Simple::Result

SCP upload to a remote host. This will create a new connection for each invocation.

Examples:

# SCP Upload
Net::SSH::Simple.scp_put('localhost', '/tmp/local_foo', '/tmp/remote_bar')
# Pass a block to monitor progress
Net::SSH::Simple.scp_put('localhost', '/tmp/local_foo', '/tmp/remote_bar') do |sent, total|
  puts "Bytes uploaded: #{sent} of #{total}"
end

Parameters:

  • host (String)

    Destination hostname or ip-address

  • src (String)

    Source path (on localhost)

  • dst (String)

    Destination path (on remote host)

  • block (Block)

    Progress callback (optional)

  • opts (Hash)

    SSH options

Returns:

Raises:



292
293
294
295
296
297
# File 'lib/net/ssh/simple.rb', line 292

def self.scp_put(*args, &block)
  s = self.new
  r = s.scp_put(*args, &block)
  s.close
  r
end

.ssh(*args, &block) ⇒ Net::SSH::Simple::Result

Perform ssh command on a remote host and capture the result. This will create a new connection for each invocation.

Examples:

Net::SSH::Simple.ssh('localhost', 'echo Hello').class #=> Net::SSH::Simple::Result
Net::SSH::Simple.ssh('localhost', 'echo Hello').stdout #=> "Hello"

Parameters:

  • host (String)

    Destination hostname or ip-address

  • cmd (String)

    Shell command to execute

  • block (Block)

    Use the event-API (see example above)

  • opts (Hash)

    SSH options

Returns:

Raises:



268
269
270
271
272
273
# File 'lib/net/ssh/simple.rb', line 268

def self.ssh(*args, &block)
  s = self.new
  r = s.ssh(*args, &block)
  s.close
  r
end

.sync(opts = {}, &block) ⇒ Net::SSH::Simple::Result

Perform a sequence of ssh/scp operations.

Parameters:

  • opts (Hash) (defaults to: {})

    SSH options

Returns:



575
576
577
578
579
580
# File 'lib/net/ssh/simple.rb', line 575

def self.sync(opts={}, &block)
  s = self.new(opts)
  r = Blockenspiel.invoke(block, s)
  s.close
  r
end

Instance Method Details

#async(opts = {}, &block) ⇒ Thread

Spawn a Thread to perform a sequence of ssh/scp operations.

Parameters:

  • block (Block)
  • opts (Hash) (defaults to: {})

    SSH options

Returns:

  • (Thread)

    Thread executing the SSH-Block.



564
565
566
567
# File 'lib/net/ssh/simple.rb', line 564

def async(opts={}, &block)
  opts = @opts.merge(opts)
  self.class.async(opts, &block)
end

#closeNet::SSH::Simple::Result

Close and cleanup.

Returns:



587
588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/net/ssh/simple.rb', line 587

def close
  Thread.current[:ssh_simple_sessions].values.each do |session|
    begin
      ::Timeout.timeout(@opts[:close_timeout] || 5) { session.close }
    rescue => e
      begin
        session.shutdown!
      rescue
      end
    end
  end
  @result
end

#scp_get(host, src, dst, opts = {}, &block) ⇒ Net::SSH::Simple::Result

SCP download from a remote host. The underlying Net::SSH::Simple instance will re-use existing connections for optimal performance.

Parameters:

  • host (String)

    Destination hostname or ip-address

  • src (String)

    Source path (on remote host)

  • dst (String)

    Destination path (on localhost)

  • block (Block)

    Progress callback (optional)

  • opts (Hash) (defaults to: {})

    SSH options

Returns:

See Also:



354
355
356
357
# File 'lib/net/ssh/simple.rb', line 354

def scp_get(host, src, dst, opts={}, &block)
  opts = @opts.merge(opts)
  scp(:download, host, src, dst, opts, &block)
end

#scp_put(host, src, dst, opts = {}, &block) ⇒ Net::SSH::Simple::Result

SCP upload to a remote host. The underlying Net::SSH::Simple instance will re-use existing connections for optimal performance.

Parameters:

  • host (String)

    Destination hostname or ip-address

  • src (String)

    Source path (on localhost)

  • dst (String)

    Destination path (on remote host)

  • block (Block)

    Progress callback (optional)

  • opts (Hash) (defaults to: {})

    SSH options

Returns:



336
337
338
339
# File 'lib/net/ssh/simple.rb', line 336

def scp_put(host, src, dst, opts={}, &block)
  opts = @opts.merge(opts)
  scp(:upload, host, src, dst, opts, &block)
end

#ssh(host, cmd, opts = {}, &block) ⇒ Net::SSH::Simple::Result

Perform SSH operation on a remote host and capture the result. The underlying Net::SSH::Simple instance will re-use existing connections for optimal performance.

Parameters:

  • host (String)

    Destination hostname or ip-address

  • cmd (String)

    Shell command to execute

  • block (Block)

    Use the event-API (see example above)

  • opts (Hash) (defaults to: {})

    SSH options

Options Hash (opts):

  • :auth_methods (Array)

    an array of authentication methods to try

  • :compression (String)

    the compression algorithm to use, or true to use whatever is supported.

  • :compression_level (Number)

    the compression level to use when sending data

  • :opts (String/boolean) — default: true

    set to true to load the default OpenSSH opts files (~/.ssh/opts, /etc/ssh_opts), or to false to not load them, or to a file-name (or array of file-names) to load those specific configuration files.

  • :encryption (Array)

    the encryption cipher (or ciphers) to use

  • :forward_agent (boolean)

    set to true if you want the SSH agent connection to be forwarded

  • :global_known_hosts_file (String/Array) — default: ['/etc/ssh/known_hosts', '/etc/ssh/known_hosts2']

    the location of the global known hosts file. Set to an array if you want to specify multiple global known hosts files.

  • :hmac (String/Array)

    the hmac algorithm (or algorithms) to use

  • :host_key (String)

    the host key algorithm (or algorithms) to use

  • :host_key_alias (String)

    the host name to use when looking up or adding a host to a known_hosts dictionary file

  • :host_name (String)

    the real host name or IP to log into. This is used instead of the host parameter, and is primarily only useful when specified in an SSH configuration file. It lets you specify an alias, similarly to adding an entry in /etc/hosts but without needing to modify /etc/hosts.

  • :kex (String/Array)

    the key exchange algorithm (or algorithms) to use

  • :keys (Array)

    an array of file names of private keys to use for publickey and hostbased authentication

  • :key_data (Array)

    an array of strings, with each element of the array being a raw private key in PEM format.

  • :keys_only (boolean)

    set to true to use only private keys from keys and key_data parameters, even if ssh-agent offers more identities. This option is intended for situations where ssh-agent offers many different identites.

  • :logger (Logger)

    the logger instance to use when logging

  • :paranoid (boolean/:very)

    either true, false, or :very, specifying how strict host-key verification should be

  • :passphrase (String) — default: nil

    the passphrase to use when loading a private key (default is nil, for no passphrase)

  • :password (String)

    the password to use to login

  • :port (Integer)

    the port to use when connecting to the remote host

  • :properties (Hash)

    a hash of key/value pairs to add to the new connection’s properties (see Net::SSH::Connection::Session#properties)

  • :proxy (String)

    a proxy instance (see Proxy) to use when connecting

  • :rekey_blocks_limit (Integer)

    the max number of blocks to process before rekeying

  • :rekey_limit (Integer)

    the max number of bytes to process before rekeying

  • :rekey_packet_limit (Integer)

    the max number of packets to process before rekeying

  • :timeout (Integer) — default: 60

    maximum idle time before a connection will time out (0 = disable).

  • :operation_timeout (Integer) — default: 3600

    maximum time before aborting an operation (0 = disable). you may use this to guard against run-away processes.

  • :keepalive_interval (Integer) — default: 60

    send keep-alive probes at this interval to prevent connections from timing out unexpectedly.

  • :close_timeout (Integer) — default: 5

    grace-period on close before the connection will be terminated forcefully (0 = terminate immediately).

  • :user (String)

    the username to log in as

  • :user_known_hosts_file (String/Array) — default: ['~/.ssh/known_hosts, ~/.ssh/known_hosts2']

    the location of the user known hosts file. Set to an array to specify multiple user known hosts files.

  • :verbose (Symbol)

    how verbose to be (Logger verbosity constants, Logger::DEBUG is very verbose, Logger::FATAL is all but silent). Logger::FATAL is the default. The symbols :debug, :info, :warn, :error, and :fatal are also supported and are translated to the corresponding Logger constant.

Returns:

See Also:



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/net/ssh/simple.rb', line 488

def ssh(host, cmd, opts={}, &block)
  opts = @opts.merge(opts)
  with_session(host, opts) do |session|
    @result = Result.new(
      { :op => :ssh, :host => host, :cmd => cmd, :start_at => Time.new,
        :last_event_at => Time.new, :opts => opts, :stdout => '', :stderr => '',
        :success => nil
      } )

    channel = session.open_channel do |chan|
      chan.exec cmd do |ch, success|
        @result[:success] = success
        ch.on_data do |c, data|
          @result[:last_event_at] = Time.new
          r = block.call(:stdout, ch, data) if block
          @result[:stdout] += data.to_s unless r == :no_append
        end
        ch.on_extended_data do |c, type, data|
          @result[:last_event_at] = Time.new
          r = block.call(:stderr, ch, data) if block
          @result[:stderr] += data.to_s unless r == :no_append
        end
        ch.on_request('exit-status') do |c, data|
          @result[:last_event_at] = Time.new
          exit_code = data.read_long
          block.call(:exit_code, ch, exit_code) if block
          @result[:exit_code] = exit_code
        end
        ch.on_request('exit-signal') do |c, data|
          @result[:last_event_at] = Time.new
          exit_signal = data.read_string
          r = block.call(:exit_signal, ch, exit_signal) if block
          @result[:exit_signal] = exit_signal
          @result[:success] = false
          unless r == :no_raise
            raise "Killed by SIG#{@result[:exit_signal]}"
          end
        end
        block.call(:start, ch, nil) if block
      end
    end
    wait_for_channel session, channel, @result, opts
    @result[:finish_at] = Time.new
    block.call(:finish, channel, nil) if block
    @result
  end
end