Merge pull request #10717 from briancain/add-vbox-share-folders-bsd
Add proper VirtualBox share folders support for FreeBSD guests
This commit is contained in:
commit
e399aeaf70
|
@ -0,0 +1,76 @@
|
||||||
|
require_relative "../../../synced_folders/unix_mount_helpers"
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module GuestFreeBSD
|
||||||
|
module Cap
|
||||||
|
class VirtualBox
|
||||||
|
extend SyncedFolder::UnixMountHelpers
|
||||||
|
|
||||||
|
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 vboxvfs"
|
||||||
|
addon_mount_type = "-t vboxvfs"
|
||||||
|
|
||||||
|
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_command = "mount #{addon_mount_type} -o #{mount_options} #{name} #{guest_path}"
|
||||||
|
|
||||||
|
# Create the guest path if it doesn't exist
|
||||||
|
machine.communicate.sudo("mkdir -p #{guest_path}")
|
||||||
|
|
||||||
|
stderr = ""
|
||||||
|
result = machine.communicate.sudo(mount_command, error_check: false) do |type, data|
|
||||||
|
stderr << data if type == :stderr
|
||||||
|
end
|
||||||
|
|
||||||
|
if result != 0
|
||||||
|
if stderr.include?("-cit")
|
||||||
|
@@logger.info("Detected builtin vboxvfs module, modifying mount command")
|
||||||
|
mount_command.sub!(addon_mount_type, builtin_mount_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Attempt to mount the folder. We retry here a few times because
|
||||||
|
# it can fail early on.
|
||||||
|
stderr = ""
|
||||||
|
retryable(on: Vagrant::Errors::VirtualBoxMountFailed, tries: 3, sleep: 5) do
|
||||||
|
machine.communicate.sudo(mount_command,
|
||||||
|
error_class: Vagrant::Errors::VirtualBoxMountFailed,
|
||||||
|
error_key: :virtualbox_mount_failed,
|
||||||
|
command: mount_command,
|
||||||
|
output: stderr,
|
||||||
|
) { |type, data| stderr = data if type == :stderr }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Chown the directory to the proper user. We skip this if the
|
||||||
|
# mount options contained a readonly flag, because it won't work.
|
||||||
|
if !options[:mount_options] || !options[:mount_options].include?("ro")
|
||||||
|
chown_command = "chown #{mount_uid}:#{mount_gid} #{guest_path}"
|
||||||
|
machine.communicate.sudo(chown_command)
|
||||||
|
end
|
||||||
|
|
||||||
|
emit_upstart_notification(machine, guest_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def self.unmount_virtualbox_shared_folder(machine, guestpath, options)
|
||||||
|
guest_path = Shellwords.escape(guestpath)
|
||||||
|
|
||||||
|
result = machine.communicate.sudo("umount #{guest_path}", error_check: false)
|
||||||
|
if result == 0
|
||||||
|
machine.communicate.sudo("rmdir #{guest_path}", error_check: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -50,6 +50,11 @@ module VagrantPlugins
|
||||||
require_relative "cap/shell_expand_guest_path"
|
require_relative "cap/shell_expand_guest_path"
|
||||||
Cap::ShellExpandGuestPath
|
Cap::ShellExpandGuestPath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
guest_capability(:freebsd, :mount_virtualbox_shared_folder) do
|
||||||
|
require_relative "cap/virtualbox"
|
||||||
|
Cap::VirtualBox
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
require_relative "../../../../base"
|
||||||
|
|
||||||
|
describe "VagrantPlugins::GuestBSD::Cap::VirtualBox" do
|
||||||
|
let(:caps) do
|
||||||
|
VagrantPlugins::GuestBSD::Plugin
|
||||||
|
.components
|
||||||
|
.guest_capabilities[:bsd]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:machine) { double("machine") }
|
||||||
|
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||||
|
let(:mount_owner){ "vagrant" }
|
||||||
|
let(:mount_group){ "vagrant" }
|
||||||
|
let(:mount_uid){ "1000" }
|
||||||
|
let(:mount_gid){ "1000" }
|
||||||
|
let(:mount_name){ "vagrant" }
|
||||||
|
let(:mount_guest_path){ "/vagrant" }
|
||||||
|
let(:folder_options) do
|
||||||
|
{
|
||||||
|
owner: mount_owner,
|
||||||
|
group: mount_group,
|
||||||
|
hostpath: "/host/directory/path"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:cap){ caps.get(:mount_virtualbox_shared_folder) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(machine).to receive(:communicate).and_return(comm)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
comm.verify_expectations!
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".mount_virtualbox_shared_folder" do
|
||||||
|
it "raises an error as unsupported" do
|
||||||
|
expect {cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options) }.
|
||||||
|
to raise_error(Vagrant::Errors::VirtualBoxMountNotSupportedBSD)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,225 @@
|
||||||
|
require_relative "../../../../base"
|
||||||
|
|
||||||
|
describe "VagrantPlugins::GuestFreeBSD::Cap::VirtualBox" do
|
||||||
|
let(:caps) do
|
||||||
|
VagrantPlugins::GuestFreeBSD::Plugin
|
||||||
|
.components
|
||||||
|
.guest_capabilities[:freebsd]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:machine) { double("machine") }
|
||||||
|
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||||
|
let(:mount_owner){ "vagrant" }
|
||||||
|
let(:mount_group){ "vagrant" }
|
||||||
|
let(:mount_uid){ "1000" }
|
||||||
|
let(:mount_gid){ "1000" }
|
||||||
|
let(:mount_name){ "vagrant" }
|
||||||
|
let(:mount_guest_path){ "/vagrant" }
|
||||||
|
let(:folder_options) do
|
||||||
|
{
|
||||||
|
owner: mount_owner,
|
||||||
|
group: mount_group,
|
||||||
|
hostpath: "/host/directory/path"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:cap){ caps.get(:mount_virtualbox_shared_folder) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(machine).to receive(:communicate).and_return(comm)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
comm.verify_expectations!
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".mount_virtualbox_shared_folder" do
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(comm).to receive(:sudo).with(any_args)
|
||||||
|
allow(comm).to receive(:execute).with(any_args)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "generates the expected default mount command" do
|
||||||
|
expect(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{mount_gid}:")
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -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(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{mount_gid}:")
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -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
|
||||||
|
|
||||||
|
context "with owner user ID explicitly defined" do
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{mount_gid}:")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with user ID provided as Integer" do
|
||||||
|
let(:mount_owner){ 2000 }
|
||||||
|
|
||||||
|
it "generates the expected mount command using mount_owner directly" do
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{mount_owner},gid=#{mount_gid} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
expect(comm).to receive(:sudo).with("chown #{mount_owner}:#{mount_gid} #{mount_guest_path}")
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with user ID provided as String" do
|
||||||
|
let(:mount_owner){ "2000" }
|
||||||
|
|
||||||
|
it "generates the expected mount command using mount_owner directly" do
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{mount_owner},gid=#{mount_gid} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
expect(comm).to receive(:sudo).with("chown #{mount_owner}:#{mount_gid} #{mount_guest_path}")
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with owner group ID explicitly defined" do
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with owner group ID provided as Integer" do
|
||||||
|
let(:mount_group){ 2000 }
|
||||||
|
|
||||||
|
it "generates the expected mount command using mount_group directly" do
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{mount_uid},gid=#{mount_group} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
expect(comm).to receive(:sudo).with("chown #{mount_uid}:#{mount_group} #{mount_guest_path}")
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with owner group ID provided as String" do
|
||||||
|
let(:mount_group){ "2000" }
|
||||||
|
|
||||||
|
it "generates the expected mount command using mount_group directly" do
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{mount_uid},gid=#{mount_group} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
expect(comm).to receive(:sudo).with("chown #{mount_uid}:#{mount_group} #{mount_guest_path}")
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-existent default owner group" do
|
||||||
|
|
||||||
|
it "fetches the effective group ID of the user" do
|
||||||
|
expect(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})
|
||||||
|
expect(comm).to receive(:execute).with("id -g #{mount_owner}", anything).and_yield(:stdout, "1").and_return(0)
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with non-existent owner group" do
|
||||||
|
|
||||||
|
it "raises an error" do
|
||||||
|
expect(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_raise(Vagrant::Errors::VirtualBoxMountFailed, {command: '', output: ''})
|
||||||
|
expect do
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end.to raise_error Vagrant::Errors::VirtualBoxMountFailed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with read-only option defined" do
|
||||||
|
|
||||||
|
it "does not chown mounted guest directory" do
|
||||||
|
expect(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{mount_gid}:")
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o ro,uid=#{mount_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
expect(comm).not_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.merge(mount_options: ["ro"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with upstart init" do
|
||||||
|
|
||||||
|
it "emits mount event" do
|
||||||
|
expect(comm).to receive(:sudo).with(/initctl emit/)
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with custom mount options" do
|
||||||
|
|
||||||
|
let(:ui){ double(:ui) }
|
||||||
|
before do
|
||||||
|
allow(ui).to receive(:warn)
|
||||||
|
allow(machine).to receive(:ui).and_return(ui)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with uid defined" do
|
||||||
|
let(:options_uid){ '1234' }
|
||||||
|
|
||||||
|
it "should only include uid defined within mount options" do
|
||||||
|
expect(comm).not_to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{mount_gid}:")
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{options_uid},gid=#{mount_gid} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: ["uid=#{options_uid}"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with gid defined" do
|
||||||
|
let(:options_gid){ '1234' }
|
||||||
|
|
||||||
|
it "should only include gid defined within mount options" do
|
||||||
|
expect(comm).to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).not_to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{mount_gid}:")
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{mount_uid},gid=#{options_gid} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: ["gid=#{options_gid}"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with uid and gid defined" do
|
||||||
|
let(:options_gid){ '1234' }
|
||||||
|
let(:options_uid){ '1234' }
|
||||||
|
|
||||||
|
it "should only include uid and gid defined within mount options" do
|
||||||
|
expect(comm).not_to receive(:execute).with("id -u #{mount_owner}", anything).and_yield(:stdout, mount_uid)
|
||||||
|
expect(comm).not_to receive(:execute).with("getent group #{mount_group}", anything).and_yield(:stdout, "vagrant:x:#{options_gid}:")
|
||||||
|
expect(comm).to receive(:sudo).with("mount -t vboxvfs -o uid=#{options_uid},gid=#{options_gid} #{mount_name} #{mount_guest_path}", anything)
|
||||||
|
cap.mount_virtualbox_shared_folder(machine, mount_name, mount_guest_path, folder_options.merge(mount_options: ["gid=#{options_gid}", "uid=#{options_uid}"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with guest builtin vboxvfs module" do
|
||||||
|
let(:vbox_stderr){ <<-EOF
|
||||||
|
mount.vboxvfs cannot be used with mainline vboxvfs; instead use:
|
||||||
|
|
||||||
|
mount -cit vboxvfs NAME MOUNTPOINT
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
it "should perform guest mount using builtin module" do
|
||||||
|
expect(comm).to receive(:sudo).with(/mount -t vboxvfs/, 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
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".unmount_virtualbox_shared_folder" do
|
||||||
|
|
||||||
|
after { cap.unmount_virtualbox_shared_folder(machine, mount_guest_path, folder_options) }
|
||||||
|
|
||||||
|
it "unmounts shared directory and deletes directory on guest" do
|
||||||
|
expect(comm).to receive(:sudo).with("umount #{mount_guest_path}", anything).and_return(0)
|
||||||
|
expect(comm).to receive(:sudo).with("rmdir #{mount_guest_path}", anything)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not delete guest directory if unmount fails" do
|
||||||
|
expect(comm).to receive(:sudo).with("umount #{mount_guest_path}", anything).and_return(1)
|
||||||
|
expect(comm).not_to receive(:sudo).with("rmdir #{mount_guest_path}", anything)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue