Merge pull request #2561 from emyl/synced-folder-cleanup-v2
core: Enhance the synced folder plugin implementation with a cleanup routine
This commit is contained in:
commit
ca521887eb
|
@ -25,6 +25,7 @@ module Vagrant
|
||||||
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 :SyncedFolders, "vagrant/action/builtin/synced_folders"
|
||||||
|
autoload :SyncedFolderCleanup, "vagrant/action/builtin/synced_folder_cleanup"
|
||||||
autoload :WaitForCommunicator, "vagrant/action/builtin/wait_for_communicator"
|
autoload :WaitForCommunicator, "vagrant/action/builtin/wait_for_communicator"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
module Vagrant
|
||||||
|
module Action
|
||||||
|
module Builtin
|
||||||
|
module MixinSyncedFolders
|
||||||
|
# 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
|
|
@ -0,0 +1,32 @@
|
||||||
|
require "log4r"
|
||||||
|
|
||||||
|
require_relative "mixin_synced_folders"
|
||||||
|
|
||||||
|
module Vagrant
|
||||||
|
module Action
|
||||||
|
module Builtin
|
||||||
|
# This middleware will run cleanup tasks for synced folders using
|
||||||
|
# the appropriate synced folder plugin.
|
||||||
|
class SyncedFolderCleanup
|
||||||
|
include MixinSyncedFolders
|
||||||
|
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
@logger = Log4r::Logger.new("vagrant::action::builtin::synced_folder_cleanup")
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
folders = synced_folders(env[:machine])
|
||||||
|
|
||||||
|
# Go through each folder and do cleanup
|
||||||
|
folders.each_key do |impl_name|
|
||||||
|
@logger.info("Invoking synced folder cleanup for: #{impl_name}")
|
||||||
|
plugins[impl_name.to_sym][0].new.cleanup(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,12 +3,15 @@ require "log4r"
|
||||||
require 'vagrant/util/platform'
|
require 'vagrant/util/platform'
|
||||||
require 'vagrant/util/scoped_hash_override'
|
require 'vagrant/util/scoped_hash_override'
|
||||||
|
|
||||||
|
require_relative "mixin_synced_folders"
|
||||||
|
|
||||||
module Vagrant
|
module Vagrant
|
||||||
module Action
|
module Action
|
||||||
module Builtin
|
module Builtin
|
||||||
# This middleware will setup the synced folders for the machine using
|
# This middleware will setup the synced folders for the machine using
|
||||||
# the appropriate synced folder plugin.
|
# the appropriate synced folder plugin.
|
||||||
class SyncedFolders
|
class SyncedFolders
|
||||||
|
include MixinSyncedFolders
|
||||||
include Vagrant::Util::ScopedHashOverride
|
include Vagrant::Util::ScopedHashOverride
|
||||||
|
|
||||||
def initialize(app, env)
|
def initialize(app, env)
|
||||||
|
@ -70,101 +73,6 @@ module Vagrant
|
||||||
plugins[impl_name.to_sym][0].new.enable(env[:machine], fs, impl_opts(impl_name, env))
|
plugins[impl_name.to_sym][0].new.enable(env[:machine], fs, impl_opts(impl_name, env))
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,9 @@ module Vagrant
|
||||||
|
|
||||||
def enable(machine, folders, opts)
|
def enable(machine, folders, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cleanup(machine)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -60,6 +60,7 @@ module VagrantPlugins
|
||||||
b.use HandleForwardedPortCollisions
|
b.use HandleForwardedPortCollisions
|
||||||
b.use PruneNFSExports
|
b.use PruneNFSExports
|
||||||
b.use ClearSharedFolders
|
b.use ClearSharedFolders
|
||||||
|
b.use SyncedFolderCleanup
|
||||||
b.use SyncedFolders
|
b.use SyncedFolders
|
||||||
b.use PrepareNFSSettings
|
b.use PrepareNFSSettings
|
||||||
b.use ClearNetworkInterfaces
|
b.use ClearNetworkInterfaces
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
require "pathname"
|
||||||
|
require "tmpdir"
|
||||||
|
|
||||||
|
require File.expand_path("../../../../base", __FILE__)
|
||||||
|
|
||||||
|
describe Vagrant::Action::Builtin::SyncedFolderCleanup 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 invoke cleanup" do
|
||||||
|
cleaned_up = nil
|
||||||
|
tracker = Class.new(impl(true, "good")) do
|
||||||
|
define_method(:cleanup) do |machine|
|
||||||
|
cleaned_up = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
plugins[:tracker] = [tracker, 15]
|
||||||
|
|
||||||
|
synced_folders["tracker"] = {
|
||||||
|
"root" => {
|
||||||
|
hostpath: "foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"other" => {
|
||||||
|
hostpath: "bar",
|
||||||
|
create: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
|
||||||
|
cleaned_up.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should invoke cleanup once per implementation" do
|
||||||
|
call_count = 0
|
||||||
|
trackers = []
|
||||||
|
(0..2).each do |tracker|
|
||||||
|
trackers << Class.new(impl(true, "good")) do
|
||||||
|
define_method(:cleanup) do |machine|
|
||||||
|
call_count += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
plugins[:tracker_0] = [trackers[0], 15]
|
||||||
|
plugins[:tracker_1] = [trackers[1], 15]
|
||||||
|
plugins[:tracker_2] = [trackers[2], 15]
|
||||||
|
|
||||||
|
synced_folders["tracker_0"] = {
|
||||||
|
"root" => {
|
||||||
|
hostpath: "foo"
|
||||||
|
},
|
||||||
|
|
||||||
|
"other" => {
|
||||||
|
hostpath: "bar",
|
||||||
|
create: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synced_folders["tracker_1"] = {
|
||||||
|
"root" => {
|
||||||
|
hostpath: "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synced_folders["tracker_2"] = {
|
||||||
|
"root" => {
|
||||||
|
hostpath: "foo"
|
||||||
|
},
|
||||||
|
|
||||||
|
"other" => {
|
||||||
|
hostpath: "bar",
|
||||||
|
create: true
|
||||||
|
},
|
||||||
|
|
||||||
|
"another" => {
|
||||||
|
hostpath: "baz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.call(env)
|
||||||
|
|
||||||
|
call_count.should == 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue