diff --git a/lib/vagrant/driver/virtualbox.rb b/lib/vagrant/driver/virtualbox.rb index 4d01a5773..ab787f5ab 100644 --- a/lib/vagrant/driver/virtualbox.rb +++ b/lib/vagrant/driver/virtualbox.rb @@ -1,19 +1,18 @@ require 'log4r' -require 'vagrant/util/busy' -require 'vagrant/util/platform' + +require 'vagrant/driver/virtualbox_base' require 'vagrant/util/subprocess' module Vagrant module Driver # This class contains the logic to drive VirtualBox. - class VirtualBox + # + # Read the VirtualBoxBase source for documentation on each method. + class VirtualBox < VirtualBoxBase # This is raised if the VM is not found when initializing a driver # with a UUID. class VMNotFound < StandardError; end - # Include this so we can use `Subprocess` more easily. - include Vagrant::Util - # The UUID of the virtual machine we represent attr_reader :uuid @@ -21,30 +20,12 @@ module Vagrant attr_reader :version def initialize(uuid) + # Setup the base + super() + @logger = Log4r::Logger.new("vagrant::driver::virtualbox") @uuid = uuid - # This flag is used to keep track of interrupted state (SIGINT) - @interrupted = false - - # Set the path to VBoxManage - @vboxmanage_path = "VBoxManage" - - if Util::Platform.windows? - @logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage") - - # On Windows, we use the VBOX_INSTALL_PATH environmental - # variable to find VBoxManage. - if ENV.has_key?("VBOX_INSTALL_PATH") - # The path usually ends with a \ but we make sure here - path = ENV["VBOX_INSTALL_PATH"] - path += "\\" if !path.end_with?("\\") - @vboxmanage_path = "#{path}VBoxManage.exe" - end - end - - @logger.info("VBoxManage path: #{@vboxmanage_path}") - # Read and assign the version of VirtualBox we know which # specific driver to instantiate. begin @@ -63,8 +44,6 @@ module Vagrant end end - # This clears the forwarded ports that have been set on the - # virtual machine. def clear_forwarded_ports args = [] read_forwarded_ports(@uuid).each do |nic, name, _, _| @@ -74,8 +53,6 @@ module Vagrant execute("modifyvm", @uuid, *args) if !args.empty? end - # This clears all the shared folders that have been set - # on the virtual machine. def clear_shared_folders execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line| if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/ @@ -84,7 +61,6 @@ module Vagrant end end - # Creates a host only network with the given options. def create_host_only_network(options) # Create the interface execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/ @@ -103,12 +79,10 @@ module Vagrant } end - # This deletes the VM with the given name. def delete execute("unregistervm", @uuid, "--delete") end - # Deletes any host only networks that aren't being used for anything. def delete_unused_host_only_networks networks = [] execute("list", "hostonlyifs").split("\n").each do |line| @@ -130,12 +104,10 @@ module Vagrant end end - # Discards any saved state associated with this VM. def discard_saved_state execute("discardstate", @uuid) end - # Enables network adapters on this virtual machine. def enable_adapters(adapters) args = [] adapters.each do |adapter| @@ -160,39 +132,15 @@ module Vagrant execute("modifyvm", @uuid, *args) end - # Executes a raw command. def execute_command(command) raw(*command) end - # Exports the virtual machine to the given path. - # - # @param [String] path Path to the OVF file. def export(path) # TODO: Progress execute("export", @uuid, "--output", path.to_s) end - # Forwards a set of ports for a VM. - # - # This will not affect any previously set forwarded ports, - # so be sure to delete those if you need to. - # - # The format of each port hash should be the following: - # - # { - # :name => "foo", - # :hostport => 8500, - # :guestport => 80, - # :adapter => 1, - # :protocol => "tcp" - # } - # - # Note that "adapter" and "protocol" are optional and will default - # to 1 and "tcp" respectively. - # - # @param [Array] ports An array of ports to set. See documentation - # for more information on the format. def forward_ports(ports) args = [] ports.each do |options| @@ -210,13 +158,10 @@ module Vagrant execute("modifyvm", @uuid, *args) end - # Halts the virtual machine. def halt execute("controlvm", @uuid, "poweroff") end - # Imports the VM with the given path to the OVF file. It returns - # the UUID as a string. def import(ovf, name) total = "" last = 0 @@ -249,10 +194,6 @@ module Vagrant nil end - # This returns a list of the forwarded ports in the form - # of `[nic, name, hostport, guestport]`. - # - # @return [Array] def read_forwarded_ports(uuid=nil, active_only=false) uuid ||= @uuid @@ -282,7 +223,6 @@ module Vagrant results end - # This reads the list of host only networks. def read_bridged_interfaces execute("list", "bridgedifs").split("\n\n").collect do |block| info = {} @@ -304,14 +244,12 @@ module Vagrant end end - # This reads the guest additions version for a VM. def read_guest_additions_version output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version") return $1.to_s if output =~ /^Value: (.+?)$/ return nil end - # Reads and returns the available host only interfaces. def read_host_only_interfaces execute("list", "hostonlyifs").split("\n\n").collect do |block| info = {} @@ -330,7 +268,6 @@ module Vagrant end end - # Reads the MAC address of the first network interface. def read_mac_address execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line| return $1.to_s if line =~ /^macaddress1="(.+?)"$/ @@ -339,7 +276,6 @@ module Vagrant nil end - # This reads the folder where VirtualBox places it's VMs. def read_machine_folder execute("list", "systemproperties").split("\n").each do |line| if line =~ /^Default machine folder:\s+(.+?)$/i @@ -350,10 +286,6 @@ module Vagrant nil end - # This reads the network interfaces and returns various information - # about them. - # - # @return [Hash] def read_network_interfaces nics = {} execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line| @@ -381,8 +313,6 @@ module Vagrant nics end - # This reads the state for the given UUID. The state of the VM - # will be returned as a symbol. def read_state output = execute("showvminfo", @uuid, "--machinereadable") if output =~ /^name=""$/ @@ -394,8 +324,6 @@ module Vagrant nil end - # This will read all the used ports for port forwarding by - # all virtual machines. def read_used_ports ports = [] execute("list", "vms").split("\n").each do |line| @@ -414,22 +342,10 @@ module Vagrant ports end - # This sets the MAC address for a network adapter. def set_mac_address(mac) execute("modifyvm", @uuid, "--macaddress1", mac) end - # Sets up the shared folder metadata for a virtual machine. - # - # The structure of a folder definition should be the following: - # - # { - # :name => "foo", - # :hostpath => "/foo/bar" - # } - # - # @param [Array] folders An array of folder definitions to - # setup. def share_folders(folders) folders.each do |folder| execute("sharedfolder", "add", @uuid, "--name", @@ -437,7 +353,6 @@ module Vagrant end end - # This reads the SSH port from the VM. def ssh_port(expected_port) # Look for the forwarded port only by comparing the guest port read_forwarded_ports.each do |_, _, hostport, guestport| @@ -447,21 +362,14 @@ module Vagrant nil end - # Starts the virtual machine in the given mode. - # - # @param [String] mode Mode to boot the VM: either "headless" or "gui" def start(mode) execute("startvm", @uuid, "--type", mode.to_s) end - # Suspends the virtual machine. def suspend execute("controlvm", @uuid, "savestate") end - # Verifies that an image can be imported properly. - # - # @return [Boolean] def verify_image(path) r = raw("import", path.to_s, "--dry-run") return r.exit_code == 0 @@ -482,38 +390,6 @@ module Vagrant # Below accounts for all of these: execute("--version").split("_")[0].split("r")[0] end - - # Execute the given subcommand for VBoxManage and return the output. - def execute(*command, &block) - # Execute the command - r = raw(*command, &block) - - # If the command was a failure, then raise an exception that is - # nicely handled by Vagrant. - if r.exit_code != 0 - if @interrupted - @logger.info("Exit code != 0, but interrupted. Ignoring.") - else - raise Errors::VBoxManageError, :command => command.inspect - end - end - - # Return the output, making sure to replace any Windows-style - # newlines with Unix-style. - r.stdout.gsub("\r\n", "\n") - end - - # Executes a command and returns the raw result object. - def raw(*command, &block) - int_callback = lambda do - @interrupted = true - @logger.info("Interrupted.") - end - - Util::Busy.busy(int_callback) do - Subprocess.execute(@vboxmanage_path, *command, &block) - end - end end end end diff --git a/lib/vagrant/driver/virtualbox_4_0.rb b/lib/vagrant/driver/virtualbox_4_0.rb new file mode 100644 index 000000000..abb09e88b --- /dev/null +++ b/lib/vagrant/driver/virtualbox_4_0.rb @@ -0,0 +1,8 @@ +module Vagrant + module Driver + # Driver to communicate with VirtualBox 4.0.x + class VirtualBox_4_0 + + end + end +end diff --git a/lib/vagrant/driver/virtualbox_4_1.rb b/lib/vagrant/driver/virtualbox_4_1.rb new file mode 100644 index 000000000..e0ac2689f --- /dev/null +++ b/lib/vagrant/driver/virtualbox_4_1.rb @@ -0,0 +1,10 @@ +module Vagrant + module Driver + # Driver to communicate with VirtualBox 4.1.x + class VirtualBox_4_1 + def initialize(uuid) + @uuid = uuid + end + end + end +end diff --git a/lib/vagrant/driver/virtualbox_base.rb b/lib/vagrant/driver/virtualbox_base.rb new file mode 100644 index 000000000..f5a25ea2b --- /dev/null +++ b/lib/vagrant/driver/virtualbox_base.rb @@ -0,0 +1,264 @@ +require 'log4r' + +require 'vagrant/util/busy' +require 'vagrant/util/platform' +require 'vagrant/util/subprocess' + +module Vagrant + module Driver + # Base class for all VirtualBox drivers. + # + # This class provides useful tools for things such as executing + # VBoxManage and handling SIGINTs and so on. + class VirtualBoxBase + # Include this so we can use `Subprocess` more easily. + include Vagrant::Util + + def initialize + @logger = Log4r::Logger.new("vagrant::driver::virtualbox_base") + + # This flag is used to keep track of interrupted state (SIGINT) + @interrupted = false + + # Set the path to VBoxManage + @vboxmanage_path = "VBoxManage" + + if Util::Platform.windows? + @logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage") + + # On Windows, we use the VBOX_INSTALL_PATH environmental + # variable to find VBoxManage. + if ENV.has_key?("VBOX_INSTALL_PATH") + # The path usually ends with a \ but we make sure here + path = ENV["VBOX_INSTALL_PATH"] + path += "\\" if !path.end_with?("\\") + @vboxmanage_path = "#{path}VBoxManage.exe" + end + end + + @logger.info("VBoxManage path: #{@vboxmanage_path}") + end + + # Clears the forwarded ports that have been set on the virtual machine. + def clear_forwarded_ports + end + + # Clears the shared folders that have been set on the virtual machine. + def clear_shared_folders + end + + # Creates a host only network with the given options. + # + # @param [Hash] options Options to create the host only network. + # @return [Hash] The details of the host only network, including + # keys `:name`, `:ip`, and `:netmask` + def create_host_only_network(options) + end + + # Deletes the virtual machine references by this driver. + def delete + end + + # Deletes any host only networks that aren't being used for anything. + def delete_unused_host_only_networks + end + + # Discards any saved state associated with this VM. + def discard_saved_state + end + + # Enables network adapters on the VM. + # + # The format of each adapter specification should be like so: + # + # { + # :type => :hostonly, + # :hostonly => "vboxnet0", + # :mac_address => "tubes" + # } + # + # This must support setting up both host only and bridged networks. + # + # @param [Array] adapters Array of adapters to enable. + def enable_adapters(adapters) + end + + # Execute a raw command straight through to VBoxManage. + # + # @param [Array] command Command to execute. + def execute_command(command) + end + + # Exports the virtual machine to the given path. + # + # @param [String] path Path to the OVF file. + # @yield [progress] Yields the block with the progress of the export. + def export(path) + end + + # Forwards a set of ports for a VM. + # + # This will not affect any previously set forwarded ports, + # so be sure to delete those if you need to. + # + # The format of each port hash should be the following: + # + # { + # :name => "foo", + # :hostport => 8500, + # :guestport => 80, + # :adapter => 1, + # :protocol => "tcp" + # } + # + # Note that "adapter" and "protocol" are optional and will default + # to 1 and "tcp" respectively. + # + # @param [Array] ports An array of ports to set. See documentation + # for more information on the format. + def forward_ports(ports) + end + + # Halts the virtual machine (pulls the plug). + def halt + end + + # Imports the VM from an OVF file. + # + # @param [String] ovf Path to the OVF file. + # @param [String] name Name of the VM. + # @return [String] UUID of the imported VM. + def import + end + + # Returns a list of forwarded ports for a VM. + # + # @param [String] uuid UUID of the VM to read from, or `nil` if this + # VM. + # @param [Boolean] active_only If true, only VMs that are running will + # be checked. + # @return [Array] + def read_forwarded_ports(uuid=nil, active_only=false) + end + + # Returns a list of bridged interfaces. + # + # @return [Hash] + def read_bridged_interfaces + end + + # Returns the guest additions version that is installed on this VM. + # + # @return [String] + def read_guest_additions_version + end + + # Returns a list of available host only interfaces. + # + # @return [Hash] + def read_host_only_interfaces + end + + # Returns the MAC address of the first network interface. + # + # @return [String] + def read_mac_address + end + + # Returns the folder where VirtualBox places it's VMs. + # + # @return [String] + def read_machine_folder + end + + # Returns a list of network interfaces of the VM. + # + # @return [Hash] + def read_network_interfaces + end + + # Returns the current state of this VM. + # + # @return [Symbol] + def read_state + end + + # Returns a list of all forwarded ports in use by active + # virtual machines. + # + # @return [Array] + def read_used_ports + end + + # Sets the MAC address of the first network adapter. + # + # @param [String] mac MAC address without any spaces/hyphens. + def set_mac_address(mac) + end + + # Share a set of folders on this VM. + # + # @param [Array] folders + def share_folders(folders) + end + + # Reads the SSH port of this VM. + # + # @param [Integer] expected Expected guest port of SSH. + def ssh_port(expected) + end + + # Starts the virtual machine. + # + # @param [String] mode Mode to boot the VM. Either "headless" + # or "gui" + def start(mode) + end + + # Suspend the virtual machine. + def suspend + end + + # Verifies that an image can be imported properly. + # + # @param [String] path Path to an OVF file. + # @return [Boolean] + def verify_image(path) + end + + protected + + # Execute the given subcommand for VBoxManage and return the output. + def execute(*command, &block) + # Execute the command + r = raw(*command, &block) + + # If the command was a failure, then raise an exception that is + # nicely handled by Vagrant. + if r.exit_code != 0 + if @interrupted + @logger.info("Exit code != 0, but interrupted. Ignoring.") + else + raise Errors::VBoxManageError, :command => command.inspect + end + end + + # Return the output, making sure to replace any Windows-style + # newlines with Unix-style. + r.stdout.gsub("\r\n", "\n") + end + + # Executes a command and returns the raw result object. + def raw(*command, &block) + int_callback = lambda do + @interrupted = true + @logger.info("Interrupted.") + end + + Util::Busy.busy(int_callback) do + Subprocess.execute(@vboxmanage_path, *command, &block) + end + end + end + end +end