Dr Nic

Capistrano variables

Here’s an example of how you can pass variables to your capistrano actions via the cap command.

So, the above action could be called as follows:

cap log
cap log -s lines=1000
cap log -s rails_env=test

For reference, you pass values to Rake tasks via environment variables:

rake db:migrate RAILS_ENV=test

Inside the db:migrate task, you’d retrieve the value ‘test’ via ENV['RAILS_ENV'].

And further more, this then compares to how you'd normally pass option values into normal command-line apps:

tail -n 1000 log/production.log

Who said the unix command line wasn't easy peasy. Bah.

Remote Shell with Ruby

I wrote Composite Primary Keys and Dr Nic’s Magic Models because we have a 50+ table legacy database and I am terrorised by explicit SQL statements on a daily basis. I have a dirty secret – I don’t use either of them at work. We’ve been unsuccessful installing Ruby on our HP-UX box, then time passed, life moved on for the people involved, and so no Ruby. Ergo, SQL hell for Nicholas.

Until today. No, we still don’t have Ruby installed on our HP box, but I do have it installed everywhere else in my life. So I guess I can still access the database remotely… that’s what the TCP/IP internet protocol is for… and for executing shell commands why not remote shell into the box?

Let’s skip over why I never thought of either of these until now. Its bad for my ego. The upside of this minor revelation is that the Composite Primary Keys project will have Oracle support soon.

Remote Shell

The point of this article though is to discuss the wonders of using remote ssh (aka remote shell) to invoke shell commands via ruby without having ruby installed locally.

I made it work, it rocks and I shall briefly sing the praises for Jamis Buck. Jamis, you rock. net-ssh is a lovely API for performing remote shell commands all from ruby. Here’s a demo of what I can do (excluding the setup code which is discussed later):

>> print shell.pwd.stdout
/home/senwilli
>> shell.cd 'demo'
=> #<struct Net::SSH::Service::Shell::SyncShell::CommandOutput stdout="", stderr=nil, status=0>
>> out = shell.ls '-lart'; print out.stdout
total 16
drwxr-xr-x   2 senwilli   sv              96 Sep 22 11:56 .
drwxr-xr-x   5 senwilli   sv            8192 Sep 22 11:56 ..
>> out = shell.send_command 'ls -lart'; print out.stdout
total 16
drwxr-xr-x   2 senwilli   sv              96 Sep 22 11:56 .
drwxr-xr-x   5 senwilli   sv            8192 Sep 22 11:56 ..

I’ve deliberately been inconsistent in the way I called the shell commands and how I handled the responses to show you some interesting things.

shell is our shell object (which we’ll cover in a bit) and it has delightfully overridden method_missing so I can send it any old shell command I like. In the example above, I call the commands: pwd, cd and ls using this method. The first parameter is the arguments you’d like to pass to the command. An optional second parameter is for any stdin you’d like to pass to the process during its execution.

The last command is a duplicate of the previous command, showing that you can explicitly pass a complete shell expression string via the send_command method.

The result of all shell commands is a struct containing stdout and stderr strings. So, to return the response of the command to the user or logger, you’ll need to store the response object (as per the out variable above) and use stdout? to test if stdout has anything in it.

In the example above, all these commands are executedly being executed synchronously, so I’m getting the stdout/stderr results back before the next command is executed. This is due to the way I created the Shell object. So, let’s look at that.

Creating your Shell object

First you’ll need to install the following gems (from rubyforge.org) in this order: needle, oepnssl, and net-ssh.

In your ruby script/console:

require 'net/ssh'

session = Net::SSH.start( 'host', 'user', 'passwd' )

To create our synchronous shell:

shell = session.shell.sync

And you’re off and racing.

Jamis has provided lots of useful syntatic sugar (for example, you can pass Net::SSH.start a block with session as its argument, and reading all the docco is a clever thing to do next.

Using sudo?

The only thing I couldn’t immediately figure out is how to invoke sudo su - envname and then continue to control my shell as per normal. All commands just blocked.

Luckily there is a mailing list for me to loiter in…