core: SyncedFolders built-in middleware
This commit is contained in:
parent
97148379d2
commit
93a4066339
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue