Class: CloudFlock::Remote::SSH

Inherits:
Object
  • Object
show all
Defined in:
lib/cloudflock/remote/ssh.rb,
lib/cloudflock/error.rb,
lib/cloudflock/errstr.rb,
lib/cloudflock/remote/ssh/watchdog.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, Watchdog

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: '')
:passphrase - The passphrase for the ssh key if applicable.
              (default: '')

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



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

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.



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

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.



131
132
133
134
# File 'lib/cloudflock/remote/ssh.rb', line 131

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.



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

def hostname
  options[:hostname]
end

#logout!Object

Public: Terminate the active ssh session.

Returns nothing.



171
172
173
174
# File 'lib/cloudflock/remote/ssh.rb', line 171

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.



87
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
114
115
116
# File 'lib/cloudflock/remote/ssh.rb', line 87

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
rescue IO::EAGAINWaitReadable
  sleep 10
  start_session
  retry
end