Move NFS to a built-in middleware
This commit is contained in:
parent
a76556f3eb
commit
6afb4326fa
|
@ -16,6 +16,7 @@ module Vagrant
|
|||
autoload :GracefulHalt, "vagrant/action/builtin/graceful_halt"
|
||||
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
|
||||
autoload :Lock, "vagrant/action/builtin/lock"
|
||||
autoload :NFS, "vagrant/action/builtin/nfs"
|
||||
autoload :Provision, "vagrant/action/builtin/provision"
|
||||
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
|
||||
autoload :SSHRun, "vagrant/action/builtin/ssh_run"
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
require 'digest/md5'
|
||||
require 'fileutils'
|
||||
require 'pathname'
|
||||
|
||||
require "log4r"
|
||||
|
||||
module Vagrant
|
||||
module Action
|
||||
module Builtin
|
||||
# This built-in middleware exports and mounts NFS shared folders.
|
||||
#
|
||||
# To use this middleware, two configuration parameters must be given
|
||||
# via the environment hash:
|
||||
#
|
||||
# - `:nfs_host_ip` - The IP of where to mount the NFS folder from.
|
||||
# - `:nfs_machine_ip` - The IP of the machine where the NFS folder
|
||||
# will be mounted.
|
||||
#
|
||||
class NFS
|
||||
def initialize(app, env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new("vagrant::action::builtin::nfs")
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# We forward things along first. We do everything on the tail
|
||||
# end of the middleware call.
|
||||
@app.call(env)
|
||||
|
||||
# Used by prepare_permission, so we need to save it
|
||||
@env = env
|
||||
|
||||
raise Errors::NFSNoHostIP if !env[:nfs_host_ip]
|
||||
raise Errors::NFSNoGuestIP if !env[:nfs_machine_ip]
|
||||
|
||||
folders = {}
|
||||
env[:machine].config.vm.synced_folders.each do |id, opts|
|
||||
# If this synced folder doesn't enable NFS, ignore it.
|
||||
next if !opts[:nfs]
|
||||
|
||||
# Expand the host path, create it if we have to and
|
||||
# store away the folder.
|
||||
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 back on the options and save it
|
||||
opts[:hostpath] = hostpath.to_s
|
||||
folders[id] = opts
|
||||
end
|
||||
|
||||
if !folders.empty?
|
||||
# Prepare the folder, this means setting up various options
|
||||
# and such on the folder itself.
|
||||
folders.each { |id, opts| prepare_folder(opts) }
|
||||
|
||||
# Export the folders
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.nfs.exporting")
|
||||
env[:host].nfs_export(env[:machine].id, env[:nfs_machine_ip], folders)
|
||||
|
||||
# Mount
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.nfs.mounting")
|
||||
|
||||
# Only mount folders that have a guest path specified.
|
||||
mount_folders = {}
|
||||
folders.each do |id, opts|
|
||||
mount_folders[id] = opts.dup if opts[:guestpath]
|
||||
end
|
||||
|
||||
# Mount them!
|
||||
env[:machine].guest.mount_nfs(env[:nfs_host_ip], mount_folders)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def prepare_folder(opts)
|
||||
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])
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -251,16 +251,12 @@ module Vagrant
|
|||
error_key(:not_found, "vagrant.actions.vm.host_only_network")
|
||||
end
|
||||
|
||||
class NFSHostRequired < VagrantError
|
||||
error_key(:host_required, "vagrant.actions.vm.nfs")
|
||||
class NFSNoGuestIP < VagrantError
|
||||
error_key(:nfs_no_guest_ip)
|
||||
end
|
||||
|
||||
class NFSNotSupported < VagrantError
|
||||
error_key(:not_supported, "vagrant.actions.vm.nfs")
|
||||
end
|
||||
|
||||
class NFSNoHostNetwork < VagrantError
|
||||
error_key(:no_host_network, "vagrant.actions.vm.nfs")
|
||||
class NFSNoHostIP < VagrantError
|
||||
error_key(:nfs_no_host_ip)
|
||||
end
|
||||
|
||||
class NoEnvironmentError < VagrantError
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
require 'log4r'
|
||||
|
||||
require "vagrant"
|
||||
require "vagrant/util/retryable"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestLinux
|
||||
class Guest < Vagrant.plugin("2", :guest)
|
||||
include Vagrant::Util::Retryable
|
||||
|
||||
class LinuxError < Vagrant::Errors::VagrantError
|
||||
error_namespace("vagrant.guest.linux")
|
||||
end
|
||||
|
@ -55,9 +58,12 @@ module VagrantPlugins
|
|||
|
||||
# Do the actual creating and mounting
|
||||
@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)
|
||||
|
||||
retryable(:on => LinuxError, :tries => 3, :sleep => 1) do
|
||||
@vm.communicate.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
|
||||
:error_class => LinuxError,
|
||||
:error_key => :mount_nfs_fail)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -195,6 +195,7 @@ module VagrantPlugins
|
|||
errors << I18n.t("vagrant.config.vm.box_not_found", :name => box) if \
|
||||
box && !box_url && !machine.box
|
||||
|
||||
has_nfs = false
|
||||
@synced_folders.each do |id, options|
|
||||
hostpath = Pathname.new(options[:hostpath]).expand_path(machine.env.root_path)
|
||||
|
||||
|
@ -203,10 +204,23 @@ module VagrantPlugins
|
|||
:path => options[:hostpath])
|
||||
end
|
||||
|
||||
if options[:nfs] && (options[:owner] || options[:group])
|
||||
# Owner/group don't work with NFS
|
||||
errors << I18n.t("vagrant.config.vm.shared_folder_nfs_owner_group",
|
||||
:path => options[:hostpath])
|
||||
if options[:nfs]
|
||||
has_nfs = true
|
||||
|
||||
if options[:owner] || options[:group]
|
||||
# Owner/group don't work with NFS
|
||||
errors << I18n.t("vagrant.config.vm.shared_folder_nfs_owner_group",
|
||||
:path => options[:hostpath])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if has_nfs
|
||||
if !machine.env.host
|
||||
errors << I18n.t("vagrant.config.vm.nfs_requires_host")
|
||||
else
|
||||
errors << I18n.t("vagrant.config.vm.nfs_not_supported") if \
|
||||
!machine.env.host.nfs?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -33,9 +33,9 @@ module VagrantPlugins
|
|||
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 :PrepareNFSSettings, File.expand_path("../action/prepare_nfs_settings", __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__)
|
||||
|
@ -61,6 +61,7 @@ module VagrantPlugins
|
|||
b.use CheckPortCollisions
|
||||
b.use PruneNFSExports
|
||||
b.use NFS
|
||||
b.use PrepareNFSSettings
|
||||
b.use ClearSharedFolders
|
||||
b.use ShareFolders
|
||||
b.use ClearNetworkInterfaces
|
||||
|
|
|
@ -1,185 +0,0 @@
|
|||
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.synced_folders.each do |id, 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[id] = 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|
|
||||
id, 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[id] = 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 == :private_network && 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.synced_folders.each do |id, options|
|
||||
return true if options[: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
|
|
@ -0,0 +1,51 @@
|
|||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
module Action
|
||||
class PrepareNFSSettings
|
||||
def initialize(app,env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@app.call(env)
|
||||
|
||||
env[:nfs_host_ip] = read_host_ip(env[:machine])
|
||||
env[:nfs_machine_ip] = read_machine_ip(env[:machine])
|
||||
end
|
||||
|
||||
# Returns the IP address of the first host only network adapter
|
||||
#
|
||||
# @param [Machine] machine
|
||||
# @return [String]
|
||||
def read_host_ip(machine)
|
||||
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 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 read_machine_ip(machine)
|
||||
machine.config.vm.networks.each do |type, args|
|
||||
if type == :private_network && args[0].is_a?(String)
|
||||
return args[0]
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -27,7 +27,8 @@ module VagrantPlugins
|
|||
def shared_folders
|
||||
{}.tap do |result|
|
||||
@env[:machine].config.vm.synced_folders.each do |id, data|
|
||||
next if data[:disabled]
|
||||
# Ignore NFS shared folders
|
||||
next if data[:nfs]
|
||||
|
||||
# This to prevent overwriting the actual shared folders data
|
||||
result[id] = data.dup
|
||||
|
|
|
@ -162,6 +162,12 @@ en:
|
|||
A multi-vm environment is required for name specification to this command.
|
||||
multi_vm_target_required: |-
|
||||
This command requires a specific VM name to target in a multi-VM environment.
|
||||
nfs_no_guest_ip: |-
|
||||
No guest IP was given to the Vagrant core NFS helper. This is an
|
||||
internal error that should be reported as a bug.
|
||||
nfs_no_host_ip: |-
|
||||
No host IP was given to the Vagrant core NFS helper. This is
|
||||
an internal error that should be reported as a bug.
|
||||
no_env: |-
|
||||
A Vagrant environment is required to run this command. Run `vagrant init`
|
||||
to set one up in this directory, or change to a directory with a
|
||||
|
@ -406,6 +412,15 @@ en:
|
|||
network_ip_ends_one: |-
|
||||
The host only network IP '%{ip}' must not end in a 1, as this
|
||||
is reserved for the host machine.
|
||||
nfs_not_supported: |-
|
||||
It appears your machine doesn't support NFS, or there is not an
|
||||
adapter to enable NFS on this machine for Vagrant. Please verify
|
||||
that `nfsd` is installed on your machine, and try again. If you're
|
||||
on Windows, NFS isn't supported. If the problem persists, please
|
||||
contact Vagrant support.
|
||||
nfs_requires_host: |-
|
||||
Using NFS shared folders requires a host to be specified
|
||||
using `config.vagrant.host`.
|
||||
shared_folder_hostpath_missing: |-
|
||||
The host path of the shared folder is missing: %{path}
|
||||
shared_folder_nfs_owner_group: |-
|
||||
|
@ -697,18 +712,6 @@ en:
|
|||
specified network manually.
|
||||
preparing: "Preparing host only network..."
|
||||
nfs:
|
||||
host_required: |-
|
||||
A host class is required for NFS shared folders. By default, these
|
||||
are auto-detected, but can be overridden with `config.vagrant.host`.
|
||||
There is currently no host class loaded.
|
||||
no_host_network: |-
|
||||
NFS shared folders requires that host only networking is enabled
|
||||
with a static IP. Please enable host only network and assign a
|
||||
static IP via `config.vm.network`.
|
||||
not_supported: |-
|
||||
The host class is reporting that NFS is not supported by this host,
|
||||
or `nfsd` may not be installed. Please verify that `nfsd` is installed
|
||||
on your machine, and retry.
|
||||
exporting: Exporting NFS shared folders...
|
||||
mounting: Mounting NFS shared folders...
|
||||
persist:
|
||||
|
|
Loading…
Reference in New Issue