Merge branch 'machine-abstraction'

This branch brings in the "machine abstraction" code. This is a major
milestone in the development of Vagrant as it abstracts all of the
VirtualBox-specific code out into a plugin. There is zero VirtualBox
specific code in the core ("lib/") directory at this point. Read on for
important points.

== Gotchas

White it is technically possible now to write plugins for other
providers, there is still major work to be done to make this feasible.
The plugin interface itself is pretty much done, but there are some
issues:

* ":virtualbox" is the hardcoded provider to be used at the moment.

* There is no way to configure a provider. For example,
  `config.vm.customize` would never work for anything other than
  VirtualBox, so there needs to be a way to have provider-specific
  configuration. This will come soon.

* Shared folders and networking need to be rearchitected to be friendly
  for multiple providers, since it is unrealistic that a provider such as
  EC2 could provide the same level of networking, for example.

* There is no way easy way (like `vagrant package --base`) to create
  boxes for providers other than VirtualBox. This will be addressed in a
  whole new feature of Vagrant probably in a future release after
  provider stuff has shipped.

== Writing a Provider

To write a provider, you create a Vagrant plugin that defines a
"provider". See the "plugins/providers/virtualbox/plugin.rb" for more
details. Providers themselves have an exremely simple API. The burden
for writing providers mostly rests on the fact that you must define
complex middleware sequences.

Lots more work to come in the future, but this is a BIG MILESTONE!
This commit is contained in:
Mitchell Hashimoto 2012-08-19 19:27:09 -07:00
commit 391dc39267
122 changed files with 4157 additions and 2916 deletions

View File

@ -66,7 +66,6 @@ module Vagrant
autoload :BoxCollection, 'vagrant/box_collection'
autoload :CLI, 'vagrant/cli'
autoload :Command, 'vagrant/command'
autoload :Communication, 'vagrant/communication'
autoload :Config, 'vagrant/config'
autoload :DataStore, 'vagrant/data_store'
autoload :Downloaders, 'vagrant/downloaders'
@ -76,27 +75,23 @@ module Vagrant
autoload :Errors, 'vagrant/errors'
autoload :Guest, 'vagrant/guest'
autoload :Hosts, 'vagrant/hosts'
autoload :Machine, 'vagrant/machine'
autoload :Plugin, 'vagrant/plugin'
autoload :SSH, 'vagrant/ssh'
autoload :TestHelpers, 'vagrant/test_helpers'
autoload :UI, 'vagrant/ui'
autoload :Util, 'vagrant/util'
autoload :VM, 'vagrant/vm'
# These are the various plugin versions and their components in
# a lazy loaded Hash-like structure.
c = PLUGIN_COMPONENTS = Registry.new
c.register(:"1") { Plugin::V1::Plugin }
c.register([:"1", :command]) { Plugin::V1::Command }
c.register([:"1", :config]) { Plugin::V1::Config }
c.register([:"1", :guest]) { Plugin::V1::Guest }
c.register([:"1", :host]) { Plugin::V1::Host }
c.register([:"1", :provisioner]) { Plugin::V1::Provisioner }
# Returns a `Vagrant::Registry` object that contains all the built-in
# middleware stacks.
def self.actions
@actions ||= Vagrant::Action::Builtin.new
PLUGIN_COMPONENTS = Registry.new.tap do |c|
c.register(:"1") { Plugin::V1::Plugin }
c.register([:"1", :command]) { Plugin::V1::Command }
c.register([:"1", :communicator]) { Plugin::V1::Communicator }
c.register([:"1", :config]) { Plugin::V1::Config }
c.register([:"1", :guest]) { Plugin::V1::Guest }
c.register([:"1", :host]) { Plugin::V1::Host }
c.register([:"1", :provider]) { Plugin::V1::Provider }
c.register([:"1", :provisioner]) { Plugin::V1::Provisioner }
end
# The source root is the path to the root directory of

View File

@ -2,60 +2,35 @@ require 'vagrant/action/builder'
module Vagrant
module Action
autoload :Builtin, 'vagrant/action/builtin'
autoload :Environment, 'vagrant/action/environment'
autoload :Runner, 'vagrant/action/runner'
autoload :Warden, 'vagrant/action/warden'
module Box
autoload :Add, 'vagrant/action/box/add'
autoload :Download, 'vagrant/action/box/download'
autoload :Verify, 'vagrant/action/box/verify'
end
module Env
autoload :Set, 'vagrant/action/env/set'
# Builtin contains middleware classes that are shipped with Vagrant-core
# and are thus available to all plugins as a "standard library" of sorts.
module Builtin
autoload :BoxAdd, "vagrant/action/builtin/box_add"
autoload :Call, "vagrant/action/builtin/call"
autoload :Confirm, "vagrant/action/builtin/confirm"
autoload :EnvSet, "vagrant/action/builtin/env_set"
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
autoload :SSHRun, "vagrant/action/builtin/ssh_run"
end
module General
autoload :CheckVirtualbox, 'vagrant/action/general/check_virtualbox'
autoload :Package, 'vagrant/action/general/package'
autoload :Validate, 'vagrant/action/general/validate'
end
module VM
autoload :Boot, 'vagrant/action/vm/boot'
autoload :CheckAccessible, 'vagrant/action/vm/check_accessible'
autoload :CheckBox, 'vagrant/action/vm/check_box'
autoload :CheckGuestAdditions, 'vagrant/action/vm/check_guest_additions'
autoload :CheckPortCollisions, 'vagrant/action/vm/check_port_collisions'
autoload :CleanMachineFolder, 'vagrant/action/vm/clean_machine_folder'
autoload :ClearForwardedPorts, 'vagrant/action/vm/clear_forwarded_ports'
autoload :ClearNetworkInterfaces, 'vagrant/action/vm/clear_network_interfaces'
autoload :ClearSharedFolders, 'vagrant/action/vm/clear_shared_folders'
autoload :Customize, 'vagrant/action/vm/customize'
autoload :DefaultName, 'vagrant/action/vm/default_name'
autoload :Destroy, 'vagrant/action/vm/destroy'
autoload :DestroyUnusedNetworkInterfaces, 'vagrant/action/vm/destroy_unused_network_interfaces'
autoload :DiscardState, 'vagrant/action/vm/discard_state'
autoload :Export, 'vagrant/action/vm/export'
autoload :ForwardPorts, 'vagrant/action/vm/forward_ports'
autoload :Halt, 'vagrant/action/vm/halt'
autoload :HostName, 'vagrant/action/vm/host_name'
autoload :Import, 'vagrant/action/vm/import'
autoload :MatchMACAddress, 'vagrant/action/vm/match_mac_address'
autoload :Network, 'vagrant/action/vm/network'
autoload :NFS, 'vagrant/action/vm/nfs'
autoload :Package, 'vagrant/action/vm/package'
autoload :PackageVagrantfile, 'vagrant/action/vm/package_vagrantfile'
autoload :Provision, 'vagrant/action/vm/provision'
autoload :ProvisionerCleanup, 'vagrant/action/vm/provisioner_cleanup'
autoload :PruneNFSExports, 'vagrant/action/vm/prune_nfs_exports'
autoload :Resume, 'vagrant/action/vm/resume'
autoload :SaneDefaults, 'vagrant/action/vm/sane_defaults'
autoload :ShareFolders, 'vagrant/action/vm/share_folders'
autoload :SetupPackageFiles, 'vagrant/action/vm/setup_package_files'
autoload :Suspend, 'vagrant/action/vm/suspend'
# This is the action that will add a box from a URL. This middleware
# sequence is built-in to Vagrant. Plugins can hook into this like any
# other middleware sequence. This is particularly useful for provider
# plugins, which can hook in to do things like verification of boxes
# that are downloaded.
def self.action_box_add
Builder.new.tap do |b|
b.use Builtin::BoxAdd
end
end
end
end

View File

@ -1,31 +0,0 @@
module Vagrant
module Action
module Box
# Adds a downloaded box file to the environment's box collection.
# This handles unpacking the box. See {BoxCollection#add} for more
# information.
class Add
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env[:ui].info I18n.t("vagrant.actions.box.add.adding", :name => env[:box_name])
begin
env[:box_collection].add(env[:box_download_temp_path], env[:box_name])
rescue Vagrant::Errors::BoxUpgradeRequired
# Upgrade the box
env[:box_collection].upgrade(env[:box_name])
# Try adding it again
retry
end
@app.call(env)
end
end
end
end
end

View File

@ -1,84 +0,0 @@
module Vagrant
module Action
module Box
class Download
BASENAME = "box"
include Util
attr_reader :temp_path
def initialize(app, env)
@app = app
@env = env
@env["download.classes"] ||= []
@env["download.classes"] += [Downloaders::HTTP, Downloaders::File]
@downloader = nil
end
def call(env)
@env = env
download if instantiate_downloader
@app.call(@env)
recover(env) # called in both cases to cleanup workspace
end
def instantiate_downloader
# Assign to a temporary variable since this is easier to type out,
# since it is used so many times.
classes = @env["download.classes"]
# Find the class to use.
classes.each_index do |i|
klass = classes[i]
# Use the class if it matches the given URI or if this
# is the last class...
if classes.length == (i + 1) || klass.match?(@env[:box_url])
@env[:ui].info I18n.t("vagrant.actions.box.download.with", :class => klass.to_s)
@downloader = klass.new(@env[:ui])
break
end
end
# This line should never be reached, but we'll keep this here
# just in case for now.
raise Errors::BoxDownloadUnknownType if !@downloader
@downloader.prepare(@env[:box_url])
true
end
def download
with_tempfile do |tempfile|
download_to(tempfile)
@temp_path = @env[:box_download_temp_path] = tempfile.path
end
end
def recover(env)
if temp_path && File.exist?(temp_path)
env[:ui].info I18n.t("vagrant.actions.box.download.cleaning")
File.unlink(temp_path)
end
end
def with_tempfile
File.open(box_temp_path, Platform.tar_file_options) do |tempfile|
yield tempfile
end
end
def box_temp_path
@env[:tmp_path].join(BASENAME + Time.now.to_i.to_s)
end
def download_to(f)
@downloader.download!(@env[:box_url], f)
end
end
end
end
end

View File

@ -1,24 +0,0 @@
module Vagrant
module Action
module Box
class Verify
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env[:ui].info I18n.t("vagrant.actions.box.verify.verifying")
box = env[:box_collection].find(env[:box_name], :virtualbox)
driver = Driver::VirtualBox.new(nil)
if !driver.verify_image(box.directory.join("box.ovf").to_s)
raise Errors::BoxVerificationFailed
end
@app.call(env)
end
end
end
end
end

View File

@ -9,18 +9,21 @@ module Vagrant
#
# Building an action sequence is very easy:
#
# app = Vagrant::Action::Builder.new do
# use MiddlewareA
# use MiddlewareB
# app = Vagrant::Action::Builder.new.tap do |b|
# b.use MiddlewareA
# b.use MiddlewareB
# end
#
# Vagrant::Action.run(app)
#
class Builder
# Initializes the builder. An optional block can be passed which
# will be evaluated in the context of the instance.
def initialize(&block)
instance_eval(&block) if block_given?
# This is a shortcut for a middleware sequence with only one item
# in it. For a description of the arguments and the documentation, please
# see {#use} instead.
#
# @return [Builder]
def self.build(middleware, *args, &block)
new.use(middleware, *args, &block)
end
# Returns a mergeable version of the builder. If `use` is called with
@ -38,11 +41,6 @@ module Vagrant
#
# @param [Class] middleware The middleware class
def use(middleware, *args, &block)
# Prepend with a environment setter if args are given
if !args.empty? && args.first.is_a?(Hash) && middleware != Env::Set
self.use(Env::Set, args.shift, &block)
end
if middleware.kind_of?(Builder)
# Merge in the other builder's stack into our own
self.stack.concat(middleware.stack)

View File

@ -1,157 +0,0 @@
module Vagrant
module Action
# A registry object containing the built-in middleware stacks.
class Builtin < Registry
def initialize
# Properly initialize the registry object
super
# Register all the built-in stacks
register_builtin!
end
protected
def register_builtin!
# We do this so that the blocks below have a variable to access the
# outer registry.
registry = self
# provision - Provisions a running VM
register(:provision) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use VM::Provision
end
end
# start - Starts a VM, assuming it already exists on the
# environment.
register(:start) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use VM::CleanMachineFolder
use VM::ClearForwardedPorts
use VM::CheckPortCollisions, :port_collision_handler => :correct
use VM::ForwardPorts
use VM::Provision
use VM::PruneNFSExports
use VM::NFS
use VM::ClearSharedFolders
use VM::ShareFolders
use VM::ClearNetworkInterfaces
use VM::Network
use VM::HostName
use VM::SaneDefaults
use VM::Customize
use VM::Boot
end
end
# halt - Halts the VM, attempting gracefully but then forcing
# a restart if fails.
register(:halt) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use VM::DiscardState
use VM::Halt
end
end
# suspend - Suspends the VM
register(:suspend) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use VM::Suspend
end
end
# resume - Resume a VM
register(:resume) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use VM::CheckPortCollisions
use VM::Resume
end
end
# reload - Halts then restarts the VM
register(:reload) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use registry.get(:halt)
use registry.get(:start)
end
end
# up - Imports, prepares, then starts a fresh VM.
register(:up) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use VM::CheckBox
use VM::Import
use VM::CheckGuestAdditions
use VM::DefaultName
use VM::MatchMACAddress
use registry.get(:start)
end
end
# destroy - Halts, cleans up, and destroys an existing VM
register(:destroy) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::CheckAccessible
use registry.get(:halt), :force => true
use VM::ProvisionerCleanup
use VM::PruneNFSExports
use VM::Destroy
use VM::CleanMachineFolder
use VM::DestroyUnusedNetworkInterfaces
end
end
# package - Export and package the VM
register(:package) do
Builder.new do
use General::CheckVirtualbox
use General::Validate
use VM::SetupPackageFiles
use VM::CheckAccessible
use registry.get(:halt)
use VM::ClearForwardedPorts
use VM::ClearSharedFolders
use VM::Export
use VM::PackageVagrantfile
use VM::Package
end
end
# box_add - Download and add a box.
register(:box_add) do
Builder.new do
use General::CheckVirtualbox
use Box::Download
use Box::Add
use Box::Verify
end
end
end
end
end
end

View File

@ -0,0 +1,75 @@
require "vagrant/util/platform"
module Vagrant
module Action
module Builtin
# This middleware will download a remote box and add it to the
# given box collection.
class BoxAdd
def initialize(app, env)
@app = app
end
def call(env)
# Instantiate the downloader
downloader = download_klass(env[:box_url]).new(env[:ui])
env[:ui].info I18n.t("vagrant.actions.box.download.with",
:class => downloader.class.to_s)
# Download the box to a temporary path. We store the temporary
# path as an instance variable so that the `#recover` method can
# access it.
@temp_path = env[:tmp_path].join("box" + Time.now.to_i.to_s)
File.open(@temp_path, Vagrant::Util::Platform.tar_file_options) do |f|
downloader.download!(env[:box_url], f)
end
# Add the box
env[:ui].info I18n.t("vagrant.actions.box.add.adding", :name => env[:box_name])
begin
env[:box_collection].add(@temp_path, env[:box_name])
rescue Vagrant::Errors::BoxUpgradeRequired
# Upgrade the box
env[:box_collection].upgrade(env[:box_name])
# Try adding it again
retry
end
# Call the 'recover' method in all cases to clean up the
# downloaded temporary file.
recover(env)
# Carry on!
@app.call(env)
end
def download_klass(url)
# This is hardcoded for now. In the future I'd like to make this
# pluginnable as well.
classes = [Downloaders::HTTP, Downloaders::File]
# Find the class to use.
classes.each_index do |i|
klass = classes[i]
# Use the class if it matches the given URI or if this
# is the last class...
return klass if classes.length == (i + 1) || klass.match?(url)
end
# If no downloader knows how to download this file, then we
# raise an exception.
raise Errors::BoxDownloadUnknownType
end
def recover(env)
if @temp_path && File.exist?(@temp_path)
env[:ui].info I18n.t("vagrant.actions.box.download.cleaning")
File.unlink(@temp_path)
end
end
end
end
end
end

View File

@ -0,0 +1,53 @@
module Vagrant
module Action
module Builtin
# This middleware class allows a sort of "conditional" run within
# a single middlware sequence. It takes another middleware runnable,
# runs it with the same environment, then yields the resulting env to a block,
# allowing that block to determine the next course of action in the
# middleware sequence.
#
# The first argument to this middleware sequence is anywhere middleware
# runnable, whether it be a class, lambda, or something else that
# responds to `call`. This middleware runnable is run with the same
# environment as this class.
#
# After running, {Call} takes the environment and yields it to a block
# given to initialize the class, along with an instance of {Builder}.
# The result is used to build up a new sequence on the given builder.
# This builder is then run.
class Call
# For documentation, read the description of the {Call} class.
#
# @param [Object] callable A valid middleware runnable object. This
# can be a class, a lambda, or an object that responds to `call`.
# @yield [result, builder] This block is expected to build on `builder`
# which is the next middleware sequence that will be run.
def initialize(app, env, callable, &block)
raise ArgumentError, "A block must be given to Call" if !block
@app = app
@callable = callable
@block = block
end
def call(env)
runner = Runner.new
# Run our callable with our environment
new_env = runner.run(@callable, env)
# Build our new builder based on the result
builder = Builder.new
@block.call(new_env, builder)
# Run the result with our new environment
final_env = runner.run(builder, new_env)
# Call the next step using our final environment
@app.call(final_env)
end
end
end
end
end

View File

@ -0,0 +1,28 @@
module Vagrant
module Action
module Builtin
# This class asks the user to confirm some sort of question with
# a "Y/N" question. The only parameter is the text to ask the user.
# The result is placed in `env[:result]` so that it can be used
# with the {Call} class.
class Confirm
# For documentation, read the description of the {Confirm} class.
#
# @param [String] message The message to ask the user.
def initialize(app, env, message)
@app = app
@message = message
end
def call(env)
# Ask the user the message and store the result
choice = nil
choice = env[:ui].ask(@message)
env[:result] = choice && choice.upcase == "Y"
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,24 @@
module Vagrant
module Action
module Builtin
# This middleware class allows you to modify the environment hash
# in the middle of a middleware sequence. The new environmental data
# will take affect at this stage in the middleware and will persist
# through.
class EnvSet
def initialize(app, env, new_env=nil)
@app = app
@new_env = new_env || {}
end
def call(env)
# Merge in the new data
env.merge!(@new_env)
# Carry on
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,38 @@
require "vagrant/util/ssh"
module Vagrant
module Action
module Builtin
# This class will exec into a full fledged SSH console into the
# remote machine. This middleware assumes that the VM is running and
# ready for SSH, and uses the {Machine#ssh_info} method to retrieve
# SSH information necessary to connect.
#
# Note: If there are any middleware after `SSHExec`, they will **not**
# run, since exec replaces the currently running process.
class SSHExec
# For quick access to the `SSH` class.
include Vagrant::Util
def initialize(app, env)
@app = app
end
def call(env)
# Grab the SSH info from the machine
info = env[:machine].ssh_info
# If the result is nil, then the machine is telling us that it is
# not yet ready for SSH, so we raise this exception.
raise Errors::SSHNotReady if info.nil?
# Check the SSH key permissions
SSH.check_key_permissions(info[:private_key_path])
# Exec!
SSH.exec(info, env[:ssh_opts])
end
end
end
end
end

View File

@ -0,0 +1,43 @@
require "log4r"
module Vagrant
module Action
module Builtin
# This class will run a single command on the remote machine and will
# mirror the output to the UI. The resulting exit status of the command
# will exist in the `:ssh_run_exit_status` key in the environment.
class SSHRun
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::ssh_run")
end
def call(env)
command = env[:ssh_run_command]
@logger.debug("Executing command: #{command}")
exit_status = 0
exit_status = env[:machine].communicate.execute(command, :error_check => false) do |type, data|
# Determine the proper channel to send the output onto depending
# on the type of data we are receiving.
channel = type == :stdout ? :out : :error
# Print the output as it comes in, but don't prefix it and don't
# force a new line so that the output is properly preserved however
# it may be formatted.
env[:ui].info(data.to_s,
:prefix => false,
:new_line => false,
:channel => channel)
end
# Set the exit status on a known environmental variable
env[:ssh_run_exit_status] = exit_status
# Call the next middleware
@app.call(env)
end
end
end
end
end

View File

@ -1,21 +0,0 @@
module Vagrant
module Action
module Env
# A middleware which just sets up the environment with some
# options which are passed to it.
class Set
def initialize(app, env, options=nil)
@app = app
@options = options || {}
end
def call(env)
# Merge the options that were given to us
env.merge!(@options)
@app.call(env)
end
end
end
end
end

View File

@ -1,28 +0,0 @@
module Vagrant
module Action
module General
# Checks that virtualbox is installed and ready to be used.
class CheckVirtualbox
def initialize(app, env)
@app = app
end
def call(env)
# Certain actions may not actually have a VM, and thus no
# driver, so we have to be clever about obtaining an instance
# of the driver.
driver = nil
driver = env[:vm].driver if env[:vm]
driver = Driver::VirtualBox.new(nil) if !driver
# Verify that it is ready to go! This will raise an exception
# if anything goes wrong.
driver.verify!
# Carry on.
@app.call(env)
end
end
end
end
end

View File

@ -9,7 +9,10 @@ module Vagrant
end
def call(env)
env[:vm].config.validate!(env[:vm].env) if !env.has_key?("validate") || env["validate"]
if !env.has_key?(:validate) || env[:validate]
env[:machine].config.validate!(env[:machine].env)
end
@app.call(env)
end
end

View File

@ -10,8 +10,7 @@ module Vagrant
class Runner
@@reported_interrupt = false
def initialize(registry, globals=nil, &block)
@registry = registry
def initialize(globals=nil, &block)
@globals = globals || {}
@lazy_globals = block
@logger = Log4r::Logger.new("vagrant::action::runner")
@ -19,8 +18,7 @@ module Vagrant
def run(callable_id, options=nil)
callable = callable_id
callable = Builder.new.use(callable_id) if callable_id.kind_of?(Class)
callable = registry_sequence(callable_id) if callable_id.kind_of?(Symbol)
callable = Builder.build(callable_id) if callable_id.kind_of?(Class)
raise ArgumentError, "Argument to run must be a callable object or registered action." if !callable || !callable.respond_to?(:call)
# Create the initial environment with the options given
@ -47,28 +45,10 @@ module Vagrant
# We place a process lock around every action that is called
@logger.info("Running action: #{callable_id}")
Util::Busy.busy(int_callback) { callable.call(environment) }
end
protected
def registry_sequence(id)
# Attempt to get the sequence
seq = @registry.get(id)
return nil if !seq
# Go through all the registered plugins and get all the hooks
# for this sequence.
Vagrant.plugin("1").registered.each do |plugin|
hooks = plugin.action_hook(Vagrant::Plugin::V1::Plugin::ALL_ACTIONS)
hooks += plugin.action_hook(id)
hooks.each do |hook|
hook.call(seq)
end
end
# Return the sequence
seq
# Return the environment in case there are things in there that
# the caller wants to use.
environment
end
end
end

View File

@ -1,20 +0,0 @@
module Vagrant
module Action
module VM
# Destroys the unused host only interfaces. This middleware cleans
# up any created host only networks.
class DestroyUnusedNetworkInterfaces
def initialize(app, env)
@app = app
end
def call(env)
env[:vm].driver.delete_unused_host_only_networks
# Continue along
@app.call(env)
end
end
end
end
end

View File

@ -1,22 +0,0 @@
module Vagrant
module Action
module VM
# Discards the saved state of the VM if its saved. If its
# not saved, does nothing.
class DiscardState
def initialize(app, env)
@app = app
end
def call(env)
if env[:vm].state == :saved
env[:ui].info I18n.t("vagrant.actions.vm.discard_state.discarding")
env[:vm].driver.discard_saved_state
end
@app.call(env)
end
end
end
end
end

View File

@ -1,26 +0,0 @@
module Vagrant
module Action
module VM
class ProvisionerCleanup
def initialize(app, env)
@app = app
@env = env
end
def call(env)
enabled_provisioners.each do |instance|
instance.cleanup
end
@app.call(env)
end
def enabled_provisioners
@env[:vm].config.vm.provisioners.map do |provisioner|
provisioner.provisioner.new(@env, provisioner.config)
end
end
end
end
end
end

View File

@ -1,7 +0,0 @@
module Vagrant
module Communication
autoload :Base, 'vagrant/communication/base'
autoload :SSH, 'vagrant/communication/ssh'
end
end

View File

@ -1,63 +0,0 @@
module Vagrant
module Communication
# The base class for any classes that provide an API for communicating
# with the virtual machine.
#
# There are various stages that require Vagrant to copy files or
# run commands on the target system, and communication classes provide
# the abstraction necessary to perform these tasks, via SSH or some
# other mechanism.
#
# Any subclasses of this class **must** implement all of the methods
# below.
class Base
# Checks if the target machine is ready for communication.
#
# @return [Boolean]
def ready?
end
# Download a file from the virtual machine to the local machine.
#
# @param [String] from Path of the file on the virtual machine.
# @param [String] to Path to where to save the remote file.
def download(from, to)
end
# Upload a file to the virtual machine.
#
# @param [String] from Path to a file to upload.
# @param [String] to Path to where to save this file.
def upload(from, to)
end
# Execute a command on the remote machine.
#
# @param [String] command Command to execute.
# @yield [type, data] Realtime output of the command being executed.
# @yieldparam [String] type Type of the output, `:stdout`, `:stderr`, etc.
# @yieldparam [String] data Data for the given output.
# @return [Integer] Exit code of the command.
def execute(command, opts=nil)
end
# Execute a comand with super user privileges.
#
# See #execute for parameter information.
def sudo(command, opts=nil)
end
# Executes a command and returns a boolean statement if it was successful
# or not.
#
# This is implemented by default as expecting `execute` to return 0.
def test(command, opts=nil)
# Disable error checking no matter what
opts = (opts || {}).merge(:error_check => false)
# Successful if the exit status is 0
execute(command, opts) == 0
end
end
end
end

View File

@ -14,9 +14,6 @@ module Vagrant
# handle.
def self.match?(url); false; end
# Called prior to execution so any error checks can be done
def prepare(source_url); end
# Downloads the source file to the destination file. It is up to
# implementors of this class to handle the logic.
def download!(source_url, destination_file); end

View File

@ -9,11 +9,9 @@ module Vagrant
::File.file?(::File.expand_path(uri))
end
def prepare(source_url)
raise Errors::DownloaderFileDoesntExist if !::File.file?(::File.expand_path(source_url))
end
def download!(source_url, destination_file)
raise Errors::DownloaderFileDoesntExist if !::File.file?(::File.expand_path(source_url))
@ui.info I18n.t("vagrant.downloaders.file.download")
FileUtils.cp(::File.expand_path(source_url), destination_file.path)
end

View File

@ -1,7 +0,0 @@
module Vagrant
module Driver
autoload :VirtualBox, 'vagrant/driver/virtualbox'
autoload :VirtualBox_4_0, 'vagrant/driver/virtualbox_4_0'
autoload :VirtualBox_4_1, 'vagrant/driver/virtualbox_4_1'
end
end

View File

@ -1,140 +0,0 @@
require 'forwardable'
require 'log4r'
require 'vagrant/driver/virtualbox_base'
module Vagrant
module Driver
# This class contains the logic to drive 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
# We use forwardable to do all our driver forwarding
extend Forwardable
# The UUID of the virtual machine we represent
attr_reader :uuid
# The version of virtualbox that is running.
attr_reader :version
def initialize(uuid=nil)
# Setup the base
super()
@logger = Log4r::Logger.new("vagrant::driver::virtualbox")
@uuid = uuid
# Read and assign the version of VirtualBox we know which
# specific driver to instantiate.
begin
@version = read_version || ""
rescue Subprocess::LaunchError
# This means that VirtualBox was not found, so we raise this
# error here.
raise Errors::VirtualBoxNotDetected
end
# Instantiate the proper version driver for VirtualBox
@logger.debug("Finding driver for VirtualBox version: #{@version}")
driver_map = {
"4.0" => VirtualBox_4_0,
"4.1" => VirtualBox_4_1
}
driver_klass = nil
driver_map.each do |key, klass|
if @version.start_with?(key)
driver_klass = klass
break
end
end
if !driver_klass
supported_versions = driver_map.keys.sort.join(", ")
raise Errors::VirtualBoxInvalidVersion, :supported_versions => supported_versions
end
@logger.info("Using VirtualBox driver: #{driver_klass}")
@driver = driver_klass.new(@uuid)
if @uuid
# Verify the VM exists, and if it doesn't, then don't worry
# about it (mark the UUID as nil)
raise VMNotFound if !@driver.vm_exists?(@uuid)
end
end
def_delegators :@driver, :clear_forwarded_ports,
:clear_shared_folders,
:create_dhcp_server,
:create_host_only_network,
:delete,
:delete_unused_host_only_networks,
:discard_saved_state,
:enable_adapters,
:execute_command,
:export,
:forward_ports,
:halt,
:import,
:read_forwarded_ports,
:read_bridged_interfaces,
:read_guest_additions_version,
:read_host_only_interfaces,
:read_mac_address,
:read_mac_addresses,
:read_machine_folder,
:read_network_interfaces,
:read_state,
:read_used_ports,
:read_vms,
:set_mac_address,
:set_name,
:share_folders,
:ssh_port,
:start,
:suspend,
:verify!,
:verify_image,
:vm_exists?
protected
# This returns the version of VirtualBox that is running.
#
# @return [String]
def read_version
# The version string is usually in one of the following formats:
#
# * 4.1.8r1234
# * 4.1.8r1234_OSE
# * 4.1.8_MacPortsr1234
#
# Below accounts for all of these.
# Note: We split this into multiple lines because apparently "".split("_")
# is [], so we have to check for an empty array in between.
output = execute("--version")
if output =~ /vboxdrv kernel module is not loaded/
raise Errors::VirtualBoxKernelModuleNotLoaded
elsif output =~ /Please install/
# Check for installation incomplete warnings, for example:
# "WARNING: The character device /dev/vboxdrv does not
# exist. Please install the virtualbox-ose-dkms package and
# the appropriate headers, most likely linux-headers-generic."
raise Errors::VirtualBoxInstallIncomplete
end
parts = output.split("_")
return nil if parts.empty?
parts[0].split("r")[0]
end
end
end
end

View File

@ -1,474 +0,0 @@
require 'log4r'
require 'vagrant/driver/virtualbox_base'
module Vagrant
module Driver
# Driver for VirtualBox 4.0.x
class VirtualBox_4_0 < VirtualBoxBase
def initialize(uuid)
super()
@logger = Log4r::Logger.new("vagrant::driver::virtualbox_4_0")
@uuid = uuid
end
def clear_forwarded_ports
args = []
read_forwarded_ports(@uuid).each do |nic, name, _, _|
args.concat(["--natpf#{nic}", "delete", name])
end
execute("modifyvm", @uuid, *args) if !args.empty?
end
def clear_shared_folders
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if name = line[/^SharedFolderNameMachineMapping\d+="(.+?)"$/, 1]
execute("sharedfolder", "remove", @uuid, "--name", name)
end
end
end
def create_dhcp_server(network, options)
execute("dhcpserver", "add", "--ifname", network,
"--ip", options[:dhcp_ip],
"--netmask", options[:netmask],
"--lowerip", options[:dhcp_lower],
"--upperip", options[:dhcp_upper],
"--enable")
end
def create_host_only_network(options)
# Create the interface
interface = execute("hostonlyif", "create")
name = interface[/^Interface '(.+?)' was successfully created$/, 1]
# Configure it
execute("hostonlyif", "ipconfig", name,
"--ip", options[:adapter_ip],
"--netmask", options[:netmask])
# Return the details
return {
:name => name,
:ip => options[:adapter_ip],
:netmask => options[:netmask],
:dhcp => nil
}
end
def delete
execute("unregistervm", @uuid, "--delete")
end
def delete_unused_host_only_networks
networks = []
execute("list", "hostonlyifs").split("\n").each do |line|
if network_name = line[/^Name:\s+(.+?)$/, 1]
networks << network_name
end
end
execute("list", "vms").split("\n").each do |line|
if vm_name = line[/^".+?"\s+\{(.+?)\}$/, 1]
info = execute("showvminfo", vm_name, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1]
networks.delete(network_name)
end
end
end
end
networks.each do |name|
# First try to remove any DHCP servers attached. We use `raw` because
# it is okay if this fails. It usually means that a DHCP server was
# never attached.
raw("dhcpserver", "remove", "--ifname", name)
# Delete the actual host only network interface.
execute("hostonlyif", "remove", name)
end
end
def discard_saved_state
execute("discardstate", @uuid)
end
def enable_adapters(adapters)
args = []
adapters.each do |adapter|
args.concat(["--nic#{adapter[:adapter]}", adapter[:type].to_s])
if adapter[:bridge]
args.concat(["--bridgeadapter#{adapter[:adapter]}",
adapter[:bridge]])
end
if adapter[:hostonly]
args.concat(["--hostonlyadapter#{adapter[:adapter]}",
adapter[:hostonly]])
end
if adapter[:mac_address]
args.concat(["--macaddress#{adapter[:adapter]}",
adapter[:mac_address]])
end
if adapter[:nic_type]
args.concat(["--nictype#{adapter[:adapter]}", adapter[:nic_type].to_s])
end
end
execute("modifyvm", @uuid, *args)
end
def execute_command(command)
raw(*command)
end
def export(path)
# TODO: Progress
execute("export", @uuid, "--output", path.to_s)
end
def forward_ports(ports)
args = []
ports.each do |options|
pf_builder = [options[:name],
options[:protocol] || "tcp",
"",
options[:hostport],
"",
options[:guestport]]
args.concat(["--natpf#{options[:adapter] || 1}",
pf_builder.join(",")])
end
execute("modifyvm", @uuid, *args) if !args.empty?
end
def halt
execute("controlvm", @uuid, "poweroff")
end
def import(ovf)
output = ""
total = ""
last = 0
execute("import", ovf) do |type, data|
if type == :stdout
# Keep track of the stdout so that we can get the VM name
output << data
elsif type == :stderr
# Append the data so we can see the full view
total << data
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
if lines.include?("OK.")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
if current = lines.last[/.+(\d{2})%/, 1]
current = current.to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
# Find the name of the VM name
name = output[/Suggested VM name "(.+?)"/, 1]
if !name
@logger.error("Couldn't find VM name in the output.")
return nil
end
output = execute("list", "vms")
if existing_vm = output[/^"#{Regexp.escape(name)}" \{(.+?)\}$/, 1]
return existing_vm
end
nil
end
def read_forwarded_ports(uuid=nil, active_only=false)
uuid ||= @uuid
@logger.debug("read_forward_ports: uuid=#{uuid} active_only=#{active_only}")
results = []
current_nic = nil
info = execute("showvminfo", uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
# This is how we find the nic that a FP is attached to,
# since this comes first.
if nic = line[/^nic(\d+)=".+?"$/, 1]
current_nic = nic.to_i
end
# If we care about active VMs only, then we check the state
# to verify the VM is running.
if active_only && (state = line[/^VMState="(.+?)"$/, 1] and state != "running")
return []
end
# Parse out the forwarded port information
if matcher = /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/.match(line)
result = [current_nic, matcher[1], matcher[2].to_i, matcher[3].to_i]
@logger.debug(" - #{result.inspect}")
results << result
end
end
results
end
def read_bridged_interfaces
execute("list", "bridgedifs").split("\n\n").collect do |block|
info = {}
block.split("\n").each do |line|
if name = line[/^Name:\s+(.+?)$/, 1]
info[:name] = name
elsif ip = line[/^IPAddress:\s+(.+?)$/, 1]
info[:ip] = ip
elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1]
info[:netmask] = netmask
elsif status = line[/^Status:\s+(.+?)$/, 1]
info[:status] = status
end
end
# Return the info to build up the results
info
end
end
def read_guest_additions_version
output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version",
:retryable => true)
if value = output[/^Value: (.+?)$/, 1]
# Split the version by _ since some distro versions modify it
# to look like this: 4.1.2_ubuntu, and the distro part isn't
# too important.
return value.split("_").first
end
return nil
end
def read_host_only_interfaces
dhcp = {}
execute("list", "dhcpservers", :retryable => true).split("\n\n").each do |block|
info = {}
block.split("\n").each do |line|
if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1]
info[:network] = network
elsif ip = line[/^IP:\s+(.+?)$/, 1]
info[:ip] = ip
elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1]
info[:lower] = lower
elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1]
info[:upper] = upper
end
end
# Set the DHCP info
dhcp[info[:network]] = info
end
execute("list", "hostonlyifs", :retryable => true).split("\n\n").collect do |block|
info = {}
block.split("\n").each do |line|
if name = line[/^Name:\s+(.+?)$/, 1]
info[:name] = name
elsif ip = line[/^IPAddress:\s+(.+?)$/, 1]
info[:ip] = ip
elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1]
info[:netmask] = netmask
elsif status = line[/^Status:\s+(.+?)$/, 1]
info[:status] = status
end
end
# Set the DHCP info if it exists
info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]]
info
end
end
def read_mac_address
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if mac = line[/^macaddress1="(.+?)"$/, 1]
return mac
end
end
nil
end
def read_mac_addresses
macs = {}
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if matcher = /^macaddress(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
mac = matcher[2].to_s
macs[adapter] = mac
end
end
macs
end
def read_machine_folder
execute("list", "systemproperties", :retryable => true).split("\n").each do |line|
if folder = line[/^Default machine folder:\s+(.+?)$/i, 1]
return folder
end
end
nil
end
def read_network_interfaces
nics = {}
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if matcher = /^nic(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
type = matcher[2].to_sym
nics[adapter] ||= {}
nics[adapter][:type] = type
elsif matcher = /^hostonlyadapter(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
network = matcher[2].to_s
nics[adapter] ||= {}
nics[adapter][:hostonly] = network
elsif matcher = /^bridgeadapter(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
network = matcher[2].to_s
nics[adapter] ||= {}
nics[adapter][:bridge] = network
end
end
nics
end
def read_state
output = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
if output =~ /^name="<inaccessible>"$/
return :inaccessible
elsif state = output[/^VMState="(.+?)"$/, 1]
return state.to_sym
end
nil
end
def read_used_ports
ports = []
execute("list", "vms", :retryable => true).split("\n").each do |line|
if uuid = line[/^".+?" \{(.+?)\}$/, 1]
# Ignore our own used ports
next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
end
end
ports
end
def read_vms
results = []
execute("list", "vms", :retryable => true).split("\n").each do |line|
if vm = line[/^".+?" \{(.+?)\}$/, 1]
results << vm
end
end
results
end
def set_mac_address(mac)
execute("modifyvm", @uuid, "--macaddress1", mac)
end
def set_name(name)
execute("modifyvm", @uuid, "--name", name)
end
def share_folders(folders)
folders.each do |folder|
args = ["--name",
folder[:name],
"--hostpath",
folder[:hostpath]]
args << "--transient" if folder.has_key?(:transient) && folder[:transient]
execute("sharedfolder", "add", @uuid, *args)
end
end
def ssh_port(expected_port)
@logger.debug("Searching for SSH port: #{expected_port.inspect}")
# Look for the forwarded port only by comparing the guest port
read_forwarded_ports.each do |_, _, hostport, guestport|
return hostport if guestport == expected_port
end
nil
end
def start(mode)
command = ["startvm", @uuid, "--type", mode.to_s]
r = raw(*command)
if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/
# Some systems return an exit code 1 for some reason. For that
# we depend on the output.
return true
end
# If we reached this point then it didn't work out.
raise Errors::VBoxManageError, :command => command.inspect
end
def suspend
execute("controlvm", @uuid, "savestate")
end
def verify!
# This command sometimes fails if kernel drivers aren't properly loaded
# so we just run the command and verify that it succeeded.
execute("list", "hostonlyifs")
end
def verify_image(path)
r = raw("import", path.to_s, "--dry-run")
return r.exit_code == 0
end
def vm_exists?(uuid)
raw("showvminfo", uuid).exit_code == 0
end
end
end
end

View File

@ -1,474 +0,0 @@
require 'log4r'
require 'vagrant/driver/virtualbox_base'
module Vagrant
module Driver
# Driver for VirtualBox 4.1.x
class VirtualBox_4_1 < VirtualBoxBase
def initialize(uuid)
super()
@logger = Log4r::Logger.new("vagrant::driver::virtualbox_4_1")
@uuid = uuid
end
def clear_forwarded_ports
args = []
read_forwarded_ports(@uuid).each do |nic, name, _, _|
args.concat(["--natpf#{nic}", "delete", name])
end
execute("modifyvm", @uuid, *args) if !args.empty?
end
def clear_shared_folders
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if folder = line[/^SharedFolderNameMachineMapping\d+="(.+?)"$/, 1]
execute("sharedfolder", "remove", @uuid, "--name", folder)
end
end
end
def create_dhcp_server(network, options)
execute("dhcpserver", "add", "--ifname", network,
"--ip", options[:dhcp_ip],
"--netmask", options[:netmask],
"--lowerip", options[:dhcp_lower],
"--upperip", options[:dhcp_upper],
"--enable")
end
def create_host_only_network(options)
# Create the interface
interface = execute("hostonlyif", "create")
name = interface[/^Interface '(.+?)' was successfully created$/, 1]
# Configure it
execute("hostonlyif", "ipconfig", name,
"--ip", options[:adapter_ip],
"--netmask", options[:netmask])
# Return the details
return {
:name => name,
:ip => options[:adapter_ip],
:netmask => options[:netmask],
:dhcp => nil
}
end
def delete
execute("unregistervm", @uuid, "--delete")
end
def delete_unused_host_only_networks
networks = []
execute("list", "hostonlyifs").split("\n").each do |line|
if network = line[/^Name:\s+(.+?)$/, 1]
networks << network
end
end
execute("list", "vms").split("\n").each do |line|
if vm = line[/^".+?"\s+\{(.+?)\}$/, 1]
info = execute("showvminfo", vm, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1]
networks.delete(adapter)
end
end
end
end
networks.each do |name|
# First try to remove any DHCP servers attached. We use `raw` because
# it is okay if this fails. It usually means that a DHCP server was
# never attached.
raw("dhcpserver", "remove", "--ifname", name)
# Delete the actual host only network interface.
execute("hostonlyif", "remove", name)
end
end
def discard_saved_state
execute("discardstate", @uuid)
end
def enable_adapters(adapters)
args = []
adapters.each do |adapter|
args.concat(["--nic#{adapter[:adapter]}", adapter[:type].to_s])
if adapter[:bridge]
args.concat(["--bridgeadapter#{adapter[:adapter]}",
adapter[:bridge]])
end
if adapter[:hostonly]
args.concat(["--hostonlyadapter#{adapter[:adapter]}",
adapter[:hostonly]])
end
if adapter[:mac_address]
args.concat(["--macaddress#{adapter[:adapter]}",
adapter[:mac_address]])
end
if adapter[:nic_type]
args.concat(["--nictype#{adapter[:adapter]}", adapter[:nic_type].to_s])
end
end
execute("modifyvm", @uuid, *args)
end
def execute_command(command)
raw(*command)
end
def export(path)
# TODO: Progress
execute("export", @uuid, "--output", path.to_s)
end
def forward_ports(ports)
args = []
ports.each do |options|
pf_builder = [options[:name],
options[:protocol] || "tcp",
"",
options[:hostport],
"",
options[:guestport]]
args.concat(["--natpf#{options[:adapter] || 1}",
pf_builder.join(",")])
end
execute("modifyvm", @uuid, *args) if !args.empty?
end
def halt
execute("controlvm", @uuid, "poweroff")
end
def import(ovf)
output = ""
total = ""
last = 0
execute("import", ovf) do |type, data|
if type == :stdout
# Keep track of the stdout so that we can get the VM name
output << data
elsif type == :stderr
# Append the data so we can see the full view
total << data
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
if lines.include?("OK.")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
if current = lines.last[/.+(\d{2})%/, 1]
current = current.to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
# Find the name of the VM name
name = output[/Suggested VM name "(.+?)"/, 1]
if !name
@logger.error("Couldn't find VM name in the output.")
return nil
end
output = execute("list", "vms")
if existing_vm = output[/^"#{Regexp.escape(name)}" \{(.+?)\}$/, 1]
return existing_vm
end
nil
end
def read_forwarded_ports(uuid=nil, active_only=false)
uuid ||= @uuid
@logger.debug("read_forward_ports: uuid=#{uuid} active_only=#{active_only}")
results = []
current_nic = nil
info = execute("showvminfo", uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
# This is how we find the nic that a FP is attached to,
# since this comes first.
if nic = line[/^nic(\d+)=".+?"$/, 1]
current_nic = nic.to_i
end
# If we care about active VMs only, then we check the state
# to verify the VM is running.
if active_only && (state = line[/^VMState="(.+?)"$/, 1] and state != "running")
return []
end
# Parse out the forwarded port information
if matcher = /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/.match(line)
result = [current_nic, matcher[1], matcher[2].to_i, matcher[3].to_i]
@logger.debug(" - #{result.inspect}")
results << result
end
end
results
end
def read_bridged_interfaces
execute("list", "bridgedifs").split("\n\n").collect do |block|
info = {}
block.split("\n").each do |line|
if name = line[/^Name:\s+(.+?)$/, 1]
info[:name] = name
elsif ip = line[/^IPAddress:\s+(.+?)$/, 1]
info[:ip] = ip
elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1]
info[:netmask] = netmask
elsif status = line[/^Status:\s+(.+?)$/, 1]
info[:status] = status
end
end
# Return the info to build up the results
info
end
end
def read_guest_additions_version
output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version",
:retryable => true)
if value = output[/^Value: (.+?)$/, 1]
# Split the version by _ since some distro versions modify it
# to look like this: 4.1.2_ubuntu, and the distro part isn't
# too important.
return value.split("_").first
end
return nil
end
def read_host_only_interfaces
dhcp = {}
execute("list", "dhcpservers", :retryable => true).split("\n\n").each do |block|
info = {}
block.split("\n").each do |line|
if network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1]
info[:network] = network
elsif ip = line[/^IP:\s+(.+?)$/, 1]
info[:ip] = ip
elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1]
info[:lower] = lower
elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1]
info[:upper] = upper
end
end
# Set the DHCP info
dhcp[info[:network]] = info
end
execute("list", "hostonlyifs", :retryable => true).split("\n\n").collect do |block|
info = {}
block.split("\n").each do |line|
if name = line[/^Name:\s+(.+?)$/, 1]
info[:name] = name
elsif ip = line[/^IPAddress:\s+(.+?)$/, 1]
info[:ip] = ip
elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1]
info[:netmask] = netmask
elsif status = line[/^Status:\s+(.+?)$/, 1]
info[:status] = status
end
end
# Set the DHCP info if it exists
info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]]
info
end
end
def read_mac_address
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if mac = line[/^macaddress1="(.+?)"$/, 1]
return mac
end
end
nil
end
def read_mac_addresses
macs = {}
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if matcher = /^macaddress(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
mac = matcher[2].to_s
macs[adapter] = mac
end
end
macs
end
def read_machine_folder
execute("list", "systemproperties", :retryable => true).split("\n").each do |line|
if folder = line[/^Default machine folder:\s+(.+?)$/i, 1]
return folder
end
end
nil
end
def read_network_interfaces
nics = {}
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if matcher = /^nic(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
type = matcher[2].to_sym
nics[adapter] ||= {}
nics[adapter][:type] = type
elsif matcher = /^hostonlyadapter(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
network = matcher[2].to_s
nics[adapter] ||= {}
nics[adapter][:hostonly] = network
elsif matcher = /^bridgeadapter(\d+)="(.+?)"$/.match(line)
adapter = matcher[1].to_i
network = matcher[2].to_s
nics[adapter] ||= {}
nics[adapter][:bridge] = network
end
end
nics
end
def read_state
output = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
if output =~ /^name="<inaccessible>"$/
return :inaccessible
elsif state = output[/^VMState="(.+?)"$/, 1]
return state.to_sym
end
nil
end
def read_used_ports
ports = []
execute("list", "vms", :retryable => true).split("\n").each do |line|
if uuid = line[/^".+?" \{(.+?)\}$/, 1]
# Ignore our own used ports
next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
end
end
ports
end
def read_vms
results = []
execute("list", "vms", :retryable => true).split("\n").each do |line|
if vm = line[/^".+?" \{(.+?)\}$/, 1]
results << vm
end
end
results
end
def set_mac_address(mac)
execute("modifyvm", @uuid, "--macaddress1", mac)
end
def set_name(name)
execute("modifyvm", @uuid, "--name", name)
end
def share_folders(folders)
folders.each do |folder|
args = ["--name",
folder[:name],
"--hostpath",
folder[:hostpath]]
args << "--transient" if folder.has_key?(:transient) && folder[:transient]
execute("sharedfolder", "add", @uuid, *args)
end
end
def ssh_port(expected_port)
@logger.debug("Searching for SSH port: #{expected_port.inspect}")
# Look for the forwarded port only by comparing the guest port
read_forwarded_ports.each do |_, _, hostport, guestport|
return hostport if guestport == expected_port
end
nil
end
def start(mode)
command = ["startvm", @uuid, "--type", mode.to_s]
r = raw(*command)
if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/
# Some systems return an exit code 1 for some reason. For that
# we depend on the output.
return true
end
# If we reached this point then it didn't work out.
raise Errors::VBoxManageError, :command => command.inspect
end
def suspend
execute("controlvm", @uuid, "savestate")
end
def verify!
# This command sometimes fails if kernel drivers aren't properly loaded
# so we just run the command and verify that it succeeded.
execute("list", "hostonlyifs")
end
def verify_image(path)
r = raw("import", path.to_s, "--dry-run")
return r.exit_code == 0
end
def vm_exists?(uuid)
raw("showvminfo", uuid).exit_code == 0
end
end
end
end

View File

@ -1,326 +0,0 @@
require 'log4r'
require 'vagrant/util/busy'
require 'vagrant/util/platform'
require 'vagrant/util/retryable'
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
include Vagrant::Util::Retryable
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")
# Get the path.
path = ENV["VBOX_INSTALL_PATH"]
@logger.debug("VBOX_INSTALL_PATH value: #{path}")
# There can actually be multiple paths in here, so we need to
# split by the separator ";" and see which is a good one.
path.split(";").each do |single|
# Make sure it ends with a \
single += "\\" if !single.end_with?("\\")
# If the executable exists, then set it as the main path
# and break out
vboxmanage = "#{path}VBoxManage.exe"
if File.file?(vboxmanage)
@vboxmanage_path = vboxmanage
break
end
end
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 DHCP server for a host only network.
#
# @param [String] network Name of the host-only network.
# @param [Hash] options Options for the DHCP server.
def create_dhcp_server(network, options)
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<Hash>] 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<Hash>] 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.
# @return [String] UUID of the imported VM.
def import(ovf)
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<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
# Returns a list of all UUIDs of virtual machines currently
# known by VirtualBox.
#
# @return [Array<String>]
def read_vms
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<Hash>] 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 the driver is ready to accept work.
#
# This should raise a VagrantError if things are not ready.
def verify!
end
# Verifies that an image can be imported properly.
#
# @param [String] path Path to an OVF file.
# @return [Boolean]
def verify_image(path)
end
# Checks if a VM with the given UUID exists.
#
# @return [Boolean]
def vm_exists?(uuid)
end
# Execute the given subcommand for VBoxManage and return the output.
def execute(*command, &block)
# Get the options hash if it exists
opts = {}
opts = command.pop if command.last.is_a?(Hash)
tries = 0
tries = 3 if opts[:retryable]
# Variable to store our execution result
r = nil
retryable(:on => Errors::VBoxManageError, :tries => tries, :sleep => 1) do
# 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
else
# Sometimes, VBoxManage fails but doesn't actual return a non-zero
# exit code. For this we inspect the output and determine if an error
# occurred.
if r.stderr =~ /VBoxManage: error:/
@logger.info("VBoxManage error text found, assuming error.")
raise Errors::VBoxManageError, :command => command.inspect
end
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
# Append in the options for subprocess
command << { :notify => [:stdout, :stderr] }
Util::Busy.busy(int_callback) do
Subprocess.execute(@vboxmanage_path, *command, &block)
end
end
end
end
end

View File

@ -104,6 +104,14 @@ module Vagrant
activate_plugins
end
# Return a human-friendly string for pretty printed or inspected
# instances.
#
# @return [String]
def inspect
"#<#{self.class}: #{@cwd}>"
end
#---------------------------------------------------------------
# Helpers
#---------------------------------------------------------------
@ -114,7 +122,7 @@ module Vagrant
# @return [Pathname]
def dotfile_path
return nil if !root_path
root_path.join(File.expand_path(config.global.vagrant.dotfile_name))
root_path.join(config.global.vagrant.dotfile_name)
end
# Returns the collection of boxes for the environment.
@ -205,7 +213,7 @@ module Vagrant
#
# @return [Action::Runner]
def action_runner
@action_runner ||= Action::Runner.new(action_registry) do
@action_runner ||= Action::Runner.new do
{
:action_runner => action_runner,
:box_collection => boxes,
@ -218,16 +226,6 @@ module Vagrant
end
end
# Action registry for registering new actions with this environment.
#
# @return [Registry]
def action_registry
# For now we return the global built-in actions registry. In the future
# we may want to create an isolated registry that inherits from this
# global one, but for now there isn't a use case that calls for it.
Vagrant.actions
end
# Loads on initial access and reads data from the global data store.
# The global data store is global to Vagrant everywhere (in every environment),
# so it can be used to store system-wide information. Note that "system-wide"
@ -445,11 +443,22 @@ module Vagrant
# Loads the persisted VM (if it exists) for this environment.
def load_vms!
result = {}
# This is hardcoded for now.
provider = nil
Vagrant.plugin("1").registered.each do |plugin|
provider = plugin.provider.get(:virtualbox)
break if provider
end
raise "VirtualBox provider not found." if !provider
# Load all the virtual machine instances.
result = {}
config.vms.each do |name|
result[name] = Vagrant::VM.new(name, self, config.for_vm(name))
vm_config = config.for_vm(name)
box = boxes.find(vm_config.vm.box, :virtualbox)
result[name] = Vagrant::Machine.new(name, provider, vm_config, box, self)
end
result

View File

@ -226,6 +226,10 @@ module Vagrant
error_key(:port_collision_resume)
end
class MachineGuestNotReady < VagrantError
error_key(:machine_guest_not_ready)
end
class MultiVMEnvironmentRequired < VagrantError
status_code(5)
error_key(:multi_vm_required)
@ -356,6 +360,10 @@ module Vagrant
error_key(:ssh_key_type_not_supported)
end
class SSHNotReady < VagrantError
error_key(:ssh_not_ready)
end
class SSHPortNotDetected < VagrantError
status_code(50)
error_key(:ssh_port_not_detected)
@ -376,6 +384,10 @@ module Vagrant
error_key(:ui_expects_tty)
end
class UnimplementedProviderAction < VagrantError
error_key(:unimplemented_provider_action)
end
class VagrantInterrupt < VagrantError
status_code(40)
error_key(:interrupted)

296
lib/vagrant/machine.rb Normal file
View File

@ -0,0 +1,296 @@
require "log4r"
module Vagrant
# This represents a machine that Vagrant manages. This provides a singular
# API for querying the state and making state changes to the machine, which
# is backed by any sort of provider (VirtualBox, VMWare, etc.).
class Machine
# The box that is backing this machine.
#
# @return [Box]
attr_reader :box
# Configuration for the machine.
#
# @return [Object]
attr_reader :config
# The environment that this machine is a part of.
#
# @return [Environment]
attr_reader :env
# ID of the machine. This ID comes from the provider and is not
# guaranteed to be of any particular format except that it is
# a string.
#
# @return [String]
attr_reader :id
# Name of the machine. This is assigned by the Vagrantfile.
#
# @return [String]
attr_reader :name
# The provider backing this machine.
#
# @return [Object]
attr_reader :provider
# Initialize a new machine.
#
# @param [String] name Name of the virtual machine.
# @param [Class] provider The provider backing this machine. This is
# currently expected to be a V1 `provider` plugin.
# @param [Object] config The configuration for this machine.
# @param [Box] box The box that is backing this virtual machine.
# @param [Environment] env The environment that this machine is a
# part of.
def initialize(name, provider_cls, config, box, env, base=false)
@logger = Log4r::Logger.new("vagrant::machine")
@logger.info("Initializing machine: #{name}")
@logger.info(" - Provider: #{provider_cls}")
@logger.info(" - Box: #{box}")
@name = name
@box = box
@config = config
@env = env
# Read the ID, which is usually in local storage
@id = nil
# XXX: This is temporary. This will be removed very soon.
if base
@id = name
else
@id = @env.local_data[:active][@name.to_s] if @env.local_data[:active]
end
# Initializes the provider last so that it has access to all the
# state we setup on this machine.
@provider = provider_cls.new(self)
end
# This calls an action on the provider. The provider may or may not
# actually implement the action.
#
# @param [Symbol] name Name of the action to run.
# @param [Hash] extra_env This data will be passed into the action runner
# as extra data set on the environment hash for the middleware
# runner.
def action(name, extra_env=nil)
@logger.debug("Calling action: #{name} on provider #{@provider}")
# Get the callable from the provider.
callable = @provider.action(name)
# If this action doesn't exist on the provider, then an exception
# must be raised.
if callable.nil?
raise Errors::UnimplementedProviderAction,
:action => name,
:provider => @provider.to_s
end
# Run the action with the action runner on the environment
env = { :machine => self }.merge(extra_env || {})
@env.action_runner.run(callable, env)
end
# Returns a communication object for executing commands on the remote
# machine. Note that the _exact_ semantics of this are up to the
# communication provider itself. Despite this, the semantics are expected
# to be consistent across operating systems. For example, all linux-based
# systems should have similar communication (usually a shell). All
# Windows systems should have similar communication as well. Therefore,
# prior to communicating with the machine, users of this method are
# expected to check the guest OS to determine their behavior.
#
# This method will _always_ return some valid communication object.
# The `ready?` API can be used on the object to check if communication
# is actually ready.
#
# @return [Object]
def communicate
if !@communicator
# For now, we always return SSH. In the future, we'll abstract
# this and allow plugins to define new methods of communication.
Vagrant.plugin("1").registered.each do |plugin|
klass = plugin.communicator[:ssh]
if klass
# This plugin has the SSH communicator, use it.
@communicator = klass.new(self)
break
end
end
end
@communicator
end
# Returns a guest implementation for this machine. The guest implementation
# knows how to do guest-OS specific tasks, such as configuring networks,
# mounting folders, etc.
#
# @return [Object]
def guest
raise Errors::MachineGuestNotReady if !communicate.ready?
# Load the initial guest.
last_guest = config.vm.guest
guest = load_guest(last_guest)
# Loop and distro dispatch while there are distros.
while true
distro = guest.distro_dispatch
break if !distro
# This is just some really basic loop detection and avoiding for
# guest classes. This is just here to help implementers a bit
# avoid a situation that is fairly easy, since if you subclass
# a parent which does `distro_dispatch`, you'll end up dispatching
# forever.
if distro == last_guest
@logger.warn("Distro dispatch loop in '#{distro}'. Exiting loop.")
break
end
last_guest = distro
guest = load_guest(distro)
end
# Return the result
guest
end
# This sets the unique ID associated with this machine. This will
# persist this ID so that in the future Vagrant will be able to find
# this machine again. The unique ID must be absolutely unique to the
# virtual machine, and can be used by providers for finding the
# actual machine associated with this instance.
#
# **WARNING:** Only providers should ever use this method.
#
# @param [String] value The ID.
def id=(value)
@env.local_data[:active] ||= {}
if value
# Set the value
@env.local_data[:active][@name] = value
else
# Delete it from the active hash
@env.local_data[:active].delete(@name)
end
# Commit the local data so that the next time Vagrant is initialized,
# it realizes the VM exists (or doesn't).
@env.local_data.commit
# 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
# a pretty print (`p`) results in a readable value.
#
# @return [String]
def inspect
"#<#{self.class}: #{@name} (#{@provider.class})>"
end
# This returns the SSH info for accessing this machine. This SSH info
# is queried from the underlying provider. This method returns `nil` if
# the machine is not ready for SSH communication.
#
# The structure of the resulting hash is guaranteed to contain the
# following structure, although it may return other keys as well
# not documented here:
#
# {
# :host => "1.2.3.4",
# :port => "22",
# :username => "mitchellh",
# :private_key_path => "/path/to/my/key"
# }
#
# Note that Vagrant makes no guarantee that this info works or is
# correct. This is simply the data that the provider gives us or that
# is configured via a Vagrantfile. It is still possible after this
# point when attempting to connect via SSH to get authentication
# errors.
#
# @return [Hash] SSH information.
def ssh_info
# First, ask the provider for their information. If the provider
# returns nil, then the machine is simply not ready for SSH, and
# we return nil as well.
info = @provider.ssh_info
return nil if info.nil?
# Delete out the nil entries.
info.dup.each do |key, value|
info.delete(key) if value.nil?
end
# Next, we default some fields if they weren't given to us by
# the provider.
info[:host] = @config.ssh.host if @config.ssh.host
info[:port] = @config.ssh.port if @config.ssh.port
info[:username] = @config.ssh.username if @config.ssh.username
# We also set some fields that are purely controlled by Varant
info[:forward_agent] = @config.ssh.forward_agent
info[:forward_x11] = @config.ssh.forward_x11
# Set the private key path. If a specific private key is given in
# the Vagrantfile we set that. Otherwise, we use the default (insecure)
# private key, but only if the provider didn't give us one.
info[:private_key_path] = @config.ssh.private_key_path if @config.ssh.private_key_path
info[:private_key_path] = @env.default_private_key_path if !info[:private_key_path]
# Return the final compiled SSH info data
info
end
# Returns the state of this machine. The state is queried from the
# backing provider, so it can be any arbitrary symbol.
#
# @return [Symbol]
def state
@provider.state
end
protected
# Given a guest name (such as `:windows`), this will load the associated
# guest implementation and return an instance.
#
# @param [Symbol] guest The name of the guest implementation.
# @return [Object]
def load_guest(guest)
@logger.info("Loading guest: #{guest}")
klass = nil
Vagrant.plugin("1").registered.each do |plugin|
if plugin.guest.has_key?(guest)
klass = plugin.guest[guest]
break
end
end
if klass.nil?
raise Errors::VMGuestError,
:_key => :unknown_type,
:guest => guest.to_s
end
return klass.new(self)
end
end
end

View File

@ -6,10 +6,12 @@ module Vagrant
module Plugin
module V1
autoload :Command, "vagrant/plugin/v1/command"
autoload :Communicator, "vagrant/plugin/v1/communicator"
autoload :Config, "vagrant/plugin/v1/config"
autoload :Guest, "vagrant/plugin/v1/guest"
autoload :Host, "vagrant/plugin/v1/host"
autoload :Plugin, "vagrant/plugin/v1/plugin"
autoload :Provider, "vagrant/plugin/v1/provider"
autoload :Provisioner, "vagrant/plugin/v1/provisioner"
end
end

View File

@ -0,0 +1,98 @@
module Vagrant
module Plugin
module V1
# Base class for a communicator in Vagrant. A communicator is
# responsible for communicating with a machine in some way. There
# are various stages of Vagrant that require things such as uploading
# files to the machine, executing shell commands, etc. Implementors
# of this class are expected to provide this functionality in some
# way.
#
# Note that a communicator must provide **all** of the methods
# in this base class. There is currently no way for one communicator
# to provide say a more efficient way of uploading a file, but not
# provide shell execution. This sort of thing will come in a future
# version.
class Communicator
# This returns true/false depending on if the given machine
# can be communicated with using this communicator. If this returns
# `true`, then this class will be used as the primary communication
# method for the machine.
#
# @return [Boolean]
def self.match?(machine)
false
end
# Initializes the communicator with the machine that we will be
# communicating with. This base method does nothing (it doesn't
# even store the machine in an instance variable for you), so you're
# expected to override this and do something with the machine if
# you care about it.
#
# @param [Machine] machine The machine this instance is expected to
# communicate with.
def initialize(machine)
end
# Checks if the target machine is ready for communication. If this
# returns true, then all the other methods for communicating with
# the machine are expected to be functional.
#
# @return [Boolean]
def ready?
false
end
# Download a file from the remote machine to the local machine.
#
# @param [String] from Path of the file on the remote machine.
# @param [String] to Path of where to save the file locally.
def download(from, to)
end
# Upload a file to the remote machine.
#
# @param [String] from Path of the file locally to upload.
# @param [String] to Path of where to save the file on the remote
# machine.
def upload(from, to)
end
# Execute a command on the remote machine. The exact semantics
# of this method are up to the implementor, but in general the
# users of this class will expect this to be a shell.
#
# This method gives you no way to write data back to the remote
# machine, so only execute commands that don't expect input.
#
# @param [String] command Command to execute.
# @yield [type, data] Realtime output of the command being executed.
# @yieldparam [String] type Type of the output. This can be
# `:stdout`, `:stderr`, etc. The exact types are up to the
# implementor.
# @yieldparam [String] data Data for the given output.
# @return [Integer] Exit code of the command.
def execute(command, opts=nil)
end
# Executes a command on the remote machine with administrative
# privileges. See {#execute} for documentation, as the API is the
# same.
#
# @see #execute
def sudo(command, opts=nil)
end
# Executes a command and returns true if the command succeeded,
# and false otherwise. By default, this executes as a normal user,
# and it is up to the communicator implementation if they expose an
# option for running tests as an administrator.
#
# @see #execute
def test(command, opts=nil)
end
end
end
end
end

View File

@ -27,9 +27,8 @@ module Vagrant
# Vagrant can successfully SSH into the machine) to give the system a chance
# to determine the distro and return a distro-specific system.
#
# **Warning:** If a return value which subclasses from {Base} is
# returned, Vagrant will use it as the new system instance for the
# class.
# If this method returns nil, then this instance is assumed to be
# the most specific guest implementation.
def distro_dispatch
end

View File

@ -103,6 +103,24 @@ module Vagrant
data[:command]
end
# Defines additional communicators to be available. Communicators
# should be returned by a block passed to this method. This is done
# to ensure that the class is lazy loaded, so if your class inherits
# from or uses any Vagrant internals specific to Vagrant 1.0, then
# the plugin can still be defined without breaking anything in future
# versions of Vagrant.
#
# @param [String] name Communicator name.
def self.communicator(name=UNSET_VALUE, &block)
data[:communicator] ||= Registry.new
# Register a new communicator class only if a name was given.
data[:communicator].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:communicator]
end
# Defines additional configuration keys to be available in the
# Vagrantfile. The configuration class should be returned by a
# block passed to this method. This is done to ensure that the class
@ -181,6 +199,19 @@ module Vagrant
data[:hosts]
end
# Registers additional providers to be available.
#
# @param [Symbol] name Name of the provider.
def self.provider(name=UNSET_VALUE, &block)
data[:providers] ||= Registry.new
# Register a new provider class only if a name was given
data[:providers].register(name.to_sym, &block) if name != UNSET_VALUE
# Return the registry
data[:providers]
end
# Registers additional provisioners to be available.
#
# @param [String] name Name of the provisioner.

View File

@ -0,0 +1,68 @@
module Vagrant
module Plugin
module V1
# This is the base class for a provider for the V1 API. A provider
# is responsible for creating compute resources to match the needs
# of a Vagrant-configured system.
class Provider
# Initialize the provider to represent the given machine.
#
# @param [Vagrant::Machine] machine The machine that this provider
# is responsible for.
def initialize(machine)
end
# This should return an action callable for the given name.
#
# @param [Symbol] name Name of the action.
# @return [Object] A callable action sequence object, whether it
# is a proc, object, etc.
def action(name)
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.
#
# The general structure of this returned hash should be the
# following:
#
# {
# :host => "1.2.3.4",
# :port => "22",
# :username => "mitchellh",
# :private_key_path => "/path/to/my/key"
# }
#
# **Note:** Vagrant only supports private key based authentication,
# mainly for the reason that there is no easy way to exec into an
# `ssh` prompt with a password, whereas we can pass a private key
# via commandline.
#
# @return [Hash] SSH information. For the structure of this hash
# read the accompanying documentation for this method.
def ssh_info
nil
end
# This should return the state of the machine within this provider.
# The state can be any symbol.
#
# @return [Symbol]
def state
nil
end
end
end
end
end

View File

@ -1,128 +0,0 @@
require 'log4r'
require 'vagrant/util/file_mode'
require 'vagrant/util/platform'
require 'vagrant/util/safe_exec'
module Vagrant
# Manages SSH connection information as well as allows opening an
# SSH connection.
class SSH
include Util::SafeExec
def initialize(vm)
@vm = vm
@logger = Log4r::Logger.new("vagrant::ssh")
end
# Returns a hash of information necessary for accessing this
# virtual machine via SSH.
#
# @return [Hash]
def info
results = {
:host => @vm.config.ssh.host,
:port => @vm.config.ssh.port || @vm.driver.ssh_port(@vm.config.ssh.guest_port),
:username => @vm.config.ssh.username,
:forward_agent => @vm.config.ssh.forward_agent,
:forward_x11 => @vm.config.ssh.forward_x11
}
# This can happen if no port is set and for some reason Vagrant
# can't detect an SSH port.
raise Errors::SSHPortNotDetected if !results[:port]
# Determine the private key path, which is either set by the
# configuration or uses just the built-in insecure key.
pk_path = @vm.config.ssh.private_key_path || @vm.env.default_private_key_path
results[:private_key_path] = File.expand_path(pk_path, @vm.env.root_path)
# We need to check and fix the private key permissions
# to make sure that SSH gets a key with 0600 perms.
check_key_permissions(results[:private_key_path])
# Return the results
return results
end
# Connects to the environment's virtual machine, replacing the ruby
# process with an SSH process.
#
# @param [Hash] opts Options hash
# @options opts [Boolean] :plain_mode If True, doesn't authenticate with
# the machine, only connects, allowing the user to connect.
def exec(opts={})
# Get the SSH information and cache it here
ssh_info = info
if Util::Platform.windows?
raise Errors::SSHUnavailableWindows, :host => ssh_info[:host],
:port => ssh_info[:port],
:username => ssh_info[:username],
:key_path => ssh_info[:private_key_path]
end
raise Errors::SSHUnavailable if !Kernel.system("which ssh > /dev/null 2>&1")
# If plain mode is enabled then we don't do any authentication (we don't
# set a user or an identity file)
plain_mode = opts[:plain_mode]
options = {}
options[:host] = ssh_info[:host]
options[:port] = ssh_info[:port]
options[:username] = ssh_info[:username]
options[:private_key_path] = ssh_info[:private_key_path]
# Command line options
command_options = ["-p", options[:port].to_s, "-o", "UserKnownHostsFile=/dev/null",
"-o", "StrictHostKeyChecking=no", "-o", "LogLevel=FATAL"]
# Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the IdentitiesOnly option
# (Also don't use it in plain mode, it'll skip user agents.)
command_options += ["-o", "IdentitiesOnly=yes"] if !(Util::Platform.solaris? || plain_mode)
command_options += ["-i", options[:private_key_path]] if !plain_mode
command_options += ["-o", "ForwardAgent=yes"] if ssh_info[:forward_agent]
# If there are extra options, then we append those
command_options.concat(opts[:extra_args]) if opts[:extra_args]
if ssh_info[:forward_x11]
# Both are required so that no warnings are shown regarding X11
command_options += ["-o", "ForwardX11=yes"]
command_options += ["-o", "ForwardX11Trusted=yes"]
end
host_string = options[:host]
host_string = "#{options[:username]}@#{host_string}" if !plain_mode
command_options << host_string
@logger.info("Invoking SSH: #{command_options.inspect}")
safe_exec("ssh", *command_options)
end
# Checks the file permissions for a private key, resetting them
# if needed.
def check_key_permissions(key_path)
# Windows systems don't have this issue
return if Util::Platform.windows?
@logger.debug("Checking key permissions: #{key_path}")
stat = File.stat(key_path)
if stat.owned? && Util::FileMode.from_octal(stat.mode) != "600"
@logger.info("Attempting to correct key permissions to 0600")
File.chmod(0600, key_path)
stat = File.stat(key_path)
if Util::FileMode.from_octal(stat.mode) != "600"
raise Errors::SSHKeyBadPermissions, :key_path => key_path
end
end
rescue Errno::EPERM
# This shouldn't happen since we verified we own the file, but
# it is possible in theory, so we raise an error.
raise Errors::SSHKeyBadPermissions, :key_path => key_path
end
end
end

View File

@ -6,8 +6,8 @@ module Vagrant
# This issue causes `exec` to fail if there is more than one system
# thread. In that case, `safe_exec` automatically falls back to
# forking.
module SafeExec
def safe_exec(command, *args)
class SafeExec
def self.exec(command, *args)
# Create a list of things to rescue from. Since this is OS
# specific, we need to do some defined? checks here to make
# sure they exist.

119
lib/vagrant/util/ssh.rb Normal file
View File

@ -0,0 +1,119 @@
require "log4r"
require "vagrant/util/file_mode"
require "vagrant/util/platform"
require "vagrant/util/safe_exec"
module Vagrant
module Util
# This is a class that has helpers on it for dealing with SSH. These
# helpers don't depend on any part of Vagrant except what is given
# via the parameters.
class SSH
LOGGER = Log4r::Logger.new("vagrant::util::ssh")
# Checks that the permissions for a private key are valid, and fixes
# them if possible. SSH requires that permissions on the private key
# are 0600 on POSIX based systems. This will make a best effort to
# fix these permissions if they are not properly set.
#
# @param [Pathname] key_path The path to the private key.
def self.check_key_permissions(key_path)
# Don't do anything if we're on Windows, since Windows doesn't worry
# about key permissions.
return if Platform.windows?
LOGGER.debug("Checking key permissions: #{key_path}")
stat = key_path.stat
if stat.owned? && FileMode.from_octal(stat.mode) != "600"
LOGGER.info("Attempting to correct key permissions to 0600")
key_path.chmod(0600)
# Re-stat the file to get the new mode, and verify it worked
stat = key_path.stat
if FileMode.from_octal(stat.mode) != "600"
raise Errors::SSHKeyBadPermissions, :key_path => key_path
end
end
rescue Errno::EPERM
# This shouldn't happen since we verify we own the file, but
# it is possible in theory, so we raise an error.
raise Errors::SSHKeyBadPermissions, :key_path => key_path
end
# Halts the running of this process and replaces it with a full-fledged
# SSH shell into a remote machine.
#
# Note: This method NEVER returns. The process ends after this.
#
# @param [Hash] ssh_info This is the SSH information. For the keys
# required please see the documentation of {Machine#ssh_info}.
# @param [Hash] opts These are additional options that are supported
# by exec.
def self.exec(ssh_info, opts={})
# If we're running Windows, raise an exception since we currently
# still don't support exec-ing into SSH. In the future this should
# certainly be possible if we can detect we're in an environment that
# supports it.
if Platform.windows?
raise Errors::SSHUnavailableWindows,
:host => ssh_info[:host],
:port => ssh_info[:port],
:username => ssh_info[:username],
:key_path => ssh_info[:private_key_path]
end
# Verify that we have SSH available on the system.
raise Errors::SSHUnavailable if !Kernel.system("which ssh > /dev/null 2>&1")
# If plain mode is enabled then we don't do any authentication (we don't
# set a user or an identity file)
plain_mode = opts[:plain_mode]
options = {}
options[:host] = ssh_info[:host]
options[:port] = ssh_info[:port]
options[:username] = ssh_info[:username]
options[:private_key_path] = ssh_info[:private_key_path]
# Command line options
command_options = [
"-p", options[:port].to_s,
"-o", "LogLevel=FATAL",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null"]
# Configurables
command_options += ["-o", "ForwardAgent=yes"] if ssh_info[:forward_agent]
command_options.concat(opts[:extra_args]) if opts[:extra_args]
# Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the
# IdentitiesOnly option. Also, we don't enable it in plain mode so
# that SSH properly searches our identities and tries to do it itself.
if !Platform.solaris? && !plain_mode
command_options += ["-o", "IdentitiesOnly=yes"]
end
# If we're not in plain mode, attach the private key path.
command_options += ["-i", options[:private_key_path].to_s] if !plain_mode
if ssh_info[:forward_x11]
# Both are required so that no warnings are shown regarding X11
command_options += [
"-o", "ForwardX11=yes",
"-o", "ForwardX11Trusted=yes"]
end
# Build up the host string for connecting
host_string = options[:host]
host_string = "#{options[:username]}@#{host_string}" if !plain_mode
command_options << host_string
# Invoke SSH with all our options
LOGGER.info("Invoking SSH: #{command_options.inspect}")
SafeExec.exec("ssh", *command_options)
end
end
end
end

View File

@ -1,205 +0,0 @@
require 'log4r'
module Vagrant
class VM
include Vagrant::Util
attr_reader :uuid
attr_reader :env
attr_reader :name
attr_reader :vm
attr_reader :box
attr_reader :config
attr_reader :driver
def initialize(name, env, config, opts=nil)
@logger = Log4r::Logger.new("vagrant::vm")
@name = name
@vm = nil
@env = env
@config = config
@box = nil
@box = env.boxes.find(config.vm.box, :virtualbox) if config.vm.box
opts ||= {}
if opts[:base]
# The name is the ID we use.
@uuid = name
else
# Load the UUID if its saved.
active = env.local_data[:active] || {}
@uuid = active[@name.to_s]
end
# Reload ourselves to get the state
reload!
# Load the associated guest.
load_guest!
@loaded_guest_distro = false
end
# Loads the guest associated with the VM. The guest class is
# responsible for OS-specific functionality. More information
# can be found by reading the documentation on {Vagrant::Guest::Base}.
#
# **This method should never be called manually.**
def load_guest!(guest=nil)
guest ||= config.vm.guest
@logger.info("Loading guest: #{guest}")
if guest.is_a?(Class)
raise Errors::VMGuestError, :_key => :invalid_class, :guest => guest.to_s if !(guest <= Vagrant.plugin("1", :guest))
@guest = guest.new(self)
elsif guest.is_a?(Symbol)
# Look for the guest as a registered plugin
guest_klass = nil
Vagrant.plugin("1").registered.each do |plugin|
if plugin.guest.has_key?(guest)
guest_klass = plugin.guest[guest]
break
end
end
raise Errors::VMGuestError, :_key => :unknown_type, :guest => guest.to_s if !guest_klass
@guest = guest_klass.new(self)
else
raise Errors::VMGuestError, :unspecified
end
end
# Returns a channel object to communicate with the virtual
# machine.
def channel
@channel ||= Communication::SSH.new(self)
end
# Returns the guest for this VM, loading the distro of the system if
# we can.
def guest
if !@loaded_guest_distro && state == :running
# Load the guest distro for the first time
result = @guest.distro_dispatch
load_guest!(result)
@loaded_guest_distro = true
@logger.info("Guest class: #{@guest.class}")
end
@guest
end
# Access the {Vagrant::SSH} object associated with this VM, which
# is used to get SSH credentials with the virtual machine.
def ssh
@ssh ||= SSH.new(self)
end
# Returns the state of the VM as a symbol.
#
# @return [Symbol]
def state
return :not_created if !@uuid
state = @driver.read_state
return :not_created if !state
return state
end
# Returns a boolean true if the VM has been created, otherwise
# returns false.
#
# @return [Boolean]
def created?
state != :not_created
end
# Sets the currently active VM for this VM. If the VM is a valid,
# created virtual machine, then it will also update the local data
# to persist the VM. Otherwise, it will remove itself from the
# local data (if it exists).
def uuid=(value)
env.local_data[:active] ||= {}
if value
env.local_data[:active][name.to_s] = value
else
env.local_data[:active].delete(name.to_s)
end
# Commit the local data so that the next time vagrant is initialized,
# it realizes the VM exists
env.local_data.commit
# Store the uuid and reload the instance
@uuid = value
reload!
end
def reload!
begin
@driver = Driver::VirtualBox.new(@uuid)
rescue Driver::VirtualBox::VMNotFound
# Clear the UUID since this VM doesn't exist.
@uuid = nil
# Reset the driver. This shouldn't raise a VMNotFound since we won't
# feed it a UUID.
@driver = Driver::VirtualBox.new
end
end
def package(options=nil)
run_action(:package, { "validate" => false }.merge(options || {}))
end
def up(options=nil)
run_action(:up, options)
end
def start(options=nil)
return if state == :running
return resume if state == :saved
run_action(:start, options)
end
def halt(options=nil)
run_action(:halt, options)
end
def reload(options=nil)
run_action(:reload, options)
end
def provision
run_action(:provision)
end
def destroy
run_action(:destroy)
end
def suspend
run_action(:suspend)
end
def resume
run_action(:resume)
end
def ui
return @_ui if defined?(@_ui)
@_ui = @env.ui.dup
@_ui.resource = @name
@_ui
end
def run_action(name, options=nil)
options = {
:vm => self,
:ui => ui
}.merge(options || {})
env.action_runner.run(name, options)
end
end
end

View File

@ -7,11 +7,11 @@ module VagrantPlugins
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant box add <name> <url>"
opts.separator ""
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box add <name> <url>"
o.separator ""
opts.on("-f", "--force", "Overwrite an existing box if it exists.") do |f|
o.on("-f", "--force", "Overwrite an existing box if it exists.") do |f|
options[:force] = f
end
end
@ -28,8 +28,7 @@ module VagrantPlugins
existing.destroy! if existing
end
# Invoke the "box_add" middleware sequence.
@env.action_runner.run(:box_add, {
@env.action_runner.run(Vagrant::Action.action_box_add, {
:box_name => argv[0],
:box_provider => :virtualbox,
:box_url => argv[1]

View File

@ -4,11 +4,11 @@ module VagrantPlugins
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant destroy [vm-name]"
opts.separator ""
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant destroy [vm-name]"
o.separator ""
opts.on("-f", "--force", "Destroy without confirmation.") do |f|
o.on("-f", "--force", "Destroy without confirmation.") do |f|
options[:force] = f
end
end
@ -19,50 +19,12 @@ module VagrantPlugins
@logger.debug("'Destroy' each target VM...")
with_target_vms(argv, :reverse => true) do |vm|
if vm.created?
# Boolean whether we should actually go through with the destroy
# or not. This is true only if the "--force" flag is set or if the
# user confirms it.
do_destroy = false
if options[:force]
do_destroy = true
else
choice = nil
begin
choice = @env.ui.ask(I18n.t("vagrant.commands.destroy.confirmation",
:name => vm.name))
rescue Interrupt
# Ctrl-C was pressed (or SIGINT). We just exit immediately
# with a non-zero exit status.
return 1
rescue Vagrant::Errors::UIExpectsTTY
# We raise a more specific error but one which basically
# means the same thing.
raise Vagrant::Errors::DestroyRequiresForce
end
do_destroy = choice.upcase == "Y"
end
if do_destroy
@logger.info("Destroying: #{vm.name}")
vm.destroy
else
@logger.info("Not destroying #{vm.name} since confirmation was declined.")
@env.ui.success(I18n.t("vagrant.commands.destroy.will_not_destroy",
:name => vm.name), :prefix => false)
end
else
@logger.info("Not destroying #{vm.name}, since it isn't created.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_created")
end
vm.action(:destroy)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -22,13 +22,8 @@ module VagrantPlugins
@logger.debug("Halt command: #{argv.inspect} #{options.inspect}")
with_target_vms(argv) do |vm|
if vm.created?
@logger.info("Halting #{vm.name}")
vm.halt(:force => options[:force])
else
@logger.info("Not halting #{vm.name}, since not created.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_created")
end
# XXX: "force"
vm.action(:halt)
end
# Success, exit status 0

View File

@ -6,25 +6,25 @@ module VagrantPlugins
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant package [vm-name] [--base name] [--output name.box]"
opts.separator " [--include one,two,three] [--vagrantfile file]"
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant package [vm-name] [--base name] [--output name.box]"
o.separator " [--include one,two,three] [--vagrantfile file]"
opts.separator ""
o.separator ""
opts.on("--base NAME", "Name of a VM in virtualbox to package as a base box") do |b|
o.on("--base NAME", "Name of a VM in virtualbox to package as a base box") do |b|
options[:base] = b
end
opts.on("--output NAME", "Name of the file to output") do |o|
options[:output] = o
o.on("--output NAME", "Name of the file to output") do |output|
options[:output] = output
end
opts.on("--include x,y,z", Array, "Additional files to package with the box.") do |i|
o.on("--include x,y,z", Array, "Additional files to package with the box.") do |i|
options[:include] = i
end
opts.on("--vagrantfile file", "Vagrantfile to package with the box.") do |v|
o.on("--vagrantfile file", "Vagrantfile to package with the box.") do |v|
options[:vagrantfile] = v
end
end
@ -42,20 +42,28 @@ module VagrantPlugins
# Success, exit status 0
0
end
end
protected
def package_base(options)
vm = Vagrant::VM.new(options[:base], @env, @env.config.global, :base => true)
raise Vagrant::Errors::BaseVMNotFound, :name => options[:base] if !vm.created?
# XXX: This whole thing is hardcoded and very temporary. The whole
# `vagrant package --base` process is deprecated for something much
# better in the future. We just hardcode this to keep VirtualBox working
# for now.
provider = nil
Vagrant.plugin("1").registered.each do |plugin|
provider = plugin.provider.get(:virtualbox)
break if provider
end
vm = Vagrant::Machine.new(options[:base], provider, @env.config.global, nil, @env, true)
@logger.debug("Packaging base VM: #{vm.name}")
package_vm(vm, options)
end
def package_target(name, options)
with_target_vms(name, :single_target => true) do |vm|
raise Vagrant::Errors::VMNotCreatedError if !vm.created?
@logger.debug("Packaging VM: #{vm.name}")
package_vm(vm, options)
end
@ -68,7 +76,7 @@ module VagrantPlugins
acc
end
vm.package(opts)
vm.action(:package, opts)
end
end
end

View File

@ -4,10 +4,8 @@ module VagrantPlugins
module CommandProvision
class Command < Vagrant.plugin("1", :command)
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant provision [vm-name]"
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant provision [vm-name]"
end
# Parse the options
@ -16,25 +14,13 @@ module VagrantPlugins
# Go over each VM and provision!
@logger.debug("'provision' each target VM...")
with_target_vms(argv) do |vm|
if vm.created?
if vm.state == :running
@logger.info("Provisioning: #{vm.name}")
vm.provision
else
@logger.info("#{vm.name} not running. Not provisioning.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_running")
end
else
@logger.info("#{vm.name} not created. Not provisioning.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_created")
end
with_target_vms(argv) do |machine|
machine.action(:provision)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -14,10 +14,10 @@ module VagrantPlugins
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant reload [vm-name]"
opts.separator ""
build_start_options(opts, options)
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant reload [vm-name]"
o.separator ""
build_start_options(o, options)
end
# Parse the options
@ -25,19 +25,13 @@ module VagrantPlugins
return if !argv
@logger.debug("'reload' each target VM...")
with_target_vms(argv) do |vm|
if vm.created?
@logger.info("Reloading: #{vm.name}")
vm.reload(options)
else
@logger.info("Not created: #{vm.name}. Not reloading.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_created")
end
with_target_vms(argv) do |machine|
machine.action(:reload)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -4,10 +4,8 @@ module VagrantPlugins
module CommandResume
class Command < Vagrant.plugin("1", :command)
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant resume [vm-name]"
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant resume [vm-name]"
end
# Parse the options
@ -15,19 +13,13 @@ module VagrantPlugins
return if !argv
@logger.debug("'resume' each target VM...")
with_target_vms(argv) do |vm|
if vm.created?
@logger.info("Resume: #{vm.name}")
vm.resume
else
@logger.info("Not created: #{vm.name}. Not resuming.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_created")
end
with_target_vms(argv) do |machine|
machine.action(:resume)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -37,53 +37,29 @@ module VagrantPlugins
# Execute the actual SSH
with_target_vms(argv, :single_target => true) do |vm|
# Basic checks that are required for proper SSH
raise Vagrant::Errors::VMNotCreatedError if !vm.created?
raise Vagrant::Errors::VMInaccessible if !vm.state == :inaccessible
raise Vagrant::Errors::VMNotRunningError if vm.state != :running
if options[:command]
ssh_execute(vm, options[:command])
@logger.debug("Executing single command on remote machine: #{options[:command]}")
env = vm.action(:ssh_run, :ssh_run_command => options[:command])
# Exit with the exit status of the command or a 0 if we didn't
# get one.
exit_status = env[:ssh_run_exit_status] || 0
return exit_status
else
opts = {
:plain_mode => options[:plain_mode],
:extra_args => options[:ssh_args]
}
ssh_connect(vm, opts)
@logger.debug("Invoking `ssh` action on machine")
vm.action(:ssh, :ssh_opts => opts)
# We should never reach this point, since the point of `ssh`
# is to exec into the proper SSH shell, but we'll just return
# an exit status of 0 anyways.
return 0
end
end
# Success, exit status 0
0
end
protected
def ssh_execute(vm, command=nil)
exit_status = 0
@logger.debug("Executing command: #{command}")
exit_status = vm.channel.execute(command, :error_check => false) do |type, data|
# Determine the proper channel to send the output onto depending
# on the type of data we are receiving.
channel = type == :stdout ? :out : :error
# Print the SSH output as it comes in, but don't prefix it and don't
# force a new line so that the output is properly preserved
vm.ui.info(data.to_s,
:prefix => false,
:new_line => false,
:channel => channel)
end
# Exit with the exit status we got from executing the command
exit exit_status
end
def ssh_connect(vm, opts)
@logger.debug("`exec` into ssh prompt")
vm.ssh.exec(opts)
end
end
end

View File

@ -10,12 +10,11 @@ module VagrantPlugins
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant ssh-config [vm-name] [--host name]"
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant ssh-config [vm-name] [--host name]"
o.separator ""
opts.separator ""
opts.on("--host NAME", "Name the host for the config..") do |h|
o.on("--host COMMAND", "Name the host for the config..") do |h|
options[:host] = h
end
end
@ -23,13 +22,12 @@ module VagrantPlugins
argv = parse_options(opts)
return if !argv
with_target_vms(argv, :single_target => true) do |vm|
raise Vagrant::Errors::VMNotCreatedError if !vm.created?
raise Vagrant::Errors::VMInaccessible if !vm.state == :inaccessible
with_target_vms(argv, :single_target => true) do |machine|
ssh_info = machine.ssh_info
raise Vagrant::Errors::SSHNotReady if ssh_info.nil?
ssh_info = vm.ssh.info
variables = {
:host_key => options[:host] || vm.name || "vagrant",
:host_key => options[:host] || machine.name || "vagrant",
:ssh_host => ssh_info[:host],
:ssh_port => ssh_info[:port],
:ssh_user => ssh_info[:username],
@ -45,7 +43,7 @@ module VagrantPlugins
# Success, exit status 0
0
end
end
end
end
end

View File

@ -4,10 +4,8 @@ module VagrantPlugins
module CommandSuspend
class Command < Vagrant.plugin("1", :command)
def execute
options = {}
opts = OptionParser.new do |opts|
opts.banner = "Usage: vagrant suspend [vm-name]"
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant suspend [vm-name]"
end
# Parse the options
@ -16,18 +14,12 @@ module VagrantPlugins
@logger.debug("'suspend' each target VM...")
with_target_vms(argv) do |vm|
if vm.created?
@logger.info("Suspending: #{vm.name}")
vm.suspend
else
@logger.info("Not created: #{vm.name}. Not suspending.")
vm.ui.info I18n.t("vagrant.commands.common.vm_not_created")
end
vm.action(:suspend)
end
# Success, exit status 0
0
end
end
end
end
end

View File

@ -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

View File

@ -8,17 +8,23 @@ require 'vagrant/util/ansi_escape_code_remover'
require 'vagrant/util/file_mode'
require 'vagrant/util/platform'
require 'vagrant/util/retryable'
require 'vagrant/util/ssh'
module Vagrant
module Communication
# Provides communication with the VM via SSH.
class SSH < Base
include Util::ANSIEscapeCodeRemover
include Util::Retryable
module VagrantPlugins
module CommunicatorSSH
# This class provides communication with the VM via SSH.
class Communicator < Vagrant.plugin("1", :communicator)
include Vagrant::Util::ANSIEscapeCodeRemover
include Vagrant::Util::Retryable
def initialize(vm)
@vm = vm
@logger = Log4r::Logger.new("vagrant::communication::ssh")
def self.match?(machine)
# All machines are currently expected to have SSH.
true
end
def initialize(machine)
@machine = machine
@logger = Log4r::Logger.new("vagrant::communication::ssh")
@connection = nil
end
@ -31,7 +37,7 @@ module Vagrant
# If we reached this point then we successfully connected
@logger.info("SSH is ready!")
true
rescue Errors::VagrantError => e
rescue Vagrant::Errors::VagrantError => e
# We catch a `VagrantError` which would signal that something went
# wrong expectedly in the `connect`, which means we didn't connect.
@logger.info("SSH not up: #{e.inspect}")
@ -41,7 +47,7 @@ module Vagrant
def execute(command, opts=nil, &block)
opts = {
:error_check => true,
:error_class => Errors::VagrantError,
:error_class => Vagrant::Errors::VagrantError,
:error_key => :ssh_bad_exit_status,
:command => command,
:sudo => false
@ -79,6 +85,11 @@ module Vagrant
end
end
def test(command, opts=nil)
opts = { :error_check => false }.merge(opts || {})
execute(command, opts) == 0
end
def upload(from, to)
@logger.debug("Uploading: #{from} to #{to}")
@ -93,7 +104,7 @@ module Vagrant
# Otherwise, it is a permission denied, so let's raise a proper
# exception
raise Errors::SCPPermissionDenied, :path => from.to_s
raise Vagrant::Errors::SCPPermissionDenied, :path => from.to_s
end
protected
@ -120,7 +131,8 @@ module Vagrant
end
end
ssh_info = @vm.ssh.info
# XXX: We need to raise some exception if SSH is not ready
ssh_info = @machine.ssh_info
# Build the options we'll use to initiate the connection via Net::SSH
opts = {
@ -134,38 +146,38 @@ module Vagrant
}
# Check that the private key permissions are valid
@vm.ssh.check_key_permissions(ssh_info[:private_key_path])
Vagrant::Util::SSH.check_key_permissions(ssh_info[:private_key_path])
# Connect to SSH, giving it a few tries
connection = nil
begin
exceptions = [Errno::ECONNREFUSED, Net::SSH::Disconnect, Timeout::Error]
connection = retryable(:tries => @vm.config.ssh.max_tries, :on => exceptions) do
Timeout.timeout(@vm.config.ssh.timeout) do
connection = retryable(:tries => @machine.config.ssh.max_tries, :on => exceptions) do
Timeout.timeout(@machine.config.ssh.timeout) do
@logger.info("Attempting to connect to SSH: #{ssh_info[:host]}:#{ssh_info[:port]}")
Net::SSH.start(ssh_info[:host], ssh_info[:username], opts)
end
end
rescue Timeout::Error
# This happens if we continued to timeout when attempting to connect.
raise Errors::SSHConnectionTimeout
raise Vagrant::Errors::SSHConnectionTimeout
rescue Net::SSH::AuthenticationFailed
# This happens if authentication failed. We wrap the error in our
# own exception.
raise Errors::SSHAuthenticationFailed
raise Vagrant::Errors::SSHAuthenticationFailed
rescue Net::SSH::Disconnect
# This happens if the remote server unexpectedly closes the
# connection. This is usually raised when SSH is running on the
# other side but can't properly setup a connection. This is
# usually a server-side issue.
raise Errors::SSHDisconnected
raise Vagrant::Errors::SSHDisconnected
rescue Errno::ECONNREFUSED
# This is raised if we failed to connect the max amount of times
raise Errors::SSHConnectionRefused
raise Vagrant::Errors::SSHConnectionRefused
rescue NotImplementedError
# This is raised if a private key type that Net-SSH doesn't support
# is used. Show a nicer error.
raise Errors::SSHKeyTypeNotSupported
raise Vagrant::Errors::SSHKeyTypeNotSupported
end
@connection = connection
@ -178,7 +190,7 @@ module Vagrant
# Yield the connection that is ready to be used and
# return the value of the block
return yield connection if block_given?
end
end
# Executes the command on an SSH connection within a login shell.
def shell_execute(connection, command, sudo=false)
@ -187,7 +199,7 @@ module Vagrant
# Determine the shell to execute. If we are using `sudo` then we
# need to wrap the shell in a `sudo` call.
shell = @vm.config.ssh.shell
shell = @machine.config.ssh.shell
shell = "sudo -H #{shell}" if sudo
# Open the channel so we can execute or command
@ -248,7 +260,7 @@ module Vagrant
end
rescue Net::SCP::Error => e
# If we get the exit code of 127, then this means SCP is unavailable.
raise Errors::SCPUnavailable if e.message =~ /\(127\)/
raise Vagrant::Errors::SCPUnavailable if e.message =~ /\(127\)/
# Otherwise, just raise the error up
raise

View File

@ -0,0 +1,19 @@
require "vagrant"
module VagrantPlugins
module CommunicatorSSH
class Plugin < Vagrant.plugin("1")
name "ssh communiator"
description <<-DESC
This plugin allows Vagrant to communicate with remote machines using
SSH as the underlying protocol, powered internally by Ruby's
net-ssh library.
DESC
communicator("ssh") do
require File.expand_path("../communicator", __FILE__)
Communicator
end
end
end
end

View File

@ -16,23 +16,25 @@ module VagrantPlugins
end
def distro_dispatch
if @vm.channel.test("cat /etc/debian_version")
return :debian if @vm.channel.test("cat /proc/version | grep 'Debian'")
return :ubuntu if @vm.channel.test("cat /proc/version | grep 'Ubuntu'")
end
@vm.communicate.tap do |comm|
if comm.test("cat /etc/debian_version") == 0
return :debian if comm.test("cat /proc/version | grep 'Debian'") == 0
return :ubuntu if comm.test("cat /proc/version | grep 'Ubuntu'") == 0
end
return :gentoo if @vm.channel.test("cat /etc/gentoo-release")
return :fedora if @vm.channel.test("grep 'Fedora release 16' /etc/redhat-release")
return :redhat if @vm.channel.test("cat /etc/redhat-release")
return :suse if @vm.channel.test("cat /etc/SuSE-release")
return :arch if @vm.channel.test("cat /etc/arch-release")
return :gentoo if comm.test("cat /etc/gentoo-release") == 0
return :fedora if comm.test("grep 'Fedora release 16' /etc/redhat-release") == 0
return :redhat if comm.test("cat /etc/redhat-release") == 0
return :suse if comm.test("cat /etc/SuSE-release") == 0
return :arch if comm.test("cat /etc/arch-release") == 0
end
# Can't detect the distro, assume vanilla linux
nil
end
def halt
@vm.channel.sudo("shutdown -h now")
@vm.communicate.sudo("shutdown -h now")
# Wait until the VM's state is actually powered off. If this doesn't
# occur within a reasonable amount of time (15 seconds by default),
@ -50,9 +52,9 @@ module VagrantPlugins
real_guestpath = expanded_guest_path(guestpath)
@logger.debug("Shell expanded guest path: #{real_guestpath}")
@vm.channel.sudo("mkdir -p #{real_guestpath}")
@vm.communicate.sudo("mkdir -p #{real_guestpath}")
mount_folder(name, real_guestpath, options)
@vm.channel.sudo("chown `id -u #{options[:owner]}`:`id -g #{options[:group]}` #{real_guestpath}")
@vm.communicate.sudo("chown `id -u #{options[:owner]}`:`id -g #{options[:group]}` #{real_guestpath}")
end
def mount_nfs(ip, folders)
@ -63,8 +65,8 @@ module VagrantPlugins
real_guestpath = expanded_guest_path(opts[:guestpath])
# Do the actual creating and mounting
@vm.channel.sudo("mkdir -p #{real_guestpath}")
@vm.channel.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
@vm.communicate.sudo("mkdir -p #{real_guestpath}")
@vm.communicate.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
:error_class => LinuxError,
:error_key => :mount_nfs_fail)
end
@ -80,7 +82,7 @@ module VagrantPlugins
# @return [String] The expanded guestpath
def expanded_guest_path(guestpath)
real_guestpath = nil
@vm.channel.execute("printf #{guestpath}") do |type, data|
@vm.communicate.execute("printf #{guestpath}") do |type, data|
if type == :stdout
real_guestpath ||= ""
real_guestpath += data
@ -105,7 +107,7 @@ module VagrantPlugins
attempts = 0
while true
success = true
@vm.channel.sudo("mount -t vboxsf #{mount_options} #{name} #{guestpath}") do |type, data|
@vm.communicate.sudo("mount -t vboxsf #{mount_options} #{name} #{guestpath}") do |type, data|
success = false if type == :stderr && data =~ /No such device/i
end

View File

@ -0,0 +1,289 @@
require "vagrant/action/builder"
module VagrantPlugins
module ProviderVirtualBox
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__)
autoload :CleanMachineFolder, File.expand_path("../action/clean_machine_folder", __FILE__)
autoload :ClearForwardedPorts, File.expand_path("../action/clear_forwarded_ports", __FILE__)
autoload :ClearNetworkInterfaces, File.expand_path("../action/clear_network_interfaces", __FILE__)
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__)
autoload :DiscardState, File.expand_path("../action/discard_state", __FILE__)
autoload :Export, File.expand_path("../action/export", __FILE__)
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__)
autoload :Network, File.expand_path("../action/network", __FILE__)
autoload :NFS, File.expand_path("../action/nfs", __FILE__)
autoload :Package, File.expand_path("../action/package", __FILE__)
autoload :PackageVagrantfile, File.expand_path("../action/package_vagrantfile", __FILE__)
autoload :Provision, File.expand_path("../action/provision", __FILE__)
autoload :ProvisionerCleanup, File.expand_path("../action/provisioner_cleanup", __FILE__)
autoload :PruneNFSExports, File.expand_path("../action/prune_nfs_exports", __FILE__)
autoload :Resume, File.expand_path("../action/resume", __FILE__)
autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__)
autoload :SetupPackageFiles, File.expand_path("../action/setup_package_files", __FILE__)
autoload :ShareFolders, File.expand_path("../action/share_folders", __FILE__)
autoload :Suspend, File.expand_path("../action/suspend", __FILE__)
# Include the built-in modules so that we can use them as top-level
# 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
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env1, b2|
if !env1[:result]
b2.use MessageNotCreated
next
end
b2.use Call, DestroyConfirm do |env2, b3|
if env2[:result]
b3.use Vagrant::Action::General::Validate
b3.use CheckAccessible
b3.use EnvSet, :force => true
b3.use action_halt
b3.use ProvisionerCleanup
b3.use PruneNFSExports
b3.use Destroy
b3.use CleanMachineFolder
b3.use DestroyUnusedNetworkInterfaces
else
b3.use MessageWillNotDestroy
end
end
end
end
end
# This is the action that is primarily responsible for halting
# the virtual machine, gracefully or by force.
def self.action_halt
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env, b2|
if env[:result]
b2.use CheckAccessible
b2.use DiscardState
b2.use Halt
else
b2.use MessageNotCreated
end
end
end
end
# This action packages the virtual machine into a single box file.
def self.action_package
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env1, b2|
if !env1[:result]
b2.use MessageNotCreated
next
end
b2.use SetupPackageFiles
b2.use CheckAccessible
b2.use action_halt
b2.use ClearForwardedPorts
b2.use ClearSharedFolders
b2.use Export
b2.use PackageVagrantfile
b2.use Package
end
end
end
# This action just runs the provisioners on the machine.
def self.action_provision
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Vagrant::Action::General::Validate
b.use Call, Created do |env1, b2|
if !env1[:result]
b2.use MessageNotCreated
next
end
b2.use Call, IsRunning do |env2, b3|
if !env2[:result]
b3.use MessageNotRunning
next
end
b3.use CheckAccessible
b3.use Provision
end
end
end
end
# This action is responsible for reloading the machine, which
# brings it down, sucks in new configuration, and brings the
# machine back up with the new configuration.
def self.action_reload
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env1, b2|
if !env1[:result]
b2.use MessageNotCreated
next
end
b2.use Vagrant::Action::General::Validate
b2.use action_halt
b2.use action_start
end
end
end
# This is the action that is primarily responsible for resuming
# suspended machines.
def self.action_resume
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env, b2|
if env[:result]
b2.use CheckAccessible
b2.use CheckPortCollisions
b2.use Resume
else
b2.use MessageNotCreated
end
end
end
end
# This is the action that will exec into an SSH shell.
def self.action_ssh
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use CheckCreated
b.use CheckAccessible
b.use CheckRunning
b.use SSHExec
end
end
# This is the action that will run a single SSH command.
def self.action_ssh_run
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use CheckCreated
b.use CheckAccessible
b.use CheckRunning
b.use SSHRun
end
end
# This action starts a VM, assuming it is already imported and exists.
# A precondition of this action is that the VM exists.
def self.action_start
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Vagrant::Action::General::Validate
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
# This is the action that is primarily responsible for suspending
# the virtual machine.
def self.action_suspend
Vagrant::Action::Builder.new.tap do |b|
b.use CheckVirtualbox
b.use Call, Created do |env, b2|
if env[:result]
b2.use CheckAccessible
b2.use Suspend
else
b2.use MessageNotCreated
end
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

View File

@ -1,32 +1,27 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Boot
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env = env
# Start up the VM and wait for it to boot.
boot
env[:ui].info I18n.t("vagrant.actions.vm.boot.booting")
env[:machine].provider.driver.start(@env[:machine].config.vm.boot_mode)
raise Errors::VMFailedToBoot if !wait_for_boot
@app.call(env)
end
def boot
@env[:ui].info I18n.t("vagrant.actions.vm.boot.booting")
@env[:vm].driver.start(@env[:vm].config.vm.boot_mode)
end
def wait_for_boot
@env[:ui].info I18n.t("vagrant.actions.vm.boot.waiting")
@env[:vm].config.ssh.max_tries.to_i.times do |i|
if @env[:vm].channel.ready?
@env[:machine].config.ssh.max_tries.to_i.times do |i|
if @env[:machine].communicate.ready?
@env[:ui].info I18n.t("vagrant.actions.vm.boot.ready")
return true
end
@ -37,7 +32,7 @@ module Vagrant
# If the VM is not starting or running, something went wrong
# and we need to show a useful error.
state = @env[:vm].state
state = @env[:machine].provider.state
raise Errors::VMFailedToRun if state != :starting && state != :running
sleep 2 if !@env["vagrant.test"]
@ -46,8 +41,8 @@ module Vagrant
@env[:ui].error I18n.t("vagrant.actions.vm.boot.failed")
false
end
end
end
end
end

View File

@ -1,18 +1,18 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class CheckAccessible
def initialize(app, env)
@app = app
end
def call(env)
if env[:vm].state == :inaccessible
if env[:machine].state == :inaccessible
# The VM we are attempting to manipulate is inaccessible. This
# is a very bad situation and can only be fixed by the user. It
# also prohibits us from actually doing anything with the virtual
# machine, so we raise an error.
raise Errors::VMInaccessible
raise Vagrant::Errors::VMInaccessible
end
@app.call(env)

View File

@ -1,18 +1,18 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class CheckBox
def initialize(app, env)
@app = app
end
def call(env)
box_name = env[:vm].config.vm.box
raise Errors::BoxNotSpecified if !box_name
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[:vm].config.vm.box_url
raise Errors::BoxSpecifiedDoesntExist, :name => box_name if !box_url
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.
@ -21,8 +21,8 @@ module Vagrant
env[:box_collection].reload!
# Reload the environment and set the VM to be the new loaded VM.
env[:vm].env.reload!
env[:vm] = env[:vm].env.vms[env[:vm].name]
env[:machine].env.reload!
env[:machine] = env[:machine].env.vms[env[:machine].name]
end
@app.call(env)

View File

@ -0,0 +1,21 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
# This middleware checks that the VM is created, and raises an exception
# if it is not, notifying the user that creation is required.
class CheckCreated
def initialize(app, env)
@app = app
end
def call(env)
if env[:machine].state == :not_created
raise Vagrant::Errors::VMNotCreatedError
end
@app.call(env)
end
end
end
end
end

View File

@ -1,9 +1,6 @@
module Vagrant
module Action
module VM
# Middleware which verifies that the VM has the proper guest additions
# installed and prints a warning if they're not detected or if the
# version does not match the installed VirtualBox version.
module VagrantPlugins
module ProviderVirtualBox
module Action
class CheckGuestAdditions
def initialize(app, env)
@app = app
@ -12,13 +9,13 @@ module Vagrant
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[:vm].driver.read_guest_additions_version
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[:vm].driver.version].map do |v|
guest_version, vb_version = [version, env[:machine].provider.driver.version].map do |v|
v.gsub(/[-_]ose/i, '')
end
@ -32,6 +29,7 @@ module Vagrant
# Continue
@app.call(env)
end
end
end
end

View File

@ -1,12 +1,10 @@
require "vagrant/util/is_port_open"
module Vagrant
module Action
module VM
# Action that checks to make sure there are no forwarded port collisions,
# and raises an exception if there is.
module VagrantPlugins
module ProviderVirtualBox
module Action
class CheckPortCollisions
include Util::IsPortOpen
include Vagrant::Util::IsPortOpen
def initialize(app, env)
@app = app
@ -17,17 +15,17 @@ module Vagrant
@env = env
# Figure out how we handle port collisions. By default we error.
handler = env[:port_collision_handler] || :error
handler = env[:port_collision_handler] || :error
# Read our forwarded ports, if we have any, to override what
# we have configured.
current = {}
env[:vm].driver.read_forwarded_ports.each do |nic, name, hostport, guestport|
env[:machine].provider.driver.read_forwarded_ports.each do |nic, name, hostport, guestport|
current[name] = hostport.to_i
end
existing = env[:vm].driver.read_used_ports
env[:vm].config.vm.forwarded_ports.each do |options|
existing = env[:machine].provider.driver.read_used_ports
env[:machine].config.vm.forwarded_ports.each do |options|
# Use the proper port, whether that be the configured port or the
# port that is currently on use of the VM.
hostport = options[:hostport].to_i
@ -44,7 +42,7 @@ module Vagrant
# Handles a port collision by raising an exception.
def handle_error(options, existing_ports)
raise Errors::ForwardPortCollisionResume
raise Vagrant::Errors::ForwardPortCollisionResume
end
# Handles a port collision by attempting to fix it.
@ -55,21 +53,23 @@ module Vagrant
if !options[:auto]
# Auto fixing is disabled for this port forward, so we
# must throw an error so the user can fix it.
raise Errors::ForwardPortCollision, :host_port => options[:hostport].to_s,
:guest_port => options[:guestport].to_s
raise Vagrant::Errors::ForwardPortCollision,
:host_port => options[:hostport].to_s,
:guest_port => options[:guestport].to_s
end
# Get the auto port range and get rid of the used ports and
# ports which are being used in other forwards so we're just
# left with available ports.
range = @env[:vm].config.vm.auto_port_range.to_a
range -= @env[:vm].config.vm.forwarded_ports.collect { |opts| opts[:hostport].to_i }
range = @env[:machine].config.vm.auto_port_range.to_a
range -= @env[:machine].config.vm.forwarded_ports.collect { |opts| opts[:hostport].to_i }
range -= existing_ports
if range.empty?
raise Errors::ForwardPortAutolistEmpty, :vm_name => @env[:vm].name,
:host_port => options[:hostport].to_s,
:guest_port => options[:guestport].to_s
raise Vagrant::Errors::ForwardPortAutolistEmpty,
:vm_name => @env[:machine].name,
:host_port => options[:hostport].to_s,
:guest_port => options[:guestport].to_s
end
# Set the port up to be the first one and add that port to

View File

@ -0,0 +1,21 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
# This middleware checks that the VM is running, and raises an exception
# if it is not, notifying the user that the VM must be running.
class CheckRunning
def initialize(app, env)
@app = app
end
def call(env)
if env[:machine].state != :running
raise Vagrant::Errors::VMNotRunningError
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,22 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
# Checks that VirtualBox is installed and ready to be used.
class CheckVirtualbox
def initialize(app, env)
@app = app
end
def call(env)
# This verifies that VirtualBox is installed and the driver is
# ready to function. If not, then an exception will be raised
# which will break us out of execution of the middleware sequence.
Driver::Meta.new.verify!
# Carry on.
@app.call(env)
end
end
end
end
end

View File

@ -1,8 +1,8 @@
require 'fileutils'
require "fileutils"
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
# Cleans up the VirtualBox machine folder for any ".xml-prev"
# files which VirtualBox may have left over. This is a bug in
# VirtualBox. As soon as this is fixed, this middleware can and
@ -13,7 +13,7 @@ module Vagrant
end
def call(env)
clean_machine_folder(env[:vm].driver.read_machine_folder)
clean_machine_folder(env[:machine].provider.driver.read_machine_folder)
@app.call(env)
end

View File

@ -1,6 +1,6 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class ClearForwardedPorts
def initialize(app, env)
@app = app
@ -8,7 +8,7 @@ module Vagrant
def call(env)
env[:ui].info I18n.t("vagrant.actions.vm.clear_forward_ports.deleting")
env[:vm].driver.clear_forwarded_ports
env[:machine].provider.driver.clear_forwarded_ports
@app.call(env)
end

View File

@ -1,6 +1,6 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class ClearNetworkInterfaces
def initialize(app, env)
@app = app
@ -21,7 +21,7 @@ module Vagrant
# "Enable" all the adapters we setup.
env[:ui].info I18n.t("vagrant.actions.vm.clear_network_interfaces.deleting")
env[:vm].driver.enable_adapters(adapters)
env[:machine].provider.driver.enable_adapters(adapters)
@app.call(env)
end

View File

@ -1,14 +1,13 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class ClearSharedFolders
def initialize(app, env)
@app = app
@env = env
end
def call(env)
env[:vm].driver.clear_shared_folders
env[:machine].provider.driver.clear_shared_folders
@app.call(env)
end

View File

@ -0,0 +1,20 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class Created
def initialize(app, env)
@app = app
end
def call(env)
# Set the result to be true if the machine is created.
env[:result] = env[:machine].state != :not_created
# 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

View File

@ -1,24 +1,24 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Customize
def initialize(app, env)
@app = app
end
def call(env)
customizations = env[:vm].config.vm.customizations
customizations = env[:machine].config.vm.customizations
if !customizations.empty?
env[:ui].info I18n.t("vagrant.actions.vm.customize.running")
# Execute each customization command.
customizations.each do |command|
processed_command = command.collect do |arg|
arg = env[:vm].uuid if arg == :id
arg = env[:machine].id if arg == :id
arg.to_s
end
result = env[:vm].driver.execute_command(processed_command)
result = env[:machine].provider.driver.execute_command(processed_command)
if result.exit_code != 0
raise Errors::VMCustomizationFailed, {
:command => processed_command.inspect,

View File

@ -1,8 +1,8 @@
require 'log4r'
require "log4r"
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class DefaultName
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::defaultname")
@ -12,7 +12,7 @@ module Vagrant
def call(env)
@logger.info("Setting the default name of the VM")
name = env[:root_path].basename.to_s + "_#{Time.now.to_i}"
env[:vm].driver.set_name(name)
env[:machine].provider.driver.set_name(name)
@app.call(env)
end

View File

@ -1,6 +1,6 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Destroy
def initialize(app, env)
@app = app
@ -8,8 +8,8 @@ module Vagrant
def call(env)
env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying")
env[:vm].driver.delete
env[:vm].uuid = nil
env[:machine].provider.driver.delete
env[:machine].id = nil
@app.call(env)
end

View File

@ -0,0 +1,16 @@
require "vagrant/action/builtin/confirm"
module VagrantPlugins
module ProviderVirtualBox
module Action
class DestroyConfirm < Vagrant::Action::Builtin::Confirm
def initialize(app, env)
message = I18n.t("vagrant.commands.destroy.confirmation",
:name => env[:machine].name)
super(app, env, message)
end
end
end
end
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class DestroyUnusedNetworkInterfaces
def initialize(app, env)
@app = app
end
def call(env)
env[:machine].provider.driver.delete_unused_host_only_networks
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,20 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class DiscardState
def initialize(app, env)
@app = app
end
def call(env)
if env[:machine].provider.state == :saved
env[:ui].info I18n.t("vagrant.actions.vm.discard_state.discarding")
env[:machine].provider.driver.discard_saved_state
end
@app.call(env)
end
end
end
end
end

View File

@ -1,20 +1,19 @@
require 'fileutils'
require "fileutils"
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Export
attr_reader :temp_dir
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env = env
raise Errors::VMPowerOffToPackage if @env["vm"].state != :poweroff
raise Vagrant::Errors::VMPowerOffToPackage if @env[:machine].provider.state != :poweroff
setup_temp_dir
export
@ -38,7 +37,7 @@ module Vagrant
def export
@env[:ui].info I18n.t("vagrant.actions.vm.export.exporting")
@env[:vm].driver.export(ovf_path) do |progress|
@env[:machine].provider.driver.export(ovf_path) do |progress|
@env[:ui].clear_line
@env[:ui].report_progress(progress.percent, 100, false)
end

View File

@ -1,8 +1,8 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class ForwardPorts
def initialize(app,env)
def initialize(app, env)
@app = app
end
@ -31,7 +31,7 @@ module Vagrant
# organize them by their guestport, taking the "last one wins"
# approach.
guest_port_mapping = {}
@env[:vm].config.vm.forwarded_ports.each do |options|
@env[:machine].config.vm.forwarded_ports.each do |options|
guest_port_mapping[options[:guestport]] = options
end
@ -53,7 +53,7 @@ module Vagrant
def forward_ports(mappings)
ports = []
interfaces = @env[:vm].driver.read_network_interfaces
interfaces = @env[:machine].provider.driver.read_network_interfaces
mappings.each do |options|
message_attributes = {
@ -83,7 +83,7 @@ module Vagrant
if !ports.empty?
# We only need to forward ports if there are any to forward
@env[:vm].driver.forward_ports(ports)
@env[:machine].provider.driver.forward_ports(ports)
end
end
end

View File

@ -1,26 +1,25 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Halt
def initialize(app, env, options=nil)
def initialize(app, env)
@app = app
env.merge!(options || {})
end
def call(env)
current_state = env[:vm].state
current_state = env[:machine].provider.state
if current_state == :running || current_state == :gurumeditation
# If the VM is running and we're not forcing, we can
# attempt a graceful shutdown
if current_state == :running && !env["force"]
if current_state == :running && !env[:force]
env[:ui].info I18n.t("vagrant.actions.vm.halt.graceful")
env[:vm].guest.halt
env[:machine].guest.halt
end
# If we're not powered off now, then force it
if env[:vm].state != :poweroff
if env[:machine].provider.state != :poweroff
env[:ui].info I18n.t("vagrant.actions.vm.halt.force")
env[:vm].driver.halt
env[:machine].provider.driver.halt
end
# Sleep for a second to verify that the VM properly

View File

@ -1,6 +1,6 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class HostName
def initialize(app, env)
@app = app
@ -9,10 +9,10 @@ module Vagrant
def call(env)
@app.call(env)
host_name = env[:vm].config.vm.host_name
host_name = env[:machine].config.vm.host_name
if !host_name.nil?
env[:ui].info I18n.t("vagrant.actions.vm.host_name.setting")
env[:vm].guest.change_host_name(host_name)
env[:machine].guest.change_host_name(host_name)
end
end
end

View File

@ -1,22 +1,18 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Import
# The name for easy reference
def self.name
:import
end
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info I18n.t("vagrant.actions.vm.import.importing", :name => env[:vm].box.name)
env[:ui].info I18n.t("vagrant.actions.vm.import.importing",
:name => env[:machine].box.name)
# Import the virtual machine
ovf_file = env[:vm].box.directory.join("box.ovf").to_s
env[:vm].uuid = env[:vm].driver.import(ovf_file) do |progress|
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
@ -30,21 +26,21 @@ module Vagrant
return if env[:interrupted]
# Flag as erroneous and return if import failed
raise Errors::VMImportFailure if !env[:vm].uuid
raise Vagrant::Errors::VMImportFailure if !env[:machine].id
# Import completed successfully. Continue the chain
@app.call(env)
end
def recover(env)
if env[:vm].created?
return if env["vagrant.error"].is_a?(Errors::VagrantError)
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(:destroy, destroy_env)
env[:action_runner].run(Action.action_destroy, destroy_env)
end
end
end

View File

@ -0,0 +1,20 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class IsRunning
def initialize(app, env)
@app = app
end
def call(env)
# Set the result to be true if the machine is running.
env[:result] = env[:machine].state == :running
# 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

View File

@ -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

View File

@ -1,17 +1,17 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class MatchMACAddress
def initialize(app, env)
@app = app
end
def call(env)
raise Errors::VMBaseMacNotSpecified if !env[:vm].config.vm.base_mac
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[:vm].driver.set_mac_address(env[:vm].config.vm.base_mac)
env[:machine].provider.driver.set_mac_address(env[:machine].config.vm.base_mac)
@app.call(env)
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class MessageNotCreated
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info I18n.t("vagrant.commands.common.vm_not_created")
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class MessageNotRunning
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info I18n.t("vagrant.commands.common.vm_not_running")
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,17 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class MessageWillNotDestroy
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info I18n.t("vagrant.commands.destroy.will_not_destroy",
:name => env[:machine].name)
@app.call(env)
end
end
end
end
end

View File

@ -1,17 +1,15 @@
require 'set'
require "set"
require 'log4r'
require "log4r"
require 'vagrant/util/network_ip'
require "vagrant/util/network_ip"
module Vagrant
module Action
module VM
# This action handles all `config.vm.network` configurations by
# setting up the VM properly and enabling the networks afterword.
module VagrantPlugins
module ProviderVirtualBox
module Action
class Network
# Utilities to deal with network addresses
include Util::NetworkIP
include Vagrant::Util::NetworkIP
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::network")
@ -28,7 +26,7 @@ module Vagrant
@logger.debug("Determining adapters and networks...")
adapters = []
networks = []
env[:vm].config.vm.networks.each do |type, args|
env[:machine].config.vm.networks.each do |type, args|
# Get the normalized configuration we'll use around
config = send("#{type}_config", args)
@ -54,7 +52,7 @@ module Vagrant
# Create all the network interfaces
@logger.info("Enabling adapters...")
env[:ui].info I18n.t("vagrant.actions.vm.network.preparing")
env[:vm].driver.enable_adapters(adapters)
env[:machine].provider.driver.enable_adapters(adapters)
end
# Continue the middleware chain. We're done with our VM
@ -69,7 +67,7 @@ module Vagrant
# want to configure the networks that have `auto_config` setup.
networks_to_configure = networks.select { |n| n[:_auto_config] }
env[:ui].info I18n.t("vagrant.actions.vm.network.configuring")
env[:vm].guest.configure_networks(networks_to_configure)
env[:machine].guest.configure_networks(networks_to_configure)
end
end
@ -86,7 +84,7 @@ module Vagrant
available = Set.new(1..8)
# Determine which NICs are actually available.
interfaces = @env[:vm].driver.read_network_interfaces
interfaces = @env[:machine].provider.driver.read_network_interfaces
interfaces.each do |number, nic|
# Remove the number from the available NICs if the
# NIC is in use.
@ -102,7 +100,7 @@ module Vagrant
if !adapter[:adapter]
# If we have no available adapters, then that is an exceptional
# event.
raise Errors::NetworkNoAdapters if available.empty?
raise Vagrant::Errors::NetworkNoAdapters if available.empty?
# Otherwise, assign as the adapter the next available item
adapter[:adapter] = available.shift
@ -116,7 +114,7 @@ module Vagrant
# Verify that there are no collisions in the adapters being used.
used = Set.new
adapters.each do |adapter|
raise Errors::NetworkAdapterCollision if used.include?(adapter[:adapter])
raise Vagrant::Errors::NetworkAdapterCollision if used.include?(adapter[:adapter])
used.add(adapter[:adapter])
end
end
@ -134,7 +132,7 @@ module Vagrant
adapter_to_interface = {}
# Make a first pass to assign interface numbers by adapter location
vm_adapters = @env[:vm].driver.read_network_interfaces
vm_adapters = @env[:machine].provider.driver.read_network_interfaces
vm_adapters.sort.each do |number, adapter|
if adapter[:type] != :none
# Not used, so assign the interface number and increment
@ -220,7 +218,7 @@ module Vagrant
# It is an error case if a specific name was given but the network
# doesn't exist.
if config[:name]
raise Errors::NetworkNotFound, :name => config[:name]
raise Vagrant::Errors::NetworkNotFound, :name => config[:name]
end
# Otherwise, we create a new network and put the net network
@ -238,13 +236,13 @@ module Vagrant
interface[:dhcp][:lower] == config[:dhcp_lower] &&
interface[:dhcp][:upper] == config[:dhcp_upper]
raise Errors::NetworkDHCPAlreadyAttached if !valid
raise Vagrant::Errors::NetworkDHCPAlreadyAttached if !valid
@logger.debug("DHCP server already properly configured")
else
# Configure the DHCP server for the network.
@logger.debug("Creating a DHCP server...")
@env[:vm].driver.create_dhcp_server(interface[:name], config)
@env[:machine].provider.driver.create_dhcp_server(interface[:name], config)
end
end
@ -274,7 +272,7 @@ module Vagrant
options = config.dup
options[:ip] = options[:adapter_ip]
@env[:vm].driver.create_host_only_network(options)
@env[:machine].provider.driver.create_host_only_network(options)
end
# Finds a host only network that matches our configuration on VirtualBox.
@ -282,7 +280,7 @@ module Vagrant
def find_matching_hostonly_network(config)
this_netaddr = network_address(config[:ip], config[:netmask])
@env[:vm].driver.read_host_only_interfaces.each do |interface|
@env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
if config[:name] && config[:name] == interface[:name]
return interface
elsif this_netaddr == network_address(interface[:ip], interface[:netmask])
@ -302,9 +300,9 @@ module Vagrant
def verify_no_bridge_collision(options)
this_netaddr = network_address(options[:ip], options[:netmask])
@env[:vm].driver.read_bridged_interfaces.each do |interface|
@env[:machine].provider.driver.read_bridged_interfaces.each do |interface|
that_netaddr = network_address(interface[:ip], interface[:netmask])
raise Errors::NetworkCollision if this_netaddr == that_netaddr && interface[:status] != "Down"
raise Vagrant::Errors::NetworkCollision if this_netaddr == that_netaddr && interface[:status] != "Down"
end
end
@ -323,7 +321,7 @@ module Vagrant
def bridged_adapter(config)
# Find the bridged interfaces that are available
bridgedifs = @env[:vm].driver.read_bridged_interfaces
bridgedifs = @env[:machine].provider.driver.read_bridged_interfaces
bridgedifs.delete_if { |interface| interface[:status] == "Down" }
# The name of the chosen bridge interface will be assigned to this

View File

@ -4,21 +4,9 @@ require 'pathname'
require 'log4r'
module Vagrant
module Action
module VM
# Enables NFS based shared folders. `nfsd` must already be installed
# on the host machine, and NFS client must already be installed on
# the guest machine.
#
# This is a two part process:
#
# 1. Adds an entry to `/etc/exports` on the host machine using
# the host class to export the proper folder to the proper
# machine.
# 2. After boot, runs `mount` on the guest to mount the shared
# folder.
#
module VagrantPlugins
module ProviderVirtualBox
module Action
class NFS
def initialize(app,env)
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
@ -54,7 +42,7 @@ module Vagrant
def extract_folders
# Load the NFS enabled shared folders
@folders = {}
@env[:vm].config.vm.shared_folders.each do |key, opts|
@env[:machine].config.vm.shared_folders.each do |key, opts|
if opts[:nfs]
# Duplicate the options, set the hostpath, and set disabled on the original
# options so the ShareFolders middleware doesn't try to mount it.
@ -68,7 +56,8 @@ module Vagrant
begin
FileUtils.mkpath(hostpath)
rescue Errno::EACCES
raise Errors::SharedFolderCreateFailed, :path => hostpath.to_s
raise Vagrant::Errors::SharedFolderCreateFailed,
:path => hostpath.to_s
end
end
@ -112,7 +101,7 @@ module Vagrant
# The options on the hash get priority, then the default
# values
value = opts.has_key?(key) ? opts[key] : @env[:vm].config.nfs.send(key)
value = opts.has_key?(key) ? opts[key] : @env[:machine].config.nfs.send(key)
return value if value != :auto
# Get UID/GID from folder if we've made it this far
@ -126,7 +115,7 @@ module Vagrant
# up to the host class to define the specific behavior.
def export_folders
@env[:ui].info I18n.t("vagrant.actions.vm.nfs.exporting")
@env[:host].nfs_export(@env[:vm].uuid, guest_ip, folders)
@env[:host].nfs_export(@env[:machine].id, guest_ip, folders)
end
# Uses the system class to mount the NFS folders.
@ -141,16 +130,16 @@ module Vagrant
end
end
@env[:vm].guest.mount_nfs(host_ip, mount_folders)
@env[:machine].guest.mount_nfs(host_ip, mount_folders)
end
# Returns the IP address of the first host only network adapter
#
# @return [String]
def host_ip
@env[:vm].driver.read_network_interfaces.each do |adapter, opts|
@env[:machine].provider.driver.read_network_interfaces.each do |adapter, opts|
if opts[:type] == :hostonly
@env[:vm].driver.read_host_only_interfaces.each do |interface|
@env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
if interface[:name] == opts[:hostonly]
return interface[:ip]
end
@ -166,7 +155,7 @@ module Vagrant
#
# @return [String]
def guest_ip
@env[:vm].config.vm.networks.each do |type, args|
@env[:machine].config.vm.networks.each do |type, args|
if type == :hostonly && args[0].is_a?(String)
return args[0]
end
@ -177,7 +166,7 @@ module Vagrant
# Checks if there are any NFS enabled shared folders.
def nfs_enabled?
@env[:vm].config.vm.shared_folders.each do |key, opts|
@env[:machine].config.vm.shared_folders.each do |key, opts|
return true if opts[:nfs]
end
@ -186,9 +175,9 @@ module Vagrant
# Verifies that the host is set and supports NFS.
def verify_settings
raise Errors::NFSHostRequired if @env[:host].nil?
raise Errors::NFSNotSupported if !@env[:host].nfs?
raise Errors::NFSNoHostNetwork if !guest_ip
raise Vagrant::Errors::NFSHostRequired if @env[:host].nil?
raise Vagrant::Errors::NFSNotSupported if !@env[:host].nfs?
raise Vagrant::Errors::NFSNoHostNetwork if !guest_ip
end
end
end

View File

@ -1,12 +1,9 @@
require 'vagrant/action/general/package'
module Vagrant
module Action
module VM
# A subclass of {General::Package} which simply makes sure that
# the package directory is set to the directory which the VM
# was exported to.
class Package < General::Package
module VagrantPlugins
module ProviderVirtualBox
module Action
class Package < Vagrant::Action::General::Package
# Doing this so that we can test that the parent is properly
# called in the unit tests.
alias_method :general_call, :call

View File

@ -1,17 +1,14 @@
require 'vagrant/util/template_renderer'
module Vagrant
module Action
module VM
# Puts a generated Vagrantfile into the package directory so that
# it can be included in the package.
module VagrantPlugins
module ProviderVirtualBox
module Action
class PackageVagrantfile
# For TemplateRenderer
include Util
include Vagrant::Util
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@ -26,7 +23,7 @@ module Vagrant
def create_vagrantfile
File.open(File.join(@env["export.temp_dir"], "Vagrantfile"), "w") do |f|
f.write(TemplateRenderer.render("package_Vagrantfile", {
:base_mac => @env["vm"].driver.read_mac_address
:base_mac => @env[:machine].provider.driver.read_mac_address
}))
end
end

View File

@ -1,8 +1,8 @@
require "log4r"
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Provision
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::provision")
@ -19,6 +19,7 @@ module Vagrant
# We set this here so that even if this value is changed in the future,
# it stays constant to what we expect here in this moment.
enabled = env["provision.enabled"]
# Instantiate and prepare the provisioners. Preparation must happen here
# so that shared folders and such can properly take effect.
provisioners = enabled_provisioners
@ -38,7 +39,7 @@ module Vagrant
def enabled_provisioners
enabled = []
@env[:vm].config.vm.provisioners.each do |provisioner|
@env[:machine].config.vm.provisioners.each do |provisioner|
if @env["provision.types"]
# If we've specified types of provisioners to enable, then we
# only use those provisioners, and skip any that we haven't

View File

@ -0,0 +1,25 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class ProvisionerCleanup
def initialize(app, env)
@app = app
end
def call(env)
# Instantiate all the enabled provisioners
provisioners = env[:machine].config.vm.provisioners.map do |provisioner|
provisioner.provisioner.new(env, provisioner.config)
end
# Call cleanup on each
provisioners.each do |instance|
instance.cleanup
end
@app.call(env)
end
end
end
end
end

View File

@ -1,6 +1,6 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class PruneNFSExports
def initialize(app, env)
@app = app
@ -8,7 +8,7 @@ module Vagrant
def call(env)
if env[:host]
valid_ids = env[:vm].driver.read_vms
valid_ids = env[:machine].provider.driver.read_vms
env[:host].nfs_prune(valid_ids)
end

View File

@ -1,13 +1,13 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Resume
def initialize(app, env)
@app = app
end
def call(env)
if env[:vm].state == :saved
if env[:machine].provider.state == :saved
env[:ui].info I18n.t("vagrant.actions.vm.resume.resuming")
env[:action_runner].run(Boot, env)
end

View File

@ -1,11 +1,8 @@
require "log4r"
module Vagrant
module Action
module VM
# This middleware enforces some sane defaults on the virtualbox
# VM which help with performance, stability, and in some cases
# behavior.
module VagrantPlugins
module ProviderVirtualBox
module Action
class SaneDefaults
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::sanedefaults")
@ -22,7 +19,7 @@ module Vagrant
# errors. The Host IO cache vastly improves disk IO performance
# for VMs.
command = [
"storagectl", env[:vm].uuid,
"storagectl", env[:machine].id,
"--name", "SATA Controller",
"--hostiocache", "on"
]
@ -46,14 +43,14 @@ module Vagrant
# Enable/disable the NAT DNS proxy as necessary
if enable_dns_proxy
command = [
"modifyvm", env[:vm].uuid,
"modifyvm", env[:machine].id,
"--natdnsproxy1", "on"
]
attempt_and_log(command, "Enable the NAT DNS proxy on adapter 1...")
else
command = [ "modifyvm", env[:vm].uuid, "--natdnsproxy1", "off" ]
command = [ "modifyvm", env[:machine].id, "--natdnsproxy1", "off" ]
attempt_and_log(command, "Disable the NAT DNS proxy on adapter 1...")
command = [ "modifyvm", env[:vm].uuid, "--natdnshostresolver1", "off" ]
command = [ "modifyvm", env[:machine].id, "--natdnshostresolver1", "off" ]
attempt_and_log(command, "Disable the NAT DNS resolver on adapter 1...")
end
@ -69,7 +66,7 @@ module Vagrant
# @param [Array] command Command to run
# @param [String] log Log message to write.
def attempt_and_log(command, log)
result = @env[:vm].driver.execute_command(command)
result = @env[:machine].provider.driver.execute_command(command)
@logger.info("#{log} (exit status = #{result.exit_code})")
end
end

View File

@ -1,10 +1,6 @@
require 'pathname'
module Vagrant
module Action
module VM
# Sets up the mapping of files to copy into the package and verifies
# that the files can be properly copied.
module VagrantPlugins
module ProviderVirtualBox
module Action
class SetupPackageFiles
def initialize(app, env)
@app = app
@ -40,7 +36,8 @@ module Vagrant
# Verify the mapping
files.each do |from, _|
raise Errors::PackageIncludeMissing, :file => from if !File.exist?(from)
raise Vagrant::Errors::PackageIncludeMissing,
:file => from if !File.exist?(from)
end
# Save the mapping

View File

@ -1,10 +1,10 @@
require 'pathname'
require "pathname"
require 'log4r'
require "log4r"
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class ShareFolders
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::share_folders")
@ -25,7 +25,7 @@ module Vagrant
# This method returns an actual list of VirtualBox shared
# folders to create and their proper path.
def shared_folders
@env[:vm].config.vm.shared_folders.inject({}) do |acc, data|
@env[:machine].config.vm.shared_folders.inject({}) do |acc, data|
key, value = data
next acc if value[:disabled]
@ -50,7 +50,8 @@ module Vagrant
begin
hostpath.mkpath
rescue Errno::EACCES
raise Errors::SharedFolderCreateFailed, :path => hostpath.to_s
raise Vagrant::Errors::SharedFolderCreateFailed,
:path => hostpath.to_s
end
end
end
@ -68,7 +69,7 @@ module Vagrant
}
end
@env[:vm].driver.share_folders(folders)
@env[:machine].provider.driver.share_folders(folders)
end
def mount_shared_folders
@ -96,11 +97,11 @@ module Vagrant
data = data.dup
# Calculate the owner and group
data[:owner] ||= @env[:vm].config.ssh.username
data[:group] ||= @env[:vm].config.ssh.username
data[:owner] ||= @env[:machine].config.ssh.username
data[:group] ||= @env[:machine].config.ssh.username
# Mount the actual folder
@env[:vm].guest.mount_shared_folder(name, data[:guestpath], data)
@env[:machine].guest.mount_shared_folder(name, data[:guestpath], data)
else
# If no guest path is specified, then automounting is disabled
@env[:ui].info(I18n.t("vagrant.actions.vm.share_folders.nomount_entry",

View File

@ -1,15 +1,15 @@
module Vagrant
module Action
module VM
module VagrantPlugins
module ProviderVirtualBox
module Action
class Suspend
def initialize(app, env)
@app = app
end
def call(env)
if env[:vm].state == :running
if env[:machine].provider.state == :running
env[:ui].info I18n.t("vagrant.actions.vm.suspend.suspending")
env[:vm].driver.suspend
env[:machine].provider.driver.suspend
end
@app.call(env)

View File

@ -0,0 +1,327 @@
require 'log4r'
require 'vagrant/util/busy'
require 'vagrant/util/platform'
require 'vagrant/util/retryable'
require 'vagrant/util/subprocess'
module VagrantPlugins
module ProviderVirtualBox
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 Base
# Include this so we can use `Subprocess` more easily.
include Vagrant::Util::Retryable
def initialize
@logger = Log4r::Logger.new("vagrant::provider::virtualbox::base")
# This flag is used to keep track of interrupted state (SIGINT)
@interrupted = false
# Set the path to VBoxManage
@vboxmanage_path = "VBoxManage"
if Vagrant::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")
# Get the path.
path = ENV["VBOX_INSTALL_PATH"]
@logger.debug("VBOX_INSTALL_PATH value: #{path}")
# There can actually be multiple paths in here, so we need to
# split by the separator ";" and see which is a good one.
path.split(";").each do |single|
# Make sure it ends with a \
single += "\\" if !single.end_with?("\\")
# If the executable exists, then set it as the main path
# and break out
vboxmanage = "#{path}VBoxManage.exe"
if File.file?(vboxmanage)
@vboxmanage_path = vboxmanage
break
end
end
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 DHCP server for a host only network.
#
# @param [String] network Name of the host-only network.
# @param [Hash] options Options for the DHCP server.
def create_dhcp_server(network, options)
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<Hash>] 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<Hash>] 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.
# @return [String] UUID of the imported VM.
def import(ovf)
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<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
# Returns a list of all UUIDs of virtual machines currently
# known by VirtualBox.
#
# @return [Array<String>]
def read_vms
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<Hash>] 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 the driver is ready to accept work.
#
# This should raise a VagrantError if things are not ready.
def verify!
end
# Verifies that an image can be imported properly.
#
# @param [String] path Path to an OVF file.
# @return [Boolean]
def verify_image(path)
end
# Checks if a VM with the given UUID exists.
#
# @return [Boolean]
def vm_exists?(uuid)
end
# Execute the given subcommand for VBoxManage and return the output.
def execute(*command, &block)
# Get the options hash if it exists
opts = {}
opts = command.pop if command.last.is_a?(Hash)
tries = 0
tries = 3 if opts[:retryable]
# Variable to store our execution result
r = nil
retryable(:on => Vagrant::Errors::VBoxManageError, :tries => tries, :sleep => 1) do
# 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 Vagrant::Errors::VBoxManageError, :command => command.inspect
end
else
# Sometimes, VBoxManage fails but doesn't actual return a non-zero
# exit code. For this we inspect the output and determine if an error
# occurred.
if r.stderr =~ /VBoxManage: error:/
@logger.info("VBoxManage error text found, assuming error.")
raise Vagrant::Errors::VBoxManageError, :command => command.inspect
end
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
# Append in the options for subprocess
command << { :notify => [:stdout, :stderr] }
Vagrant::Util::Busy.busy(int_callback) do
Vagrant::Util::Subprocess.execute(@vboxmanage_path, *command, &block)
end
end
end
end
end
end

View File

@ -0,0 +1,139 @@
require "forwardable"
require "log4r"
require File.expand_path("../base", __FILE__)
module VagrantPlugins
module ProviderVirtualBox
module Driver
class Meta < Base
# This is raised if the VM is not found when initializing a driver
# with a UUID.
class VMNotFound < StandardError; end
# We use forwardable to do all our driver forwarding
extend Forwardable
# The UUID of the virtual machine we represent
attr_reader :uuid
# The version of virtualbox that is running.
attr_reader :version
def initialize(uuid=nil)
# Setup the base
super()
@logger = Log4r::Logger.new("vagrant::provider::virtualbox::meta")
@uuid = uuid
# Read and assign the version of VirtualBox we know which
# specific driver to instantiate.
begin
@version = read_version || ""
rescue Vagrant::Util::Subprocess::LaunchError
# This means that VirtualBox was not found, so we raise this
# error here.
raise Vagrant::Errors::VirtualBoxNotDetected
end
# Instantiate the proper version driver for VirtualBox
@logger.debug("Finding driver for VirtualBox version: #{@version}")
driver_map = {
"4.0" => Version_4_0,
"4.1" => Version_4_1
}
driver_klass = nil
driver_map.each do |key, klass|
if @version.start_with?(key)
driver_klass = klass
break
end
end
if !driver_klass
supported_versions = driver_map.keys.sort.join(", ")
raise Vagrant::Errors::VirtualBoxInvalidVersion, :supported_versions => supported_versions
end
@logger.info("Using VirtualBox driver: #{driver_klass}")
@driver = driver_klass.new(@uuid)
if @uuid
# Verify the VM exists, and if it doesn't, then don't worry
# about it (mark the UUID as nil)
raise VMNotFound if !@driver.vm_exists?(@uuid)
end
end
def_delegators :@driver, :clear_forwarded_ports,
:clear_shared_folders,
:create_dhcp_server,
:create_host_only_network,
:delete,
:delete_unused_host_only_networks,
:discard_saved_state,
:enable_adapters,
:execute_command,
:export,
:forward_ports,
:halt,
:import,
:read_forwarded_ports,
:read_bridged_interfaces,
:read_guest_additions_version,
:read_host_only_interfaces,
:read_mac_address,
:read_mac_addresses,
:read_machine_folder,
:read_network_interfaces,
:read_state,
:read_used_ports,
:read_vms,
:set_mac_address,
:set_name,
:share_folders,
:ssh_port,
:start,
:suspend,
:verify!,
:verify_image,
:vm_exists?
protected
# This returns the version of VirtualBox that is running.
#
# @return [String]
def read_version
# The version string is usually in one of the following formats:
#
# * 4.1.8r1234
# * 4.1.8r1234_OSE
# * 4.1.8_MacPortsr1234
#
# Below accounts for all of these.
# Note: We split this into multiple lines because apparently "".split("_")
# is [], so we have to check for an empty array in between.
output = execute("--version")
if output =~ /vboxdrv kernel module is not loaded/
raise Vagrant::Errors::VirtualBoxKernelModuleNotLoaded
elsif output =~ /Please install/
# Check for installation incomplete warnings, for example:
# "WARNING: The character device /dev/vboxdrv does not
# exist. Please install the virtualbox-ose-dkms package and
# the appropriate headers, most likely linux-headers-generic."
raise Vagrant::Errors::VirtualBoxInstallIncomplete
end
parts = output.split("_")
return nil if parts.empty?
parts[0].split("r")[0]
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More