From b659191a023e1e29442cf696193db4239f9ca36f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 14 Aug 2012 22:38:41 -0700 Subject: [PATCH] `vagrant up`! --- lib/vagrant/machine.rb | 4 + lib/vagrant/plugin/v1/provider.rb | 9 +++ plugins/commands/up/command.rb | 21 ++--- plugins/providers/virtualbox/action.rb | 81 +++++++++++++++---- .../providers/virtualbox/action/check_box.rb | 33 ++++++++ .../action/check_guest_additions.rb | 36 +++++++++ .../virtualbox/action/default_name.rb | 22 +++++ plugins/providers/virtualbox/action/import.rb | 49 +++++++++++ .../providers/virtualbox/action/is_saved.rb | 20 +++++ .../virtualbox/action/match_mac_address.rb | 21 +++++ plugins/providers/virtualbox/provider.rb | 30 ++++--- test/unit/vagrant/machine_test.rb | 14 ++++ 12 files changed, 300 insertions(+), 40 deletions(-) create mode 100644 plugins/providers/virtualbox/action/check_box.rb create mode 100644 plugins/providers/virtualbox/action/check_guest_additions.rb create mode 100644 plugins/providers/virtualbox/action/default_name.rb create mode 100644 plugins/providers/virtualbox/action/import.rb create mode 100644 plugins/providers/virtualbox/action/is_saved.rb create mode 100644 plugins/providers/virtualbox/action/match_mac_address.rb diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index ea448a6ad..0f8e8671c 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -184,6 +184,10 @@ module Vagrant # Store the ID locally @id = value + + # Notify the provider that the ID changed in case it needs to do + # any accounting from it. + @provider.machine_id_changed end # This returns a clean inspect value so that printing the value via diff --git a/lib/vagrant/plugin/v1/provider.rb b/lib/vagrant/plugin/v1/provider.rb index 513b4c2d8..0a71e134c 100644 --- a/lib/vagrant/plugin/v1/provider.rb +++ b/lib/vagrant/plugin/v1/provider.rb @@ -21,6 +21,15 @@ module Vagrant nil end + # This method is called if the underying machine ID changes. Providers + # can use this method to load in new data for the actual backing + # machine or to realize that the machine is now gone (the ID can + # become `nil`). No parameters are given, since the underlying machine + # is simply the machine instance given to this object. And no + # return value is necessary. + def machine_id_changed + end + # This should return a hash of information that explains how to # SSH into the machine. If the machine is not at a point where # SSH is even possible, then `nil` should be returned. diff --git a/plugins/commands/up/command.rb b/plugins/commands/up/command.rb index 1336760ed..186f0c98e 100644 --- a/plugins/commands/up/command.rb +++ b/plugins/commands/up/command.rb @@ -11,10 +11,10 @@ module VagrantPlugins def execute options = {} - opts = OptionParser.new do |opts| - opts.banner = "Usage: vagrant up [vm-name] [--[no-]provision] [-h]" - opts.separator "" - build_start_options(opts, options) + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant up [vm-name] [--[no-]provision] [-h]" + o.separator "" + build_start_options(o, options) end # Parse the options @@ -23,20 +23,13 @@ module VagrantPlugins # Go over each VM and bring it up @logger.debug("'Up' each target VM...") - with_target_vms(argv) do |vm| - if vm.created? - @logger.info("Booting: #{vm.name}") - vm.ui.info I18n.t("vagrant.commands.up.vm_created") - vm.start(options) - else - @logger.info("Creating: #{vm.name}") - vm.up(options) - end + with_target_vms(argv) do |machine| + machine.action(:up) end # Success, exit status 0 0 - end + end end end end diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index e27d089f9..47f5021c0 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -5,7 +5,9 @@ module VagrantPlugins module Action autoload :Boot, File.expand_path("../action/boot", __FILE__) autoload :CheckAccessible, File.expand_path("../action/check_accessible", __FILE__) + autoload :CheckBox, File.expand_path("../action/check_box", __FILE__) autoload :CheckCreated, File.expand_path("../action/check_created", __FILE__) + autoload :CheckGuestAdditions, File.expand_path("../action/check_guest_additions", __FILE__) autoload :CheckPortCollisions, File.expand_path("../action/check_port_collisions", __FILE__) autoload :CheckRunning, File.expand_path("../action/check_running", __FILE__) autoload :CheckVirtualbox, File.expand_path("../action/check_virtualbox", __FILE__) @@ -15,6 +17,7 @@ module VagrantPlugins autoload :ClearSharedFolders, File.expand_path("../action/clear_shared_folders", __FILE__) autoload :Created, File.expand_path("../action/created", __FILE__) autoload :Customize, File.expand_path("../action/customize", __FILE__) + autoload :DefaultName, File.expand_path("../action/default_name", __FILE__) autoload :Destroy, File.expand_path("../action/destroy", __FILE__) autoload :DestroyConfirm, File.expand_path("../action/destroy_confirm", __FILE__) autoload :DestroyUnusedNetworkInterfaces, File.expand_path("../action/destroy_unused_network_interfaces", __FILE__) @@ -22,7 +25,10 @@ module VagrantPlugins autoload :ForwardPorts, File.expand_path("../action/forward_ports", __FILE__) autoload :Halt, File.expand_path("../action/halt", __FILE__) autoload :HostName, File.expand_path("../action/host_name", __FILE__) + autoload :Import, File.expand_path("../action/import", __FILE__) autoload :IsRunning, File.expand_path("../action/is_running", __FILE__) + autoload :IsSaved, File.expand_path("../action/is_saved", __FILE__) + autoload :MatchMACAddress, File.expand_path("../action/match_mac_address", __FILE__) autoload :MessageNotCreated, File.expand_path("../action/message_not_created", __FILE__) autoload :MessageNotRunning, File.expand_path("../action/message_not_running", __FILE__) autoload :MessageWillNotDestroy, File.expand_path("../action/message_will_not_destroy", __FILE__) @@ -40,6 +46,29 @@ module VagrantPlugins # things. include Vagrant::Action::Builtin + # This action boots the VM, assuming the VM is in a state that requires + # a bootup (i.e. not saved). + def self.action_boot + Vagrant::Action::Builder.new.tap do |b| + b.use CheckAccessible + b.use CleanMachineFolder + b.use ClearForwardedPorts + b.use EnvSet, :port_collision_handler => :correct + b.use ForwardPorts + b.use Provision + b.use PruneNFSExports + b.use NFS + b.use ClearSharedFolders + b.use ShareFolders + b.use ClearNetworkInterfaces + b.use Network + b.use HostName + b.use SaneDefaults + b.use Customize + b.use Boot + end + end + # This is the action that is primarily responsible for completely # freeing the resources of the underlying virtual machine. def self.action_destroy @@ -175,22 +204,21 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use CheckVirtualbox b.use Vagrant::Action::General::Validate - b.use CheckAccessible - b.use CleanMachineFolder - b.use ClearForwardedPorts - b.use EnvSet, :port_collision_handler => :correct - b.use ForwardPorts - b.use Provision - b.use PruneNFSExports - b.use NFS - b.use ClearSharedFolders - b.use ShareFolders - b.use ClearNetworkInterfaces - b.use Network - b.use HostName - b.use SaneDefaults - b.use Customize - b.use Boot + b.use Call, IsRunning do |env, b2| + # If the VM is running, then our work here is done, exit + next if env[:result] + + b2.use Call, IsSaved do |env2, b3| + if env2[:result] + # The VM is saved, so just resume it + b3.use action_resume + else + # The VM is not saved, so we must have to boot it up + # like normal. Boot! + b3.use action_boot + end + end + end end end @@ -209,6 +237,27 @@ module VagrantPlugins end end end + + # This action brings the machine up from nothing, including importing + # the box, configuring metadata, and booting. + def self.action_up + Vagrant::Action::Builder.new.tap do |b| + b.use CheckVirtualbox + b.use Vagrant::Action::General::Validate + b.use Call, Created do |env, b2| + # If the VM is NOT created yet, then do the setup steps + if !env[:result] + b2.use CheckAccessible + b2.use CheckBox + b2.use Import + b2.use CheckGuestAdditions + b2.use DefaultName + b2.use MatchMACAddress + end + end + b.use action_start + end + end end end end diff --git a/plugins/providers/virtualbox/action/check_box.rb b/plugins/providers/virtualbox/action/check_box.rb new file mode 100644 index 000000000..02c6fa866 --- /dev/null +++ b/plugins/providers/virtualbox/action/check_box.rb @@ -0,0 +1,33 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class CheckBox + def initialize(app, env) + @app = app + end + + def call(env) + box_name = env[:machine].config.vm.box + raise Vagrant::Errors::BoxNotSpecified if !box_name + + if !env[:box_collection].find(box_name, :virtualbox) + box_url = env[:machine].config.vm.box_url + raise Vagrant::Errors::BoxSpecifiedDoesntExist, :name => box_name if !box_url + + # Add the box then reload the box collection so that it becomes + # aware of it. + env[:ui].info I18n.t("vagrant.actions.vm.check_box.not_found", :name => box_name) + env[:box_collection].add(box_name, box_url) + env[:box_collection].reload! + + # Reload the environment and set the VM to be the new loaded VM. + env[:machine].env.reload! + env[:machine] = env[:machine].env.vms[env[:machine].name] + end + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/check_guest_additions.rb b/plugins/providers/virtualbox/action/check_guest_additions.rb new file mode 100644 index 000000000..2fd798814 --- /dev/null +++ b/plugins/providers/virtualbox/action/check_guest_additions.rb @@ -0,0 +1,36 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class CheckGuestAdditions + def initialize(app, env) + @app = app + end + + def call(env) + # Use the raw interface for now, while the virtualbox gem + # doesn't support guest properties (due to cross platform issues) + version = env[:machine].provider.driver.read_guest_additions_version + if !version + env[:ui].warn I18n.t("vagrant.actions.vm.check_guest_additions.not_detected") + else + # Strip the -OSE/_OSE off from the guest additions and the virtual box + # version since all the matters are that the version _numbers_ match up. + guest_version, vb_version = [version, env[:machine].provider.driver.version].map do |v| + v.gsub(/[-_]ose/i, '') + end + + if guest_version != vb_version + env[:ui].warn(I18n.t("vagrant.actions.vm.check_guest_additions.version_mismatch", + :guest_version => version, + :virtualbox_version => vb_version)) + end + end + + # Continue + @app.call(env) + end + + end + end + end +end diff --git a/plugins/providers/virtualbox/action/default_name.rb b/plugins/providers/virtualbox/action/default_name.rb new file mode 100644 index 000000000..9ef89cf70 --- /dev/null +++ b/plugins/providers/virtualbox/action/default_name.rb @@ -0,0 +1,22 @@ +require "log4r" + +module VagrantPlugins + module ProviderVirtualBox + module Action + class DefaultName + def initialize(app, env) + @logger = Log4r::Logger.new("vagrant::action::vm::defaultname") + @app = app + end + + def call(env) + @logger.info("Setting the default name of the VM") + name = env[:root_path].basename.to_s + "_#{Time.now.to_i}" + env[:machine].provider.driver.set_name(name) + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/import.rb b/plugins/providers/virtualbox/action/import.rb new file mode 100644 index 000000000..64e2507ab --- /dev/null +++ b/plugins/providers/virtualbox/action/import.rb @@ -0,0 +1,49 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class Import + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info I18n.t("vagrant.actions.vm.import.importing", + :name => env[:machine].box.name) + + # Import the virtual machine + ovf_file = env[:machine].box.directory.join("box.ovf").to_s + env[:machine].id = env[:machine].provider.driver.import(ovf_file) do |progress| + env[:ui].clear_line + env[:ui].report_progress(progress, 100, false) + end + + # Clear the line one last time since the progress meter doesn't disappear + # immediately. + env[:ui].clear_line + + # If we got interrupted, then the import could have been + # interrupted and its not a big deal. Just return out. + return if env[:interrupted] + + # Flag as erroneous and return if import failed + raise Vagrant::Errors::VMImportFailure if !env[:machine].id + + # Import completed successfully. Continue the chain + @app.call(env) + end + + def recover(env) + if env[:machine].provider.state != :not_created + return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError) + + # Interrupted, destroy the VM. We note that we don't want to + # validate the configuration here. + destroy_env = env.clone + destroy_env[:validate] = false + env[:action_runner].run(Action.action_destroy, destroy_env) + end + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/is_saved.rb b/plugins/providers/virtualbox/action/is_saved.rb new file mode 100644 index 000000000..749f64964 --- /dev/null +++ b/plugins/providers/virtualbox/action/is_saved.rb @@ -0,0 +1,20 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class IsSaved + def initialize(app, env) + @app = app + end + + def call(env) + # Set the result to be true if the machine is saved. + env[:result] = env[:machine].state == :saved + + # Call the next if we have one (but we shouldn't, since this + # middleware is built to run with the Call-type middlewares) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/action/match_mac_address.rb b/plugins/providers/virtualbox/action/match_mac_address.rb new file mode 100644 index 000000000..a66f40bad --- /dev/null +++ b/plugins/providers/virtualbox/action/match_mac_address.rb @@ -0,0 +1,21 @@ +module VagrantPlugins + module ProviderVirtualBox + module Action + class MatchMACAddress + def initialize(app, env) + @app = app + end + + def call(env) + raise Vagrant::Errors::VMBaseMacNotSpecified if !env[:machine].config.vm.base_mac + + # Create the proc which we want to use to modify the virtual machine + env[:ui].info I18n.t("vagrant.actions.vm.match_mac.matching") + env[:machine].provider.driver.set_mac_address(env[:machine].config.vm.base_mac) + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/provider.rb b/plugins/providers/virtualbox/provider.rb index 2854a7d9c..44407c031 100644 --- a/plugins/providers/virtualbox/provider.rb +++ b/plugins/providers/virtualbox/provider.rb @@ -9,16 +9,9 @@ module VagrantPlugins @logger = Log4r::Logger.new("vagrant::provider::virtualbox") @machine = machine - begin - @logger.debug("Instantiating the driver for machine ID: #{@machine.id.inspect}") - @driver = Driver::Meta.new(@machine.id) - rescue Driver::Meta::VMNotFound - # The virtual machine doesn't exist, so we probably have a stale - # ID. Just clear the id out of the machine and reload it. - @logger.debug("VM not found! Clearing saved machine ID and reloading.") - @machine.id = nil - retry - end + # This method will load in our driver, so we call it now to + # initialize it. + machine_id_changed end # @see Vagrant::Plugin::V1::Provider#action @@ -31,6 +24,23 @@ module VagrantPlugins nil end + # If the machine ID changed, then we need to rebuild our underlying + # driver. + def machine_id_changed + id = @machine.id + + begin + @logger.debug("Instantiating the driver for machine ID: #{@machine.id.inspect}") + @driver = Driver::Meta.new(id) + rescue Driver::Meta::VMNotFound + # The virtual machine doesn't exist, so we probably have a stale + # ID. Just clear the id out of the machine and reload it. + @logger.debug("VM not found! Clearing saved machine ID and reloading.") + id = nil + retry + end + end + # Returns the SSH info for accessing the VirtualBox VM. def ssh_info # If the VM is not created then we cannot possibly SSH into it, so diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 20661d55e..ecf056c75 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -90,6 +90,10 @@ describe Vagrant::Machine do end it "should have access to the ID" do + # Stub this because #id= calls it. + provider.stub(:machine_id_changed) + + # Set the ID on the previous instance so that it is persisted instance.id = "foo" provider_init_test do |machine| @@ -270,6 +274,10 @@ describe Vagrant::Machine do end describe "setting the ID" do + before(:each) do + provider.stub(:machine_id_changed) + end + it "should not have an ID by default" do instance.id.should be_nil end @@ -279,6 +287,12 @@ describe Vagrant::Machine do instance.id.should == "bar" end + it "should notify the machine that the ID changed" do + provider.should_receive(:machine_id_changed).once + + instance.id = "bar" + end + it "should persist the ID" do instance.id = "foo" new_instance.id.should == "foo"