Search code examples
rubysshruby-1.9.3net-ssh

Executing a Ruby method inside a Net::SSH session


Ruby 1.9.3, net-ssh 2.9.2

I am working on a project, in which I need to diff the same directory (and its subdirectories) on two different servers (local and remote). From there, I need to copy the newest/recently modified files to the correct server, and delete from the remote if a file is not present in the local.

NOTE: I cannot use rsync. We are backing up Asterisk-related directories to GlusterFS. At thousands of files, rsync comparing local to the Gluster volume is very slow (when we need it under 1 minute).

Here is my current code. I am omitting my work for copying/removing files, as I want to take this one step at a time.

require 'thread'
require 'date'
require 'rubygems'
require 'net/ssh'

SERVERS = ['local17', 'development']
CLIENT = SERVERS[0]
CLIENT_PATH = '/home/hstevens/temp_gfs'
BRICK_PATH = '/export/hunter_test'

@files = {
  SERVERS[0] => {},
  SERVERS[1] => {}
}

def grab_filenames_and_dates(files, server)
  files.reject { |x| File.directory? x }
  files.each do |file|
    name = `ls --full-time "#{file}" | awk '{$1=$2=$3=$4=$5=$6=$7=$8=""; print $0}'`.strip
    date = `ls --full-time "#{file}" | awk '{print $6, $7, $8}'`.strip
    @files[server][name] = DateTime.parse(date)
  end
end

# Collect diff information on all servers
ls_threads = SERVERS.map do |server|
  Thread.new do
    if server == CLIENT
      files = Dir.glob("#{CLIENT_PATH}/**/*")
      grab_filenames_and_dates(files, server)
    else
      Net::SSH.start(server, 'hstevens') do |session|
        files = session.exec!(%Q(ruby -e 'puts Dir.glob("#{BRICK_PATH}/**/*")')).split("\n")
        grab_filenames_and_dates(files, server)
      end
    end
  end
end
ls_threads.each(&:join)

When I run my program, it works for the local server (CLIENT/local17), but fails on the remote server. I tried debugging statements (printing pwd to console`, and it appears that although the method is called inside the Net::SSH session block, it is acting on my local server.

ls: cannot access /export/hunter_test/sorttable.js: No such file or directory
ls: cannot access /export/hunter_test/sorttable.js: No such file or directory
./gluster_rsync.rb:36:in `parse': invalid date (ArgumentError)
    from ./gluster_rsync.rb:36:in `block in grab_filenames_and_dates'
    from ./gluster_rsync.rb:33:in `each'
    from ./gluster_rsync.rb:33:in `grab_filenames_and_dates'
    from ./gluster_rsync.rb:53:in `block (3 levels) in <main>'
    from /usr/local/lib/ruby/gems/1.9.1/gems/net-ssh-2.9.2/lib/net/ssh.rb:215:in `start'
    from ./gluster_rsync.rb:51:in `block (2 levels) in <main>'

How can I properly wrap a method call inside a Net::SSH session?


Solution

  • Ruby code running inside the net::ssh block still runs on your computer (this includes methods that run commands like system or backticks)

    To execute a command on the remote server you need to use session.exec or session.exec! (the latter is blocking, the former requires you to run the ssh event loop). You can also open a channel explicitly and execute a command there - these methods are conscience wrappers.

    There is no special support for running ruby remotely. You can of course use exec! to run ruby on the other machine (assuming it is installed) but that's it