Merge pull request #8122 from chrisroberts/virtualbox/uid-gid
Use uid/gid from mount_options if provided for synced folders.
This commit is contained in:
commit
dbf01572ef
|
@ -1,29 +1,23 @@
|
|||
require "shellwords"
|
||||
require_relative "../../../synced_folders/unix_mount_helpers"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestLinux
|
||||
module Cap
|
||||
class MountSMBSharedFolder
|
||||
|
||||
extend SyncedFolder::UnixMountHelpers
|
||||
|
||||
def self.mount_smb_shared_folder(machine, name, guestpath, options)
|
||||
expanded_guest_path = machine.guest.capability(
|
||||
:shell_expand_guest_path, guestpath)
|
||||
|
||||
mount_commands = []
|
||||
mount_device = "//#{options[:smb_host]}/#{name}"
|
||||
|
||||
if options[:owner].is_a? Integer
|
||||
mount_uid = options[:owner]
|
||||
else
|
||||
mount_uid = "`id -u #{options[:owner]}`"
|
||||
end
|
||||
|
||||
if options[:group].is_a? Integer
|
||||
mount_gid = options[:group]
|
||||
mount_gid_old = options[:group]
|
||||
else
|
||||
mount_gid = "`getent group #{options[:group]} | cut -d: -f3`"
|
||||
mount_gid_old = "`id -g #{options[:group]}`"
|
||||
end
|
||||
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]
|
||||
|
||||
# If a domain is provided in the username, separate it
|
||||
username, domain = (options[:smb_username] || '').split('@', 2)
|
||||
|
@ -33,15 +27,9 @@ module VagrantPlugins
|
|||
options[:mount_options] << "sec=ntlm"
|
||||
options[:mount_options] << "credentials=/etc/smb_creds_#{name}"
|
||||
|
||||
# First mount command uses getent to get the group
|
||||
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}"
|
||||
mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options]
|
||||
mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
|
||||
|
||||
# Second mount command uses the old style `id -g`
|
||||
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid_old}"
|
||||
mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options]
|
||||
mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
|
||||
mount_options += ",#{Array(options[:mount_options]).join(",")}" if options[:mount_options]
|
||||
mount_command = "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
|
||||
|
||||
# Create the guest path if it doesn't exist
|
||||
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
|
||||
|
@ -58,46 +46,25 @@ SCRIPT
|
|||
|
||||
# Attempt to mount the folder. We retry here a few times because
|
||||
# it can fail early on.
|
||||
attempts = 0
|
||||
while true
|
||||
success = true
|
||||
|
||||
retryable(on: Vagrant::Errors::LinuxMountFailed, tries: 10, sleep: 2) do
|
||||
no_such_device = false
|
||||
stderr = ""
|
||||
mount_commands.each do |command|
|
||||
no_such_device = false
|
||||
stderr = ""
|
||||
status = machine.communicate.sudo(command, error_check: false) do |type, data|
|
||||
if type == :stderr
|
||||
no_such_device = true if data =~ /No such device/i
|
||||
stderr += data.to_s
|
||||
end
|
||||
status = machine.communicate.sudo(mount_command, error_check: false) do |type, data|
|
||||
if type == :stderr
|
||||
no_such_device = true if data =~ /No such device/i
|
||||
stderr += data.to_s
|
||||
end
|
||||
|
||||
success = status == 0 && !no_such_device
|
||||
break if success
|
||||
end
|
||||
|
||||
break if success
|
||||
|
||||
attempts += 1
|
||||
if attempts > 10
|
||||
command = mount_commands.join("\n")
|
||||
command.gsub!(smb_password, "PASSWORDHIDDEN")
|
||||
|
||||
if status != 0 || no_such_device
|
||||
clean_command = mount_command.gsub(smb_password, "PASSWORDHIDDEN")
|
||||
raise Vagrant::Errors::LinuxMountFailed,
|
||||
command: command,
|
||||
command: clean_command,
|
||||
output: stderr
|
||||
end
|
||||
|
||||
sleep 2
|
||||
end
|
||||
|
||||
# Emit an upstart event if we can
|
||||
machine.communicate.sudo <<-SCRIPT
|
||||
if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
|
||||
/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}'
|
||||
fi
|
||||
SCRIPT
|
||||
emit_upstart_notification(machine, expanded_guest_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,67 +1,23 @@
|
|||
require "shellwords"
|
||||
|
||||
require "vagrant/util/retryable"
|
||||
require_relative "../../../synced_folders/unix_mount_helpers"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestLinux
|
||||
module Cap
|
||||
class MountVirtualBoxSharedFolder
|
||||
@@logger = Log4r::Logger.new("vagrant::guest::linux::mount_virtualbox_shared_folder")
|
||||
|
||||
extend Vagrant::Util::Retryable
|
||||
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})")
|
||||
|
||||
if options[:owner].to_i.to_s == options[:owner].to_s
|
||||
mount_uid = options[:owner]
|
||||
@@logger.debug("Owner user ID (provided): #{mount_uid}")
|
||||
else
|
||||
output = {stdout: '', stderr: ''}
|
||||
uid_command = "id -u #{options[:owner]}"
|
||||
machine.communicate.execute(uid_command,
|
||||
error_class: Vagrant::Errors::VirtualBoxMountFailed,
|
||||
error_key: :virtualbox_mount_failed,
|
||||
command: uid_command,
|
||||
output: output[:stderr]
|
||||
) { |type, data| output[type] << data if output[type] }
|
||||
mount_uid = output[:stdout].chomp
|
||||
@@logger.debug("Owner user ID (lookup): #{options[:owner]} -> #{mount_uid}")
|
||||
end
|
||||
|
||||
if options[:group].to_i.to_s == options[:group].to_s
|
||||
mount_gid = options[:group]
|
||||
@@logger.debug("Owner group ID (provided): #{mount_gid}")
|
||||
else
|
||||
begin
|
||||
output = {stdout: '', stderr: ''}
|
||||
gid_command = "getent group #{options[:group]}"
|
||||
machine.communicate.execute(gid_command,
|
||||
error_class: Vagrant::Errors::VirtualBoxMountFailed,
|
||||
error_key: :virtualbox_mount_failed,
|
||||
command: gid_command,
|
||||
output: output[:stderr]
|
||||
) { |type, data| output[type] << data if output[type] }
|
||||
mount_gid = output[:stdout].split(':').at(2)
|
||||
@@logger.debug("Owner group ID (lookup): #{options[:group]} -> #{mount_gid}")
|
||||
rescue Vagrant::Errors::VirtualBoxMountFailed
|
||||
if options[:owner] == options[:group]
|
||||
@@logger.debug("Failed to locate group `#{options[:group]}`. Group name matches owner. Fetching effective group ID.")
|
||||
output = {stdout: ''}
|
||||
result = machine.communicate.execute("id -g #{options[:owner]}",
|
||||
error_check: false
|
||||
) { |type, data| output[type] << data if output[type] }
|
||||
mount_gid = output[:stdout].chomp if result == 0
|
||||
@@logger.debug("Owner group ID (effective): #{mount_gid}")
|
||||
end
|
||||
raise unless mount_gid
|
||||
end
|
||||
end
|
||||
|
||||
mount_options = options.fetch(:mount_options, [])
|
||||
mount_options += ["uid=#{mount_uid}", "gid=#{mount_gid}"]
|
||||
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 -t vboxsf -o #{mount_options} #{name} #{guest_path}"
|
||||
|
||||
|
@ -87,14 +43,10 @@ module VagrantPlugins
|
|||
machine.communicate.sudo(chown_command)
|
||||
end
|
||||
|
||||
# Emit an upstart event if we can
|
||||
machine.communicate.sudo <<-EOH.gsub(/^ {12}/, "")
|
||||
if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
|
||||
/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path}
|
||||
fi
|
||||
EOH
|
||||
emit_upstart_notification(machine, guest_path)
|
||||
end
|
||||
|
||||
|
||||
def self.unmount_virtualbox_shared_folder(machine, guestpath, options)
|
||||
guest_path = Shellwords.escape(guestpath)
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
require "vagrant/util/retryable"
|
||||
require_relative "../../../synced_folders/unix_mount_helpers"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestLinux
|
||||
module Cap
|
||||
class NFS
|
||||
extend Vagrant::Util::Retryable
|
||||
extend SyncedFolder::UnixMountHelpers
|
||||
|
||||
def self.nfs_client_installed(machine)
|
||||
machine.communicate.test("test -x /sbin/mount.nfs")
|
||||
|
@ -30,18 +30,7 @@ module VagrantPlugins
|
|||
|
||||
machine.communicate.sudo("mkdir -p #{guest_path}")
|
||||
|
||||
# Perform the mount operation and emit mount event if applicable
|
||||
command = <<-EOH.gsub(/^ */, '')
|
||||
mount -o #{mount_opts} #{ip}:#{host_path} #{guest_path}
|
||||
result=$?
|
||||
if test $result -eq 0; then
|
||||
if test -x /sbin/initctl && command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
|
||||
/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path}
|
||||
fi
|
||||
else
|
||||
exit $result
|
||||
fi
|
||||
EOH
|
||||
command = "mount -o #{mount_opts} #{ip}:#{host_path} #{guest_path}"
|
||||
|
||||
# Run the command, raising a specific error.
|
||||
retryable(on: Vagrant::Errors::NFSMountFailed, tries: 3, sleep: 5) do
|
||||
|
@ -49,6 +38,8 @@ module VagrantPlugins
|
|||
error_class: Vagrant::Errors::NFSMountFailed,
|
||||
)
|
||||
end
|
||||
|
||||
emit_upstart_notification(machine, guest_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
require "vagrant/util/retryable"
|
||||
require_relative "../../../synced_folders/unix_mount_helpers"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestTinyCore
|
||||
module Cap
|
||||
class MountNFS
|
||||
extend Vagrant::Util::Retryable
|
||||
extend SyncedFolder::UnixMountHelpers
|
||||
|
||||
def self.mount_nfs_folder(machine, ip, folders)
|
||||
folders.each do |name, opts|
|
||||
|
@ -32,12 +32,7 @@ module VagrantPlugins
|
|||
error_class: Vagrant::Errors::NFSMountFailed)
|
||||
end
|
||||
|
||||
# Emit an upstart event if we can
|
||||
machine.communicate.sudo <<-SCRIPT
|
||||
if command -v /sbin/init && /sbin/init --version | grep upstart; then
|
||||
/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}'
|
||||
fi
|
||||
SCRIPT
|
||||
emit_upstart_notification(machine, expanded_guest_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
require "shellwords"
|
||||
require "vagrant/util/retryable"
|
||||
|
||||
module VagrantPlugins
|
||||
module SyncedFolder
|
||||
module UnixMountHelpers
|
||||
|
||||
def self.extended(klass)
|
||||
if !klass.class_variable_defined?(:@@logger)
|
||||
klass.class_variable_set(:@@logger, Log4r::Logger.new(klass.name.downcase))
|
||||
end
|
||||
klass.extend Vagrant::Util::Retryable
|
||||
end
|
||||
|
||||
def detect_owner_group_ids(machine, guest_path, mount_options, options)
|
||||
mount_uid = find_mount_options_id("uid", mount_options)
|
||||
mount_gid = find_mount_options_id("gid", mount_options)
|
||||
|
||||
if mount_uid.nil?
|
||||
if options[:owner].to_i.to_s == options[:owner].to_s
|
||||
mount_uid = options[:owner]
|
||||
self.class_variable_get(:@@logger).debug("Owner user ID (provided): #{mount_uid}")
|
||||
else
|
||||
output = {stdout: '', stderr: ''}
|
||||
uid_command = "id -u #{options[:owner]}"
|
||||
machine.communicate.execute(uid_command,
|
||||
error_class: Vagrant::Errors::VirtualBoxMountFailed,
|
||||
error_key: :virtualbox_mount_failed,
|
||||
command: uid_command,
|
||||
output: output[:stderr]
|
||||
) { |type, data| output[type] << data if output[type] }
|
||||
mount_uid = output[:stdout].chomp
|
||||
self.class_variable_get(:@@logger).debug("Owner user ID (lookup): #{options[:owner]} -> #{mount_uid}")
|
||||
end
|
||||
else
|
||||
machine.ui.warn "Detected mount owner ID within mount options. (uid: #{mount_uid} guestpath: #{guest_path})"
|
||||
end
|
||||
|
||||
if mount_gid.nil?
|
||||
if options[:group].to_i.to_s == options[:group].to_s
|
||||
mount_gid = options[:group]
|
||||
self.class_variable_get(:@@logger).debug("Owner group ID (provided): #{mount_gid}")
|
||||
else
|
||||
begin
|
||||
output = {stdout: '', stderr: ''}
|
||||
gid_command = "getent group #{options[:group]}"
|
||||
machine.communicate.execute(gid_command,
|
||||
error_class: Vagrant::Errors::VirtualBoxMountFailed,
|
||||
error_key: :virtualbox_mount_failed,
|
||||
command: gid_command,
|
||||
output: output[:stderr]
|
||||
) { |type, data| output[type] << data if output[type] }
|
||||
mount_gid = output[:stdout].split(':').at(2)
|
||||
self.class_variable_get(:@@logger).debug("Owner group ID (lookup): #{options[:group]} -> #{mount_gid}")
|
||||
rescue Vagrant::Errors::VirtualBoxMountFailed
|
||||
if options[:owner] == options[:group]
|
||||
self.class_variable_get(:@@logger).debug("Failed to locate group `#{options[:group]}`. Group name matches owner. Fetching effective group ID.")
|
||||
output = {stdout: ''}
|
||||
result = machine.communicate.execute("id -g #{options[:owner]}",
|
||||
error_check: false
|
||||
) { |type, data| output[type] << data if output[type] }
|
||||
mount_gid = output[:stdout].chomp if result == 0
|
||||
self.class_variable_get(:@@logger).debug("Owner group ID (effective): #{mount_gid}")
|
||||
end
|
||||
raise unless mount_gid
|
||||
end
|
||||
end
|
||||
else
|
||||
machine.ui.warn "Detected mount group ID within mount options. (gid: #{mount_gid} guestpath: #{guest_path})"
|
||||
end
|
||||
{:gid => mount_gid, :uid => mount_uid}
|
||||
end
|
||||
|
||||
def find_mount_options_id(id_name, mount_options)
|
||||
id_line = mount_options.detect{|line| line.include?("#{id_name}=")}
|
||||
if id_line
|
||||
match = id_line.match(/,?#{Regexp.escape(id_name)}=(?<option_id>\d+),?/)
|
||||
found_id = match["option_id"]
|
||||
updated_id_line = [
|
||||
match.pre_match,
|
||||
match.post_match
|
||||
].find_all{|string| !string.empty?}.join(',')
|
||||
if updated_id_line.empty?
|
||||
mount_options.delete(id_line)
|
||||
else
|
||||
idx = mount_options.index(id_line)
|
||||
mount_options.delete(idx)
|
||||
mount_options.insert(idx, updated_id_line)
|
||||
end
|
||||
end
|
||||
found_id
|
||||
end
|
||||
|
||||
def emit_upstart_notification(machine, guest_path)
|
||||
# Emit an upstart event if we can
|
||||
machine.communicate.sudo <<-EOH.gsub(/^ {12}/, "")
|
||||
if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
|
||||
/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path}
|
||||
fi
|
||||
EOH
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -71,7 +71,7 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do
|
|||
}
|
||||
cap.mount_nfs_folder(machine, ip, folders)
|
||||
|
||||
expect(comm.received_commands[1]).to include(
|
||||
expect(comm.received_commands[2]).to include(
|
||||
"/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guestpath}")
|
||||
end
|
||||
|
||||
|
|
|
@ -149,6 +149,49 @@ describe "VagrantPlugins::GuestLinux::Cap::MountVirtualBoxSharedFolder" do
|
|||
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 vboxsf -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 vboxsf -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 vboxsf -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
|
||||
end
|
||||
|
||||
describe ".unmount_virtualbox_shared_folder" do
|
||||
|
|
|
@ -100,6 +100,19 @@ config.vm.synced_folder "src/", "/srv/website",
|
|||
owner: "root", group: "root"
|
||||
```
|
||||
|
||||
_NOTE: Owner and group IDs defined within `mount_options` will have precedence
|
||||
over the `owner` and `group` options._
|
||||
|
||||
For example, given the following configuration:
|
||||
|
||||
```ruby
|
||||
config.vm.synced_folder ".", "/vagrant", owner: "vagrant",
|
||||
group: "vagrant", mount_options: ["uid=1234", "gid=1234"]
|
||||
```
|
||||
|
||||
the mounted synced folder will be owned by the user with ID `1234` and the
|
||||
group with ID `1234`. The `owner` and `group` options will be ignored.
|
||||
|
||||
## Symbolic Links
|
||||
|
||||
Support for symbolic links across synced folder implementations and
|
||||
|
|
Loading…
Reference in New Issue