Class: CloudFlock::Remote::SSH

Inherits:
Object
  • Object
show all
Defined in:
lib/cloudflock/remote/ssh.rb,
lib/cloudflock/error.rb,
lib/cloudflock/errstr.rb

Overview

The SSH Class wraps the tasks of logging into a host and interacting with it via SSH.

Examples

# Log into [email protected]
shell = SSH.new(host: 'host.example.com', pass: 'examplepass')
shell.puts 'ls'

Defined Under Namespace

Modules: Errstr Classes: InvalidHostname, SSHCannotConnect

Constant Summary collapse

SSH_ARGUMENTS =

Public: String containing arguments to pass to ssh(1)

'-o UserKnownHostsFile=/dev/null ' \
'-o StrictHostKeyChecking=no '     \
'-o NumberOfPasswordPrompts=1 '    \
'-o ConnectTimeout=15 '            \
'-o ServerAliveInterval=30'
NET_SSH_OPTIONS =

Public: Hash containing always-set options for Net::SSH

{ user_known_hosts_file: '/dev/null', paranoid: false }
PROMPT =

Public: Prompt to be set on a host upon successful login.

'@@CLOUDFLOCK@@'
HISTFILE =

Public: Absolute path to the history file to use.

'/root/.cloudflock_history'
DEFAULT_ARGS =

Internal: Default arguments to be used for SSH session initialization.

{username: '', password: '', ssh_key: '', port: 22}
DEFAULT_BATCH_ARGS =

Internal: Default settings for calls to #batch.

{ timeout: 30, recoverable: true }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ SSH

Public: Create a new SSH object and log in to the specified host via ssh.

args - Hash containing arguments relating to the SSH session. (default

defined in DEFAULT_ARGS):
:hostname       - String containing the address of the remote
                  host.
:username       - String containing the remote user with which to
                  log in.  (default: '')
:password       - String containing the password with which to log
                  in.  (default: '')
:port           - Fixnum specifying the port to which to connect.
                  (default: 22)
:ssh_key        - String containing the path to an ssh private
                  key.  (default: '')
:key_passphrase - The passphrase for the ssh key if applicable.
                  (default: '')

Raises InvalidHostname if host lookup fails. Raises LoginFailed if logging into the host fails.



60
61
62
63
64
# File 'lib/cloudflock/remote/ssh.rb', line 60

def initialize(args = {})
  @options = sanitize_arguments(DEFAULT_ARGS.merge(args))
  start_session
  start_keepalive_thread
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



39
40
41
# File 'lib/cloudflock/remote/ssh.rb', line 39

def options
  @options
end

Instance Method Details

#as_root(command, timeout = 30, recoverable = false) ⇒ Object

Public: Wrap query, guaranteeing that the user performing the given command is root.

command - Command to be executed. timeout - Number of seconds to allow the command to run before

terminating the channel and returning any buffer returned
so far.  A value of 0 or nil will result in no timeout.
(default: 30)

recoverable - Whether a Timeout should be considered acceptable.

(default: false)

Returns a String. Raises Timeout::Error if timeout is reached.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/cloudflock/remote/ssh.rb', line 146

def as_root(command, timeout = 30, recoverable = false)
  return query(command, timeout, recoverable) if root?

  priv    = 'su -'
  priv    = 'sudo ' + priv if options[:sudo]
  uid     = ['id;logout||exit']
  command = ["#{command};logout||exit"]

  passwordless = query(priv, timeout, true, uid)

  unless /uid=0/.match(passwordless)
    command.unshift(options[:root_password] + "\n")
  end

  buffer = query(priv, timeout, recoverable, command)
  cleanup = Regexp.new("^#{Regexp::escape(command.join)}\r\n")
  buffer.gsub(cleanup, '')
end

#batch(commands) ⇒ Object

Public: Call query on a list of commands, allowing optional timeout and recoverable settings per command.

commands - Array containing Hashes containing at minimum a command key.

Hash should follow the following specification:
command     - Command to execute.
timeout     - Timeout to specify for the call to #query.
              (default: 30)
recoverable - Whether the call should be considered
              recoverable if the timeout is reached.
              (default: true)

Returns an Array containing results of each command.



128
129
130
131
# File 'lib/cloudflock/remote/ssh.rb', line 128

def batch(commands)
  commands.map! { |c| DEFAULT_BATCH_ARGS.merge(c) }
  commands.map  { |c| query(c[:command], c[:timeout], c[:recoverable]) }
end

#hostnameObject

Public: Return the hostname of the host.

Returns a String.



69
70
71
# File 'lib/cloudflock/remote/ssh.rb', line 69

def hostname
  options[:hostname]
end

#logout!Object

Public: Terminate the active ssh session.

Returns nothing.



168
169
170
171
# File 'lib/cloudflock/remote/ssh.rb', line 168

def logout!
  @ssh.close
  @ssh = nil
end

#query(command, timeout = 30, recoverable = false, send_data = []) ⇒ Object

Public: Open a channel and execute an arbitrary command, returning any data returned over the channel.

command - Command to be executed. timeout - Number of seconds to allow the command to run before

terminating the channel and returning any buffer returned
so far.  A value of 0 or nil will result in no timeout.
(default: 30)

recoverable - Whether a Timeout should be considered acceptable.

(default: false)

send_data - Array containing data to be sent to across the channel

after the command has been run. (default: [])

Returns a String. Raises Timeout::Error if timeout is reached.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/cloudflock/remote/ssh.rb', line 88

def query(command, timeout = 30, recoverable = false, send_data = [])
  buffer  = ''
  running = 0

  channel = @ssh.open_channel do |channel|
    channel.request_pty

    channel.exec(command) do |ch, success|
      ch.on_data          { |_,    data| buffer << data }
      ch.on_extended_data { |_, _, data| buffer << data }

      ch.send_data(send_data.join + "\n")
    end
  end

  Timeout::timeout(timeout) { channel.wait }

  buffer.strip
rescue Timeout::Error
  raise unless recoverable
  channel.close
  buffer.strip
rescue EOFError
  start_session
  retry
end