`vagrant reload` now works with the new machine abstraction

This commit is contained in:
Mitchell Hashimoto 2012-08-14 21:12:41 -07:00
parent 85a499ffb8
commit 7aa083d259
13 changed files with 1118 additions and 13 deletions

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

@ -10,17 +10,28 @@ module VagrantPlugins
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 :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 :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 :MessageNotCreated, File.expand_path("../action/message_not_created", __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 :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 :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
@ -74,6 +85,25 @@ module VagrantPlugins
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
@ -113,6 +143,31 @@ module VagrantPlugins
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 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 suspending
# the virtual machine.
def self.action_suspend

View File

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

View File

@ -0,0 +1,31 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class ClearNetworkInterfaces
def initialize(app, env)
@app = app
end
def call(env)
# Create the adapters array to make all adapters nothing.
# We do adapters 2 to 8 because that is every built-in adapter
# excluding the NAT adapter on port 1 which Vagrant always
# expects to exist.
adapters = []
2.upto(8).each do |i|
adapters << {
:adapter => i,
:type => :none
}
end
# "Enable" all the adapters we setup.
env[:ui].info I18n.t("vagrant.actions.vm.clear_network_interfaces.deleting")
env[:machine].provider.driver.enable_adapters(adapters)
@app.call(env)
end
end
end
end
end

View File

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

View File

@ -0,0 +1,36 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class Customize
def initialize(app, env)
@app = app
end
def call(env)
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[:machine].id if arg == :id
arg.to_s
end
result = env[:machine].provider.driver.execute_command(processed_command)
if result.exit_code != 0
raise Errors::VMCustomizationFailed, {
:command => processed_command.inspect,
:error => result.stderr
}
end
end
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,92 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class ForwardPorts
def initialize(app, env)
@app = app
end
#--------------------------------------------------------------
# Execution
#--------------------------------------------------------------
def call(env)
@env = env
# Get the ports we're forwarding
ports = forward_port_definitions
# Warn if we're port forwarding to any privileged ports...
threshold_check(ports)
env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding")
forward_ports(ports)
@app.call(env)
end
# This returns an array of forwarded ports with overrides properly
# squashed.
def forward_port_definitions
# Get all the port mappings in the order they're defined and
# organize them by their guestport, taking the "last one wins"
# approach.
guest_port_mapping = {}
@env[:machine].config.vm.forwarded_ports.each do |options|
guest_port_mapping[options[:guestport]] = options
end
# Return the values, since the order doesn't really matter
guest_port_mapping.values
end
# This method checks for any forwarded ports on the host below
# 1024, which causes the forwarded ports to fail.
def threshold_check(ports)
ports.each do |options|
if options[:hostport] <= 1024
@env[:ui].warn I18n.t("vagrant.actions.vm.forward_ports.privileged_ports")
return
end
end
end
def forward_ports(mappings)
ports = []
interfaces = @env[:machine].provider.driver.read_network_interfaces
mappings.each do |options|
message_attributes = {
:guest_port => options[:guestport],
:host_port => options[:hostport],
:adapter => options[:adapter]
}
# Assuming the only reason to establish port forwarding is
# because the VM is using Virtualbox NAT networking. Host-only
# bridged networking don't require port-forwarding and establishing
# forwarded ports on these attachment types has uncertain behaviour.
@env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry",
message_attributes))
# Port forwarding requires the network interface to be a NAT interface,
# so verify that that is the case.
if interfaces[options[:adapter]][:type] != :nat
@env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.non_nat",
message_attributes))
next
end
# Add the options to the ports array to send to the driver later
ports << options.merge(:name => options[:name], :adapter => options[:adapter])
end
if !ports.empty?
# We only need to forward ports if there are any to forward
@env[:machine].provider.driver.forward_ports(ports)
end
end
end
end
end
end

View File

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

View File

@ -0,0 +1,404 @@
require "set"
require "log4r"
require "vagrant/util/network_ip"
module VagrantPlugins
module ProviderVirtualBox
module Action
class Network
# Utilities to deal with network addresses
include Vagrant::Util::NetworkIP
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::network")
@app = app
end
def call(env)
@env = env
# First we have to get the array of adapters that we need
# to create on the virtual machine itself, as well as the
# driver-agnostic network configurations for each.
@logger.debug("Determining adapters and networks...")
adapters = []
networks = []
env[:machine].config.vm.networks.each do |type, args|
# Get the normalized configuration we'll use around
config = send("#{type}_config", args)
# Get the virtualbox adapter configuration
adapter = send("#{type}_adapter", config)
adapters << adapter
# Get the network configuration
network = send("#{type}_network_config", config)
network[:_auto_config] = true if config[:auto_config]
networks << network
end
if !adapters.empty?
# Automatically assign an adapter number to any adapters
# that aren't explicitly set.
@logger.debug("Assigning adapter locations...")
assign_adapter_locations(adapters)
# Verify that our adapters are good just prior to enabling them.
verify_adapters(adapters)
# Create all the network interfaces
@logger.info("Enabling adapters...")
env[:ui].info I18n.t("vagrant.actions.vm.network.preparing")
env[:machine].provider.driver.enable_adapters(adapters)
end
# Continue the middleware chain. We're done with our VM
# setup until after it is booted.
@app.call(env)
if !adapters.empty? && !networks.empty?
# Determine the interface numbers for the guest.
assign_interface_numbers(networks, adapters)
# Configure all the network interfaces on the guest. We only
# 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[:machine].guest.configure_networks(networks_to_configure)
end
end
# This method assigns the adapter to use for the adapter.
# e.g. it says that the first adapter is actually on the
# virtual machine's 2nd adapter location.
#
# It determines the adapter numbers by simply finding the
# "next available" in each case.
#
# The adapters are modified in place by adding an ":adapter"
# field to each.
def assign_adapter_locations(adapters)
available = Set.new(1..8)
# Determine which NICs are actually available.
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.
available.delete(number) if nic[:type] != :none
end
# Based on the available set, assign in order to
# the adapters.
available = available.to_a.sort
@logger.debug("Available NICs: #{available.inspect}")
adapters.each do |adapter|
# Ignore the adapters that already have been assigned
if !adapter[:adapter]
# If we have no available adapters, then that is an exceptional
# event.
raise Vagrant::Errors::NetworkNoAdapters if available.empty?
# Otherwise, assign as the adapter the next available item
adapter[:adapter] = available.shift
end
end
end
# Verifies that the adapter configurations look good. This will
# raise an exception in the case that any errors occur.
def verify_adapters(adapters)
# Verify that there are no collisions in the adapters being used.
used = Set.new
adapters.each do |adapter|
raise Vagrant::Errors::NetworkAdapterCollision if used.include?(adapter[:adapter])
used.add(adapter[:adapter])
end
end
# Assigns the actual interface number of a network based on the
# enabled NICs on the virtual machine.
#
# This interface number is used by the guest to configure the
# NIC on the guest VM.
#
# The networks are modified in place by adding an ":interface"
# field to each.
def assign_interface_numbers(networks, adapters)
current = 0
adapter_to_interface = {}
# Make a first pass to assign interface numbers by adapter location
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
adapter_to_interface[number] = current
current += 1
end
end
# Make a pass through the adapters to assign the :interface
# key to each network configuration.
adapters.each_index do |i|
adapter = adapters[i]
network = networks[i]
# Figure out the interface number by simple lookup
network[:interface] = adapter_to_interface[adapter[:adapter]]
end
end
def hostonly_config(args)
ip = args[0]
options = args[1] || {}
# Determine if we're dealing with a static IP or a DHCP-served IP.
type = ip == :dhcp ? :dhcp : :static
# Default IP is in the 20-bit private network block for DHCP based networks
ip = "172.28.128.1" if type == :dhcp
options = {
:type => type,
:ip => ip,
:netmask => "255.255.255.0",
:adapter => nil,
:mac => nil,
:name => nil,
:auto_config => true
}.merge(options)
# Verify that this hostonly network wouldn't conflict with any
# bridged interfaces
verify_no_bridge_collision(options)
# Get the network address and IP parts which are used for many
# default calculations
netaddr = network_address(options[:ip], options[:netmask])
ip_parts = netaddr.split(".").map { |i| i.to_i }
# Calculate the adapter IP, which we assume is the IP ".1" at the
# end usually.
adapter_ip = ip_parts.dup
adapter_ip[3] += 1
options[:adapter_ip] ||= adapter_ip.join(".")
if type == :dhcp
# Calculate the DHCP server IP, which is the network address
# with the final octet + 2. So "172.28.0.0" turns into "172.28.0.2"
dhcp_ip = ip_parts.dup
dhcp_ip[3] += 2
options[:dhcp_ip] ||= dhcp_ip.join(".")
# Calculate the lower and upper bound for the DHCP server
dhcp_lower = ip_parts.dup
dhcp_lower[3] += 3
options[:dhcp_lower] ||= dhcp_lower.join(".")
dhcp_upper = ip_parts.dup
dhcp_upper[3] = 254
options[:dhcp_upper] ||= dhcp_upper.join(".")
end
# Return the hostonly network configuration
return options
end
def hostonly_adapter(config)
@logger.debug("Searching for matching network: #{config[:ip]}")
interface = find_matching_hostonly_network(config)
if !interface
@logger.debug("Network not found. Creating if we can.")
# It is an error case if a specific name was given but the network
# doesn't exist.
if config[:name]
raise Vagrant::Errors::NetworkNotFound, :name => config[:name]
end
# Otherwise, we create a new network and put the net network
# in the list of available networks so other network definitions
# can use it!
interface = create_hostonly_network(config)
@logger.debug("Created network: #{interface[:name]}")
end
if config[:type] == :dhcp
# Check that if there is a DHCP server attached on our interface,
# then it is identical. Otherwise, we can't set it.
if interface[:dhcp]
valid = interface[:dhcp][:ip] == config[:dhcp_ip] &&
interface[:dhcp][:lower] == config[:dhcp_lower] &&
interface[:dhcp][:upper] == config[:dhcp_upper]
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[:machine].provider.driver.create_dhcp_server(interface[:name], config)
end
end
return {
:adapter => config[:adapter],
:type => :hostonly,
:hostonly => interface[:name],
:mac_address => config[:mac],
:nic_type => config[:nic_type]
}
end
def hostonly_network_config(config)
return {
:type => config[:type],
:adapter_ip => config[:adapter_ip],
:ip => config[:ip],
:netmask => config[:netmask]
}
end
# Creates a new hostonly network that matches the network requested
# by the given host-only network configuration.
def create_hostonly_network(config)
# Create the options that are going to be used to create our
# new network.
options = config.dup
options[:ip] = options[:adapter_ip]
@env[:machine].provider.driver.create_host_only_network(options)
end
# Finds a host only network that matches our configuration on VirtualBox.
# This will return nil if a matching network does not exist.
def find_matching_hostonly_network(config)
this_netaddr = network_address(config[:ip], config[:netmask])
@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])
return interface
end
end
nil
end
# Verifies that a host-only network subnet would not collide with
# a bridged networking interface.
#
# If the subnets overlap in any way then the host only network
# will not work because the routing tables will force the traffic
# onto the real interface rather than the virtualbox interface.
def verify_no_bridge_collision(options)
this_netaddr = network_address(options[:ip], options[:netmask])
@env[:machine].provider.driver.read_bridged_interfaces.each do |interface|
that_netaddr = network_address(interface[:ip], interface[:netmask])
raise Vagrant::Errors::NetworkCollision if this_netaddr == that_netaddr && interface[:status] != "Down"
end
end
def bridged_config(args)
options = args[0] || {}
options = {} if !options.is_a?(Hash)
return {
:adapter => nil,
:mac => nil,
:bridge => nil,
:auto_config => true,
:use_dhcp_assigned_default_route => false
}.merge(options)
end
def bridged_adapter(config)
# Find the bridged interfaces that are available
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
# variable.
chosen_bridge = nil
if config[:bridge]
@logger.debug("Bridge was directly specified in config, searching for: #{config[:bridge]}")
# Search for a matching bridged interface
bridgedifs.each do |interface|
if interface[:name].downcase == config[:bridge].downcase
@logger.debug("Specific bridge found as configured in the Vagrantfile. Using it.")
chosen_bridge = interface[:name]
break
end
end
# If one wasn't found, then we notify the user here.
if !chosen_bridge
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.specific_not_found",
:bridge => config[:bridge])
end
end
# If we still don't have a bridge chosen (this means that one wasn't
# specified in the Vagrantfile, or the bridge specified in the Vagrantfile
# wasn't found), then we fall back to the normal means of searchign for a
# bridged network.
if !chosen_bridge
if bridgedifs.length == 1
# One bridgable interface? Just use it.
chosen_bridge = bridgedifs[0][:name]
@logger.debug("Only one bridged interface available. Using it by default.")
else
# More than one bridgable interface requires a user decision, so
# show options to choose from.
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.available",
:prefix => false)
bridgedifs.each_index do |index|
interface = bridgedifs[index]
@env[:ui].info("#{index + 1}) #{interface[:name]}", :prefix => false)
end
# The range of valid choices
valid = Range.new(1, bridgedifs.length)
# The choice that the user has chosen as the bridging interface
choice = nil
while !valid.include?(choice)
choice = @env[:ui].ask("What interface should the network bridge to? ")
choice = choice.to_i
end
chosen_bridge = bridgedifs[choice - 1][:name]
end
end
@logger.info("Bridging adapter #{config[:adapter]} to #{chosen_bridge}")
# Given the choice we can now define the adapter we're using
return {
:adapter => config[:adapter],
:type => :bridged,
:bridge => chosen_bridge,
:mac_address => config[:mac],
:nic_type => config[:nic_type]
}
end
def bridged_network_config(config)
return {
:type => :dhcp,
:use_dhcp_assigned_default_route => config[:use_dhcp_assigned_default_route]
}
end
end
end
end
end

View File

@ -0,0 +1,185 @@
require 'digest/md5'
require 'fileutils'
require 'pathname'
require 'log4r'
module VagrantPlugins
module ProviderVirtualBox
module Action
class NFS
def initialize(app,env)
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
@app = app
@env = env
verify_settings if nfs_enabled?
end
def call(env)
@env = env
extract_folders
if !folders.empty?
prepare_folders
export_folders
end
@app.call(env)
mount_folders if !folders.empty?
end
# Returns the folders which are to be synced via NFS.
def folders
@folders ||= {}
end
# Removes the NFS enabled shared folders from the configuration,
# so they will no longer be mounted by the actual shared folder
# task.
def extract_folders
# Load the NFS enabled shared folders
@folders = {}
@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.
folder = opts.dup
hostpath = Pathname.new(opts[:hostpath]).expand_path(@env[:root_path])
if !hostpath.directory? && opts[:create]
# Host path doesn't exist, so let's create it.
@logger.debug("Host path doesn't exist, creating: #{hostpath}")
begin
FileUtils.mkpath(hostpath)
rescue Errno::EACCES
raise Vagrant::Errors::SharedFolderCreateFailed,
:path => hostpath.to_s
end
end
# Set the hostpath now that it exists.
folder[:hostpath] = hostpath.to_s
# Assign the folder to our instance variable for later use
@folders[key] = folder
# Disable the folder so that regular shared folders don't try to
# mount it.
opts[:disabled] = true
end
end
end
# Prepares the settings for the NFS folders, such as setting the
# options on the NFS folders.
def prepare_folders
@folders = @folders.inject({}) do |acc, data|
key, opts = data
opts[:map_uid] = prepare_permission(:uid, opts)
opts[:map_gid] = prepare_permission(:gid, opts)
opts[:nfs_version] ||= 3
# The poor man's UUID. An MD5 hash here is sufficient since
# we need a 32 character "uuid" to represent the filesystem
# of an export. Hashing the host path is safe because two of
# the same host path will hash to the same fsid.
opts[:uuid] = Digest::MD5.hexdigest(opts[:hostpath])
acc[key] = opts
acc
end
end
# Prepares the UID/GID settings for a single folder.
def prepare_permission(perm, opts)
key = "map_#{perm}".to_sym
return nil if opts.has_key?(key) && opts[key].nil?
# The options on the hash get priority, then the default
# values
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
# (value == :auto)
stat = File.stat(opts[:hostpath])
return stat.send(perm)
end
# Uses the host class to export the folders via NFS. This typically
# involves adding a line to `/etc/exports` for this VM, but it is
# 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[:machine].id, guest_ip, folders)
end
# Uses the system class to mount the NFS folders.
def mount_folders
@env[:ui].info I18n.t("vagrant.actions.vm.nfs.mounting")
# Only mount the folders which have a guest path specified
mount_folders = {}
folders.each do |name, opts|
if opts[:guestpath]
mount_folders[name] = opts.dup
end
end
@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[:machine].provider.driver.read_network_interfaces.each do |adapter, opts|
if opts[:type] == :hostonly
@env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
if interface[:name] == opts[:hostonly]
return interface[:ip]
end
end
end
end
nil
end
# Returns the IP address of the guest by looking at the first
# enabled host only network.
#
# @return [String]
def guest_ip
@env[:machine].config.vm.networks.each do |type, args|
if type == :hostonly && args[0].is_a?(String)
return args[0]
end
end
nil
end
# Checks if there are any NFS enabled shared folders.
def nfs_enabled?
@env[:machine].config.vm.shared_folders.each do |key, opts|
return true if opts[:nfs]
end
false
end
# Verifies that the host is set and supports NFS.
def verify_settings
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
end
end

View File

@ -0,0 +1,62 @@
require "log4r"
module VagrantPlugins
module ProviderVirtualBox
module Action
class Provision
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::provision")
@app = app
env["provision.enabled"] = true if !env.has_key?("provision.enabled")
end
def call(env)
@env = env
provisioners = nil
# 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
provisioners.map { |p| p.prepare }
@app.call(env)
if enabled
# Take prepared provisioners and run the provisioning
provisioners.each do |instance|
@env[:ui].info I18n.t("vagrant.actions.vm.provision.beginning",
:provisioner => instance.class)
instance.provision!
end
end
end
def enabled_provisioners
enabled = []
@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
# specified.
if !@env["provision.types"].include?(provisioner.shortcut.to_s)
@logger.debug("Skipping provisioner: #{provisioner.shortcut}")
next
end
end
enabled << provisioner.provisioner.new(@env, provisioner.config)
end
# Return the enable provisioners
enabled
end
end
end
end
end

View File

@ -0,0 +1,75 @@
require "log4r"
module VagrantPlugins
module ProviderVirtualBox
module Action
class SaneDefaults
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::sanedefaults")
@app = app
end
def call(env)
# Set the env on an instance variable so we can access it in
# helpers.
@env = env
# Enable the host IO cache on the sata controller. Note that
# if this fails then its not a big deal, so we don't raise any
# errors. The Host IO cache vastly improves disk IO performance
# for VMs.
command = [
"storagectl", env[:machine].id,
"--name", "SATA Controller",
"--hostiocache", "on"
]
attempt_and_log(command, "Enabling the Host I/O cache on the SATA controller...")
enable_dns_proxy = true
begin
contents = File.read("/etc/resolv.conf")
if contents =~ /^nameserver 127\.0\.0\.1$/
# The use of both natdnsproxy and natdnshostresolver break on
# Ubuntu 12.04 that uses resolvconf with localhost. When used
# VirtualBox will give the client dns server 10.0.2.3, while
# not binding to that address itself. Therefore disable this
# feature if host uses the resolvconf server 127.0.0.1
@logger.info("Disabling DNS proxy since resolv.conf contains 127.0.0.1")
enable_dns_proxy = false
end
rescue Errno::ENOENT; end
# Enable/disable the NAT DNS proxy as necessary
if enable_dns_proxy
command = [
"modifyvm", env[:machine].id,
"--natdnsproxy1", "on"
]
attempt_and_log(command, "Enable the NAT DNS proxy on adapter 1...")
else
command = [ "modifyvm", env[:machine].id, "--natdnsproxy1", "off" ]
attempt_and_log(command, "Disable the NAT DNS proxy on adapter 1...")
command = [ "modifyvm", env[:machine].id, "--natdnshostresolver1", "off" ]
attempt_and_log(command, "Disable the NAT DNS resolver on adapter 1...")
end
@app.call(env)
end
protected
# This is just a helper method that executes a single command, logs
# the given string to the log, and also includes the exit status in
# the log message.
#
# @param [Array] command Command to run
# @param [String] log Log message to write.
def attempt_and_log(command, log)
result = @env[:machine].provider.driver.execute_command(command)
@logger.info("#{log} (exit status = #{result.exit_code})")
end
end
end
end
end

View File

@ -0,0 +1,115 @@
require "pathname"
require "log4r"
module VagrantPlugins
module ProviderVirtualBox
module Action
class ShareFolders
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant::action::vm::share_folders")
@app = app
end
def call(env)
@env = env
prepare_folders
create_metadata
@app.call(env)
mount_shared_folders
end
# This method returns an actual list of VirtualBox shared
# folders to create and their proper path.
def shared_folders
@env[:machine].config.vm.shared_folders.inject({}) do |acc, data|
key, value = data
next acc if value[:disabled]
# This to prevent overwriting the actual shared folders data
value = value.dup
acc[key] = value
acc
end
end
# Prepares the shared folders by verifying they exist and creating them
# if they don't.
def prepare_folders
shared_folders.each do |name, options|
hostpath = Pathname.new(options[:hostpath]).expand_path(@env[:root_path])
if !hostpath.directory? && options[:create]
# Host path doesn't exist, so let's create it.
@logger.debug("Host path doesn't exist, creating: #{hostpath}")
begin
hostpath.mkpath
rescue Errno::EACCES
raise Vagrant::Errors::SharedFolderCreateFailed,
:path => hostpath.to_s
end
end
end
end
def create_metadata
@env[:ui].info I18n.t("vagrant.actions.vm.share_folders.creating")
folders = []
shared_folders.each do |name, data|
folders << {
:name => name,
:hostpath => File.expand_path(data[:hostpath], @env[:root_path]),
:transient => data[:transient]
}
end
@env[:machine].provider.driver.share_folders(folders)
end
def mount_shared_folders
@env[:ui].info I18n.t("vagrant.actions.vm.share_folders.mounting")
# short guestpaths first, so we don't step on ourselves
folders = shared_folders.sort_by do |name, data|
if data[:guestpath]
data[:guestpath].length
else
# A long enough path to just do this at the end.
10000
end
end
# Go through each folder and mount
folders.each do |name, data|
if data[:guestpath]
# Guest path specified, so mount the folder to specified point
@env[:ui].info(I18n.t("vagrant.actions.vm.share_folders.mounting_entry",
:name => name,
:guest_path => data[:guestpath]))
# Dup the data so we can pass it to the guest API
data = data.dup
# Calculate the owner and group
data[:owner] ||= @env[:machine].config.ssh.username
data[:group] ||= @env[:machine].config.ssh.username
# Mount the actual folder
@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",
:name => name))
end
end
end
end
end
end
end