core: SyncedFolders built-in middleware

This commit is contained in:
Mitchell Hashimoto 2013-11-22 16:12:51 -08:00
parent 97148379d2
commit 93a4066339
8 changed files with 331 additions and 1 deletions

View File

@ -24,6 +24,7 @@ module Vagrant
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

@ -0,0 +1,139 @@
require "log4r"
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])
# Don't do anything else if this directory exists or its
# not flagged to auto-create
next 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
end
# Go through each folder and prepare the folders
folders.each do |impl, fs|
@logger.info("Invoking synced folder prepare for: #{impl}")
impl.new.prepare(env[:machine], fs)
end
# Continue, we need the VM to be booted.
@app.call(env)
# Once booted, setup the folder contents
folders.each do |impl, fs|
@logger.info("Invoking synced folder enable: #{impl}")
impl.new.enable(env[:machine], fs)
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, impl]
end
# Order the plugins by priority
ordered = ordered.sort { |a, b| b[0] <=> a[0] }.map { |p| p[1] }
# Find the proper implementation
ordered.each do |impl|
return impl if impl.new.usable?(machine)
end
return nil
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 = ["", 0]
impl = plugins[data[:type].to_sym] if data[:type]
if impl == nil
# 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
# The implementation class rather than the priority, since the
# array is [class, priority].
impl = impl[0]
# 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

View File

@ -34,7 +34,7 @@ module Vagrant
# This contains all the synced folder implementations by name.
#
# @return [Registry<Symbol, Class>]
# @return [Registry<Symbol, Array<Class, Integer>>]
attr_reader :synced_folders
def initialize

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

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

View File

@ -0,0 +1,150 @@
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) { {} }
before do
env[:root_path] = Pathname.new(Dir.mktmpdir)
subject.stub(:synced_folders => synced_folders)
end
it "should create on the host if specified" do
synced_folders[impl(true, "good")] = {
"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 = []
sf = Class.new(impl(true, "good")) do
define_method(:prepare) do |machine, folders|
order << :prepare
end
define_method(:enable) do |machine, folders|
order << :enable
end
end
synced_folders[sf] = {
"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.new.name.should == "good"
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[plugins[:default][0]].should == { "root" => folders["root"] }
result[plugins[:nfs][0]].should == { "nfs" => folders["nfs"] }
end
it "should ignore disabled folders" do
folders["root"] = {}
folders["foo"] = { disabled: true }
result = subject.synced_folders(machine)
result.length.should == 1
result[plugins[:default][0]].length.should == 1
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