Writings on various topics (mostly technical) from Oliver Hookins and Angela Collins. We currently reside in Sydney after almost a decade in Berlin, have three kids, and have far too little time to really justify having a blog.
If you saw my talk in Amsterdam or read my slides (or even work with me,
Vagrant wraps around the Ruby Net::SSH library to provide SSH access
into its VMs (which just use VirtualBox's port forwarding, since all of
the VMs are run on a NATed private network on the host). You can easily
issue vagrant ssh from within a VM root and it will just work. You can
even issue the secret vagrant ssh -e 'some command' line to run a
command within the shell, but it will wrap around
Net::SSH::Connection::Session.exec! which is completely synchronous
(probably what you want) and saves up all the output until it has the
exit code when the process has terminated (probably not what you want).
Especially when the process takes a few minutes, and you are watching
Jenkins' job console output, you want to see what is going on.
Unfortunately Vagrant's libraries don't expose the full richness of Net::SSH, but even if they did you wouldn't be much better off. Net::SSH gives you connection sessions in which you can issue multiple commands synchronously (as Vagrant typically does), or multiple commands asynchronously - and basically this equates to "assume they will run in parallel, in no particular order". There is also no direct handling of output and return codes - you need to set up callbacks for these. What this all amounts to is a bit of hackery just to get line-by-line output for our Jenkins job, and capture the return codes of each command properly.
#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
def ssh_command(session, cmd)
session.open_channel do |channel|
channel.exec(cmd) do |ch, success|
ch.on_data do |ch2, data|
$stdout.puts "STDOUT: #{data}"
end
ch.on_extended_data do |ch2, type, data|
$stderr.puts "STDERR: #{data}"
end
ch.on_request "exit-status" do |ch2, data|
return data.read_long
end
end
end
session.loop
end
Net::SSH.start 'localhost', 'vagrant' do |session|
['echo foo >&2','sleep 5','echo bar','/bin/false','echo baz'].each do |command|
if (rc = ssh_command(session,command)) != 0
puts "ERROR: #{command}"
puts "RC: #{rc}"
session.close
exit(rc)
end
end
end
What this should give you is:
$ ./ssh_test.rb
STDERR: foo
STDOUT: bar
ERROR: /bin/false
RC: 1
$ echo $?
Vagrant 0.7.3 sets up a read-only accessor to the Net::SSH::Connection::Session object contained within the Vagrant::SSH object so it's easy to just hook in to that and set up the code above to get slightly more flexible SSH access to the VM for our CI tasks:
commands = ['some','list','of','commands']
env = Vagrant::Environment.new(:cwd => VMDIR)
env.primary_vm.ssh.execute do |ssh|
commands.each do |c|
if (rc = ssh_command(ssh.session,c)) != 0
puts "ERROR: #{c}"
puts "RC: #{rc}"
ssh.session.close
exit(rc)
end
end
end