When preparing the NFS settings on VirtualBox the guest IP addresses are pulled from VirtualBox directly and any static addresses are pulled as well. This can lead to aquiring a host IP and machine IP but results in a failure of NFS mount because the IPs are not on the same network. This filters the machine IP result to validate it is within the host adapter IP range.
160 lines
5.5 KiB
Ruby
160 lines
5.5 KiB
Ruby
require "ipaddr"
|
|
require "vagrant/action/builtin/mixin_synced_folders"
|
|
|
|
module VagrantPlugins
|
|
module ProviderVirtualBox
|
|
module Action
|
|
class PrepareNFSSettings
|
|
include Vagrant::Action::Builtin::MixinSyncedFolders
|
|
include Vagrant::Util::Retryable
|
|
|
|
def initialize(app, env)
|
|
@app = app
|
|
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
|
end
|
|
|
|
def call(env)
|
|
@machine = env[:machine]
|
|
|
|
@app.call(env)
|
|
|
|
opts = {
|
|
cached: !!env[:synced_folders_cached],
|
|
config: env[:synced_folders_config],
|
|
disable_usable_check: !!env[:test],
|
|
}
|
|
folders = synced_folders(env[:machine], **opts)
|
|
|
|
if folders.key?(:nfs)
|
|
@logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP")
|
|
add_ips_to_env!(env)
|
|
end
|
|
end
|
|
|
|
# Extracts the proper host and guest IPs for NFS mounts and stores them
|
|
# in the environment for the SyncedFolder action to use them in
|
|
# mounting.
|
|
#
|
|
# The ! indicates that this method modifies its argument.
|
|
def add_ips_to_env!(env)
|
|
adapter, host_ip = find_host_only_adapter
|
|
machine_ip = read_static_machine_ips
|
|
|
|
if !machine_ip
|
|
# No static IP, attempt to use the dynamic IP.
|
|
machine_ip = read_dynamic_machine_ip(adapter)
|
|
else
|
|
# We have static IPs, also attempt to read any dynamic IPs.
|
|
# If there is no dynamic IP on the adapter, it doesn't matter. We
|
|
# already have a static IP.
|
|
begin
|
|
dynamic_ip = read_dynamic_machine_ip(adapter)
|
|
rescue Vagrant::Errors::NFSNoGuestIP
|
|
dynamic_ip = nil
|
|
end
|
|
|
|
# If we found a dynamic IP and we didn't include it in the
|
|
# machine_ip array yet, do so.
|
|
if dynamic_ip && !machine_ip.include?(dynamic_ip)
|
|
machine_ip.push(dynamic_ip)
|
|
end
|
|
end
|
|
|
|
if host_ip && !machine_ip.empty?
|
|
interface = @machine.provider.driver.read_host_only_interfaces.detect do |iface|
|
|
iface[:ip] == host_ip
|
|
end
|
|
host_ipaddr = IPAddr.new("#{host_ip}/#{interface.fetch(:netmask, "0.0.0.0")}")
|
|
|
|
case machine_ip
|
|
when String
|
|
machine_ip = nil if !host_ipaddr.include?(machine_ip)
|
|
when Array
|
|
machine_ip.delete_if do |m_ip|
|
|
!host_ipaddr.include?(m_ip)
|
|
end
|
|
machine_ip = nil if machine_ip.empty?
|
|
end
|
|
end
|
|
|
|
raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip
|
|
|
|
env[:nfs_host_ip] = host_ip
|
|
env[:nfs_machine_ip] = machine_ip
|
|
end
|
|
|
|
# Finds first host only network adapter and returns its adapter number
|
|
# and IP address
|
|
#
|
|
# @return [Integer, String] adapter number, ip address of found host-only adapter
|
|
def find_host_only_adapter
|
|
@machine.provider.driver.read_network_interfaces.each do |adapter, opts|
|
|
if opts[:type] == :hostonly
|
|
@machine.provider.driver.read_host_only_interfaces.each do |interface|
|
|
if interface[:name] == opts[:hostonly]
|
|
return adapter, interface[:ip]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
# Returns the IP address(es) of the guest by looking for static IPs
|
|
# given to host only adapters in the Vagrantfile
|
|
#
|
|
# @return [Array]<String> Configured static IPs
|
|
def read_static_machine_ips
|
|
ips = []
|
|
@machine.config.vm.networks.each do |type, options|
|
|
options = scoped_hash_override(options, :virtualbox)
|
|
|
|
if type == :private_network && options[:type] != :dhcp && options[:ip].is_a?(String)
|
|
ips << options[:ip]
|
|
end
|
|
end
|
|
|
|
if ips.empty?
|
|
return nil
|
|
end
|
|
|
|
ips
|
|
end
|
|
|
|
# Returns the IP address of the guest by looking at vbox guest property
|
|
# for the appropriate guest adapter.
|
|
#
|
|
# For DHCP interfaces, the guest property will not be present until the
|
|
# guest completes
|
|
#
|
|
# @param [Integer] adapter number to read IP for
|
|
# @return [String] ip address of adapter
|
|
def read_dynamic_machine_ip(adapter)
|
|
return nil unless adapter
|
|
|
|
# vbox guest properties are 0-indexed, while showvminfo network
|
|
# interfaces are 1-indexed. go figure.
|
|
guestproperty_adapter = adapter - 1
|
|
|
|
# we need to wait for the guest's IP to show up as a guest property.
|
|
# retry thresholds are relatively high since we might need to wait
|
|
# for DHCP, but even static IPs can take a second or two to appear.
|
|
retryable(retry_options.merge(on: Vagrant::Errors::VirtualBoxGuestPropertyNotFound)) do
|
|
@machine.provider.driver.read_guest_ip(guestproperty_adapter)
|
|
end
|
|
rescue Vagrant::Errors::VirtualBoxGuestPropertyNotFound
|
|
# this error is more specific with a better error message directing
|
|
# the user towards the fact that it's probably a reportable bug
|
|
raise Vagrant::Errors::NFSNoGuestIP
|
|
end
|
|
|
|
# Separating these out so we can stub out the sleep in tests
|
|
def retry_options
|
|
{tries: 15, sleep: 1}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|