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 :SetHostname, "vagrant/action/builtin/set_hostname"
|
||||||
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
|
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
|
||||||
autoload :SSHRun, "vagrant/action/builtin/ssh_run"
|
autoload :SSHRun, "vagrant/action/builtin/ssh_run"
|
||||||
|
autoload :SyncedFolders, "vagrant/action/builtin/synced_folders"
|
||||||
autoload :WaitForCommunicator, "vagrant/action/builtin/wait_for_communicator"
|
autoload :WaitForCommunicator, "vagrant/action/builtin/wait_for_communicator"
|
||||||
end
|
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)
|
error_key(:nfs_no_hostonly_network)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class NoDefaultSyncedFolderImpl < VagrantError
|
||||||
|
error_key(:no_default_synced_folder_impl)
|
||||||
|
end
|
||||||
|
|
||||||
class NoEnvironmentError < VagrantError
|
class NoEnvironmentError < VagrantError
|
||||||
error_key(:no_env)
|
error_key(:no_env)
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,7 +34,7 @@ module Vagrant
|
||||||
|
|
||||||
# This contains all the synced folder implementations by name.
|
# This contains all the synced folder implementations by name.
|
||||||
#
|
#
|
||||||
# @return [Registry<Symbol, Class>]
|
# @return [Registry<Symbol, Array<Class, Integer>>]
|
||||||
attr_reader :synced_folders
|
attr_reader :synced_folders
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
|
|
@ -142,6 +142,17 @@ module Vagrant
|
||||||
end
|
end
|
||||||
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
|
# This registers a plugin. This should _NEVER_ be called by the public
|
||||||
# and should only be called from within Vagrant. Vagrant will
|
# and should only be called from within Vagrant. Vagrant will
|
||||||
# automatically register V2 plugins when a name is set on the
|
# 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.
|
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
|
Please add a host-only network with a static IP to the machine
|
||||||
for NFS to work.
|
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: |-
|
no_env: |-
|
||||||
A Vagrant environment is required to run this command. Run `vagrant init`
|
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
|
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[:foo].should == "foo"
|
||||||
instance.provider_configs[:bar].should == "bar"
|
instance.provider_configs[:bar].should == "bar"
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue