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:
Mitchell Hashimoto 2013-11-23 10:51:34 -08:00
commit 14faccfd79
28 changed files with 752 additions and 287 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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