lib/soloist/remote.rb
require "net/ssh"
require "shellwords"
require "etc"
module Soloist
class RemoteError < RuntimeError; end
class Remote
attr_reader :user, :host, :key, :timeout, :stdout, :stderr, :exitstatus
attr_writer :connection
def self.from_uri(uri, key = "~/.ssh/id_rsa")
parsed = URI.parse("ssh://#{uri}")
new(parsed.user || Etc.getlogin, parsed.host, key)
end
def initialize(user, host, key, options = {})
@user = user
@host = host
@key = File.expand_path(key)
@timeout = options[:timeout] || 10000
end
def backtick(command)
@stdout = ""
@stderr = ""
exec(command)
@stdout
end
def system(command)
@stdout = STDOUT
@stderr = STDERR
exec(command)
exitstatus
end
def system!(*command)
system(*command).tap do |status|
raise RemoteError.new("#{command.join(" ")} exited #{status}") unless status == 0
end
end
def upload(from, to, opts = "--exclude .git")
Kernel.system("rsync -e 'ssh -i #{key}' -avz --delete #{from} #{user}@#{host}:#{to} #{opts}")
end
private
def connection
@connection ||= Net::SSH.start(host, user, :keys => [key], :timeout => timeout)
end
def exec(*command)
connection.open_channel do |channel|
channel.exec(*command) do |stream, success|
raise RemoteError.new("Could not run #{command.join(" ")}") unless success
stream.on_data { |_, data| stdout << data }
stream.on_extended_data { |_, type, data| stderr << data }
stream.on_request("exit-status") { |_, data| @exitstatus = data.read_long }
end
end
connection.loop
@exitstatus ||= 0
end
end
end