create a background ssh tunnel in ruby - ruby

My oracle db is only accessable via a jumpoff server and is load balanced. As a result I run the following background tunnel command in bash:
ssh ${jumpoffUser}#${jumpoffIp} -L1521:ont-db01-vip:1521 -L1522:ont-db02-vip:1521 -fN
Before I run my commands on the db using sqlplus like so:
sqlplus #{#sqlUsername}/#{#sqlPassword}#'#{#sqlUrl}' #scripts/populateASDB.sql
This all works fine.
Now I want to rubisize this procedure.
In looking up the documentation on ruby I could not find how to put the tunnel in the background (which would be my preference) but I found documentation on local port forwarding which I thought would emulate the above tunnel and subsequent sqlplus command.
Here is my code:
Net::SSH.start( #jumpoffIp, #jumpoffUser ) do |session|
session.forward.local( 1521, 'ont-db01-vip', 1521 )
session.forward.local( 1522, 'ont-db02-vip', 1521 )
puts "About to populateDB"
res = %x[sqlplus #{#sqlUsername}/#{#sqlPassword}#'#{#sqlUrl}' #scripts/populateASDB.sql > output.txt]
puts "populateDb output #{res}"
session.loop
end
When I run the above I get the line "About to populateDB" but it hangs on the actual running of the sqlplus command. Is there something wrong with my port forwarding code or how do I put the following:
ssh ${jumpoffUser}#${jumpoffIp} -L1521:ont-db01-vip:1521 -L1522:ont-db02-vip:1521 -fN
into ruby code?
A

Try to use this gem: https://github.com/net-ssh/net-ssh-gateway/
require 'net/ssh/gateway'
gateway = Net::SSH::Gateway.new(#jumpoffIp, #jumpoffUser)
gateway.open('ont-db01-vip', 1521, 1521)
gateway.open('ont-db02-vip', 1521, 1521)
res = %x[sqlplus #{#sqlUsername}/#{#sqlPassword}#'#{#sqlUrl}' #scripts/populateASDB.sql > output.txt]
puts "populateDb output #{res}"
gateway.shutdown!

You have two problems.
1) You need to use 'session.loop { true }' so that the session actually loops
2) You don't start looping the session until your sqlplus command is done, but the sqlplus needs the session looping (the forwarding to be up).
So I suggest creating a background thread using Thread.new and then killing the thread once sqlplus is done.

Thanks to David's answer, I came up with the following:
Net::SSH.start(ip_addr, 'user') do |session|
session.forward.local( 9090, 'localhost', 9090 )
# Need to run the event loop in the background for SSH callbacks to work
t = Thread.new {
session.loop { true }
}
commands.each do | command |
command.call(9090)
end
Thread.kill(t)
end

Related

How to mock a 3rd-party library [duplicate]

an important part of my project is to log in into remote server with ssh and do something with files on it:
Net::SSH.start(#host, #username, :password => #password) do |ssh|
ssh.exec!(rename_files_on_remote_server)
end
How to test it?
I think I can have local ssh server on and check file names on it (maybe it could be in my test/spec directory).
Or maybe someone could point me better solution?
I think it's enough to test that you're sending the correct commands to the ssh server. You're application presumably doesn't implement the server - so you have to trust that the server is correctly working and tested.
If you do implement the server then you'd need to test that, but as far as the SSH stuff goes, i'd do some mocking like this (RSpec 2 syntax):
describe "SSH Access" do
let (:ssh_connection) { mock("SSH Connection") }
before (:each) do
Net::SSH.stub(:start) { ssh_connection }
end
it "should send rename commands to the connection" do
ssh_connection.should_receive(:exec!).ordered.with("expected command")
ssh_connection.should_receive(:exec!).ordered.with("next expected command")
SSHAccessClass.rename_files!
end
end
Your suggested solution is similar to how I've done it before:
Log into the local machine. For convenience you could use 'localhost' or '127.0.0.1', but for a better simulation of network activity you might want to use the full hostname. On Mac OS and Linux you can grab the host easily by using:
`hostname`
or
require 'socket'
hostname = Socket.gethostname
which should be universal.
From there create or touch a file on the local machine after logging in, so you can test for the change with your test code.

Ruby - checking ping status featback with ssh, backtick via ssh?

In my project I want to write a script to check if every device in my network is online/reachable. I have a method called pingtest and it works for now..
def pingtest(destination)
system("ping -n 2 #{destination}")
if $? == 0 #checking status of the backtick
puts "\n Ping was successful!"
else
close("Device is unreachable. Check the config.txt for the correct IPs.")
#close() is just print & exit..
end
end
Now I wanted to ping via a ssh session with an other device in my network:
#--------------------------------
require 'net/ssh'
Net::SSH.start(#ip, #user, :password => #password)
#--------------------------------
#ssh = Ssh.new(#config)
#ssh.cmd("ping -c 3 #{#IP}")
The ping works fine but how can I use my backtrack idea now to determine if it was succesful or not?
I thought about using a sftp connection..
"ping -c 3 #{#IP} => tmpfile.txt" => download => check/compare => delete
(or something like that) to check if it was correct, but im not statusfied with that. Is there a possibility to check the success status like I did before?
I also tried something like this..
result = #ssh.cmd("ping -c 3 #{#IP}")
if result.success? == 0 # and so on..
I startet learning ruby some days ago, so im a newbie looking forward for your ideas to help me with this problem.
You can use Net::SSH to run the command remotely, similarly to what you've already got there.
The result returned from running the command will be whatever is written to both stdout and stderr.
You can use the contents of that returned value to check if it was successful or not.
Net::SSH.start(#ip, #user. password: #password) do |ssh|
response = ssh.exec! "ping -c 3 #{#other_ip}"
if response.include? 'Destination Host Unreachable'
close("Host unreachable. Result was: #{result}")
else
puts "\n Ping was successful"
end
end

Unable to connect remote host through net/ssh

This is pretty weird. I have my public key added at host machine. I can simply run
ssh -p <port> -l <username> hostt.com
which simply opens the remote shell. I can even run my capistrano scripts for the deployments on the same machine. But when i was trying connect with this following simple ruby script
require 'rubygems'
require 'net/ssh'
Net::SSH.start("hostt.com",
:port => <port>,
:username => <username>
) do |session|
puts session.pwd
end
it refuses immediately with the following exception:
`initialize': Connection refused - connect(2) (Errno::ECONNREFUSED)
Is there anything I'm missing here?
Appreciate your help.
Okay, now after a few days when I look back to the problem, I got a quick success with the following tweak:
Net::SSH.start("<host>", "<user>", :port => "<port>") { |ssh|
puts "logged in"
puts ssh.exec!("ls -l")
} rescue puts "failed to connect."
So the difference with the previous one is the username, which in this case is passed as the second argument rather than like an option key.
you probably need to provide the location of your SSH key, or a password to use with the username you provide in the SSH.start parameters. for the keys, you need to pass the map value as an array :keys => ["path_to_key"]. I'm not sure why the api is set up that way, but it is.

Testing ssh connection

an important part of my project is to log in into remote server with ssh and do something with files on it:
Net::SSH.start(#host, #username, :password => #password) do |ssh|
ssh.exec!(rename_files_on_remote_server)
end
How to test it?
I think I can have local ssh server on and check file names on it (maybe it could be in my test/spec directory).
Or maybe someone could point me better solution?
I think it's enough to test that you're sending the correct commands to the ssh server. You're application presumably doesn't implement the server - so you have to trust that the server is correctly working and tested.
If you do implement the server then you'd need to test that, but as far as the SSH stuff goes, i'd do some mocking like this (RSpec 2 syntax):
describe "SSH Access" do
let (:ssh_connection) { mock("SSH Connection") }
before (:each) do
Net::SSH.stub(:start) { ssh_connection }
end
it "should send rename commands to the connection" do
ssh_connection.should_receive(:exec!).ordered.with("expected command")
ssh_connection.should_receive(:exec!).ordered.with("next expected command")
SSHAccessClass.rename_files!
end
end
Your suggested solution is similar to how I've done it before:
Log into the local machine. For convenience you could use 'localhost' or '127.0.0.1', but for a better simulation of network activity you might want to use the full hostname. On Mac OS and Linux you can grab the host easily by using:
`hostname`
or
require 'socket'
hostname = Socket.gethostname
which should be universal.
From there create or touch a file on the local machine after logging in, so you can test for the change with your test code.

Ruby Telnet Lib - Weird Response

I am trying to execute cmds on a remote CPU through telnet. While some commands sent (through Ruby's stdlib for telnet) are successful, others are giving me a weird response:
*===============================================================
Welcome to Microsoft Telnet Server.
*===============================================================
C:\Documents and Settings\UserJW>ls
Desktop
Favorites
My Documents
Start Menu
Sti_Trace.log
C:\Documents and Settings\UserJW>cd\
More?
Why is telnet giving me this "More?" response, as if expecting something?
In the code, I am simply connecting to remote CPU, logging in, and sending commands:
#connection = Net::Telnet.new(...)
#connection.login( user, pwd )
#connection.cmd(...)
Any help would be appreciated.
Thanks,
J
**EDIT:
#connection = Net::Telnet.new(
"Host" => machine,
"Prompt" => /[A-Za-z]:\\.*>\z/n,
"Timeout" => 3,
"Output_log" => output )
#connection.login( user, pwd )
#connection.cmd( 'ls' )
#connection.cmd( 'ls' )
output...
C:\Documents and Settings\UserJW>
ls
Desktop
Favorites
My Documents
Start Menu
Sti_Trace.log
C:\Documents and Settings\UserJW>
ls
More?
I can't even send more than one command, apparently. Is my Prompt regex wrong? I'm trying to allow..
C:[anything...]>
I meet a same problem with you ( ruby telnet to windows 2008 ,execute command error ).I solved it. the reason is ruby net/telnet library use error newline seperator. Must be EOL(CR+LF) but CR+NULL . But I don't know who make the bug,windows or ruby? I write a monkey patch as below:
class Net::Telnet
def print(string)
string = string.gsub(/#{IAC}/no, IAC + IAC) if #options["Telnetmode"]
if #options["Binmode"]
self.write(string)
else
if #telnet_option["BINARY"] and #telnet_option["SGA"]
self.write(string.gsub(/\n/n, CR))
elsif #telnet_option["SGA"]
self.write(string.gsub(/\n/n, EOL)) ### fix here. reaplce CR+NULL bY EOL
else
self.write(string.gsub(/\n/n, EOL))
end
end
end
end

Resources