diff --git a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb index 402e07615..a2a206745 100644 --- a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb @@ -6,22 +6,17 @@ module VagrantPlugins class MountVirtualBoxSharedFolder extend SyncedFolder::UnixMountHelpers + VB_MOUNT_TYPE = "vboxsf".freeze + def self.mount_virtualbox_shared_folder(machine, name, guestpath, options) guest_path = Shellwords.escape(guestpath) @@logger.debug("Mounting #{name} (#{options[:hostpath]} to #{guestpath})") - builtin_mount_type = "-cit vboxsf" - addon_mount_type = "-t vboxsf" + builtin_mount_type = "-cit #{VB_MOUNT_TYPE}" + addon_mount_type = "-t #{VB_MOUNT_TYPE}" - mount_options = options.fetch(:mount_options, []) - detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options) - mount_uid = detected_ids[:uid] - mount_gid = detected_ids[:gid] - - mount_options << "uid=#{mount_uid}" - mount_options << "gid=#{mount_gid}" - mount_options = mount_options.join(',') + mount_options, mount_uid, mount_gid = mount_options(machine, 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 @@ -61,7 +56,6 @@ module VagrantPlugins emit_upstart_notification(machine, guest_path) end - def self.unmount_virtualbox_shared_folder(machine, guestpath, options) guest_path = Shellwords.escape(guestpath) diff --git a/plugins/guests/linux/cap/persist_mount_shared_folder.rb b/plugins/guests/linux/cap/persist_mount_shared_folder.rb new file mode 100644 index 000000000..076cd18b0 --- /dev/null +++ b/plugins/guests/linux/cap/persist_mount_shared_folder.rb @@ -0,0 +1,48 @@ +require "vagrant/util" + +require_relative "../../../synced_folders/unix_mount_helpers" + +module VagrantPlugins + module GuestLinux + module Cap + class PersistMountSharedFolder + extend SyncedFolder::UnixMountHelpers + + # 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 + # + # @param [Machine] machine The machine to run the action on + # @param [Map] A map of folders to add to fstab + # @param [String] mount type, ex. vboxfs, cifs, etc + def self.persist_mount_shared_folder(machine, fstab_folders, mount_type) + if fstab_folders.empty? + self.remove_vagrant_managed_fstab(machine) + return + end + export_folders = fstab_folders.map do |name, data| + guest_path = Shellwords.escape(data[:guestpath]) + mount_options, mount_uid, mount_gid = mount_options(machine, name, guest_path, data) + mount_options = "#{mount_options},nofail" + { + name: name, + mount_point: guest_path, + mount_type: mount_type, + mount_options: mount_options, + } + end + + fstab_entry = Vagrant::Util::TemplateRenderer.render('guests/linux/etc_fstab', folders: export_folders) + self.remove_vagrant_managed_fstab(machine) + machine.communicate.sudo("echo '#{fstab_entry}' >> /etc/fstab") + end + + private + + def self.remove_vagrant_managed_fstab(machine) + machine.communicate.sudo("sed -i '/\#VAGRANT-BEGIN/,/\#VAGRANT-END/d' /etc/fstab") + end + end + end + end +end \ No newline at end of file diff --git a/plugins/guests/linux/plugin.rb b/plugins/guests/linux/plugin.rb index 777d5cbec..c8754be9a 100644 --- a/plugins/guests/linux/plugin.rb +++ b/plugins/guests/linux/plugin.rb @@ -61,6 +61,11 @@ module VagrantPlugins Cap::MountVirtualBoxSharedFolder end + guest_capability(:linux, :persist_mount_shared_folder) do + require_relative "cap/persist_mount_shared_folder" + Cap::PersistMountSharedFolder + end + guest_capability(:linux, :network_interfaces) do require_relative "cap/network_interfaces" Cap::NetworkInterfaces diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 287a001fb..422ce6239 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -23,6 +23,7 @@ module VagrantPlugins DEFAULT_VM_NAME = :default attr_accessor :allowed_synced_folder_types + attr_accessor :allow_fstab_modification attr_accessor :base_mac attr_accessor :base_address attr_accessor :boot_timeout @@ -58,6 +59,7 @@ module VagrantPlugins @logger = Log4r::Logger.new("vagrant::config::vm") @allowed_synced_folder_types = UNSET_VALUE + @allow_fstab_modification = UNSET_VALUE @base_mac = UNSET_VALUE @base_address = UNSET_VALUE @boot_timeout = UNSET_VALUE @@ -502,6 +504,7 @@ module VagrantPlugins def finalize! # Defaults + @allow_fstab_modification = true if @allow_fstab_modification == UNSET_VALUE @allowed_synced_folder_types = nil if @allowed_synced_folder_types == UNSET_VALUE @base_mac = nil if @base_mac == UNSET_VALUE @base_address = nil if @base_address == UNSET_VALUE @@ -988,6 +991,12 @@ module VagrantPlugins end end + if ![TrueClass, FalseClass].include?(@allow_fstab_modification.class) + errors["vm"] << I18n.t("vagrant.config.vm.config_type", + option: "allow_fstab_modification", given: @allow_fstab_modification.class, required: "Boolean" + ) + end + errors end diff --git a/plugins/providers/virtualbox/synced_folder.rb b/plugins/providers/virtualbox/synced_folder.rb index 89b006cf2..fd0b8f4b8 100644 --- a/plugins/providers/virtualbox/synced_folder.rb +++ b/plugins/providers/virtualbox/synced_folder.rb @@ -33,6 +33,7 @@ module VagrantPlugins # Go through each folder and mount machine.ui.output(I18n.t("vagrant.actions.vm.share_folders.mounting")) + fstab_folders = [] folders.each do |id, data| if data[:guestpath] # Guest path specified, so mount the folder to specified point @@ -52,12 +53,20 @@ module VagrantPlugins machine.guest.capability( :mount_virtualbox_shared_folder, os_friendly_id(id), data[:guestpath], data) + fstab_folders << [os_friendly_id(id), data] else # If no guest path is specified, then automounting is disabled machine.ui.detail(I18n.t("vagrant.actions.vm.share_folders.nomount_entry", hostpath: data[:hostpath])) end end + if machine.guest.capability?(:persist_mount_shared_folder) + # If Vagrant has been configured to not allow fstab modification, then + # execute the guest capability with an empty list in order to ensure + # there is no Vagrant managed fstab entries. + fstab_folders = [] if !machine.config.vm.allow_fstab_modification + machine.guest.capability(:persist_mount_shared_folder, fstab_folders, "vboxsf") + end end def disable(machine, folders, _opts) diff --git a/plugins/synced_folders/unix_mount_helpers.rb b/plugins/synced_folders/unix_mount_helpers.rb index 3ba93cde1..f49462bb0 100644 --- a/plugins/synced_folders/unix_mount_helpers.rb +++ b/plugins/synced_folders/unix_mount_helpers.rb @@ -100,6 +100,17 @@ module VagrantPlugins EOH end + def mount_options(machine, name, guest_path, options) + mount_options = options.fetch(:mount_options, []) + detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options) + mount_uid = detected_ids[:uid] + mount_gid = detected_ids[:gid] + + mount_options << "uid=#{mount_uid}" + mount_options << "gid=#{mount_gid}" + mount_options = mount_options.join(',') + return mount_options, mount_uid, mount_gid + end end end end diff --git a/templates/guests/linux/etc_fstab.erb b/templates/guests/linux/etc_fstab.erb new file mode 100644 index 000000000..b61b04bb3 --- /dev/null +++ b/templates/guests/linux/etc_fstab.erb @@ -0,0 +1,6 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +<% folders.each do |opts| %> +<%= opts[:name] %> <%= opts[:mount_point] %> <%= opts[:mount_type] %> <%= opts[:mount_options] || 'default' %> <%= opts[:dump] || 0 %> <%= opts[:fsck] || 0 %> +<%end%> +#VAGRANT-END \ No newline at end of file diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5538bc37a..47ee108b5 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1947,6 +1947,7 @@ en: box_download_options_not_converted: |- Something went wrong converting VM config `box_download_options`. Value for provided key '%{missing_key}' is invalid type. Should be String or Bool clone_and_box: "Only one of clone or box can be specified." + config_type: "Found '%{option}' specified as type '%{given}', should be '%{required}'" hostname_invalid_characters: |- The hostname set for the VM '%{name}' should only contain letters, numbers, hyphens or dots. It cannot start with a hyphen or dot. 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 new file mode 100644 index 000000000..f4d6f58c7 --- /dev/null +++ b/test/unit/plugins/guests/linux/cap/persist_mount_shared_folder_test.rb @@ -0,0 +1,52 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do + let(:caps) do + VagrantPlugins::GuestLinux::Plugin + .components + .guest_capabilities[:linux] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:cap){ caps.get(:persist_mount_shared_folder) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".persist_mount_shared_folder" do + let(:options_gid){ '1234' } + let(:options_uid){ '1234' } + + 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(:ui){ double(:ui) } + + before do + allow(comm).to receive(:sudo).with(any_args) + allow(ui).to receive(:warn) + allow(machine).to receive(:ui).and_return(ui) + end + + it "inserts folders into /etc/fstab" do + expected_entry_vagrant = "vagrant /vagrant vboxsf uid=1234,gid=1234,nofail 0 0" + expected_entry_test = "test1 /test1 vboxsf uid=1234,gid=1234,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, fstab_folders, "vboxsf") + end + + it "does not insert an empty set of folders" do + expect(cap).to receive(:remove_vagrant_managed_fstab) + cap.persist_mount_shared_folder(machine, [], "type") + end + end +end diff --git a/test/unit/plugins/providers/virtualbox/synced_folder_test.rb b/test/unit/plugins/providers/virtualbox/synced_folder_test.rb index a5b121581..ea8682586 100644 --- a/test/unit/plugins/providers/virtualbox/synced_folder_test.rb +++ b/test/unit/plugins/providers/virtualbox/synced_folder_test.rb @@ -6,13 +6,35 @@ require Vagrant.source_root.join("plugins/providers/virtualbox/synced_folder") describe VagrantPlugins::ProviderVirtualBox::SyncedFolder do include_context "unit" + + let(:vm_config) do + double("vm_config").tap do |vm_config| + allow(vm_config).to receive(:allow_fstab_modification).and_return(true) + end + end + + let(:machine_config) do + double("machine_config").tap do |top_config| + allow(top_config).to receive(:vm).and_return(vm_config) + end + end + let(:machine) do double("machine").tap do |m| allow(m).to receive(:provider_config).and_return(VagrantPlugins::ProviderVirtualBox::Config.new) allow(m).to receive(:provider_name).and_return(:virtualbox) + allow(m).to receive(:config).and_return(machine_config) end end + let(:folders) { {"/folder"=> + {:SharedFoldersEnableSymlinksCreate=>true, + :guestpath=>"/folder", + :hostpath=>"/Users/brian/vagrant-folder", + :automount=>false, + :disabled=>false, + :__vagrantfile=>true}} } + subject { described_class.new } before do @@ -36,16 +58,55 @@ describe VagrantPlugins::ProviderVirtualBox::SyncedFolder do end end + describe "#enable" do + let(:ui){ double(:ui) } + let(:guest) { double("guest") } + + let(:no_guestpath_folder) { {"/no_guestpath_folder"=> + {:SharedFoldersEnableSymlinksCreate=>false, + :guestpath=>nil, + :hostpath=>"/Users/brian/vagrant-folder", + :automount=>false, + :disabled=>true, + :__vagrantfile=>true}} } + + before do + allow(subject).to receive(:share_folders).and_return(true) + allow(ui).to receive(:detail).with(any_args) + allow(ui).to receive(:output).with(any_args) + allow(machine).to receive(:ui).and_return(ui) + allow(machine).to receive(:ssh_info).and_return({:username => "test"}) + allow(machine).to receive(:guest).and_return(guest) + end + + it "should mount and persist all folders with a guest path" do + expect(guest).to receive(:capability).with(:mount_virtualbox_shared_folder, "folder", any_args) + expect(guest).not_to receive(:capability).with(:mount_virtualbox_shared_folder, "no_guestpath_folder", any_args) + expect(guest).to receive(:capability?).with(:persist_mount_shared_folder).and_return(true) + expect(guest).to receive(:capability).with(:persist_mount_shared_folder, any_args) + test_folders = folders.merge(no_guestpath_folder) + subject.enable(machine, test_folders, nil) + end + + context "fstab modification disabled" do + before do + allow(vm_config).to receive(:allow_fstab_modification).and_return(false) + end + + it "should not persist folders" do + expect(guest).to receive(:capability).with(:mount_virtualbox_shared_folder, "folder", any_args) + expect(guest).not_to receive(:capability).with(:mount_virtualbox_shared_folder, "no_guestpath_folder", any_args) + expect(guest).to receive(:capability?).with(:persist_mount_shared_folder).and_return(true) + expect(guest).to receive(:capability).with(:persist_mount_shared_folder, [], "vboxsf") + test_folders = folders.merge(no_guestpath_folder) + subject.enable(machine, test_folders, nil) + end + end + end + describe "#prepare" do let(:driver) { double("driver") } let(:provider) { double("driver", driver: driver) } - let(:folders) { {"/folder"=> - {:SharedFoldersEnableSymlinksCreate=>true, - :guestpath=>"/folder", - :hostpath=>"/Users/brian/vagrant-folder", - :automount=>false, - :disabled=>false, - :__vagrantfile=>true}} } let(:folders_disabled) { {"/folder"=> {:SharedFoldersEnableSymlinksCreate=>false, diff --git a/website/pages/docs/vagrantfile/machine_settings.mdx b/website/pages/docs/vagrantfile/machine_settings.mdx index aa04f6f5e..6d7cd4bdd 100644 --- a/website/pages/docs/vagrantfile/machine_settings.mdx +++ b/website/pages/docs/vagrantfile/machine_settings.mdx @@ -16,6 +16,11 @@ machine that Vagrant manages. ## Available Settings +- `config.vm.allow_fstab_modification` (boolean) - If true, will add fstab + entries for synced folders. If false, no modifications to fstab will be made + by Vagrant. Note, this may mean that folders will not be automatically mounted + on machine reboot. Defaults to true. + - `config.vm.base_mac` (string) - The MAC address to be assigned to the default NAT interface on the guest. _Support for this option is provider dependent._