diff --git a/lib/vagrant/commands.rb b/lib/vagrant/commands.rb index 4274b6d43..6ac7489b8 100644 --- a/lib/vagrant/commands.rb +++ b/lib/vagrant/commands.rb @@ -28,27 +28,27 @@ module Vagrant # useful information such as whether or not the environment is created # and if its running, suspended, etc. def status - Env.load! + env = Environment.load! wrap_output do - if !Env.persisted_vm + if !env.vm puts <<-msg The environment has not yet been created. Run `vagrant up` to create the environment. msg else additional_msg = "" - if Env.persisted_vm.vm.running? + if env.vm.vm.running? additional_msg = <<-msg To stop this VM, you can run `vagrant halt` to shut it down forcefully, or you can run `vagrant suspend` to simply suspend the virtual machine. In either case, to restart it again, simply run a `vagrant up`. msg - elsif Env.persisted_vm.vm.saved? + elsif env.vm.vm.saved? additional_msg = <<-msg To resume this VM, simply run `vagrant up`. msg - elsif Env.persisted_vm.vm.powered_off? + elsif env.vm.vm.powered_off? additional_msg = <<-msg To restart this VM, simply run `vagrant up`. msg @@ -61,7 +61,7 @@ msg puts <<-msg The environment has been created. The status of the current environment's -virtual machine is: "#{Env.persisted_vm.vm.state}."#{additional_msg} +virtual machine is: "#{env.vm.vm.state}."#{additional_msg} msg end end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index d950a315f..abe338ba0 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -13,6 +13,7 @@ module Vagrant attr_reader :config attr_reader :box attr_reader :vm + attr_reader :ssh #--------------------------------------------------------------- # Class Methods @@ -84,6 +85,7 @@ module Vagrant load_config! self.class.check_virtualbox! load_vm! + load_ssh! self end @@ -167,6 +169,11 @@ module Vagrant @vm = nil end + # Loads/initializes the SSH object + def load_ssh! + @ssh = SSH.new(self) + end + #--------------------------------------------------------------- # Methods to manage VM #--------------------------------------------------------------- diff --git a/lib/vagrant/ssh.rb b/lib/vagrant/ssh.rb index d22376794..0f81772b2 100644 --- a/lib/vagrant/ssh.rb +++ b/lib/vagrant/ssh.rb @@ -1,54 +1,77 @@ module Vagrant + # Manages SSH access to a specific environment. Allows an environment to + # replace the process with SSH itself, run a specific set of commands, + # upload files, or even check if a host is up. class SSH include Vagrant::Util - class << self - def connect(opts={}) - options = {} - [:host, :username, :private_key_path].each do |param| - options[param] = opts[param] || Vagrant.config.ssh.send(param) - end + # Reference back up to the environment which this SSH object belongs + # to + attr_accessor :env - Kernel.exec "ssh -p #{port(opts)} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i #{options[:private_key_path]} #{options[:username]}@#{options[:host]}".strip + def initialize(environment) + @env = environment + end + + # Connects to the environment's virtual machine, replacing the ruby + # process with an SSH process. This method optionally takes a hash + # of options which override the configuration values. + def connect(opts={}) + options = {} + [:host, :username, :private_key_path].each do |param| + options[param] = opts[param] || env.config.ssh.send(param) end - def execute(opts={}) - Net::SSH.start(Vagrant.config.ssh.host, - Vagrant.config[:ssh][:username], - opts.merge( :port => port, - :keys => [Vagrant.config.ssh.private_key_path])) do |ssh| - yield ssh - end - end + Kernel.exec "ssh -p #{port(opts)} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i #{options[:private_key_path]} #{options[:username]}@#{options[:host]}".strip + end - def upload!(from, to) - execute do |ssh| - scp = Net::SCP.new(ssh) - scp.upload!(from, to) - end + # Opens an SSH connection to this environment's virtual machine and yields + # a Net::SSH object which can be used to execute remote commands. + def execute(opts={}) + Net::SSH.start(env.config.ssh.host, + env.config[:ssh][:username], + opts.merge( :port => port, + :keys => [env.config.ssh.private_key_path])) do |ssh| + yield ssh end + end - def up? - check_thread = Thread.new do - begin - Thread.current[:result] = false - execute(:timeout => Vagrant.config.ssh.timeout) do |ssh| - Thread.current[:result] = true - end - rescue Errno::ECONNREFUSED, Net::SSH::Disconnect - # False, its defaulted above + # Uploads a file from `from` to `to`. `from` is expected to be a filename + # or StringIO, and `to` is expected to be a path. This method simply forwards + # the arguments to `Net::SCP#upload!` so view that for more information. + def upload!(from, to) + execute do |ssh| + scp = Net::SCP.new(ssh) + scp.upload!(from, to) + end + end + + # Checks if this environment's machine is up (i.e. responding to SSH). + # + # @return [Boolean] + def up? + check_thread = Thread.new do + begin + Thread.current[:result] = false + execute(:timeout => env.config.ssh.timeout) do |ssh| + Thread.current[:result] = true end + rescue Errno::ECONNREFUSED, Net::SSH::Disconnect + # False, its defaulted above end - - check_thread.join(Vagrant.config.ssh.timeout) - return check_thread[:result] - rescue Net::SSH::AuthenticationFailed - error_and_exit(:vm_ssh_auth_failed) end - def port(opts={}) - opts[:port] || Vagrant.config.vm.forwarded_ports[Vagrant.config.ssh.forwarded_port_key][:hostport] - end + check_thread.join(env.config.ssh.timeout) + return check_thread[:result] + rescue Net::SSH::AuthenticationFailed + error_and_exit(:vm_ssh_auth_failed) + end + + # Returns the port which is either given in the options hash or taken from + # the config by finding it in the forwarded ports hash based on the + # `config.ssh.forwarded_port_key` + def port(opts={}) + opts[:port] || env.config.vm.forwarded_ports[env.config.ssh.forwarded_port_key][:hostport] end end end diff --git a/test/vagrant/environment_test.rb b/test/vagrant/environment_test.rb index f946c975f..ac45a450f 100644 --- a/test/vagrant/environment_test.rb +++ b/test/vagrant/environment_test.rb @@ -128,6 +128,7 @@ class EnvironmentTest < Test::Unit::TestCase @env.expects(:load_config!).once.in_sequence(call_seq) Vagrant::Environment.expects(:check_virtualbox!).once.in_sequence(call_seq) @env.expects(:load_vm!).once.in_sequence(call_seq) + @env.expects(:load_ssh!).once.in_sequence(call_seq) assert_equal @env, @env.load! end end @@ -368,6 +369,19 @@ class EnvironmentTest < Test::Unit::TestCase assert_nil @env.vm end end + + context "loading SSH" do + setup do + @env = mock_environment + end + + should "initialize the SSH object with the given environment" do + ssh = mock("ssh") + Vagrant::SSH.expects(:new).with(@env).returns(ssh) + @env.load_ssh! + assert_equal ssh, @env.ssh + end + end end context "requiring properties" do diff --git a/test/vagrant/ssh_test.rb b/test/vagrant/ssh_test.rb index fd2b400da..10e1fc0a6 100644 --- a/test/vagrant/ssh_test.rb +++ b/test/vagrant/ssh_test.rb @@ -5,24 +5,35 @@ class SshTest < Test::Unit::TestCase mock_config end - context "connecting to SSH" do - test "should call exec with defaults when no options are supplied" do - ssh = Vagrant.config.ssh - ssh_exec_expect(Vagrant::SSH.port, - Vagrant.config.ssh.private_key_path, - Vagrant.config.ssh.username, - Vagrant.config.ssh.host) - Vagrant::SSH.connect + def mock_ssh + @env = mock_environment do |config| + yield config if block_given? end - test "should call exec with supplied params" do + @ssh = Vagrant::SSH.new(@env) + end + + context "connecting to external SSH" do + setup do + mock_ssh + end + + should "call exec with defaults when no options are supplied" do + ssh_exec_expect(@ssh.port, + @env.config.ssh.private_key_path, + @env.config.ssh.username, + @env.config.ssh.host) + @ssh.connect + end + + should "call exec with supplied params" do args = {:username => 'bar', :private_key_path => 'baz', :host => 'bak', :port => 'bag'} ssh_exec_expect(args[:port], args[:private_key_path], args[:username], args[:host]) - Vagrant::SSH.connect(args) + @ssh.connect(args) end def ssh_exec_expect(port, key_path, uname, host) - Kernel.expects(:exec).with() do |arg| + Kernel.expects(:exec).with() do |arg| assert arg =~ /^ssh/ assert arg =~ /-p #{port}/ assert arg =~ /-i #{key_path}/ @@ -34,91 +45,103 @@ class SshTest < Test::Unit::TestCase end context "executing ssh commands" do + setup do + mock_ssh + end + should "call net::ssh.start with the proper names" do Net::SSH.expects(:start).once.with() do |host, username, opts| - assert_equal Vagrant.config.ssh.host, host - assert_equal Vagrant.config.ssh.username, username - assert_equal Vagrant::SSH.port, opts[:port] - assert_equal [Vagrant.config.ssh.private_key_path], opts[:keys] + assert_equal @env.config.ssh.host, host + assert_equal @env.config.ssh.username, username + assert_equal @ssh.port, opts[:port] + assert_equal [@env.config.ssh.private_key_path], opts[:keys] true end - Vagrant::SSH.execute + @ssh.execute end should "use custom host if set" do - Vagrant.config.ssh.host = "foo" - Net::SSH.expects(:start).with(Vagrant.config.ssh.host, Vagrant.config.ssh.username, anything).once - Vagrant::SSH.execute + @env.config.ssh.host = "foo" + Net::SSH.expects(:start).with(@env.config.ssh.host, @env.config.ssh.username, anything).once + @ssh.execute end end context "SCPing files to the remote host" do + setup do + mock_ssh + end + should "use Vagrant::SSH execute to setup an SCP connection and upload" do scp = mock("scp") ssh = mock("ssh") scp.expects(:upload!).with("foo", "bar").once Net::SCP.expects(:new).with(ssh).returns(scp).once - Vagrant::SSH.expects(:execute).yields(ssh).once - Vagrant::SSH.upload!("foo", "bar") + @ssh.expects(:execute).yields(ssh).once + @ssh.upload!("foo", "bar") end end context "checking if host is up" do setup do - mock_config + mock_ssh end should "return true if SSH connection works" do Net::SSH.expects(:start).yields("success") - assert Vagrant::SSH.up? + assert @ssh.up? end should "return false if SSH connection times out" do Net::SSH.expects(:start) - assert !Vagrant::SSH.up? + assert !@ssh.up? end should "allow the thread the configured timeout time" do @thread = mock("thread") @thread.stubs(:[]) Thread.expects(:new).returns(@thread) - @thread.expects(:join).with(Vagrant.config.ssh.timeout).once - Vagrant::SSH.up? + @thread.expects(:join).with(@env.config.ssh.timeout).once + @ssh.up? end should "return false if the connection is refused" do Net::SSH.expects(:start).raises(Errno::ECONNREFUSED) assert_nothing_raised { - assert !Vagrant::SSH.up? + assert !@ssh.up? } end should "return false if the connection is dropped" do Net::SSH.expects(:start).raises(Net::SSH::Disconnect) assert_nothing_raised { - assert !Vagrant::SSH.up? + assert !@ssh.up? } end should "specifity the timeout as an option to execute" do - Vagrant::SSH.expects(:execute).with(:timeout => Vagrant.config.ssh.timeout).yields(true) - assert Vagrant::SSH.up? + @ssh.expects(:execute).with(:timeout => @env.config.ssh.timeout).yields(true) + assert @ssh.up? end should "error and exit if a Net::SSH::AuthenticationFailed is raised" do - Vagrant::SSH.expects(:execute).raises(Net::SSH::AuthenticationFailed) - Vagrant::SSH.expects(:error_and_exit).with(:vm_ssh_auth_failed).once - Vagrant::SSH.up? + @ssh.expects(:execute).raises(Net::SSH::AuthenticationFailed) + @ssh.expects(:error_and_exit).with(:vm_ssh_auth_failed).once + @ssh.up? end end context "getting the ssh port" do + setup do + mock_ssh + end + should "return the configured port by default" do - assert_equal Vagrant.config.vm.forwarded_ports[Vagrant.config.ssh.forwarded_port_key][:hostport], Vagrant::SSH.port + assert_equal @env.config.vm.forwarded_ports[@env.config.ssh.forwarded_port_key][:hostport], @ssh.port end should "return the port given in options if it exists" do - assert_equal "47", Vagrant::SSH.port({ :port => "47" }) + assert_equal "47", @ssh.port({ :port => "47" }) end end end