Merge pull request #11846 from soapy1/persist-smb

Persist smb
This commit is contained in:
Sophia Castellarin 2020-09-23 11:33:14 -05:00 committed by GitHub
commit ce4d884d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 198 additions and 140 deletions

View File

@ -26,7 +26,7 @@ module VagrantPlugins
mount_options = options[:mount_options];
mount_command = "mount -t smbfs " +
(mount_options ? "-o '#{mount_options.join(",")}' " : "") +
"'//#{options[:smb_username]}:#{smb_password}@#{options[:smb_host]}/#{name}' " +
"//#{options[:smb_username]}:#{smb_password}@#{options[:smb_host]}/#{name} " +
"#{expanded_guest_path}"
retryable(on: Vagrant::Errors::DarwinMountFailed, tries: 10, sleep: 2) do
machine.communicate.execute(

View File

@ -9,44 +9,32 @@ module VagrantPlugins
extend SyncedFolder::UnixMountHelpers
# Mounts and SMB folder on linux guest
#
# @param [Machine] machine
# @param [String] name of mount
# @param [String] path of mount on guest
# @param [Hash] hash of mount options
def self.mount_smb_shared_folder(machine, name, guestpath, options)
expanded_guest_path = machine.guest.capability(
:shell_expand_guest_path, guestpath)
options[:smb_id] ||= name
mount_device = "//#{options[:smb_host]}/#{name}"
mount_options = options.fetch(:mount_options, [])
detected_ids = detect_owner_group_ids(machine, guestpath, mount_options, options)
mount_uid = detected_ids[:uid]
mount_gid = detected_ids[:gid]
mount_device = options[:plugin].capability(:mount_name, options)
mount_options, _, _ = options[:plugin].capability(
:mount_options, name, expanded_guest_path, options)
mount_type = options[:plugin].capability(:mount_type)
# If a domain is provided in the username, separate it
username, domain = (options[:smb_username] || '').split('@', 2)
smb_password = options[:smb_password]
# Ensure password is scrubbed
Vagrant::Util::CredentialScrubber.sensitive(smb_password)
mnt_opts = []
if machine.env.host.capability?(:smb_mount_options)
mnt_opts += machine.env.host.capability(:smb_mount_options)
else
mnt_opts << "sec=ntlmssp"
end
mnt_opts << "credentials=/etc/smb_creds_#{name}"
mnt_opts << "uid=#{mount_uid}"
mnt_opts << "gid=#{mount_gid}"
if !ENV['VAGRANT_DISABLE_SMBMFSYMLINKS']
mnt_opts << "mfsymlinks"
end
mnt_opts = merge_mount_options(mnt_opts, options[:mount_options] || [])
mount_options = "-o #{mnt_opts.join(",")}"
if mount_options.include?("mfsymlinks")
display_mfsymlinks_warning(machine.env)
end
mount_command = "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
mount_command = "mount -t #{mount_type} -o #{mount_options} #{mount_device} #{expanded_guest_path}"
# Create the guest path if it doesn't exist
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
@ -82,27 +70,14 @@ SCRIPT
ensure
# Always remove credentials file after mounting attempts
# have been completed
machine.communicate.sudo("rm /etc/smb_creds_#{name}")
if !machine.config.vm.allow_fstab_modification
machine.communicate.sudo("rm /etc/smb_creds_#{name}")
end
end
emit_upstart_notification(machine, expanded_guest_path)
end
def self.merge_mount_options(base, overrides)
base = base.join(",").split(",")
overrides = overrides.join(",").split(",")
b_kv = Hash[base.map{|item| item.split("=", 2) }]
o_kv = Hash[overrides.map{|item| item.split("=", 2) }]
merged = {}.tap do |opts|
(b_kv.keys + o_kv.keys).uniq.each do |key|
opts[key] = o_kv.fetch(key, b_kv[key])
end
end
merged.map do |key, value|
[key, value].compact.join("=")
end
end
def self.display_mfsymlinks_warning(env)
d_file = env.data_dir.join("mfsymlinks_warning")
if !d_file.exist?

View File

@ -6,6 +6,12 @@ module VagrantPlugins
class MountVirtualBoxSharedFolder
extend SyncedFolder::UnixMountHelpers
# Mounts and virtualbox folder on linux guest
#
# @param [Machine] machine
# @param [String] name of mount
# @param [String] path of mount on guest
# @param [Hash] hash of mount options
def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)
guest_path = Shellwords.escape(guestpath)
mount_type = options[:plugin].capability(:mount_type)

View File

@ -26,14 +26,16 @@ module VagrantPlugins
ssh_info = machine.ssh_info
export_folders = folders.map { |type, folder|
folder.map { |name, data|
guest_path = Shellwords.escape(data[:guestpath])
data[:owner] ||= ssh_info[:username]
data[:group] ||= ssh_info[:username]
if data[:plugin].capability?(:mount_type)
guest_path = Shellwords.escape(data[:guestpath])
data[:owner] ||= ssh_info[:username]
data[:group] ||= ssh_info[:username]
mount_type = data[:plugin].capability(:mount_type)
mount_options, _, _ = data[:plugin].capability(
:mount_options, name, guest_path, data)
if data[:plugin].capability?(:mount_name)
name = data[:plugin].capability(:mount_name, data)
end
else
next
end

View File

@ -151,10 +151,10 @@ module VagrantPlugins
b3.use CleanupDisks
b3.use Disk
b3.use SyncedFolderCleanup
b3.use StartInstance
b3.use WaitForIPAddress
b3.use WaitForCommunicator, [:running]
b3.use SyncedFolderCleanup
b3.use SyncedFolders
b3.use SetHostname
end

View File

@ -8,7 +8,7 @@ module VagrantPlugins
VB_MOUNT_TYPE = "vboxsf".freeze
# Returns mount options for a virual box synced folder
# Returns mount options for a virtual box synced folder
#
# @param [Machine] machine
# @param [String] name of mount

View File

@ -6,8 +6,17 @@ module VagrantPlugins
module MountOptions
extend VagrantPlugins::SyncedFolder::UnixMountHelpers
MOUNT_TYPE = "cifs".freeze
# Returns mount options for a smb synced folder
#
# @param [Machine] machine
# @param [String] name of mount
# @param [String] path of mount on guest
# @param [Hash] hash of mount options
def self.mount_options(machine, name, guest_path, options)
mount_options = options.fetch(:mount_options, [])
options[:smb_id] ||= name
detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options)
mount_uid = detected_ids[:uid]
mount_gid = detected_ids[:gid]
@ -19,17 +28,28 @@ module VagrantPlugins
mnt_opts << "sec=ntlmssp"
end
mnt_opts << "credentials=/etc/smb_creds_#{name}"
mnt_opts << "credentials=/etc/smb_creds_#{options[:smb_id]}"
mnt_opts << "uid=#{mount_uid}"
mnt_opts << "gid=#{mount_gid}"
if !ENV['VAGRANT_DISABLE_SMBMFSYMLINKS']
mnt_opts << "mfsymlinks"
end
mnt_opts << "_netdev"
mnt_opts = merge_mount_options(mnt_opts, options[:mount_options] || [])
mount_options = mnt_opts.join(",")
return mount_options, mount_uid, mount_gid
end
def self.mount_type(machine)
return MOUNT_TYPE
end
def self.mount_name(machine, data)
data[:smb_host] ||= machine.guest.capability(
:choose_addressable_ip_addr, candidate_ips)
"//#{data[:smb_host]}/#{data[:smb_id]}"
end
end
end
end

View File

@ -33,6 +33,16 @@ module VagrantPlugins
Cap::MountOptions
end
synced_folder_capability("smb", "mount_name") do
require_relative "cap/mount_options"
Cap::MountOptions
end
synced_folder_capability("smb", "mount_type") do
require_relative "cap/mount_options"
Cap::MountOptions
end
protected
def self.init!

View File

@ -7,11 +7,13 @@ describe "VagrantPlugins::GuestLinux::Cap::MountSMBSharedFolder" do
.guest_capabilities[:linux]
end
let(:machine) { double("machine", env: env) }
let(:machine) { double("machine", env: env, config: config) }
let(:env) { double("env", host: host, ui: double("ui"), data_dir: double("data_dir")) }
let(:host) { double("host") }
let(:guest) { double("guest") }
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
let(:config) { double("config", vm: vm) }
let(:vm) { double("vm" ) }
let(:mount_owner){ "vagrant" }
let(:mount_group){ "vagrant" }
let(:mount_uid){ "1000" }
@ -19,19 +21,29 @@ describe "VagrantPlugins::GuestLinux::Cap::MountSMBSharedFolder" do
let(:mount_name){ "vagrant" }
let(:mount_guest_path){ "/vagrant" }
let(:folder_options) do
{
owner: mount_owner,
group: mount_group,
smb_host: "localhost",
smb_username: "user",
smb_password: "pass"
}
Vagrant::Plugin::V2::SyncedFolder::Collection[
{
owner: mount_owner,
group: mount_group,
smb_host: "localhost",
smb_username: "user",
smb_password: "pass",
plugin: folder_plugin
}
]
end
let(:folder_plugin) { double("folder_plugin") }
let(:cap){ caps.get(:mount_smb_shared_folder) }
before do
allow(machine).to receive(:communicate).and_return(comm)
allow(host).to receive(:capability?).and_return(false)
allow(vm).to receive(:allow_fstab_modification).and_return(true)
allow(folder_plugin).to receive(:capability).with(:mount_options, mount_name, mount_guest_path, folder_options).
and_return(["uid=#{mount_uid},gid=#{mount_gid},sec=ntlmssp,credentials=/etc/smb_creds_id", mount_uid, mount_gid])
allow(folder_plugin).to receive(:capability).with(:mount_type).and_return("cifs")
allow(folder_plugin).to receive(:capability).with(:mount_name, any_args).and_return("//localhost/#{mount_name}")
end
after do
@ -70,6 +82,7 @@ describe "VagrantPlugins::GuestLinux::Cap::MountSMBSharedFolder" do
end
it "removes the credentials file before completion" do
allow(vm).to receive(:allow_fstab_modification).and_return(false)
expect(comm).to receive(:sudo).with(/rm.+smb_creds_.+/)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
@ -78,69 +91,6 @@ describe "VagrantPlugins::GuestLinux::Cap::MountSMBSharedFolder" do
expect(comm).to receive(:sudo).with(/emit/)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
it "adds mfsymlinks option by default" do
expect(comm).to receive(:sudo).with(/mfsymlinks/, any_args)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
it "does not add mfsymlinks option if env var VAGRANT_DISABLE_SMBMFSYMLINKS exists" do
expect(ENV).to receive(:[]).with("VAGRANT_DISABLE_SMBMFSYMLINKS").and_return(true)
expect(comm).not_to receive(:sudo).with(/mfsymlinks/, any_args)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
context "with custom mount options" do
let(:folder_options) do
{
owner: mount_owner,
group: mount_group,
smb_host: "localhost",
smb_username: "user",
smb_password: "pass",
mount_options: ["ro", "sec=custom"]
}
end
it "adds given mount options to command" do
expect(comm).to receive(:sudo).with(/ro/, any_args)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
it "replaces defined options" do
expect(comm).to receive(:sudo).with(/sec=custom/, any_args)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
it "does not include replaced options" do
expect(comm).not_to receive(:sudo).with(/sec=ntlm/, any_args)
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
end
end
end
describe ".merge_mount_options" do
let(:base){ ["opt1", "opt2=on", "opt3", "opt4,opt5=off"] }
let(:override){ ["opt8", "opt4=on,opt6,opt7=true"] }
context "with no override" do
it "should split options into individual options" do
result = cap.merge_mount_options(base, [])
expect(result.size).to eq(5)
end
end
context "with overrides" do
it "should merge all options" do
result = cap.merge_mount_options(base, override)
expect(result.size).to eq(8)
end
it "should override options defined in base" do
result = cap.merge_mount_options(base, override)
expect(result).to include("opt4=on")
end
end
end
describe ".display_mfsymlinks_warning" do

View File

@ -27,12 +27,14 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do
]
}
let (:folders) { {
:virtualbox => fstab_folders
:folder_type => fstab_folders
} }
let(:expected_mount_options) { "uid=#{options_uid},gid=#{options_gid},nofail" }
before do
allow(machine).to receive(:communicate).and_return(comm)
allow(machine).to receive(:ssh_info).and_return(ssh_info)
allow(folder_plugin).to receive(:capability?).with(:mount_name).and_return(false)
allow(folder_plugin).to receive(:capability?).with(:mount_type).and_return(true)
allow(folder_plugin).to receive(:capability).with(:mount_options, any_args).
and_return(["uid=#{options_uid},gid=#{options_gid}", options_uid, options_gid])
@ -55,8 +57,8 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do
end
it "inserts folders into /etc/fstab" do
expected_entry_vagrant = "vagrant /vagrant vboxsf uid=#{options_uid},gid=#{options_gid},nofail 0 0"
expected_entry_test = "test1 /test1 vboxsf uid=#{options_uid},gid=#{options_gid},nofail 0 0"
expected_entry_vagrant = "vagrant /vagrant vboxsf #{expected_mount_options} 0 0"
expected_entry_test = "test1 /test1 vboxsf #{expected_mount_options} 0 0"
expect(cap).to receive(:remove_vagrant_managed_fstab)
expect(comm).to receive(:sudo).with(/#{expected_entry_test}\n#{expected_entry_vagrant}/)
@ -99,5 +101,38 @@ describe "VagrantPlugins::GuestLinux::Cap::PersistMountSharedFolder" do
cap.persist_mount_shared_folder(machine, nil)
end
end
context "smb folder" do
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", smb_host: "192.168.42.42", smb_id: "vtg-id1" },
"vagrant" => {guestpath: "/vagrant", hostpath: "/my/host/vagrant", disabled: false, plugin: folder_plugin,
__vagrantfile: true, owner: "vagrant", group: "vagrant", smb_host: "192.168.42.42", smb_id: "vtg-id2"}
}
]
}
let (:folders) { {
:smb => fstab_folders
} }
context "folder with mount_name cap" do
before do
allow(folder_plugin).to receive(:capability).with(:mount_type).and_return("cifs")
allow(folder_plugin).to receive(:capability?).with(:mount_name).and_return(true)
allow(folder_plugin).to receive(:capability).with(:mount_name, any_args).and_return("//192.168.42.42/dummyname")
end
it "inserts folders into /etc/fstab" do
expected_entry_vagrant = "//192.168.42.42/dummyname /vagrant cifs #{expected_mount_options} 0 0"
expected_entry_test = "//192.168.42.42/dummyname /test1 cifs #{expected_mount_options} 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
end
end
end
end

View File

@ -30,27 +30,48 @@ describe VagrantPlugins::SyncedFolderSMB::Cap::MountOptions do
before do
allow(machine).to receive(:communicate).and_return(comm)
allow(machine).to receive_message_chain(:env, :host, :capability?).with(:smb_mount_options).and_return(false)
ENV['VAGRANT_DISABLE_SMBMFSYMLINKS'] = "1"
allow(ENV).to receive(:[]).with("VAGRANT_DISABLE_SMBMFSYMLINKS").and_return(true)
allow(ENV).to receive(:[]).with("GEM_SKIP").and_return(false)
end
describe ".mount_options" do
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}:")
out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)
expect(out_opts).to eq("sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000")
expect(out_uid).to eq(mount_uid)
expect(out_gid).to eq(mount_gid)
end
context "with valid existent owner group" do
it "includes provided mount options" 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}:")
folder_options[:mount_options] =["ro"]
out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)
expect(out_opts).to eq("sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,ro")
expect(out_uid).to eq(mount_uid)
expect(out_gid).to eq(mount_gid)
before 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}:")
end
it "generates the expected default mount command" do
out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)
expect(out_opts).to eq("sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,_netdev")
expect(out_uid).to eq(mount_uid)
expect(out_gid).to eq(mount_gid)
end
it "includes provided mount options" do
folder_options[:mount_options] =["ro"]
out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)
expect(out_opts).to eq("sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,_netdev,ro")
expect(out_uid).to eq(mount_uid)
expect(out_gid).to eq(mount_gid)
end
it "overwrites default mount options" do
folder_options[:mount_options] =["ro", "sec=custom"]
out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)
expect(out_opts).to eq("sec=custom,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,_netdev,ro")
expect(out_uid).to eq(mount_uid)
expect(out_gid).to eq(mount_gid)
end
it "does not add mfsymlinks option if env var VAGRANT_DISABLE_SMBMFSYMLINKS exists" do
expect(ENV).to receive(:[]).with("VAGRANT_DISABLE_SMBMFSYMLINKS").and_return(false)
out_opts, out_uid, out_gid = cap.mount_options(machine, mount_name, mount_guest_path, folder_options)
expect(out_opts).to eq("sec=ntlmssp,credentials=/etc/smb_creds_vagrant,uid=1000,gid=1000,mfsymlinks,_netdev")
expect(out_uid).to eq(mount_uid)
expect(out_gid).to eq(mount_gid)
end
end
context "with non-existent owner group" do

View File

@ -0,0 +1,39 @@
require_relative "../../base"
require Vagrant.source_root.join("plugins/synced_folders/unix_mount_helpers")
describe VagrantPlugins::SyncedFolder::UnixMountHelpers do
include_context "unit"
subject{
Class.new do
@@logger = nil
extend VagrantPlugins::SyncedFolder::UnixMountHelpers
end
}
describe ".merge_mount_options" do
let(:base){ ["opt1", "opt2=on", "opt3", "opt4,opt5=off"] }
let(:override){ ["opt8", "opt4=on,opt6,opt7=true"] }
context "with no override" do
it "should split options into individual options" do
result = subject.merge_mount_options(base, [])
expect(result.size).to eq(5)
end
end
context "with overrides" do
it "should merge all options" do
result = subject.merge_mount_options(base, override)
expect(result.size).to eq(8)
end
it "should override options defined in base" do
result = subject.merge_mount_options(base, override)
expect(result).to include("opt4=on")
end
end
end
end