diff --git a/lib/vagrant/action/builtin/mixin_synced_folders.rb b/lib/vagrant/action/builtin/mixin_synced_folders.rb index 81c238fc8..f505d2241 100644 --- a/lib/vagrant/action/builtin/mixin_synced_folders.rb +++ b/lib/vagrant/action/builtin/mixin_synced_folders.rb @@ -98,7 +98,8 @@ module Vagrant end end - folder_data = JSON.dump(folders) + # Remove implementation instances + folder_data = JSON.dump(folders.to_h) # Scrub any register credentials from the synced folders # configuration data to prevent accidental leakage @@ -125,7 +126,7 @@ module Vagrant end config_folders = config.synced_folders - folders = Vagrant::Util::TypedHash.new() + folders = Vagrant::Plugin::V2::SyncedFolder::Collection.new # Determine all the synced folders as well as the implementation # they're going to use. @@ -182,8 +183,10 @@ module Vagrant # Apply the scoped hash overrides to get the options folders.dup.each do |impl_name, fs| + impl = plugins[impl_name].first.new._initialize(machine, impl_name) new_fs = {} fs.each do |id, data| + data[:plugin] = impl id = data[:id] if data[:id] new_fs[id] = scoped_hash_override(data, impl_name) end @@ -191,12 +194,7 @@ module Vagrant folders[impl_name] = new_fs end - folders.types = folders.map { |type, folders| - impl = plugins[type][0].new() - impl._initialize(machine, type) - [type, impl] - }.to_h - return folders + folders end # This finds the difference between two lists of synced folder @@ -245,32 +243,26 @@ module Vagrant protected def cached_synced_folders(machine) - folders = JSON.parse(machine.data_dir.join("synced_folders").read, object_class: Vagrant::Util::TypedHash).tap do |r| - # We have to do all sorts of things to make the proper things - # symbols and - r.keys.each do |k| - r[k].each do |ik, v| - v.keys.each do |vk| - v[vk.to_sym] = v[vk] - v.delete(vk) - end - end - - r[k.to_sym] = r[k] - r.delete(k) - end + import = JSON.parse(machine.data_dir.join("synced_folders").read) + import.each do |type, folders| + impl = plugins[type.to_sym].first.new._initialize(machine, type) + folders.each { |_, v| v[:plugin] = impl } end - folders.types = folders.map { |type, folders| - impl = plugins[type][0].new() - impl._initialize(machine, type) - [type, impl] - }.to_h || {} - folders + # Symbolize the keys we want as symbols + import.keys.dup.each do |k| + import[k].values.each do |item| + item.keys.dup.each do |ik| + item[ik.to_sym] = item.delete(ik) + end + end + import[k.to_sym] = import.delete(k) + end + Vagrant::Plugin::V2::SyncedFolder::Collection[import] rescue Errno::ENOENT # If the file doesn't exist, we probably just have a machine created # by a version of Vagrant that didn't cache shared folders. Report no # shared folders to be safe. - Vagrant::Util::TypedHash.new(types: {}) + Vagrant::Plugin::V2::SyncedFolder::Collection.new end end end diff --git a/lib/vagrant/plugin/v2/synced_folder.rb b/lib/vagrant/plugin/v2/synced_folder.rb index 06b4ec623..851b1ea05 100644 --- a/lib/vagrant/plugin/v2/synced_folder.rb +++ b/lib/vagrant/plugin/v2/synced_folder.rb @@ -3,6 +3,45 @@ module Vagrant module V2 # This is the base class for a synced folder implementation. class SyncedFolder + class Collection < Hash + + # @return [Array] names of synced folder types + def types + keys + end + + # Fetch the synced plugin folder of the given type + # + # @param [Symbol] t Synced folder type + # @return [Vagrant::Plugin::V2::SyncedFolder] + def type(t) + f = detect { |k, _| k.to_sym == t.to_sym }.last + raise KeyError, "Unknown synced folder type" if !f + f.values.first[:plugin] + end + + # Converts to a regular Hash and removes + # plugin instances so the result is ready + # for serialization + # + # @return [Hash] + def to_h + c = lambda do |h| + h.keys.each do |k| + if h[k].is_a?(Hash) + h[k] = c.call(h[k].to_h) + end + end + h + end + h = c.call(super) + h.values.each do |f| + f.delete(:plugin) + end + h + end + end + include CapabilityHost # This is called early when the synced folder is set to determine @@ -61,6 +100,7 @@ module Vagrant plugins = Vagrant.plugin("2").manager.synced_folders capabilities = Vagrant.plugin("2").manager.synced_folder_capabilities initialize_capabilities!(synced_folder_type, plugins, capabilities, machine) + self end end end diff --git a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb index abbf917d2..0d788a306 100644 --- a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb @@ -8,14 +8,14 @@ module VagrantPlugins def self.mount_virtualbox_shared_folder(machine, name, guestpath, options) guest_path = Shellwords.escape(guestpath) - mount_type = machine.synced_folders.types[:virtualbox].capability(:mount_type) + mount_type = options[:plugin].capability(:mount_type) @@logger.debug("Mounting #{name} (#{options[:hostpath]} to #{guestpath})") builtin_mount_type = "-cit #{mount_type}" addon_mount_type = "-t #{mount_type}" - mount_options, mount_uid, mount_gid = machine.synced_folders.types[:virtualbox].capability(:mount_options, name, guest_path, options) + mount_options, mount_uid, mount_gid = options[:plugin].capability(:mount_options, name, guest_path, options) mount_command = "mount #{addon_mount_type} -o #{mount_options} #{name} #{guest_path}" # Create the guest path if it doesn't exist diff --git a/plugins/guests/linux/cap/persist_mount_shared_folder.rb b/plugins/guests/linux/cap/persist_mount_shared_folder.rb index 78f187b86..c2f7c3cc5 100644 --- a/plugins/guests/linux/cap/persist_mount_shared_folder.rb +++ b/plugins/guests/linux/cap/persist_mount_shared_folder.rb @@ -10,8 +10,8 @@ module VagrantPlugins # Inserts fstab entry for a set of synced folders. Will fully replace # the currently managed group of Vagrant managed entries. Note, passing - # empty list of folders will just remove entries - # + # empty list of folders will just remove entries + # # @param [Machine] machine The machine to run the action on # @param [Map] A map of folders to add to fstab def self.persist_mount_shared_folder(machine, folders) @@ -20,15 +20,15 @@ module VagrantPlugins return end - ssh_info = machine.ssh_info + ssh_info = machine.ssh_info export_folders = folders.map { |type, folder| folder.map { |name, data| data[:owner] ||= ssh_info[:username] data[:group] ||= ssh_info[:username] guest_path = Shellwords.escape(data[:guestpath]) - mount_options, _, _ = machine.synced_folders.types[type].capability( + mount_options, _, _ = data[:plugin].capability( :mount_options, name, guest_path, data) - mount_type = machine.synced_folders.types[type].capability(:mount_type) + mount_type = data[:plugin].capability(:mount_type) mount_options = "#{mount_options},nofail" { name: name, diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 6415f58a8..4d64aace6 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -727,7 +727,8 @@ module VagrantPlugins if @allow_fstab_modification == UNSET_VALUE plugins = Vagrant.plugin("2").manager.synced_folders - machine.synced_folders.types.each do |_, inst| + machine.synced_folders.types.each do |impl_name| + inst = machine.synced_folders.type(impl_name) if inst.capability?(:default_fstab_modification) if inst.capability(:default_fstab_modification) == false @allow_fstab_modification = false diff --git a/test/unit/plugins/guests/linux/cap/mount_virtual_box_shared_folder_test.rb b/test/unit/plugins/guests/linux/cap/mount_virtual_box_shared_folder_test.rb index 3e8662f78..4ace19225 100644 --- a/test/unit/plugins/guests/linux/cap/mount_virtual_box_shared_folder_test.rb +++ b/test/unit/plugins/guests/linux/cap/mount_virtual_box_shared_folder_test.rb @@ -16,18 +16,20 @@ describe "VagrantPlugins::GuestLinux::Cap::MountVirtualBoxSharedFolder" do let(:mount_name){ "vagrant" } let(:mount_guest_path){ "/vagrant" } let(:folder_options) do - { - owner: mount_owner, - group: mount_group, - hostpath: "/host/directory/path" - } + Vagrant::Plugin::V2::SyncedFolder::Collection[ + { + owner: mount_owner, + group: mount_group, + hostpath: "/host/directory/path", + plugin: folder_plugin + } + ] end let(:cap){ caps.get(:mount_virtualbox_shared_folder) } - let(:mount_options_cap){ double("opts") } + let(:folder_plugin) { double("folder_plugin") } before do allow(machine).to receive(:communicate).and_return(comm) - allow(machine).to receive_message_chain(:synced_folders, :types).and_return( { :virtualbox => mount_options_cap } ) end after do @@ -42,17 +44,21 @@ describe "VagrantPlugins::GuestLinux::Cap::MountVirtualBoxSharedFolder" do end it "generates the expected default mount command" do - expect(mount_options_cap).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) - expect(mount_options_cap).to receive(:capability).with(:mount_type).and_return("vboxsf") + expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options). + and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) + expect(folder_plugin).to receive(:capability).with(:mount_type).and_return("vboxsf") expect(comm).to receive(:sudo).with("mount -t vboxsf -o uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}", anything) + cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options) end it "automatically chown's the mounted directory on guest" do - expect(mount_options_cap).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) - expect(mount_options_cap).to receive(:capability).with(:mount_type).and_return("vboxsf") + expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options). + and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) + expect(folder_plugin).to receive(:capability).with(:mount_type).and_return("vboxsf") expect(comm).to receive(:sudo).with("mount -t vboxsf -o uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}", anything) expect(comm).to receive(:sudo).with("chown #{mount_uid}:#{mount_gid} #{mount_guest_path}") + cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options) end @@ -60,8 +66,10 @@ describe "VagrantPlugins::GuestLinux::Cap::MountVirtualBoxSharedFolder" do it "emits mount event" do expect(comm).to receive(:sudo).with(/initctl emit/) - expect(mount_options_cap).to receive(:capability).with(:mount_type).and_return("vboxsf") - expect(mount_options_cap).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) + expect(folder_plugin).to receive(:capability).with(:mount_type).and_return("vboxsf") + expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options). + and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) + cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options) end end @@ -74,10 +82,12 @@ mount.vboxsf cannot be used with mainline vboxsf; instead use: EOF } it "should perform guest mount using builtin module" do - expect(mount_options_cap).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) - expect(mount_options_cap).to receive(:capability).with(:mount_type).and_return("vboxsf") + expect(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options). + and_return(["uid=#{mount_uid},gid=#{mount_gid}", mount_uid, mount_gid]) + expect(folder_plugin).to receive(:capability).with(:mount_type).and_return("vboxsf") expect(comm).to receive(:sudo).with(/mount -t vboxsf/, any_args).and_yield(:stderr, vbox_stderr).and_return(1) expect(comm).to receive(:sudo).with(/mount -cit/, any_args) + cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options) end end diff --git a/test/unit/plugins/guests/linux/cap/persist_mount_shared_folder_test.rb b/test/unit/plugins/guests/linux/cap/persist_mount_shared_folder_test.rb index 030134529..f7ae6b51b 100644 --- a/test/unit/plugins/guests/linux/cap/persist_mount_shared_folder_test.rb +++ b/test/unit/plugins/guests/linux/cap/persist_mount_shared_folder_test.rb @@ -12,14 +12,20 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do let(:options_gid){ '1234' } let(:options_uid){ '1234' } let(:cap){ caps.get(:persist_mount_shared_folder) } - let(:mount_options_cap){ double("opts") } + let(:folder_plugin){ double("folder_plugin") } let(:ssh_info) {{ :username => "vagrant" }} - let (:fstab_folders) { { - "test1" => {:guestpath=>"/test1", :hostpath=>"/my/host/path", :disabled=>false, :__vagrantfile=>true, :owner=>"vagrant", :group=>"vagrant", :mount_options=>["uid=1234", "gid=1234"]}, - "vagrant" => {:guestpath=>"/vagrant", :hostpath=>"/my/host/vagrant", :disabled=>false, :__vagrantfile=>true, :owner=>"vagrant", :group=>"vagrant", :mount_options=>["uid=1234", "gid=1234"]} - }} + let (:fstab_folders) { + Vagrant::Plugin::V2::SyncedFolder::Collection[ + { + "test1" => {guestpath: "/test1", hostpath: "/my/host/path", disabled: false, plugin: folder_plugin, + __vagrantfile: true, owner: "vagrant", group: "vagrant", mount_options: ["uid=#{options_uid}", "gid=#{options_gid}"]}, + "vagrant" => {guestpath: "/vagrant", hostpath: "/my/host/vagrant", disabled: false, __vagrantfile: true, + owner: "vagrant", group: "vagrant", mount_options: ["uid=#{options_uid}", "gid=#{options_gid}}"], plugin: folder_plugin} + } + ] + } let (:folders) { { :virtualbox => fstab_folders } } @@ -27,9 +33,9 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do before do allow(machine).to receive(:communicate).and_return(comm) allow(machine).to receive(:ssh_info).and_return(ssh_info) - allow(machine).to receive_message_chain(:synced_folders, :types).and_return( { :virtualbox => mount_options_cap } ) - allow(mount_options_cap).to receive(:capability).with(:mount_options, any_args).and_return(["uid=#{options_uid},gid=#{options_gid}", options_uid, options_gid]) - allow(mount_options_cap).to receive(:capability).with(:mount_type).and_return("vboxsf") + allow(folder_plugin).to receive(:capability).with(:mount_options, any_args). + and_return(["uid=#{options_uid},gid=#{options_gid}", options_uid, options_gid]) + allow(folder_plugin).to receive(:capability).with(:mount_type).and_return("vboxsf") end after do @@ -38,11 +44,6 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do describe ".persist_mount_shared_folder" do - let (:fstab_folders) { [ - ["test1", {:guestpath=>"/test1", :hostpath=>"/my/host/path", :disabled=>false, :__vagrantfile=>true, :owner=>"vagrant", :group=>"vagrant", :mount_options=>["uid=#{options_uid}", "gid=#{options_gid}"] }], - ["vagrant", {:guestpath=>"/vagrant", :hostpath=>"/my/host/vagrant", :disabled=>false, :__vagrantfile=>true, :owner=>"vagrant", :group=>"vagrant", :mount_options=>["uid=#{options_uid}", "gid=#{options_gid}"] }] - ]} - let(:ui){ double(:ui) } before do @@ -56,6 +57,7 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do expected_entry_test = "test1 /test1 vboxsf uid=#{options_uid},gid=#{options_gid},nofail 0 0" expect(cap).to receive(:remove_vagrant_managed_fstab) expect(comm).to receive(:sudo).with(/#{expected_entry_test}\n#{expected_entry_vagrant}/) + cap.persist_mount_shared_folder(machine, folders) end diff --git a/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb b/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb index adaf6af3a..5d3bc1a90 100644 --- a/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb +++ b/test/unit/vagrant/action/builtin/mixin_synced_folders_test.rb @@ -14,6 +14,7 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do end let(:data_dir) { Pathname.new(Dir.mktmpdir("vagrant-test-mixin-synced-folders")) } + let(:folders_class) { Vagrant::Plugin::V2::SyncedFolder::Collection } let(:machine) do double("machine").tap do |machine| @@ -91,7 +92,7 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do end describe "synced_folders" do - let(:folders) { {} } + let(:folders) { folders_class.new } let(:plugins) { {} } before do @@ -118,14 +119,14 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine) expect(result.length).to eq(2) expect(result[:default]).to eq({ - "another" => folders["another"].merge(__vagrantfile: true), - "foo" => folders["foo"].merge(__vagrantfile: true), - "root" => folders["root"].merge(__vagrantfile: true), + "another" => folders["another"].merge(__vagrantfile: true, plugin: true), + "foo" => folders["foo"].merge(__vagrantfile: true, plugin: true), + "root" => folders["root"].merge(__vagrantfile: true, plugin: true), }) expect(result[:nfs]).to eq({ - "nfs" => folders["nfs"].merge(__vagrantfile: true), + "nfs" => folders["nfs"].merge(__vagrantfile: true, plugin: true), }) - expect(result.types.keys).to eq([:default, :nfs]) + expect(result.types).to eq([:default, :nfs]) end it "should return the proper set of folders of a custom config" do @@ -139,9 +140,9 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine, config: other) expect(result.length).to eq(1) expect(result[:default]).to eq({ - "bar" => other_folders["bar"], + "bar" => other_folders["bar"].merge(plugin: true), }) - expect(result.types.keys).to eq([:default]) + expect(result.types).to eq([:default]) end it "should error if an explicit type is unusable" do @@ -159,7 +160,7 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine) expect(result.length).to eq(1) expect(result[:default].length).to eq(1) - expect(result.types.keys).to eq([:default]) + expect(result.types).to eq([:default]) end it "should scope hash override the settings" do @@ -171,13 +172,13 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine) expect(result[:nfs]["root"][:foo]).to eql("bar") - expect(result.types.keys).to eq([:nfs]) + expect(result.types).to eq([:nfs]) end it "returns {} if cached read with no cache" do result = subject.synced_folders(machine, cached: true) expect(result).to eql({}) - expect(result.types).to eq({}) + expect(result.types).to eq([]) end it "should be able to save and retrieve cached versions" do @@ -196,12 +197,12 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine, cached: true) expect(result.length).to eq(2) expect(result[:default]).to eq({ - "another" => old_folders["another"].merge(__vagrantfile: true), - "foo" => old_folders["foo"].merge(__vagrantfile: true), - "root" => old_folders["root"].merge(__vagrantfile: true), + "another" => old_folders["another"].merge(__vagrantfile: true, plugin: true), + "foo" => old_folders["foo"].merge(__vagrantfile: true, plugin: true), + "root" => old_folders["root"].merge(__vagrantfile: true, plugin: true), }) - expect(result[:nfs]).to eq({ "nfs" => old_folders["nfs"].merge(__vagrantfile: true) }) - expect(result.types.keys).to eq([:default, :nfs]) + expect(result[:nfs]).to eq({ "nfs" => old_folders["nfs"].merge(__vagrantfile: true, plugin: true) }) + expect(result.types).to eq([:default, :nfs]) end it "should be able to save and retrieve cached versions" do @@ -227,13 +228,13 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine, cached: true) expect(result.length).to eq(2) expect(result[:default]).to eq({ - "foo" => { type: "default" }, - "bar" => { type: "default", __vagrantfile: true}, + "foo" => { type: "default", plugin: true }, + "bar" => { type: "default", __vagrantfile: true, plugin: true }, }) expect(result[:nfs]).to eq({ - "baz" => { type: "nfs", __vagrantfile: true } + "baz" => { type: "nfs", __vagrantfile: true, plugin: true } }) - expect(result.types.keys).to eq([:default, :nfs]) + expect(result.types).to eq([:default, :nfs]) end it "should remove items from the vagrantfile that were removed" do @@ -255,17 +256,17 @@ describe Vagrant::Action::Builtin::MixinSyncedFolders do result = subject.synced_folders(machine, cached: true) expect(result.length).to eq(2) expect(result[:default]).to eq({ - "bar" => { type: "default", __vagrantfile: true}, + "bar" => { type: "default", __vagrantfile: true, plugin: true}, }) expect(result[:nfs]).to eq({ - "baz" => { type: "nfs", __vagrantfile: true } + "baz" => { type: "nfs", __vagrantfile: true, plugin: true } }) - expect(result.types.keys).to eq([:default, :nfs]) + expect(result.types).to eq([:default, :nfs]) end end describe "#save_synced_folders" do - let(:folders) { {} } + let(:folders) { folders_class.new } let(:options) { {} } let(:output_file) { double("output_file") }