class Symphony::Task::SSHScript

A base class for connecting to a remote host, then uploading and executing an Inversion templated script.

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
template:   (required) A path to the Inversion templated script
port:       (optional) The port to connect to (defaults to 22)
user:       (optional) The user to connect as (defaults to root)
key:        (optional) The path to an SSH private key
attributes: (optional) Additional data to attach to the template
nocleanup:  (optional) Leave the remote script after execution? (default to false)
delete_cmd: (optional) The command to delete the remote script.  (default to 'rm')
run_binary: (optional) Windows doesn't allow direct execution of scripts, this is prefixed to the remote command if present.
tempdir:    (optional) The destination temp directory.  (defaults to /tmp/, needs to include the separator character)

Additionally, this class responds to the 'symphony.ssh' configurability key. Currently, you can override the default ssh user and private key.

Textual output of the command is stored in the @output instance variable.

require 'symphony'
require 'symphony/tasks/sshscript'

class YourTask < Symphony::Task::SSHScript
    timeout 30
    subscribe_to 'ssh.script.*'

    def work( payload, metadata )
        status = super
        puts "Remote script said: %s" % [ @output ]
        return status.success?
    end
end

Constants

DEFAULT_SSH_OPTIONS

The defaults to use when connecting via SSH

TEMPLATE_OPTS

Template config

Public Instance Methods

work( payload, metadata ) click to toggle source

Perform the ssh connection, render the template, send it, and execute it.

# File lib/symphony/tasks/sshscript.rb, line 81
def work( payload, metadata )
        template   = payload[ 'template' ]
        attributes = payload[ 'attributes' ] || {}
        user       = payload[ 'user' ]    || Symphony::Task::SSH.user
        key        = payload[ 'key'  ]    || Symphony::Task::SSH.key
        tempdir    = payload[ 'tempdir' ] || '/tmp/'

        raise ArgumentError, "Missing required option 'template'" unless template
        raise ArgumentError, "Missing required option 'host'"     unless payload[ 'host' ]

        remote_filename = self.make_remote_filename( template, tempdir )
        source = self.generate_script( template, attributes )

        # Map any configuration parameters in the payload to ssh
        # options, for potential per-message behavior overrides.
        ssh_opts_override = payload.
                slice( *DEFAULT_SSH_OPTIONS.keys.map( &:to_s ) ).
                transform_keys{|k| k.to_sym }

        ssh_options = DEFAULT_SSH_OPTIONS.dup.merge!(
                ssh_opts_override,
                port: payload[ 'port' ] || 22,
                keys: Array( key )
        )
        ssh_options.merge!(
                logger: Loggability[ Net::SSH ],
                verbose: :debug
        ) if payload[ 'debug' ]

        Net::SSH.start( payload['host'], user, ssh_options ) do |conn|
                self.log.debug "Uploading script (%d bytes) to %s:%s." %
                        [ source.bytesize, payload['host'], remote_filename ]
                self.upload_script( conn, source, remote_filename )
                self.log.debug "  done with the upload."

                self.run_script( conn, remote_filename, payload )
                self.log.debug "Output was:\n#{@output}"
        end

        return true
end

Protected Instance Methods

generate_script( template, attributes ) click to toggle source

Generate a script by loading the script template, populating it with attributes, and returning the rendered output.

# File lib/symphony/tasks/sshscript.rb, line 146
def generate_script( template, attributes )
        tmpl = Inversion::Template.load( template, TEMPLATE_OPTS )
        tmpl.attributes.merge!( attributes )
        tmpl.task = self

        return tmpl.render
end
make_remote_filename( template, tempdir="/tmp/" ) click to toggle source

Generate a unique filename for the script on the remote host, based on template name.

# File lib/symphony/tasks/sshscript.rb, line 131
def make_remote_filename( template, tempdir="/tmp/" )
        basename = File.basename( template, File.extname(template) )
        tmpname  = "%s%s-%s" % [
                tempdir,
                basename,
                SecureRandom.hex( 6 )
        ]

        return tmpname
end
run_script( conn, remote_filename, payload ) click to toggle source

Run the remote_filename via the ssh conn. The script will be deleted automatically unless nocleanup is set in the payload.

# File lib/symphony/tasks/sshscript.rb, line 169
def run_script( conn, remote_filename, payload )
        delete_cmd = payload[ 'delete_cmd' ] || 'rm'
        command    = remote_filename
        command    = "%s %s" % [ payload['run_binary'], remote_filename ] if payload[ 'run_binary' ]

        @output = conn.exec!( command )
        conn.exec!( "#{delete_cmd} #{remote_filename}" ) unless payload[ 'nocleanup' ]
end
upload_script( conn, source, remote_filename ) click to toggle source

Upload the templated source via the ssh conn to an executable file named remote_filename.

# File lib/symphony/tasks/sshscript.rb, line 158
def upload_script( conn, source, remote_filename )
        conn.sftp.file.open( remote_filename, "w", 0755 ) do |fh|
                fh.print( source )
        end
end