diff --git a/lib/vagrant/action.rb b/lib/vagrant/action.rb index 2a8d9c6ad..d13b6d105 100644 --- a/lib/vagrant/action.rb +++ b/lib/vagrant/action.rb @@ -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 diff --git a/lib/vagrant/action/builtin/mixin_synced_folders.rb b/lib/vagrant/action/builtin/mixin_synced_folders.rb new file mode 100644 index 000000000..eb71e9dff --- /dev/null +++ b/lib/vagrant/action/builtin/mixin_synced_folders.rb @@ -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 diff --git a/lib/vagrant/action/builtin/synced_folder_cleanup.rb b/lib/vagrant/action/builtin/synced_folder_cleanup.rb new file mode 100644 index 000000000..33217e854 --- /dev/null +++ b/lib/vagrant/action/builtin/synced_folder_cleanup.rb @@ -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 diff --git a/lib/vagrant/action/builtin/synced_folders.rb b/lib/vagrant/action/builtin/synced_folders.rb index 25ef6e68c..7c7802ca2 100644 --- a/lib/vagrant/action/builtin/synced_folders.rb +++ b/lib/vagrant/action/builtin/synced_folders.rb @@ -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 diff --git a/lib/vagrant/plugin/v2/synced_folder.rb b/lib/vagrant/plugin/v2/synced_folder.rb index 6774882c8..aaec59113 100644 --- a/lib/vagrant/plugin/v2/synced_folder.rb +++ b/lib/vagrant/plugin/v2/synced_folder.rb @@ -11,6 +11,9 @@ module Vagrant def enable(machine, folders, opts) end + + def cleanup(machine) + end end end end diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 6ea902976..7f00995e2 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -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 diff --git a/test/unit/vagrant/action/builtin/synced_folder_cleanup_test.rb b/test/unit/vagrant/action/builtin/synced_folder_cleanup_test.rb new file mode 100644 index 000000000..46d28972c --- /dev/null +++ b/test/unit/vagrant/action/builtin/synced_folder_cleanup_test.rb @@ -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