(#4666) Remove duplicate export folders before writing /etc/exports

Prior to this commit, if you set up multiple folders to export with NFS
on linux with the exact same hostpath, the template used to write
/etc/exports would end up placing the same path with the same IP in
/etc/exports and cause an error preventing the folders from being
properly mounted. This commit fixes that by first looking at which
folders are being exported and if there are any duplicates. If so,
remove the duplicates and only export 1 hostpath folder. If these
duplicate folders have differing nfs linux options, an exception must be
thrown because we cannot assume which options the user intended to
export with.
This commit is contained in:
Brian Cain 2017-09-01 16:56:34 -07:00
parent 2170d40bb2
commit f0f60a1075
4 changed files with 75 additions and 0 deletions

View File

@ -468,6 +468,10 @@ module Vagrant
error_key(:nfs_bad_exports)
end
class NFSDupePerms < VagrantError
error_key(:nfs_dupe_permissions)
end
class NFSExportsFailed < VagrantError
error_key(:nfs_exports_failed)
end

View File

@ -29,6 +29,7 @@ module VagrantPlugins
nfs_start_command = env.host.capability(:nfs_start_command)
nfs_opts_setup(folders)
folders = folder_dupe_check(folders)
output = Vagrant::Util::TemplateRenderer.render('nfs/exports_linux',
uuid: id,
ips: ips,
@ -84,6 +85,37 @@ module VagrantPlugins
protected
# Takes a hash of folders and removes any duplicate exports that
# share the same hostpath to avoid duplicate entries in /etc/exports
# ref: GH-4666
def self.folder_dupe_check(folders)
return_folders = {}
# Group by hostpath to see if there are multiple exports coming
# from the same folder
export_groups = folders.values.group_by { |h| h[:hostpath] }
# We need to check that each group key only has 1 value,
# and if not, check each nfs option. If all nfs options are the same
# we're good, otherwise throw an exception
export_groups.each do |path,group|
if group.size > 1
# if the linux nfs options aren't all the same throw an exception
group1_opts = group.first[:linux__nfs_options]
if !group.all? {|g| g[:linux__nfs_options] == group1_opts}
raise Vagrant::Errors::NFSDupePerms, hostpath: group.first[:hostpath]
else
# if they're the same just pick the first one
return_folders[path] = group.first
end
else
# just return folder, there are no duplicates
return_folders[path] = group.first
end
end
return_folders
end
def self.nfs_cleanup(remove_ids)
return if !File.exist?(NFS_EXPORTS_PATH)

View File

@ -912,6 +912,9 @@ en:
command: %{command}
stdout: %{stdout}
stderr: %{stderr}
nfs_dupe_permissions: |-
You have attempted to export the same nfs host path at %{hostpath} with
different nfs permissions. Please pick one permission and reload your guest.
nfs_cant_read_exports: |-
Vagrant can't read your current NFS exports! The exports file should be
readable by any user. This is usually caused by invalid permissions

View File

@ -79,6 +79,42 @@ EOH
expect(exports_content).to include("/tmp")
expect(exports_content).not_to include("/var")
end
it "throws an exception with at least 2 different nfs options" do
folders = {"/vagrant"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/project"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","sync"]}}
expect { cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], folders) }.
to raise_error Vagrant::Errors::NFSDupePerms
end
it "writes only 1 hostpath for multiple exports" do
folders = {"/vagrant"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/otherproject"=>
{:hostpath=>"/newhome/otherproject",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/project"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]}}
valid_id = SecureRandom.uuid
content =<<-EOH
\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id}
"/home/vagrant" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)
"/newhome/otherproject" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{valid_id}
EOH
cap.nfs_export(env, ui, valid_id, ["127.0.0.1"], folders)
exports_content = File.read(exports_path)
expect(exports_content).to eq(content)
end
end
describe ".nfs_prune" do