class Symphony::Task::SSH
A base class for connecting to remote hosts, running arbitrary commands, and collecting output.
This isn't designed to be used directly. To use this in your environment, you'll want to subclass it, add the behaviors that make sense for you, then super() back to the parent in the work
method.
It expects the payload to contain the following keys:
host: (required) The hostname to connect to command: (required) The command to run on the remote host port: (optional) The port to connect to (defaults to 22) opts: (optional) Explicit SSH client options env: (optional) A hash of environment vars to set for the connection. user: (optional) The user to connect as (defaults to root) key: (optional) The path to an SSH private key
Additionally, this class responds to the 'symphony.ssh' configurability key. Currently, you can set the 'path' argument, which is the full path to the local ssh binary (defaults to '/usr/bin/ssh') and override the default ssh user, key, and client opts.
Textual output of the command is stored in the @output instance variable.
require 'symphony' require 'symphony/tasks/ssh' class YourTask < Symphony::Task::SSH timeout 5 subscribe_to 'ssh.command' def work( payload, metadata ) status = super puts "Remote host said: %s" % [ @output ] return status.success? end end
Constants
- DEFAULT_SSH_OPTS
The default set of ssh command line flags.
- SSH_CLEANUP
SSH
“informative” stdout output that should be cleaned from the command output.
Public Instance Methods
Perform the ssh connection in 'exec' mode, and retrieve any output from the remote end.
# File lib/symphony/tasks/ssh.rb, line 93 def work( payload, metadata ) raise ArgumentError, "Missing required option 'command'" unless payload[ 'command' ] raise ArgumentError, "Missing required option 'host'" unless payload[ 'host' ] exitcode = self.open_connection( payload, metadata ) do |reader, writer| #self.log.debug "Writing command #{command}..." #writer.puts( command ) self.log.debug " closing child's writer." writer.close self.log.debug " reading from child." reader.read end self.log.debug "SSH exited: %d" % [ exitcode ] return exitcode end
Protected Instance Methods
Call ssh and yield the remote IO objects to the caller, cleaning up afterwards.
# File lib/symphony/tasks/ssh.rb, line 118 def open_connection( payload, metadata=nil ) raise LocalJumpError, "no block given" unless block_given? @output = '' port = payload[ 'port' ] || 22 opts = payload[ 'opts' ] || Symphony::Task::SSH.opts user = payload[ 'user' ] || Symphony::Task::SSH.user key = payload[ 'key' ] || Symphony::Task::SSH.key env = payload[ 'env' ] || {} cmd = [] cmd << Symphony::Task::SSH.path cmd += opts cmd << '-p' << port.to_s cmd << '-i' << key if key cmd << '-l' << user cmd << payload[ 'host' ] cmd << payload[ 'command' ] cmd.flatten! self.log.debug "Running SSH command with: %p" % [ Shellwords.shelljoin(cmd) ] parent_reader, child_writer = IO.pipe child_reader, parent_writer = IO.pipe pid = Process.spawn( env, *cmd, out: child_writer, in: child_reader, close_others: true, unsetenv_others: true ) child_writer.close child_reader.close self.log.debug "Yielding back to the run block." @output = yield( parent_reader, parent_writer ) @output = @output.split( /\r?\n/ ).reject{|l| l =~ SSH_CLEANUP }.join self.log.debug " run block done." rescue => err self.log.error( err.message ) ensure if pid active = Process.kill( 0, pid ) rescue false Process.kill( :TERM, pid ) if active pid, status = Process.waitpid2( pid ) end return status end