Merge branch 'pluggable-synced-folder'
This enables synced folder implementations to be completely standalone plugins and simplifies the way synced folders are enabled for providers by providing a standard `SyncedFolder` middleware that does everything. This is all much better tested than previously. The built-in synced folder types are now: * "virtualbox" - VirtualBox shared folders * "nfs" - NFS shared folders These can now easily be extended
This commit is contained in:
commit
14faccfd79
|
@ -101,6 +101,7 @@ module Vagrant
|
|||
c.register([:"2", :host]) { Plugin::V2::Host }
|
||||
c.register([:"2", :provider]) { Plugin::V2::Provider }
|
||||
c.register([:"2", :provisioner]) { Plugin::V2::Provisioner }
|
||||
c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder }
|
||||
end
|
||||
|
||||
# This returns a true/false showing whether we're running from the
|
||||
|
|
|
@ -18,12 +18,12 @@ module Vagrant
|
|||
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
|
||||
autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions"
|
||||
autoload :Lock, "vagrant/action/builtin/lock"
|
||||
autoload :NFS, "vagrant/action/builtin/nfs"
|
||||
autoload :Provision, "vagrant/action/builtin/provision"
|
||||
autoload :ProvisionerCleanup, "vagrant/action/builtin/provisioner_cleanup"
|
||||
autoload :SetHostname, "vagrant/action/builtin/set_hostname"
|
||||
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
|
||||
autoload :SSHRun, "vagrant/action/builtin/ssh_run"
|
||||
autoload :SyncedFolders, "vagrant/action/builtin/synced_folders"
|
||||
autoload :WaitForCommunicator, "vagrant/action/builtin/wait_for_communicator"
|
||||
end
|
||||
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
require 'fileutils'
|
||||
require 'pathname'
|
||||
require 'zlib'
|
||||
|
||||
require "log4r"
|
||||
|
||||
require 'vagrant/util/platform'
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# Determine the real path by expanding symlinks and getting
|
||||
# proper casing. We have to do this after creating it
|
||||
# if it doesn't exist.
|
||||
hostpath = hostpath.realpath
|
||||
hostpath = Util::Platform.fs_real_path(hostpath)
|
||||
|
||||
# Set the hostpath back on the options and save it
|
||||
opts[:hostpath] = hostpath.to_s
|
||||
folders[id] = opts
|
||||
end
|
||||
|
||||
if !folders.empty?
|
||||
raise Errors::NFSNoHostIP if !env[:nfs_host_ip]
|
||||
raise Errors::NFSNoGuestIP if !env[:nfs_machine_ip]
|
||||
|
||||
machine_ip = env[:nfs_machine_ip]
|
||||
machine_ip = [machine_ip] if !machine_ip.is_a?(Array)
|
||||
|
||||
# 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, 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.capability(
|
||||
:mount_nfs_folder, 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
|
||||
|
||||
# We use a CRC32 to generate a 32-bit checksum so that the
|
||||
# fsid is compatible with both old and new kernels.
|
||||
opts[:uuid] = Zlib.crc32(opts[:hostpath]).to_s
|
||||
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
|
|
@ -0,0 +1,165 @@
|
|||
require "log4r"
|
||||
|
||||
require 'vagrant/util/platform'
|
||||
|
||||
module Vagrant
|
||||
module Action
|
||||
module Builtin
|
||||
# This middleware will setup the synced folders for the machine using
|
||||
# the appropriate synced folder plugin.
|
||||
class SyncedFolders
|
||||
def initialize(app, env)
|
||||
@app = app
|
||||
@logger = Log4r::Logger.new("vagrant::action::builtin::synced_folders")
|
||||
end
|
||||
|
||||
def call(env)
|
||||
folders = synced_folders(env[:machine])
|
||||
|
||||
# Log all the folders that we have enabled and with what
|
||||
# implementation...
|
||||
folders.each do |impl, fs|
|
||||
@logger.info("Synced Folder Implementation: #{impl}")
|
||||
fs.each do |id, data|
|
||||
@logger.info(" - #{id}: #{data[:hostpath]} => #{data[:guestpath]}")
|
||||
end
|
||||
end
|
||||
|
||||
# Go through each folder and make sure to create it if
|
||||
# it does not exist on host
|
||||
folders.each do |impl, fs|
|
||||
fs.each do |id, data|
|
||||
data[:hostpath] = File.expand_path(data[:hostpath], env[:root_path])
|
||||
|
||||
# Create the hostpath if it doesn't exist and we've been told to
|
||||
if !File.directory?(data[:hostpath]) && data[:create]
|
||||
@logger.info("Creating shared folder host directory: #{data[:hostpath]}")
|
||||
begin
|
||||
Pathname.new(data[:hostpath]).mkpath
|
||||
rescue Errno::EACCES
|
||||
raise Vagrant::Errors::SharedFolderCreateFailed,
|
||||
path: data[:hostpath]
|
||||
end
|
||||
end
|
||||
|
||||
if File.directory?(data[:hostpath])
|
||||
data[:hostpath] = File.realpath(data[:hostpath])
|
||||
data[:hostpath] = Util::Platform.fs_real_path(data[:hostpath]).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Go through each folder and prepare the folders
|
||||
folders.each do |impl_name, fs|
|
||||
@logger.info("Invoking synced folder prepare for: #{impl_name}")
|
||||
plugins[impl_name.to_sym][0].new.prepare(env[:machine], fs, impl_opts(impl_name, env))
|
||||
end
|
||||
|
||||
# Continue, we need the VM to be booted.
|
||||
@app.call(env)
|
||||
|
||||
# Once booted, setup the folder contents
|
||||
folders.each do |impl_name, fs|
|
||||
@logger.info("Invoking synced folder enable: #{impl_name}")
|
||||
plugins[impl_name.to_sym][0].new.enable(env[:machine], fs, impl_opts(impl_name, env))
|
||||
end
|
||||
end
|
||||
|
||||
# This goes over all the registered synced folder types and returns
|
||||
# the highest priority implementation that is usable for this machine.
|
||||
def default_synced_folder_type(machine, plugins)
|
||||
ordered = []
|
||||
|
||||
# First turn the plugins into an array
|
||||
plugins.each do |key, data|
|
||||
impl = data[0]
|
||||
priority = data[1]
|
||||
|
||||
ordered << [priority, key, impl]
|
||||
end
|
||||
|
||||
# Order the plugins by priority
|
||||
ordered = ordered.sort { |a, b| b[0] <=> a[0] }
|
||||
|
||||
# Find the proper implementation
|
||||
ordered.each do |_, key, impl|
|
||||
return key if impl.new.usable?(machine)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
# This finds the options in the env that are set for a given
|
||||
# synced folder type.
|
||||
def impl_opts(name, env)
|
||||
{}.tap do |result|
|
||||
env.each do |k, v|
|
||||
if k.to_s.start_with?("#{name}_")
|
||||
k = k.dup if !k.is_a?(Symbol)
|
||||
v = v.dup if !v.is_a?(Symbol)
|
||||
result[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This returns the available synced folder implementations. This
|
||||
# is a separate method so that it can be easily stubbed by tests.
|
||||
def plugins
|
||||
@plugins ||= Vagrant.plugin("2").manager.synced_folders
|
||||
end
|
||||
|
||||
# This returns the set of shared folders that should be done for
|
||||
# this machine. It returns the folders in a hash keyed by the
|
||||
# implementation class for the synced folders.
|
||||
def synced_folders(machine)
|
||||
folders = {}
|
||||
|
||||
# Determine all the synced folders as well as the implementation
|
||||
# they're going to use.
|
||||
machine.config.vm.synced_folders.each do |id, data|
|
||||
# Ignore disabled synced folders
|
||||
next if data[:disabled]
|
||||
|
||||
impl = ""
|
||||
impl = data[:type].to_sym if data[:type]
|
||||
|
||||
if impl != ""
|
||||
impl_class = plugins[impl]
|
||||
if !impl_class
|
||||
# This should never happen because configuration validation
|
||||
# should catch this case. But we put this here as an assert
|
||||
raise "Internal error. Report this as a bug. Invalid: #{data[:type]}"
|
||||
end
|
||||
|
||||
if !impl_class[0].new.usable?(machine)
|
||||
# Verify that explicitly defined shared folder types are
|
||||
# actually usable.
|
||||
raise Errors::SyncedFolderUnusable, type: data[:type].to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Keep track of this shared folder by the implementation.
|
||||
folders[impl] ||= {}
|
||||
folders[impl][id] = data.dup
|
||||
end
|
||||
|
||||
# If we have folders with the "default" key, then determine the
|
||||
# most appropriate implementation for this.
|
||||
if folders.has_key?("") && !folders[""].empty?
|
||||
default_impl = default_synced_folder_type(machine, plugins)
|
||||
if !default_impl
|
||||
types = plugins.to_hash.keys.map { |t| t.to_s }.sort.join(", ")
|
||||
raise Errors::NoDefaultSyncedFolderImpl, types: types
|
||||
end
|
||||
|
||||
folders[default_impl] = folders[""]
|
||||
folders.delete("")
|
||||
end
|
||||
|
||||
return folders
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -364,6 +364,10 @@ module Vagrant
|
|||
error_key(:nfs_no_hostonly_network)
|
||||
end
|
||||
|
||||
class NoDefaultSyncedFolderImpl < VagrantError
|
||||
error_key(:no_default_synced_folder_impl)
|
||||
end
|
||||
|
||||
class NoEnvironmentError < VagrantError
|
||||
error_key(:no_env)
|
||||
end
|
||||
|
@ -508,6 +512,10 @@ module Vagrant
|
|||
error_key(:ssh_unavailable_windows)
|
||||
end
|
||||
|
||||
class SyncedFolderUnusable < VagrantError
|
||||
error_key(:synced_folder_unusable)
|
||||
end
|
||||
|
||||
class UIExpectsTTY < VagrantError
|
||||
error_key(:ui_expects_tty)
|
||||
end
|
||||
|
|
|
@ -17,6 +17,7 @@ module Vagrant
|
|||
autoload :Plugin, "vagrant/plugin/v2/plugin"
|
||||
autoload :Provider, "vagrant/plugin/v2/provider"
|
||||
autoload :Provisioner, "vagrant/plugin/v2/provisioner"
|
||||
autoload :SyncedFolder, "vagrant/plugin/v2/synced_folder"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,11 @@ module Vagrant
|
|||
# @return [Hash<Symbol, Registry>]
|
||||
attr_reader :providers
|
||||
|
||||
# This contains all the synced folder implementations by name.
|
||||
#
|
||||
# @return [Registry<Symbol, Array<Class, Integer>>]
|
||||
attr_reader :synced_folders
|
||||
|
||||
def initialize
|
||||
# The action hooks hash defaults to []
|
||||
@action_hooks = Hash.new { |h, k| h[k] = [] }
|
||||
|
@ -40,6 +45,7 @@ module Vagrant
|
|||
@guests = Registry.new
|
||||
@guest_capabilities = Hash.new { |h, k| h[k] = Registry.new }
|
||||
@providers = Registry.new
|
||||
@synced_folders = Registry.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,6 +142,17 @@ module Vagrant
|
|||
end
|
||||
end
|
||||
|
||||
# This returns all synced folder implementations.
|
||||
#
|
||||
# @return [Registry]
|
||||
def synced_folders
|
||||
Registry.new.tap do |result|
|
||||
@registered.each do |plugin|
|
||||
result.merge!(plugin.components.synced_folders)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This registers a plugin. This should _NEVER_ be called by the public
|
||||
# and should only be called from within Vagrant. Vagrant will
|
||||
# automatically register V2 plugins when a name is set on the
|
||||
|
|
|
@ -194,6 +194,19 @@ module Vagrant
|
|||
data[:provisioners]
|
||||
end
|
||||
|
||||
# Registers additional synced folder implementations.
|
||||
#
|
||||
# @param [String] name Name of the implementation.
|
||||
# @param [Integer] priority The priority of the implementation,
|
||||
# higher (big) numbers are tried before lower (small) numbers.
|
||||
def self.synced_folder(name, priority=10, &block)
|
||||
components.synced_folders.register(name.to_sym) do
|
||||
[block.call, priority]
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Returns the internal data associated with this plugin. This
|
||||
# should NOT be called by the general public.
|
||||
#
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
module Vagrant
|
||||
module Plugin
|
||||
module V2
|
||||
# This is the base class for a synced folder implementation.
|
||||
class SyncedFolder
|
||||
def usable?(machine)
|
||||
end
|
||||
|
||||
def prepare(machine, folders, opts)
|
||||
end
|
||||
|
||||
def enable(machine, folders, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
require "vagrant"
|
||||
|
||||
module VagrantPlugins
|
||||
module Kernel_V2
|
||||
class NFSConfig < Vagrant.plugin("2", :config)
|
||||
attr_accessor :map_uid
|
||||
attr_accessor :map_gid
|
||||
|
||||
def to_s
|
||||
"NFS"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -294,9 +294,16 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
@__synced_folders.each do |id, options|
|
||||
if options[:nfs]
|
||||
options[:type] = :nfs
|
||||
end
|
||||
|
||||
# Make sure the type is a symbol
|
||||
options[:type] = options[:type].to_sym if options[:type]
|
||||
|
||||
# Ignore NFS on Windows
|
||||
if options[:nfs] && Vagrant::Util::Platform.windows?
|
||||
options[:nfs] = false
|
||||
if options[:type] == :nfs && Vagrant::Util::Platform.windows?
|
||||
options.delete(:type)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -381,7 +388,7 @@ module VagrantPlugins
|
|||
:path => options[:hostpath])
|
||||
end
|
||||
|
||||
if options[:nfs]
|
||||
if options[:type] == :nfs
|
||||
has_nfs = true
|
||||
|
||||
if options[:owner] || options[:group]
|
||||
|
|
|
@ -20,11 +20,6 @@ module VagrantPlugins
|
|||
SSHConfig
|
||||
end
|
||||
|
||||
config("nfs") do
|
||||
require File.expand_path("../config/nfs", __FILE__)
|
||||
NFSConfig
|
||||
end
|
||||
|
||||
config("package") do
|
||||
require File.expand_path("../config/package", __FILE__)
|
||||
PackageConfig
|
||||
|
|
|
@ -40,7 +40,6 @@ module VagrantPlugins
|
|||
autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__)
|
||||
autoload :SetName, File.expand_path("../action/set_name", __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
|
||||
|
@ -60,10 +59,9 @@ module VagrantPlugins
|
|||
b.use PrepareForwardedPortCollisionParams
|
||||
b.use HandleForwardedPortCollisions
|
||||
b.use PruneNFSExports
|
||||
b.use NFS
|
||||
b.use PrepareNFSSettings
|
||||
b.use ClearSharedFolders
|
||||
b.use ShareFolders
|
||||
b.use SyncedFolders
|
||||
b.use PrepareNFSSettings
|
||||
b.use ClearNetworkInterfaces
|
||||
b.use Network
|
||||
b.use ForwardPorts
|
||||
|
|
|
@ -12,7 +12,7 @@ module VagrantPlugins
|
|||
|
||||
using_nfs = false
|
||||
env[:machine].config.vm.synced_folders.each do |id, opts|
|
||||
if opts[:nfs]
|
||||
if opts[:type] == :nfs
|
||||
using_nfs = true
|
||||
break
|
||||
end
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
require "pathname"
|
||||
|
||||
require "log4r"
|
||||
|
||||
require "vagrant/util/platform"
|
||||
require "vagrant/util/scoped_hash_override"
|
||||
|
||||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
module Action
|
||||
class ShareFolders
|
||||
include Vagrant::Util::ScopedHashOverride
|
||||
|
||||
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
|
||||
{}.tap do |result|
|
||||
@env[:machine].config.vm.synced_folders.each do |id, data|
|
||||
data = scoped_hash_override(data, :virtualbox)
|
||||
|
||||
# Ignore NFS shared folders
|
||||
next if data[:nfs]
|
||||
|
||||
# Ignore disabled shared folders
|
||||
next if data[:disabled]
|
||||
|
||||
# This to prevent overwriting the actual shared folders data
|
||||
result[id] = data.dup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Prepares the shared folders by verifying they exist and creating them
|
||||
# if they don't.
|
||||
def prepare_folders
|
||||
shared_folders.each do |id, 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 |id, data|
|
||||
hostpath = File.expand_path(data[:hostpath], @env[:root_path])
|
||||
hostpath = Vagrant::Util::Platform.cygwin_windows_path(hostpath)
|
||||
|
||||
folders << {
|
||||
:name => id,
|
||||
:hostpath => hostpath,
|
||||
: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 |id, 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 |id, 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",
|
||||
:guest_path => data[:guestpath]))
|
||||
|
||||
# Dup the data so we can pass it to the guest API
|
||||
data = data.dup
|
||||
|
||||
# Calculate the owner and group
|
||||
ssh_info = @env[:machine].ssh_info
|
||||
data[:owner] ||= ssh_info[:username]
|
||||
data[:group] ||= ssh_info[:username]
|
||||
|
||||
# Mount the actual folder
|
||||
@env[:machine].guest.capability(
|
||||
:mount_virtualbox_shared_folder, id, 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",
|
||||
:host_path => data[:hostpath]))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,6 +18,11 @@ module VagrantPlugins
|
|||
require File.expand_path("../config", __FILE__)
|
||||
Config
|
||||
end
|
||||
|
||||
synced_folder(:virtualbox) do
|
||||
require File.expand_path("../synced_folder", __FILE__)
|
||||
SyncedFolder
|
||||
end
|
||||
end
|
||||
|
||||
autoload :Action, File.expand_path("../action", __FILE__)
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
require "vagrant/util/platform"
|
||||
|
||||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
|
||||
def usable?(machine)
|
||||
# These synced folders only work if the provider if VirtualBox
|
||||
machine.provider_name == :virtualbox
|
||||
end
|
||||
|
||||
def prepare(machine, folders, _opts)
|
||||
defs = []
|
||||
folders.each do |id, data|
|
||||
hostpath = Vagrant::Util::Platform.cygwin_windows_path(data[:hostpath])
|
||||
|
||||
defs << {
|
||||
name: id,
|
||||
hostpath: hostpath.to_s,
|
||||
transient: data[:transient],
|
||||
}
|
||||
end
|
||||
|
||||
driver(machine).share_folders(defs)
|
||||
end
|
||||
|
||||
def enable(machine, folders, _opts)
|
||||
# short guestpaths first, so we don't step on ourselves
|
||||
folders = folders.sort_by do |id, 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
|
||||
machine.ui.info(I18n.t("vagrant.actions.vm.share_folders.mounting"))
|
||||
folders.each do |id, data|
|
||||
if data[:guestpath]
|
||||
# Guest path specified, so mount the folder to specified point
|
||||
machine.ui.info(I18n.t("vagrant.actions.vm.share_folders.mounting_entry",
|
||||
:guest_path => data[:guestpath]))
|
||||
|
||||
# Dup the data so we can pass it to the guest API
|
||||
data = data.dup
|
||||
|
||||
# Calculate the owner and group
|
||||
ssh_info = machine.ssh_info
|
||||
data[:owner] ||= ssh_info[:username]
|
||||
data[:group] ||= ssh_info[:username]
|
||||
|
||||
# Mount the actual folder
|
||||
machine.guest.capability(
|
||||
:mount_virtualbox_shared_folder, id, data[:guestpath], data)
|
||||
else
|
||||
# If no guest path is specified, then automounting is disabled
|
||||
machine.ui.info(I18n.t("vagrant.actions.vm.share_folders.nomount_entry",
|
||||
:host_path => data[:hostpath]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# This is here so that we can stub it for tests
|
||||
def driver(machine)
|
||||
machine.provider.driver
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
require "vagrant"
|
||||
|
||||
module VagrantPlugins
|
||||
module SyncedFolderNFS
|
||||
class Config < Vagrant.plugin("2", :config)
|
||||
attr_accessor :map_uid
|
||||
attr_accessor :map_gid
|
||||
|
||||
def initialize
|
||||
super
|
||||
|
||||
@map_uid = UNSET_VALUE
|
||||
@map_gid = UNSET_VALUE
|
||||
end
|
||||
|
||||
def finalize!
|
||||
@map_uid = nil if @map_uid == UNSET_VALUE
|
||||
@map_gid = nil if @map_gid == UNSET_VALUE
|
||||
end
|
||||
|
||||
def to_s
|
||||
"NFS"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
require "vagrant"
|
||||
|
||||
module VagrantPlugins
|
||||
module SyncedFolderNFS
|
||||
class Plugin < Vagrant.plugin("2")
|
||||
name "NFS synced folders"
|
||||
description <<-EOF
|
||||
The NFS synced folders plugin enables you to use NFS as a synced folder
|
||||
implementation.
|
||||
EOF
|
||||
|
||||
config("nfs") do
|
||||
require File.expand_path("../config", __FILE__)
|
||||
Config
|
||||
end
|
||||
|
||||
synced_folder("nfs", 5) do
|
||||
require File.expand_path("../synced_folder", __FILE__)
|
||||
SyncedFolder
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,83 @@
|
|||
require 'fileutils'
|
||||
require 'zlib'
|
||||
|
||||
require "vagrant/util/platform"
|
||||
|
||||
module VagrantPlugins
|
||||
module SyncedFolderNFS
|
||||
# This synced folder requires that two keys be set on the environment
|
||||
# within the middleware sequence:
|
||||
#
|
||||
# - `: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 SyncedFolder < Vagrant.plugin("2", :synced_folder)
|
||||
def usable?(machine)
|
||||
# NFS is always available
|
||||
true
|
||||
end
|
||||
|
||||
def prepare(machine, folders, opts)
|
||||
# Nothing is necessary to do before VM boot.
|
||||
end
|
||||
|
||||
def enable(machine, folders, nfsopts)
|
||||
raise Vagrant::Errors::NFSNoHostIP if !nfsopts[:nfs_host_ip]
|
||||
raise Vagrant::Errors::NFSNoGuestIP if !nfsopts[:nfs_machine_ip]
|
||||
|
||||
machine_ip = nfsopts[:nfs_machine_ip]
|
||||
machine_ip = [machine_ip] if !machine_ip.is_a?(Array)
|
||||
|
||||
# Prepare the folder, this means setting up various options
|
||||
# and such on the folder itself.
|
||||
folders.each { |id, opts| prepare_folder(machine, opts) }
|
||||
|
||||
# Export the folders
|
||||
machine.ui.info I18n.t("vagrant.actions.vm.nfs.exporting")
|
||||
machine.env.host.nfs_export(machine.id, machine_ip, folders)
|
||||
|
||||
# Mount
|
||||
machine.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!
|
||||
machine.guest.capability(
|
||||
:mount_nfs_folder, nfsopts[:nfs_host_ip], mount_folders)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def prepare_folder(machine, opts)
|
||||
opts[:map_uid] = prepare_permission(machine, :uid, opts)
|
||||
opts[:map_gid] = prepare_permission(machine, :gid, opts)
|
||||
opts[:nfs_version] ||= 3
|
||||
|
||||
# We use a CRC32 to generate a 32-bit checksum so that the
|
||||
# fsid is compatible with both old and new kernels.
|
||||
opts[:uuid] = Zlib.crc32(opts[:hostpath]).to_s
|
||||
end
|
||||
|
||||
# Prepares the UID/GID settings for a single folder.
|
||||
def prepare_permission(machine, 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] : 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
|
|
@ -387,6 +387,14 @@ en:
|
|||
NFS requires a host-only network with a static IP to be created.
|
||||
Please add a host-only network with a static IP to the machine
|
||||
for NFS to work.
|
||||
no_default_synced_folder_impl: |-
|
||||
No synced folder implementation is available for your synced folders!
|
||||
Please consult the documentation to learn why this may be the case.
|
||||
You may force a synced folder implementation by specifying a "type:"
|
||||
option for the synced folders. Available synced folder implementations
|
||||
are listed below.
|
||||
|
||||
%{types}
|
||||
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
|
||||
|
@ -572,6 +580,10 @@ en:
|
|||
Port: %{port}
|
||||
Username: %{username}
|
||||
Private key: %{key_path}
|
||||
synced_folder_unusable: |
|
||||
The synced folder type '%{type}' is reporting as unusable for
|
||||
your current setup. Please verify you have all the proper
|
||||
prerequisites for using this shared folder type and try again.
|
||||
test_key: "test value"
|
||||
ui_expects_tty: |-
|
||||
Vagrant is attempting to interface with the UI in a way that requires
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
require "vagrant"
|
||||
require Vagrant.source_root.join("test/unit/base")
|
||||
|
||||
require Vagrant.source_root.join("plugins/providers/virtualbox/synced_folder")
|
||||
|
||||
# TODO(mitchellh): tag with v2
|
||||
describe VagrantPlugins::ProviderVirtualBox::SyncedFolder do
|
||||
let(:machine) do
|
||||
double("machine").tap do |m|
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new }
|
||||
|
||||
describe "usable" do
|
||||
it "should be with virtualbox provider" do
|
||||
machine.stub(provider_name: :virtualbox)
|
||||
subject.should be_usable(machine)
|
||||
end
|
||||
|
||||
it "should not be with another provider" do
|
||||
machine.stub(provider_name: :vmware_fusion)
|
||||
subject.should_not be_usable(machine)
|
||||
end
|
||||
end
|
||||
|
||||
describe "prepare" do
|
||||
let(:driver) { double("driver") }
|
||||
|
||||
before do
|
||||
machine.stub(driver: driver)
|
||||
end
|
||||
|
||||
it "should share the folders" do
|
||||
pending
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,180 @@
|
|||
require "pathname"
|
||||
require "tmpdir"
|
||||
|
||||
require File.expand_path("../../../../base", __FILE__)
|
||||
|
||||
describe Vagrant::Action::Builtin::SyncedFolders do
|
||||
let(:app) { lambda { |env| } }
|
||||
let(:env) { { :machine => machine, :ui => ui } }
|
||||
let(:machine) do
|
||||
double("machine").tap do |machine|
|
||||
machine.stub(:config).and_return(machine_config)
|
||||
end
|
||||
end
|
||||
|
||||
let(:machine_config) do
|
||||
double("machine_config").tap do |top_config|
|
||||
top_config.stub(:vm => vm_config)
|
||||
end
|
||||
end
|
||||
|
||||
let(:vm_config) { double("machine_vm_config") }
|
||||
|
||||
let(:ui) do
|
||||
double("ui").tap do |result|
|
||||
result.stub(:info)
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new(app, env) }
|
||||
|
||||
# This creates a synced folder implementation.
|
||||
def impl(usable, name)
|
||||
Class.new(Vagrant.plugin("2", :synced_folder)) do
|
||||
define_method(:name) do
|
||||
name
|
||||
end
|
||||
|
||||
define_method(:usable?) do |machine|
|
||||
usable
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "call" do
|
||||
let(:synced_folders) { {} }
|
||||
let(:plugins) { {} }
|
||||
|
||||
before do
|
||||
plugins[:default] = [impl(true, "default"), 10]
|
||||
plugins[:nfs] = [impl(true, "nfs"), 5]
|
||||
|
||||
env[:root_path] = Pathname.new(Dir.mktmpdir)
|
||||
subject.stub(:plugins => plugins)
|
||||
subject.stub(:synced_folders => synced_folders)
|
||||
end
|
||||
|
||||
it "should create on the host if specified" do
|
||||
synced_folders["default"] = {
|
||||
"root" => {
|
||||
hostpath: "foo",
|
||||
},
|
||||
|
||||
"other" => {
|
||||
hostpath: "bar",
|
||||
create: true,
|
||||
}
|
||||
}
|
||||
|
||||
subject.call(env)
|
||||
|
||||
env[:root_path].join("foo").should_not be_directory
|
||||
env[:root_path].join("bar").should be_directory
|
||||
end
|
||||
|
||||
it "should invoke prepare then enable" do
|
||||
order = []
|
||||
tracker = Class.new(impl(true, "good")) do
|
||||
define_method(:prepare) do |machine, folders, opts|
|
||||
order << :prepare
|
||||
end
|
||||
|
||||
define_method(:enable) do |machine, folders, opts|
|
||||
order << :enable
|
||||
end
|
||||
end
|
||||
|
||||
plugins[:tracker] = [tracker, 15]
|
||||
|
||||
synced_folders["tracker"] = {
|
||||
"root" => {
|
||||
hostpath: "foo",
|
||||
},
|
||||
|
||||
"other" => {
|
||||
hostpath: "bar",
|
||||
create: true,
|
||||
}
|
||||
}
|
||||
|
||||
subject.call(env)
|
||||
|
||||
order.should == [:prepare, :enable]
|
||||
end
|
||||
end
|
||||
|
||||
describe "default_synced_folder_type" do
|
||||
it "returns the usable implementation" do
|
||||
plugins = {
|
||||
"bad" => [impl(false, "bad"), 0],
|
||||
"nope" => [impl(true, "nope"), 1],
|
||||
"good" => [impl(true, "good"), 5],
|
||||
}
|
||||
|
||||
result = subject.default_synced_folder_type(machine, plugins)
|
||||
result.should == "good"
|
||||
end
|
||||
end
|
||||
|
||||
describe "impl_opts" do
|
||||
it "should return only relevant keys" do
|
||||
env = {
|
||||
:foo_bar => "baz",
|
||||
:bar_bar => "nope",
|
||||
:foo_baz => "bar",
|
||||
}
|
||||
|
||||
result = subject.impl_opts("foo", env)
|
||||
result.length.should == 2
|
||||
result[:foo_bar].should == "baz"
|
||||
result[:foo_baz].should == "bar"
|
||||
end
|
||||
end
|
||||
|
||||
describe "synced_folders" do
|
||||
let(:folders) { {} }
|
||||
let(:plugins) { {} }
|
||||
|
||||
before do
|
||||
plugins[:default] = [impl(true, "default"), 10]
|
||||
plugins[:nfs] = [impl(true, "nfs"), 5]
|
||||
|
||||
subject.stub(:plugins => plugins)
|
||||
vm_config.stub(:synced_folders => folders)
|
||||
end
|
||||
|
||||
it "should raise exception if bad type is given" do
|
||||
folders["root"] = { type: "bad" }
|
||||
|
||||
expect { subject.synced_folders(machine) }.
|
||||
to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "should return the proper set of folders" do
|
||||
folders["root"] = {}
|
||||
folders["nfs"] = { type: "nfs" }
|
||||
|
||||
result = subject.synced_folders(machine)
|
||||
result.length.should == 2
|
||||
result[:default].should == { "root" => folders["root"] }
|
||||
result[:nfs].should == { "nfs" => folders["nfs"] }
|
||||
end
|
||||
|
||||
it "should error if an explicit type is unusable" do
|
||||
plugins[:unusable] = [impl(false, "bad"), 15]
|
||||
folders["root"] = { type: "unusable" }
|
||||
|
||||
expect { subject.synced_folders(machine) }.
|
||||
to raise_error
|
||||
end
|
||||
|
||||
it "should ignore disabled folders" do
|
||||
folders["root"] = {}
|
||||
folders["foo"] = { disabled: true }
|
||||
|
||||
result = subject.synced_folders(machine)
|
||||
result.length.should == 1
|
||||
result[:default].length.should == 1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,15 +3,19 @@ require File.expand_path("../../../../base", __FILE__)
|
|||
require "vagrant/registry"
|
||||
|
||||
describe Vagrant::Plugin::V2::Components do
|
||||
let(:instance) { described_class.new }
|
||||
subject { described_class.new }
|
||||
|
||||
it "should have synced folders" do
|
||||
subject.synced_folders.should be_kind_of(Vagrant::Registry)
|
||||
end
|
||||
|
||||
describe "configs" do
|
||||
it "should have configs" do
|
||||
instance.configs.should be_kind_of(Hash)
|
||||
subject.configs.should be_kind_of(Hash)
|
||||
end
|
||||
|
||||
it "should default the values to registries" do
|
||||
instance.configs[:i_probably_dont_exist].should be_kind_of(Vagrant::Registry)
|
||||
subject.configs[:i_probably_dont_exist].should be_kind_of(Vagrant::Registry)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -171,4 +171,21 @@ describe Vagrant::Plugin::V2::Manager do
|
|||
instance.provider_configs[:foo].should == "foo"
|
||||
instance.provider_configs[:bar].should == "bar"
|
||||
end
|
||||
|
||||
it "should enumerate all registered synced folder implementations" do
|
||||
pA = plugin do |p|
|
||||
p.synced_folder("foo") { "bar" }
|
||||
end
|
||||
|
||||
pB = plugin do |p|
|
||||
p.synced_folder("bar", 50) { "baz" }
|
||||
end
|
||||
|
||||
instance.register(pA)
|
||||
instance.register(pB)
|
||||
|
||||
instance.synced_folders.to_hash.length.should == 2
|
||||
instance.synced_folders[:foo].should == ["bar", 10]
|
||||
instance.synced_folders[:bar].should == ["baz", 50]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -278,6 +278,42 @@ describe Vagrant::Plugin::V2::Plugin do
|
|||
end
|
||||
end
|
||||
|
||||
describe "synced folders" do
|
||||
it "should register implementations" do
|
||||
plugin = Class.new(described_class) do
|
||||
synced_folder("foo") { "bar" }
|
||||
end
|
||||
|
||||
plugin.components.synced_folders[:foo].should == ["bar", 10]
|
||||
end
|
||||
|
||||
it "should be able to specify priorities" do
|
||||
plugin = Class.new(described_class) do
|
||||
synced_folder("foo", 50) { "bar" }
|
||||
end
|
||||
|
||||
plugin.components.synced_folders[:foo].should == ["bar", 50]
|
||||
end
|
||||
|
||||
it "should lazily register implementations" do
|
||||
# Below would raise an error if the value of the config class was
|
||||
# evaluated immediately. By asserting that this does not raise an
|
||||
# error, we verify that the value is actually lazily loaded
|
||||
plugin = nil
|
||||
expect {
|
||||
plugin = Class.new(described_class) do
|
||||
synced_folder("foo") { raise StandardError, "FAIL!" }
|
||||
end
|
||||
}.to_not raise_error
|
||||
|
||||
# Now verify when we actually get the configuration key that
|
||||
# a proper error is raised.
|
||||
expect {
|
||||
plugin.components.synced_folders[:foo]
|
||||
}.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "plugin registration" do
|
||||
let(:manager) { described_class.manager }
|
||||
|
||||
|
|
|
@ -24,6 +24,23 @@ describe Vagrant do
|
|||
end
|
||||
end
|
||||
|
||||
describe "v2" do
|
||||
it "returns the proper class for version 2" do
|
||||
described_class.plugin("2").should == Vagrant::Plugin::V2::Plugin
|
||||
end
|
||||
|
||||
it "returns the proper components for version 2" do
|
||||
described_class.plugin("2", :command).should == Vagrant::Plugin::V2::Command
|
||||
described_class.plugin("2", :communicator).should == Vagrant::Plugin::V2::Communicator
|
||||
described_class.plugin("2", :config).should == Vagrant::Plugin::V2::Config
|
||||
described_class.plugin("2", :guest).should == Vagrant::Plugin::V2::Guest
|
||||
described_class.plugin("2", :host).should == Vagrant::Plugin::V2::Host
|
||||
described_class.plugin("2", :provider).should == Vagrant::Plugin::V2::Provider
|
||||
described_class.plugin("2", :provisioner).should == Vagrant::Plugin::V2::Provisioner
|
||||
described_class.plugin("2", :synced_folder).should == Vagrant::Plugin::V2::SyncedFolder
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an exception if an unsupported version is given" do
|
||||
expect { described_class.plugin("88") }.
|
||||
to raise_error(ArgumentError)
|
||||
|
|
Loading…
Reference in New Issue