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 :SSHRun, "vagrant/action/builtin/ssh_run"
|
||||
autoload :SyncedFolders, "vagrant/action/builtin/synced_folders"
|
||||
autoload :SyncedFolderCleanup, "vagrant/action/builtin/synced_folder_cleanup"
|
||||
autoload :WaitForCommunicator, "vagrant/action/builtin/wait_for_communicator"
|
||||
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/scoped_hash_override'
|
||||
|
||||
require_relative "mixin_synced_folders"
|
||||
|
||||
module Vagrant
|
||||
module Action
|
||||
module Builtin
|
||||
# This middleware will setup the synced folders for the machine using
|
||||
# the appropriate synced folder plugin.
|
||||
class SyncedFolders
|
||||
include MixinSyncedFolders
|
||||
include Vagrant::Util::ScopedHashOverride
|
||||
|
||||
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))
|
||||
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
|
||||
|
|
|
@ -11,6 +11,9 @@ module Vagrant
|
|||
|
||||
def enable(machine, folders, opts)
|
||||
end
|
||||
|
||||
def cleanup(machine)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,6 +60,7 @@ module VagrantPlugins
|
|||
b.use HandleForwardedPortCollisions
|
||||
b.use PruneNFSExports
|
||||
b.use ClearSharedFolders
|
||||
b.use SyncedFolderCleanup
|
||||
b.use SyncedFolders
|
||||
b.use PrepareNFSSettings
|
||||
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