Merge pull request #9294 from chrisroberts/e-smb
SMB synced folders enhancements
This commit is contained in:
commit
b0fc6a06a1
|
@ -1,5 +1,10 @@
|
|||
Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports
|
||||
Cmnd_Alias VAGRANT_NFSD = /sbin/nfsd restart
|
||||
Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports
|
||||
%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE
|
||||
|
||||
Cmnd_Alias VAGRANT_SMB_ADD = /usr/sbin/sharing -a * -S * -s * -g * -n *
|
||||
Cmnd_Alias VAGRANT_SMB_REMOVE = /usr/sbin/sharing -r *
|
||||
Cmnd_Alias VAGRANT_SMB_LIST = /usr/sbin/sharing -l
|
||||
Cmnd_Alias VAGRANT_SMB_PLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist
|
||||
Cmnd_Alias VAGRANT_SMB_DLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smbd.plist
|
||||
Cmnd_Alias VAGRANT_SMB_DSTART = /bin/launchctl start com.apple.smbd
|
||||
%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST, VAGRANT_SMB_PLOAD, VAGRANT_SMB_DLOAD VAGRANT_SMB_DSTART
|
|
@ -16,6 +16,7 @@ module Vagrant
|
|||
@keys = keys || {}
|
||||
@config_map = config_map
|
||||
@missing_key_calls = Set.new
|
||||
@logger = Log4r::Logger.new("vagrant::config")
|
||||
end
|
||||
|
||||
# We use method_missing as a way to get the configuration that is
|
||||
|
@ -30,6 +31,7 @@ module Vagrant
|
|||
@keys[name] = config_klass.new
|
||||
return @keys[name]
|
||||
else
|
||||
@logger.debug("missing key request name=#{name} loc=#{caller.first}")
|
||||
# Record access to a missing key as an error
|
||||
@missing_key_calls.add(name.to_s)
|
||||
return DummyConfig.new
|
||||
|
|
|
@ -552,6 +552,10 @@ module Vagrant
|
|||
error_key(:powershell_invalid_version)
|
||||
end
|
||||
|
||||
class PowerShellError < VagrantError
|
||||
error_key(:powershell_error, "vagrant_ps.errors.powershell_error")
|
||||
end
|
||||
|
||||
class ProviderCantInstall < VagrantError
|
||||
error_key(:provider_cant_install)
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require "tmpdir"
|
||||
require_relative "subprocess"
|
||||
require_relative "which"
|
||||
|
||||
|
@ -25,27 +26,33 @@ module Vagrant
|
|||
# @return [Subprocess::Result]
|
||||
def self.execute(path, *args, **opts, &block)
|
||||
validate_install!
|
||||
command = [
|
||||
"powershell",
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"&('#{path}')",
|
||||
args
|
||||
].flatten
|
||||
|
||||
# Append on the options hash since Subprocess doesn't use
|
||||
# Ruby 2.0 style options yet.
|
||||
command << opts
|
||||
if opts.delete(:sudo) || opts.delete(:runas)
|
||||
powerup_command(path, args, opts)
|
||||
else
|
||||
command = [
|
||||
"powershell",
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"&('#{path}')",
|
||||
args
|
||||
].flatten
|
||||
|
||||
Subprocess.execute(*command, &block)
|
||||
# Append on the options hash since Subprocess doesn't use
|
||||
# Ruby 2.0 style options yet.
|
||||
command << opts
|
||||
|
||||
Subprocess.execute(*command, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Execute a powershell command.
|
||||
#
|
||||
# @param [String] command PowerShell command to execute.
|
||||
# @return [Subprocess::Result]
|
||||
# @return [nil, String] Returns nil if exit code is non-zero.
|
||||
# Returns stdout string if exit code is zero.
|
||||
def self.execute_cmd(command)
|
||||
validate_install!
|
||||
c = [
|
||||
|
@ -56,7 +63,7 @@ module Vagrant
|
|||
"-ExecutionPolicy", "Bypass",
|
||||
"-Command",
|
||||
command
|
||||
].flatten
|
||||
].flatten.compact
|
||||
|
||||
r = Subprocess.execute(*c)
|
||||
return nil if r.exit_code != 0
|
||||
|
@ -75,7 +82,7 @@ module Vagrant
|
|||
"-NonInteractive",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-Command",
|
||||
"$PSVersionTable.PSVersion.Major"
|
||||
"Write-Output $PSVersionTable.PSVersion.Major"
|
||||
].flatten
|
||||
|
||||
r = Subprocess.execute(*command)
|
||||
|
@ -101,6 +108,60 @@ module Vagrant
|
|||
end
|
||||
@_powershell_validation
|
||||
end
|
||||
|
||||
# Powerup the given command to perform privileged operations.
|
||||
#
|
||||
# @param [String] path
|
||||
# @param [Array<String>] args
|
||||
# @return [Array<String>]
|
||||
def self.powerup_command(path, args, opts)
|
||||
Dir.mktmpdir("vagrant") do |dpath|
|
||||
all_args = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", path] + args
|
||||
arg_list = "@('" + all_args.join("', '") + "')"
|
||||
stdout = File.join(dpath, "stdout.txt")
|
||||
stderr = File.join(dpath, "stderr.txt")
|
||||
exitcode = File.join(dpath, "exitcode.txt")
|
||||
|
||||
script = "$sp = Start-Process -FilePath powershell -ArgumentList #{arg_list} " \
|
||||
"-PassThru -Wait -RedirectStandardOutput '#{stdout}' -RedirectStandardError '#{stderr}' -WindowStyle Hidden; " \
|
||||
"if($sp){ Set-Content -Path '#{exitcode}' -Value $sp.ExitCode;exit $sp.ExitCode; }else{ exit 1 }"
|
||||
|
||||
# escape quotes so we can nest our script within a start-process
|
||||
script.gsub!("'", "''")
|
||||
|
||||
cmd = [
|
||||
"powershell",
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-Command", "$p = Start-Process -FilePath powershell -ArgumentList " \
|
||||
"@('-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', '#{script}') " \
|
||||
"-PassThru -Wait -WindowStyle Hidden -Verb RunAs; if($p){ exit $p.ExitCode; }else{ exit 1 }"
|
||||
]
|
||||
|
||||
result = Subprocess.execute(*cmd.push(opts))
|
||||
if File.exist?(stdout)
|
||||
r_stdout = File.read(stdout)
|
||||
else
|
||||
r_stdout = result.stdout
|
||||
end
|
||||
if File.exist?(stderr)
|
||||
r_stderr = File.read(stderr)
|
||||
else
|
||||
r_stderr = result.stderr
|
||||
end
|
||||
|
||||
code = 1
|
||||
if File.exist?(exitcode)
|
||||
code_txt = File.read(exitcode).strip
|
||||
if code_txt.match(/^\d+$/)
|
||||
code = code_txt.to_i
|
||||
end
|
||||
end
|
||||
Subprocess::Result.new(code, r_stdout, r_stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,9 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
smb_password = Shellwords.shellescape(options[:smb_password])
|
||||
# Ensure password is scrubbed
|
||||
Vagrant::Util::CredentialScrubber.sensitive(smb_password)
|
||||
|
||||
mount_options = options[:mount_options];
|
||||
mount_command = "mount -t smbfs " +
|
||||
(mount_options ? "-o '#{mount_options.join(",")}' " : "") +
|
||||
|
|
|
@ -22,9 +22,15 @@ module VagrantPlugins
|
|||
# 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)
|
||||
|
||||
options[:mount_options] ||= []
|
||||
options[:mount_options] << "sec=ntlm"
|
||||
if machine.env.host.capability?(:smb_mount_options)
|
||||
options[:mount_options] += machine.env.host.capability(:smb_mount_options)
|
||||
else
|
||||
options[:mount_options] << "sec=ntlm"
|
||||
end
|
||||
options[:mount_options] << "credentials=/etc/smb_creds_#{name}"
|
||||
|
||||
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}"
|
||||
|
@ -46,22 +52,26 @@ SCRIPT
|
|||
|
||||
# Attempt to mount the folder. We retry here a few times because
|
||||
# it can fail early on.
|
||||
|
||||
retryable(on: Vagrant::Errors::LinuxMountFailed, tries: 10, sleep: 2) do
|
||||
no_such_device = false
|
||||
stderr = ""
|
||||
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
|
||||
begin
|
||||
retryable(on: Vagrant::Errors::LinuxMountFailed, tries: 10, sleep: 2) do
|
||||
no_such_device = false
|
||||
stderr = ""
|
||||
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
|
||||
end
|
||||
if status != 0 || no_such_device
|
||||
raise Vagrant::Errors::LinuxMountFailed,
|
||||
command: mount_command,
|
||||
output: stderr
|
||||
end
|
||||
end
|
||||
if status != 0 || no_such_device
|
||||
clean_command = mount_command.gsub(smb_password, "PASSWORDHIDDEN")
|
||||
raise Vagrant::Errors::LinuxMountFailed,
|
||||
command: clean_command,
|
||||
output: stderr
|
||||
end
|
||||
ensure
|
||||
# Always remove credentials file after mounting attempts
|
||||
# have been completed
|
||||
machine.communicate.sudo("rm /etc/smb_creds_#{name}")
|
||||
end
|
||||
|
||||
emit_upstart_notification(machine, expanded_guest_path)
|
||||
|
|
|
@ -18,6 +18,10 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def self.mount_smb_shared_folder(machine, name, guestpath, options)
|
||||
if !options[:smb_password].to_s.empty?
|
||||
# Ensure password is scrubbed
|
||||
Vagrant::Util::CredentialScrubber.sensitive(options[:smb_password])
|
||||
end
|
||||
machine.communicate.execute("cmdkey /add:#{options[:smb_host]} /user:#{options[:smb_username]} /pass:#{options[:smb_password]}", {shell: :powershell, elevated: true})
|
||||
mount_shared_folder(machine, name, guestpath, "\\\\#{options[:smb_host]}\\")
|
||||
end
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
require "socket"
|
||||
|
||||
module VagrantPlugins
|
||||
module HostDarwin
|
||||
module Cap
|
||||
class ConfiguredIPAddresses
|
||||
|
||||
def self.configured_ip_addresses(env)
|
||||
Socket.getifaddrs.map do |interface|
|
||||
if interface.addr.ipv4? && !interface.addr.ipv4_loopback?
|
||||
interface.addr.ip_address
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,125 @@
|
|||
module VagrantPlugins
|
||||
module HostDarwin
|
||||
module Cap
|
||||
class SMB
|
||||
|
||||
@@logger = Log4r::Logger.new("vagrant::host::darwin::smb")
|
||||
|
||||
# If we have the sharing binary available, smb is installed
|
||||
def self.smb_installed(env)
|
||||
File.exist?("/usr/sbin/sharing")
|
||||
end
|
||||
|
||||
# Check if the required SMB services are loaded and enabled. If they are
|
||||
# not, then start them up
|
||||
def self.smb_start(env)
|
||||
result = Vagrant::Util::Subprocess.execute("pwpolicy", "gethashtypes")
|
||||
if result.exit_code == 0 && !result.stdout.include?("SMB-NT")
|
||||
@@logger.error("SMB compatible password has not been stored")
|
||||
raise SyncedFolderSMB::Errors::SMBCredentialsMissing
|
||||
end
|
||||
result = Vagrant::Util::Subprocess.execute("launchctl", "list", "com.apple.smb.preferences")
|
||||
if result.exit_code != 0
|
||||
@@logger.warn("smb preferences service not enabled. enabling and starting...")
|
||||
cmd = ["/bin/launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.smb.preferences.plist"]
|
||||
result = Vagrant::Util::Subprocess.execute("/usr/bin/sudo", *cmd)
|
||||
if result.exit_code != 0
|
||||
raise SyncedFolderSMB::Errors::SMBStartFailed,
|
||||
command: cmd.join(" "),
|
||||
stderr: result.stderr,
|
||||
stdout: result.stdout
|
||||
end
|
||||
end
|
||||
result = Vagrant::Util::Subprocess.execute("launchctl", "list", "com.apple.smbd")
|
||||
if result.exit_code != 0
|
||||
@@logger.warn("smbd service not enabled. enabling and starting...")
|
||||
cmd = ["/bin/launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.smbd.plist"]
|
||||
result = Vagrant::Util::Subprocess.execute("/usr/bin/sudo", *cmd)
|
||||
if result.exit_code != 0
|
||||
raise SyncedFolderSMB::Errors::SMBStartFailed,
|
||||
command: cmd.join(" "),
|
||||
stderr: result.stderr,
|
||||
stdout: result.stdout
|
||||
end
|
||||
Vagrant::Util::Subprocess.execute("/usr/bin/sudo", "/bin/launchctl", "start", "com.apple.smbd")
|
||||
end
|
||||
end
|
||||
|
||||
# Required options for mounting a share hosted
|
||||
# on macos.
|
||||
def self.smb_mount_options(env)
|
||||
["sec=ntlmssp", "nounix", "noperm"]
|
||||
end
|
||||
|
||||
def self.smb_cleanup(env, machine, opts)
|
||||
m_id = machine_id(machine)
|
||||
result = Vagrant::Util::Subprocess.execute("/usr/bin/sudo", "/usr/sbin/sharing", "-l")
|
||||
if result.exit_code != 0
|
||||
@@logger.warn("failed to locate any shares for cleanup")
|
||||
end
|
||||
shares = result.stdout.split("\n").map do |line|
|
||||
if line.start_with?("name:")
|
||||
share_name = line.sub("name:", "").strip
|
||||
share_name if share_name.start_with?("vgt-#{m_id}")
|
||||
end
|
||||
end.compact
|
||||
@@logger.debug("shares to be removed: #{shares}")
|
||||
shares.each do |share_name|
|
||||
@@logger.info("removing share name=#{share_name}")
|
||||
share_name.strip!
|
||||
result = Vagrant::Util::Subprocess.execute("/usr/bin/sudo",
|
||||
"/usr/sbin/sharing", "-r", share_name)
|
||||
if result.exit_code != 0
|
||||
# Removing always returns 0 even if there are currently
|
||||
# guests attached so if we get a non-zero value just
|
||||
# log it as unexpected
|
||||
@@logger.warn("removing share `#{share_name}` returned non-zero")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.smb_prepare(env, machine, folders, opts)
|
||||
folders.each do |id, data|
|
||||
hostpath = data[:hostpath]
|
||||
|
||||
chksum_id = Digest::MD5.hexdigest(id)
|
||||
name = "vgt-#{machine_id(machine)}-#{chksum_id}"
|
||||
data[:smb_id] ||= name
|
||||
|
||||
@@logger.info("creating new share name=#{name} id=#{data[:smb_id]}")
|
||||
|
||||
cmd = [
|
||||
"/usr/bin/sudo",
|
||||
"/usr/sbin/sharing",
|
||||
"-a", hostpath,
|
||||
"-S", data[:smb_id],
|
||||
"-s", "001",
|
||||
"-g", "000",
|
||||
"-n", name
|
||||
]
|
||||
|
||||
r = Vagrant::Util::Subprocess.execute(*cmd)
|
||||
|
||||
if r.exit_code != 0
|
||||
raise VagrantPlugins::SyncedFolderSMB::Errors::DefineShareFailed,
|
||||
host: hostpath.to_s,
|
||||
stderr: r.stderr,
|
||||
stdout: r.stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Generates a unique identifier for the given machine
|
||||
# based on the name, provider name, and working directory
|
||||
# of the environment.
|
||||
#
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @return [String]
|
||||
def self.machine_id(machine)
|
||||
@@logger.debug("generating machine ID name=#{machine.name} cwd=#{machine.env.cwd}")
|
||||
Digest::MD5.hexdigest("#{machine.name}-#{machine.provider_name}-#{machine.env.cwd}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,6 +20,36 @@ module VagrantPlugins
|
|||
require_relative "cap/rdp"
|
||||
Cap::RDP
|
||||
end
|
||||
|
||||
host_capability("darwin", "smb_installed") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("darwin", "smb_prepare") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("darwin", "smb_mount_options") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("darwin", "smb_cleanup") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("darwin", "smb_start") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("darwin", "configured_ip_addresses") do
|
||||
require_relative "cap/configured_ip_addresses"
|
||||
Cap::ConfiguredIPAddresses
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
require "pathname"
|
||||
require "tempfile"
|
||||
|
||||
require "vagrant/util/downloader"
|
||||
require "vagrant/util/file_checksum"
|
||||
require "vagrant/util/powershell"
|
||||
require "vagrant/util/subprocess"
|
||||
|
||||
module VagrantPlugins
|
||||
module HostWindows
|
||||
module Cap
|
||||
class ConfiguredIPAddresses
|
||||
|
||||
def self.configured_ip_addresses(env)
|
||||
script_path = File.expand_path("../../scripts/host_info.ps1", __FILE__)
|
||||
r = Vagrant::Util::PowerShell.execute(script_path)
|
||||
if r.exit_code != 0
|
||||
raise Vagrant::Errors::PowerShellError,
|
||||
script: script_path,
|
||||
stderr: r.stderr
|
||||
end
|
||||
|
||||
res = JSON.parse(r.stdout)["ip_addresses"]
|
||||
Array(res)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,136 @@
|
|||
module VagrantPlugins
|
||||
module HostWindows
|
||||
module Cap
|
||||
class SMB
|
||||
|
||||
# Number of seconds to display UAC warning to user
|
||||
UAC_PROMPT_WAIT = 4
|
||||
|
||||
@@logger = Log4r::Logger.new("vagrant::host::windows::smb")
|
||||
|
||||
def self.smb_installed(env)
|
||||
psv = Vagrant::Util::PowerShell.version.to_i
|
||||
if psv < 3
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def self.smb_cleanup(env, machine, opts)
|
||||
script_path = File.expand_path("../../scripts/unset_share.ps1", __FILE__)
|
||||
|
||||
m_id = machine_id(machine)
|
||||
prune_shares = existing_shares.map do |share_name, share_info|
|
||||
if share_info["Description"].start_with?("vgt-#{m_id}-")
|
||||
@@logger.info("removing smb share name=#{share_name} id=#{m_id}")
|
||||
share_name
|
||||
else
|
||||
@@logger.info("skipping smb share removal, not owned name=#{share_name}")
|
||||
@@logger.debug("smb share ID not present name=#{share_name} id=#{m_id} description=#{share_info["Description"]}")
|
||||
nil
|
||||
end
|
||||
end.compact
|
||||
|
||||
@@logger.debug("shares to be removed: #{prune_shares}")
|
||||
|
||||
if prune_shares.size > 0
|
||||
machine.env.ui.warn("\n" + I18n.t("vagrant_sf_smb.uac.prune_warning") + "\n")
|
||||
sleep UAC_PROMPT_WAIT
|
||||
@@logger.info("remove shares: #{prune_shares}")
|
||||
result = Vagrant::Util::PowerShell.execute(script_path, *prune_shares, sudo: true)
|
||||
if result.exit_code != 0
|
||||
failed_name = result.stdout.to_s.sub("share name: ", "")
|
||||
raise SyncedFolderSMB::Errors::PruneShareFailed,
|
||||
name: failed_name,
|
||||
stderr: result.stderr,
|
||||
stdout: result.stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.smb_prepare(env, machine, folders, opts)
|
||||
script_path = File.expand_path("../../scripts/set_share.ps1", __FILE__)
|
||||
|
||||
shares = []
|
||||
current_shares = existing_shares
|
||||
folders.each do |id, data|
|
||||
hostpath = data[:hostpath].to_s
|
||||
|
||||
chksum_id = Digest::MD5.hexdigest(id)
|
||||
name = "vgt-#{machine_id(machine)}-#{chksum_id}"
|
||||
data[:smb_id] ||= name
|
||||
|
||||
# Check if this name is already in use
|
||||
if share_info = current_shares[data[:smb_id]]
|
||||
exist_path = File.expand_path(share_info["Path"]).downcase
|
||||
request_path = File.expand_path(hostpath).downcase
|
||||
if !hostpath.empty? && exist_path != request_path
|
||||
raise SyncedFolderSMB::Errors::SMBNameError,
|
||||
path: hostpath,
|
||||
existing_path: share_info["Path"],
|
||||
name: data[:smb_id]
|
||||
end
|
||||
@@logger.info("skip creation of existing share name=#{name} id=#{data[:smb_id]}")
|
||||
next
|
||||
end
|
||||
|
||||
@@logger.info("creating new share name=#{name} id=#{data[:smb_id]}")
|
||||
|
||||
shares << [
|
||||
"\"#{hostpath.gsub("/", "\\")}\"",
|
||||
name,
|
||||
data[:smb_id]
|
||||
]
|
||||
end
|
||||
if !shares.empty?
|
||||
machine.env.ui.warn("\n" + I18n.t("vagrant_sf_smb.uac.create_warning") + "\n")
|
||||
sleep(UAC_PROMPT_WAIT)
|
||||
result = Vagrant::Util::PowerShell.execute(script_path, *shares, sudo: true)
|
||||
if result.exit_code != 0
|
||||
share_path = result.stdout.to_s.sub("share path: ", "")
|
||||
raise SyncedFolderSMB::Errors::DefineShareFailed,
|
||||
host: share_path,
|
||||
stderr: result.stderr,
|
||||
stdout: result.stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Generate a list of existing local smb shares
|
||||
#
|
||||
# @return [Hash]
|
||||
def self.existing_shares
|
||||
result = Vagrant::Util::PowerShell.execute_cmd("Get-SmbShare|Format-List")
|
||||
if result.nil?
|
||||
raise SyncedFolderSMB::Errors::SMBListFailed
|
||||
end
|
||||
shares = {}
|
||||
name = nil
|
||||
result.lines.each do |line|
|
||||
key, value = line.split(":", 2).map(&:strip)
|
||||
if key == "Name"
|
||||
name = value
|
||||
shares[name] = {}
|
||||
end
|
||||
next if name.nil? || key.to_s.empty?
|
||||
shares[name][key] = value
|
||||
end
|
||||
@@logger.debug("local share listing: #{shares}")
|
||||
shares
|
||||
end
|
||||
|
||||
# Generates a unique identifier for the given machine
|
||||
# based on the name, provider name, and working directory
|
||||
# of the environment.
|
||||
#
|
||||
# @param [Vagrant::Machine] machine
|
||||
# @return [String]
|
||||
def self.machine_id(machine)
|
||||
@@logger.debug("generating machine ID name=#{machine.name} cwd=#{machine.env.cwd}")
|
||||
Digest::MD5.hexdigest("#{machine.name}-#{machine.provider_name}-#{machine.env.cwd}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,6 +30,26 @@ module VagrantPlugins
|
|||
require_relative "cap/ps"
|
||||
Cap::PS
|
||||
end
|
||||
|
||||
host_capability("windows", "smb_installed") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("windows", "smb_prepare") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("windows", "smb_cleanup") do
|
||||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
host_capability("windows", "configured_ip_addresses") do
|
||||
require_relative "cap/configured_ip_addresses"
|
||||
Cap::ConfiguredIPAddresses
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# The names of the user are language dependent!
|
||||
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0")
|
||||
$objUser = $objSID.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
$grant = "$objUser,Full"
|
||||
|
||||
for ($i=0; $i -le $args.length; $i = $i + 3) {
|
||||
$path = $args[$i]
|
||||
$share_name = $args[$i+1]
|
||||
$share_id = $args[$i+2]
|
||||
|
||||
|
||||
if ($path -eq $null) {
|
||||
Write-Warning "empty path argument encountered - complete"
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($share_name -eq $null) {
|
||||
Write-Output "share path: ${path}"
|
||||
Write-Error "error - no share name provided"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($share_id -eq $null) {
|
||||
Write-Output "share path: ${path}"
|
||||
Write-Error "error - no share ID provided"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$result = net share $share_id=$path /unlimited /GRANT:$grant /REMARK:"${share_name}"
|
||||
if ($LastExitCode -ne 0) {
|
||||
$host.ui.WriteLine("share path: ${path}")
|
||||
$host.ui.WriteErrorLine("error ${result}")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
exit 0
|
|
@ -0,0 +1,10 @@
|
|||
ForEach ($share_name in $args) {
|
||||
$result = net share $share_name /DELETE
|
||||
if ($LastExitCode -ne 0) {
|
||||
Write-Output "share name: ${share_name}"
|
||||
Write-Error "error - ${result}"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
Write-Output "share removal completed"
|
||||
exit 0
|
|
@ -41,6 +41,7 @@ module VagrantPlugins
|
|||
b2.use ProvisionerCleanup, :before
|
||||
b2.use StopInstance
|
||||
b2.use DeleteVM
|
||||
b2.use SyncedFolderCleanup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -144,6 +145,7 @@ module VagrantPlugins
|
|||
b2.use StartInstance
|
||||
b2.use WaitForIPAddress
|
||||
b2.use WaitForCommunicator, [:running]
|
||||
b2.use SyncedFolderCleanup
|
||||
b2.use SyncedFolders
|
||||
b2.use SetHostname
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
require "vagrant"
|
||||
|
||||
module VagrantPlugins
|
||||
module SyncedFolderSMB
|
||||
class Config < Vagrant.plugin("2", :config)
|
||||
attr_accessor :functional
|
||||
|
||||
def initialize
|
||||
super
|
||||
|
||||
@functional = UNSET_VALUE
|
||||
end
|
||||
|
||||
def finalize!
|
||||
@functional = true if @functional == UNSET_VALUE
|
||||
end
|
||||
|
||||
def to_s
|
||||
"SMB"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,10 +6,34 @@ module VagrantPlugins
|
|||
error_namespace("vagrant_sf_smb.errors")
|
||||
end
|
||||
|
||||
class SMBNotSupported < SMBError
|
||||
error_key(:not_supported)
|
||||
end
|
||||
|
||||
class SMBStartFailed < SMBError
|
||||
error_key(:start_failed)
|
||||
end
|
||||
|
||||
class SMBCredentialsMissing < SMBError
|
||||
error_key(:credentials_missing)
|
||||
end
|
||||
|
||||
class SMBListFailed < SMBError
|
||||
error_key(:list_failed)
|
||||
end
|
||||
|
||||
class SMBNameError < SMBError
|
||||
error_key(:name_error)
|
||||
end
|
||||
|
||||
class DefineShareFailed < SMBError
|
||||
error_key(:define_share_failed)
|
||||
end
|
||||
|
||||
class PruneShareFailed < SMBError
|
||||
error_key(:prune_share_failed)
|
||||
end
|
||||
|
||||
class NoHostIPAddr < SMBError
|
||||
error_key(:no_routable_host_addr)
|
||||
end
|
||||
|
|
|
@ -9,9 +9,14 @@ module VagrantPlugins
|
|||
name "SMB synced folders"
|
||||
description <<-EOF
|
||||
The SMB synced folders plugin enables you to use SMB folders on
|
||||
Windows and share them to guest machines.
|
||||
Windows or macOS and share them to guest machines.
|
||||
EOF
|
||||
|
||||
config("smb") do
|
||||
require_relative "config"
|
||||
Config
|
||||
end
|
||||
|
||||
synced_folder("smb", 7) do
|
||||
require_relative "synced_folder"
|
||||
init!
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$path,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$share_name,
|
||||
[string]$host_share_username = $null
|
||||
)
|
||||
|
||||
$ErrorAction = "Stop"
|
||||
|
||||
if (net share | Select-String $share_name) {
|
||||
net share $share_name /delete /y
|
||||
}
|
||||
|
||||
# The names of the user are language dependent!
|
||||
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-1-0")
|
||||
$objUser = $objSID.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
$grant = "$objUser,Full"
|
||||
|
||||
if (![string]::IsNullOrEmpty($host_share_username)) {
|
||||
$computer_name = $(Get-WmiObject Win32_Computersystem).name
|
||||
$grant = "$computer_name\$host_share_username,Full"
|
||||
|
||||
# Here we need to set the proper ACL for this folder. This lets full
|
||||
# recursive access to this folder.
|
||||
<#
|
||||
Get-ChildItem $path -recurse -Force |% {
|
||||
$current_acl = Get-ACL $_.fullname
|
||||
$permission = "$computer_name\$host_share_username","FullControl","ContainerInherit,ObjectInherit","None","Allow"
|
||||
$acl_access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
|
||||
$current_acl.SetAccessRule($acl_access_rule)
|
||||
$current_acl | Set-Acl $_.fullname
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
$result = net share $share_name=$path /unlimited /GRANT:$grant
|
||||
if ($LastExitCode -eq 0) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
$host.ui.WriteErrorLine("Error: $result")
|
||||
exit 1
|
|
@ -6,6 +6,8 @@ require "log4r"
|
|||
require "vagrant/util/platform"
|
||||
require "vagrant/util/powershell"
|
||||
|
||||
require_relative "errors"
|
||||
|
||||
module VagrantPlugins
|
||||
module SyncedFolderSMB
|
||||
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
|
||||
|
@ -13,76 +15,57 @@ module VagrantPlugins
|
|||
super
|
||||
|
||||
@logger = Log4r::Logger.new("vagrant::synced_folders::smb")
|
||||
@creds = {}
|
||||
end
|
||||
|
||||
def usable?(machine, raise_error=false)
|
||||
if !Vagrant::Util::Platform.windows?
|
||||
raise Errors::WindowsHostRequired if raise_error
|
||||
return false
|
||||
end
|
||||
|
||||
if !Vagrant::Util::Platform.windows_admin?
|
||||
raise Errors::WindowsAdminRequired if raise_error
|
||||
return false
|
||||
end
|
||||
|
||||
psv = Vagrant::Util::PowerShell.version.to_i
|
||||
if psv < 3
|
||||
if raise_error
|
||||
raise Errors::PowershellVersion,
|
||||
version: psv.to_s
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
# If the machine explicitly states SMB is not supported, then
|
||||
# believe it
|
||||
return false if !machine.config.smb.functional
|
||||
return true if machine.env.host.capability?(:smb_installed) &&
|
||||
machine.env.host.capability(:smb_installed)
|
||||
return false if !raise_error
|
||||
raise Errors::SMBNotSupported
|
||||
end
|
||||
|
||||
def prepare(machine, folders, opts)
|
||||
machine.ui.output(I18n.t("vagrant_sf_smb.preparing"))
|
||||
|
||||
script_path = File.expand_path("../scripts/set_share.ps1", __FILE__)
|
||||
# Check if this host can start and SMB service
|
||||
if machine.env.host.capability?(:smb_start)
|
||||
machine.env.host.capability(:smb_start)
|
||||
end
|
||||
|
||||
smb_username = smb_password = nil
|
||||
|
||||
# If we need auth information, then ask the user.
|
||||
have_auth = false
|
||||
folders.each do |id, data|
|
||||
if data[:smb_username] && data[:smb_password]
|
||||
@creds[:username] = data[:smb_username]
|
||||
@creds[:password] = data[:smb_password]
|
||||
smb_username = data[:smb_username]
|
||||
smb_password = data[:smb_password]
|
||||
have_auth = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if !have_auth
|
||||
machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ")
|
||||
@creds[:username] = machine.ui.ask("Username: ")
|
||||
@creds[:password] = machine.ui.ask("Password (will be hidden): ", echo: false)
|
||||
machine.env.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ")
|
||||
smb_username = machine.env.ui.ask("Username: ")
|
||||
smb_password = machine.env.ui.ask("Password (will be hidden): ", echo: false)
|
||||
end
|
||||
|
||||
folders.each do |id, data|
|
||||
hostpath = data[:hostpath]
|
||||
data[:smb_username] ||= smb_username
|
||||
data[:smb_password] ||= smb_password
|
||||
|
||||
data[:smb_id] ||= Digest::MD5.hexdigest(
|
||||
"#{machine.id}-#{id.gsub("/", "-")}")
|
||||
|
||||
args = []
|
||||
args << "-path" << "\"#{hostpath.gsub("/", "\\")}\""
|
||||
args << "-share_name" << data[:smb_id]
|
||||
#args << "-host_share_username" << @creds[:username]
|
||||
|
||||
r = Vagrant::Util::PowerShell.execute(script_path, *args)
|
||||
if r.exit_code != 0
|
||||
raise Errors::DefineShareFailed,
|
||||
host: hostpath.to_s,
|
||||
stderr: r.stderr,
|
||||
stdout: r.stdout
|
||||
end
|
||||
# Register password as sensitive
|
||||
Vagrant::Util::CredentialScrubber.sensitive(data[:smb_password])
|
||||
end
|
||||
|
||||
machine.env.host.capability(:smb_prepare, machine, folders, opts)
|
||||
end
|
||||
|
||||
def enable(machine, folders, nfsopts)
|
||||
def enable(machine, folders, opts)
|
||||
machine.ui.output(I18n.t("vagrant_sf_smb.mounting"))
|
||||
|
||||
# Make sure that this machine knows this dance
|
||||
|
@ -109,7 +92,7 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
if need_host_ip
|
||||
candidate_ips = load_host_ips
|
||||
candidate_ips = machine.env.host.capability(:configured_ip_addresses)
|
||||
@logger.debug("Potential host IPs: #{candidate_ips.inspect}")
|
||||
host_ip = machine.guest.capability(
|
||||
:choose_addressable_ip_addr, candidate_ips)
|
||||
|
@ -122,10 +105,7 @@ module VagrantPlugins
|
|||
ssh_info = machine.ssh_info
|
||||
|
||||
folders.each do |id, data|
|
||||
data = data.dup
|
||||
data[:smb_host] ||= host_ip
|
||||
data[:smb_username] ||= @creds[:username]
|
||||
data[:smb_password] ||= @creds[:password]
|
||||
|
||||
# Default the owner/group of the folder to the SSH user
|
||||
data[:owner] ||= ssh_info[:username]
|
||||
|
@ -141,25 +121,8 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def cleanup(machine, opts)
|
||||
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def load_host_ips
|
||||
script_path = File.expand_path("../scripts/host_info.ps1", __FILE__)
|
||||
r = Vagrant::Util::PowerShell.execute(script_path)
|
||||
if r.exit_code != 0
|
||||
raise Errors::PowershellError,
|
||||
script: script_path,
|
||||
stderr: r.stderr
|
||||
end
|
||||
|
||||
res = JSON.parse(r.stdout)["ip_addresses"]
|
||||
if res.instance_of? String
|
||||
[ res ]
|
||||
else
|
||||
res
|
||||
if machine.env.host.capability?(:smb_cleanup)
|
||||
machine.env.host.capability(:smb_cleanup, machine, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
en:
|
||||
vagrant_sf_smb:
|
||||
not_supported: |-
|
||||
It appears your machine doesn't support SMB, has not been
|
||||
properly configured for SMB, or there is not an adapter to
|
||||
enable SMB on this machine for Vagrant. Ensure SMB host
|
||||
functionality is available on this machine and try again.
|
||||
mounting: |-
|
||||
Mounting SMB shared folders...
|
||||
mounting_single: |-
|
||||
|
@ -9,9 +14,29 @@ en:
|
|||
warning_password: |-
|
||||
You will be asked for the username and password to use for the SMB
|
||||
folders shortly. Please use the proper username/password of your
|
||||
Windows account.
|
||||
account.
|
||||
|
||||
uac:
|
||||
prune_warning: |-
|
||||
Vagrant requires administator access for pruning SMB shares and
|
||||
may request access to complete removal of stale shares.
|
||||
create_warning: |-
|
||||
Vagrant requires administator access to create SMB shares and
|
||||
may request access to complete setup of configured shares.
|
||||
errors:
|
||||
start_failed: |-
|
||||
Vagrant failed to automatically start the SMB service. Ensure the
|
||||
required services can be started and try again.
|
||||
|
||||
Command: %{command}
|
||||
|
||||
Stderr: %{stderr}
|
||||
|
||||
Stdout: %{stdout}
|
||||
credentials_missing: |-
|
||||
Vagrant SMB synced folders require the account password to be stored
|
||||
in an NT compatible format. Please update your sharing settings to
|
||||
enable a Windows compatible password and try again.
|
||||
define_share_failed: |-
|
||||
Exporting an SMB share failed! Details about the failure are shown
|
||||
below. Please inspect the error message and correct any problems.
|
||||
|
@ -21,6 +46,27 @@ en:
|
|||
Stderr: %{stderr}
|
||||
|
||||
Stdout: %{stdout}
|
||||
prune_share_failed: |-
|
||||
Pruning an SMB share failed! Details about the failure are shown
|
||||
below. Please inspect the error message and correct any problems.
|
||||
|
||||
Share name: %{name}
|
||||
|
||||
Stderr: %{stderr}
|
||||
|
||||
Stdout: %{stdout}
|
||||
name_error: |-
|
||||
Vagrant is unable to setup a requested SMB share. An SMB share already
|
||||
exists with the given name.
|
||||
|
||||
Share name: %{name}
|
||||
|
||||
Current path: %{existing_path}
|
||||
|
||||
Requested path: %{path}
|
||||
list_failed: |-
|
||||
Vagrant failed to generate a list of local SMB shares. Please try
|
||||
running the command again.
|
||||
no_routable_host_addr: |-
|
||||
We couldn't detect an IP address that was routable to this
|
||||
machine from the guest machine! Please verify networking is properly
|
||||
|
@ -29,16 +75,6 @@ en:
|
|||
|
||||
As another option, you can manually specify an IP for the machine
|
||||
to mount from using the `smb_host` option to the synced folder.
|
||||
powershell_error: |-
|
||||
An error occurred while executing a PowerShell script. This error
|
||||
is shown below. Please read the error message and see if this is
|
||||
a configuration error with your system. If it is not, then please
|
||||
report a bug.
|
||||
|
||||
Script: %{script}
|
||||
Error:
|
||||
|
||||
%{stderr}
|
||||
powershell_version: |-
|
||||
PowerShell version 3 or later is required for SMB synced folders
|
||||
to work on Windows. You have version: '%{version}'. Please update
|
||||
|
|
|
@ -63,6 +63,11 @@ describe "VagrantPlugins::GuestLinux::Cap::MountSMBSharedFolder" do
|
|||
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||
end
|
||||
|
||||
it "removes the credentials file before completion" do
|
||||
expect(comm).to receive(:sudo).with(/rm.+smb_creds_.+/)
|
||||
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||
end
|
||||
|
||||
it "sends upstart notification after mount" do
|
||||
expect(comm).to receive(:sudo).with(/emit/)
|
||||
cap.mount_smb_shared_folder(machine, mount_name, mount_guest_path, folder_options)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
require_relative "../../../../../../plugins/hosts/darwin/cap/configured_ip_addresses"
|
||||
|
||||
describe VagrantPlugins::HostDarwin::Cap::ConfiguredIPAddresses do
|
||||
|
||||
let(:subject){ VagrantPlugins::HostDarwin::Cap::ConfiguredIPAddresses }
|
||||
let(:interfaces){ ["192.168.1.2"] }
|
||||
before{ allow(Socket).to receive(:getifaddrs).and_return(
|
||||
interfaces.map{|i| double(:socket, addr: Addrinfo.ip(i))}) }
|
||||
|
||||
it "should get list of available addresses" do
|
||||
expect(subject.configured_ip_addresses(nil)).to eq(["192.168.1.2"])
|
||||
end
|
||||
|
||||
context "with loopback address" do
|
||||
let(:interfaces){ ["192.168.1.2", "127.0.0.1"] }
|
||||
|
||||
it "should not include loopback address" do
|
||||
expect(subject.configured_ip_addresses(nil)).not_to include(["127.0.0.1"])
|
||||
end
|
||||
end
|
||||
|
||||
context "with IPv6 address" do
|
||||
let(:interfaces){ ["192.168.1.2", "2001:200:dff:fff1:216:3eff:feb1:44d7"] }
|
||||
|
||||
it "should not include IPv6 address" do
|
||||
expect(subject.configured_ip_addresses(nil)).not_to include(["2001:200:dff:fff1:216:3eff:feb1:44d7"])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
require_relative "../../../../../../plugins/hosts/darwin/cap/smb"
|
||||
|
||||
describe VagrantPlugins::HostDarwin::Cap::SMB do
|
||||
include_context "unit"
|
||||
|
||||
let(:subject){ VagrantPlugins::HostDarwin::Cap::SMB }
|
||||
let(:machine){ double(:machine) }
|
||||
let(:env){ double(:env) }
|
||||
let(:options){ {} }
|
||||
let(:result){ Vagrant::Util::Subprocess::Result }
|
||||
|
||||
before{ allow(subject).to receive(:machine_id).and_return("CUSTOM_ID") }
|
||||
|
||||
describe ".smb_installed" do
|
||||
it "is installed if sharing binary exists" do
|
||||
expect(File).to receive(:exist?).with("/usr/sbin/sharing").and_return(true)
|
||||
expect(subject.smb_installed(nil)).to be(true)
|
||||
end
|
||||
|
||||
it "is not installed if sharing binary does not exist" do
|
||||
expect(File).to receive(:exist?).with("/usr/sbin/sharing").and_return(false)
|
||||
expect(subject.smb_installed(nil)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".smb_start" do
|
||||
before{ allow(Vagrant::Util::Subprocess).to receive(:execute)
|
||||
.and_return(result.new(0, "SMB-NT", "")) }
|
||||
|
||||
it "should check for NT compatible password" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("pwpolicy", "gethashtypes").
|
||||
and_return(result.new(0, "SMB-NT", ""))
|
||||
subject.smb_start(env)
|
||||
end
|
||||
|
||||
it "should raise error if NT compatible password is not set" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("pwpolicy", "gethashtypes").
|
||||
and_return(result.new(0, "", ""))
|
||||
expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBCredentialsMissing)
|
||||
end
|
||||
|
||||
it "should ignore if the command returns non-zero" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("pwpolicy", "gethashtypes").
|
||||
and_return(result.new(1, "", ""))
|
||||
subject.smb_start(env)
|
||||
end
|
||||
|
||||
it "should not load smb preferences if it is already loaded" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /preferences/).and_return(result.new(0, "", ""))
|
||||
expect(Vagrant::Util::Subprocess).not_to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /preferences/)
|
||||
subject.smb_start(env)
|
||||
end
|
||||
|
||||
it "should load smb preferences if it is not already loaded" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /preferences/).and_return(result.new(1, "", ""))
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /preferences/).and_return(result.new(0, "", ""))
|
||||
subject.smb_start(env)
|
||||
end
|
||||
|
||||
it "should raise error if load smb preferences fails" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /preferences/).and_return(result.new(1, "", ""))
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /preferences/).and_return(result.new(1, "", ""))
|
||||
expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBStartFailed)
|
||||
end
|
||||
|
||||
it "should not load smbd if it is already loaded" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /smbd/).and_return(result.new(0, "", ""))
|
||||
expect(Vagrant::Util::Subprocess).not_to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /smbd/)
|
||||
subject.smb_start(env)
|
||||
end
|
||||
|
||||
it "should load smbd if it is not already loaded" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /smbd/).and_return(result.new(1, "", ""))
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /smbd/).and_return(result.new(0, "", ""))
|
||||
subject.smb_start(env)
|
||||
end
|
||||
|
||||
it "should raise error if load smbd fails" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /smbd/).and_return(result.new(1, "", ""))
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /smbd/).and_return(result.new(1, "", ""))
|
||||
expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBStartFailed)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".smb_cleanup" do
|
||||
after{ subject.smb_cleanup(env, machine, options) }
|
||||
|
||||
it "should search for shares with generated machine ID" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(
|
||||
"/usr/bin/sudo", /sharing/, "-l").and_return(result.new(0, "", ""))
|
||||
end
|
||||
|
||||
it "should remove shares individually" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).
|
||||
with("/usr/bin/sudo", /sharing/, "-l").
|
||||
and_return(result.new(0, "name: vgt-CUSTOM_ID-1\nname: vgt-CUSTOM_ID-2\n", ""))
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /sharing/, anything, /CUSTOM_ID/).
|
||||
twice.and_return(result.new(0, "", ""))
|
||||
end
|
||||
end
|
||||
|
||||
describe ".smb_prepare" do
|
||||
let(:folders){ {"/first/path" => {hostpath: "/first/host", smb_id: "ID1"},
|
||||
"/second/path" => {hostpath: "/second/host"}} }
|
||||
before{ allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result.new(0, "", "")) }
|
||||
it "should provide ID value if not set" do
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
expect(folders["/second/path"][:smb_id]).to start_with("vgt-")
|
||||
end
|
||||
|
||||
it "should not modify ID if already set" do
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
expect(folders["/first/path"][:smb_id]).to eq("ID1")
|
||||
end
|
||||
|
||||
it "should raise error when sharing command fails" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(result.new(1, "", ""))
|
||||
expect{ subject.smb_prepare(env, machine, folders, options) }.to raise_error(
|
||||
VagrantPlugins::SyncedFolderSMB::Errors::DefineShareFailed)
|
||||
end
|
||||
|
||||
it "should add shares individually" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, any_args).twice.and_return(result.new(0, "", ""))
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
require_relative "../../../../../../plugins/hosts/windows/cap/configured_ip_addresses"
|
||||
|
||||
describe VagrantPlugins::HostWindows::Cap::ConfiguredIPAddresses do
|
||||
|
||||
let(:subject){ VagrantPlugins::HostWindows::Cap::ConfiguredIPAddresses }
|
||||
let(:result){ Vagrant::Util::Subprocess::Result }
|
||||
let(:addresses){ [] }
|
||||
let(:execute_result){ result.new(0, {ip_addresses: addresses}.to_json, "") }
|
||||
|
||||
before{ allow(Vagrant::Util::PowerShell).to receive(:execute).
|
||||
and_return(execute_result) }
|
||||
|
||||
it "should return an array" do
|
||||
expect(subject.configured_ip_addresses(nil)).to be_kind_of(Array)
|
||||
end
|
||||
|
||||
context "with single address returned" do
|
||||
let(:addresses){ "ADDRESS" }
|
||||
|
||||
it "should return an array" do
|
||||
expect(subject.configured_ip_addresses(nil)).to eq([addresses])
|
||||
end
|
||||
end
|
||||
|
||||
context "with multiple addresses returned" do
|
||||
let(:addresses){ ["ADDRESS1", "ADDRESS2"] }
|
||||
|
||||
it "should return an array" do
|
||||
expect(subject.configured_ip_addresses(nil)).to eq(addresses)
|
||||
end
|
||||
end
|
||||
|
||||
context "with failed script execution" do
|
||||
let(:execute_result){ result.new(1, "", "") }
|
||||
|
||||
it "should raise error" do
|
||||
expect{ subject.configured_ip_addresses(nil) }.to raise_error(
|
||||
Vagrant::Errors::PowerShellError)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,148 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
require_relative "../../../../../../plugins/hosts/windows/cap/smb"
|
||||
|
||||
describe VagrantPlugins::HostWindows::Cap::SMB do
|
||||
let(:subject){ VagrantPlugins::HostWindows::Cap::SMB }
|
||||
let(:machine){ double(:machine, env: double(:machine_env, ui: double(:ui))) }
|
||||
let(:env){ double(:env) }
|
||||
let(:options){ {} }
|
||||
let(:result){ Vagrant::Util::Subprocess::Result }
|
||||
let(:powershell_version){ "3" }
|
||||
let(:smblist){ <<-EOF
|
||||
Name : vgt-CUSTOM_ID-1
|
||||
Path : /a/path
|
||||
Description : vgt-CUSTOM_ID-1
|
||||
|
||||
Name : vgt-CUSTOM_ID-2
|
||||
Path : /other/path
|
||||
Description : vgt-CUSTOM_ID-2
|
||||
|
||||
Name : my-share
|
||||
Path : /my/path
|
||||
Description : Not Vagrant Owned
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:machine_id).and_return("CUSTOM_ID")
|
||||
allow(Vagrant::Util::PowerShell).to receive(:version).and_return(powershell_version)
|
||||
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).and_return("")
|
||||
allow(machine.env.ui).to receive(:warn)
|
||||
allow(subject).to receive(:sleep)
|
||||
end
|
||||
|
||||
describe ".smb_installed" do
|
||||
context "when powershell version is greater than 2" do
|
||||
it "is valid installation" do
|
||||
expect(subject.smb_installed(nil)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when powershell version is less than 3" do
|
||||
let(:powershell_version){ "2" }
|
||||
|
||||
it "is not a valid installation" do
|
||||
expect(subject.smb_installed(nil)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".smb_cleanup" do
|
||||
before do
|
||||
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/Get-SmbShare/).
|
||||
and_return(smblist)
|
||||
allow(Vagrant::Util::PowerShell).to receive(:execute).and_return(result.new(0, "", ""))
|
||||
end
|
||||
after{ subject.smb_cleanup(env, machine, options) }
|
||||
|
||||
it "should pause after warning user" do
|
||||
expect(machine.env.ui).to receive(:warn)
|
||||
expect(subject).to receive(:sleep)
|
||||
end
|
||||
|
||||
it "should remove all shares in single call" do
|
||||
expect(Vagrant::Util::PowerShell).to receive(:execute).with(any_args, sudo: true).once
|
||||
end
|
||||
|
||||
context "when no shares are defined" do
|
||||
before do
|
||||
expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/Get-SmbShare/).
|
||||
and_return("")
|
||||
end
|
||||
|
||||
it "should not attempt to remove shares" do
|
||||
expect(Vagrant::Util::PowerShell).not_to receive(:execute).with(any_args, sudo: true)
|
||||
end
|
||||
|
||||
it "should not warn user" do
|
||||
expect(machine.env.ui).not_to receive(:warn)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".smb_prepare" do
|
||||
let(:folders){ {"/first/path" => {hostpath: "/host/1"}, "/second/path" => {hostpath: "/host/2", smb_id: "ID1"}} }
|
||||
let(:options){ {} }
|
||||
|
||||
before{ allow(Vagrant::Util::PowerShell).to receive(:execute).and_return(result.new(0, "", "")) }
|
||||
|
||||
it "should add ID when not defined" do
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
expect(folders["/first/path"][:smb_id]).to start_with("vgt-")
|
||||
end
|
||||
|
||||
it "should not modify ID when defined" do
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
expect(folders["/second/path"][:smb_id]).to eq("ID1")
|
||||
end
|
||||
|
||||
it "should pause after warning user" do
|
||||
expect(machine.env.ui).to receive(:warn)
|
||||
expect(subject).to receive(:sleep)
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
end
|
||||
|
||||
it "should add all shares in single call" do
|
||||
expect(Vagrant::Util::PowerShell).to receive(:execute).with(any_args, sudo: true).once
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
end
|
||||
|
||||
context "when share already exists" do
|
||||
let(:shares){ {"ID1" => {"Path" => "/host/2"}} }
|
||||
before do
|
||||
allow(File).to receive(:expand_path).and_call_original
|
||||
expect(subject).to receive(:existing_shares).and_return(shares)
|
||||
end
|
||||
|
||||
it "should expand paths when comparing existing to requested" do
|
||||
expect(File).to receive(:expand_path).at_least(2).with("/host/2").and_return("expanded_path")
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
end
|
||||
|
||||
context "with different path" do
|
||||
let(:shares){ {"ID1" => {"Path" => "/host/3"}} }
|
||||
|
||||
it "should raise an error" do
|
||||
expect{
|
||||
subject.smb_prepare(env, machine, folders, options)
|
||||
}.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBNameError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no shared are defined" do
|
||||
after{ subject.smb_prepare(env, machine, {}, options) }
|
||||
|
||||
it "should not attempt to add shares" do
|
||||
expect(Vagrant::Util::PowerShell).not_to receive(:execute).with(any_args, sudo: true)
|
||||
end
|
||||
|
||||
it "should not warn user" do
|
||||
expect(machine.env.ui).not_to receive(:warn)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,6 +30,8 @@ describe VagrantPlugins::ProviderVirtualBox::Action::PrepareNFSSettings do
|
|||
env[:test] = true
|
||||
allow(machine.env).to receive(:host) { host }
|
||||
allow(host).to receive(:capability).with(:nfs_installed) { true }
|
||||
# We don't care about smb support so return not installed
|
||||
allow(host).to receive(:capability?).with(:smb_installed).and_return(false)
|
||||
end
|
||||
|
||||
it "calls the next action in the chain" do
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
require_relative "../../../base"
|
||||
|
||||
require Vagrant.source_root.join("plugins/synced_folders/smb/synced_folder")
|
||||
|
||||
describe VagrantPlugins::SyncedFolderSMB::SyncedFolder do
|
||||
include_context "unit"
|
||||
|
||||
let(:iso_env) do
|
||||
env = isolated_environment
|
||||
env.vagrantfile("")
|
||||
env.create_vagrant_env
|
||||
end
|
||||
|
||||
let(:guest){ double("guest") }
|
||||
let(:host){ double("host") }
|
||||
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
|
||||
let(:host_caps){ [] }
|
||||
let(:guest_caps){ [] }
|
||||
let(:folders){ {"/first/path" => {}, "/second/path" => {}} }
|
||||
let(:options){ {} }
|
||||
|
||||
before do
|
||||
allow(machine.env).to receive(:host).and_return(host)
|
||||
allow(machine).to receive(:guest).and_return(guest)
|
||||
allow(machine).to receive(:ssh_info).and_return(username: 'sshuser')
|
||||
allow(guest).to receive(:name).and_return("guest_name")
|
||||
allow(host).to receive(:capability?).and_return(false)
|
||||
host_caps.each do |cap|
|
||||
allow(host).to receive(:capability?).with(cap).and_return(true)
|
||||
allow(host).to receive(:capability).with(cap, any_args).and_return(true)
|
||||
end
|
||||
allow(guest).to receive(:capability?).and_return(false)
|
||||
guest_caps.each do |cap|
|
||||
allow(guest).to receive(:capability?).with(cap).and_return(true)
|
||||
allow(guest).to receive(:capability).with(cap, any_args).and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".usable?" do
|
||||
context "without supporting capabilities" do
|
||||
it "is not usable" do
|
||||
expect(subject.usable?(machine)).to be(false)
|
||||
end
|
||||
|
||||
it "raises exception when raise_error enabled" do
|
||||
expect{subject.usable?(machine, true)}.to raise_error(
|
||||
VagrantPlugins::SyncedFolderSMB::Errors::SMBNotSupported)
|
||||
end
|
||||
end
|
||||
|
||||
context "with smb not installed" do
|
||||
let(:host_caps){ [:smb_installed] }
|
||||
|
||||
it "is not usable" do
|
||||
expect(host).to receive(:capability).with(:smb_installed).and_return(false)
|
||||
expect(subject.usable?(machine)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "with smb installed" do
|
||||
let(:host_caps){ [:smb_installed] }
|
||||
|
||||
it "is usable" do
|
||||
expect(subject.usable?(machine)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".prepare" do
|
||||
let(:host_caps){ [:smb_start, :smb_prepare] }
|
||||
|
||||
context "without credentials provided" do
|
||||
before do
|
||||
expect(machine.env.ui).to receive(:ask).and_return('username')
|
||||
expect(machine.env.ui).to receive(:ask).and_return('password')
|
||||
end
|
||||
|
||||
it "should prompt for credentials" do
|
||||
subject.prepare(machine, folders, options)
|
||||
end
|
||||
|
||||
it "should set credential information into all folder options" do
|
||||
subject.prepare(machine, folders, options)
|
||||
expect(folders['/first/path'][:smb_username]).to eq('username')
|
||||
expect(folders['/first/path'][:smb_password]).to eq('password')
|
||||
expect(folders['/second/path'][:smb_username]).to eq('username')
|
||||
expect(folders['/second/path'][:smb_password]).to eq('password')
|
||||
end
|
||||
|
||||
it "should start the SMB service if capability is available" do
|
||||
expect(host).to receive(:capability).with(:smb_start, any_args)
|
||||
subject.prepare(machine, folders, options)
|
||||
end
|
||||
end
|
||||
|
||||
context "with credentials provided" do
|
||||
context "in single share entry" do
|
||||
let(:folders){ {'/first/path' => {}, '/second/path' => {smb_username: 'smbuser', smb_password: 'smbpass'}} }
|
||||
|
||||
it "should not prompt for credentials" do
|
||||
expect(machine.env.ui).not_to receive(:ask)
|
||||
subject.prepare(machine, folders, options)
|
||||
end
|
||||
|
||||
it "should add existing credentials to folder options without" do
|
||||
subject.prepare(machine, folders, options)
|
||||
expect(folders['/first/path'][:smb_username]).to eq('smbuser')
|
||||
expect(folders['/first/path'][:smb_password]).to eq('smbpass')
|
||||
end
|
||||
end
|
||||
|
||||
context "in both entries" do
|
||||
let(:folders){ {'/first/path' => {smb_username: 'user', smb_password: 'pass'},
|
||||
'/second/path' => {smb_username: 'smbuser', smb_password: 'smbpass'}} }
|
||||
|
||||
it "should not modify existing credentials" do
|
||||
subject.prepare(machine, folders, options)
|
||||
expect(folders['/first/path'][:smb_username]).to eq('user')
|
||||
expect(folders['/first/path'][:smb_password]).to eq('pass')
|
||||
expect(folders['/second/path'][:smb_username]).to eq('smbuser')
|
||||
expect(folders['/second/path'][:smb_password]).to eq('smbpass')
|
||||
end
|
||||
|
||||
it "should register passwords with scrubber" do
|
||||
expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with('pass')
|
||||
expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with('smbpass')
|
||||
subject.prepare(machine, folders, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".enable" do
|
||||
it "fails when guest does not support capability" do
|
||||
expect{
|
||||
subject.enable(machine, folders, options)
|
||||
}.to raise_error(Vagrant::Errors::GuestCapabilityNotFound)
|
||||
end
|
||||
|
||||
context "with guest capability supported" do
|
||||
let(:guest_caps){ [:mount_smb_shared_folder, :choose_addressable_ip_addr] }
|
||||
let(:host_caps){ [:configured_ip_addresses] }
|
||||
|
||||
it "should attempt to install smb on guest" do
|
||||
expect(guest).to receive(:capability?).with(:smb_install).and_return(true)
|
||||
expect(guest).to receive(:capability).with(:smb_install, any_args)
|
||||
subject.enable(machine, folders, options)
|
||||
end
|
||||
|
||||
it "should request host IP addresses" do
|
||||
expect(host).to receive(:capability).with(:configured_ip_addresses)
|
||||
subject.enable(machine, folders, options)
|
||||
end
|
||||
|
||||
it "should determine guest accessible address" do
|
||||
expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args)
|
||||
subject.enable(machine, folders, options)
|
||||
end
|
||||
|
||||
it "should error if no guest accessible address is available" do
|
||||
expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return(nil)
|
||||
expect{ subject.enable(machine, folders, options) }.to raise_error(
|
||||
VagrantPlugins::SyncedFolderSMB::Errors::NoHostIPAddr)
|
||||
end
|
||||
|
||||
it "should default owner and group to ssh username" do
|
||||
subject.enable(machine, folders, options)
|
||||
expect(folders["/first/path"][:owner]).to eq("sshuser")
|
||||
expect(folders["/first/path"][:group]).to eq("sshuser")
|
||||
expect(folders["/second/path"][:owner]).to eq("sshuser")
|
||||
expect(folders["/second/path"][:group]).to eq("sshuser")
|
||||
end
|
||||
|
||||
it "should set the host address in folder options" do
|
||||
expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return("ADDR")
|
||||
subject.enable(machine, folders, options)
|
||||
expect(folders["/first/path"][:smb_host]).to eq("ADDR")
|
||||
expect(folders["/second/path"][:smb_host]).to eq("ADDR")
|
||||
end
|
||||
|
||||
context "with smb_host option set" do
|
||||
let(:folders){ {"/first/path" => {smb_host: "ADDR"}, "/second/path" => {}} }
|
||||
|
||||
it "should not update the value" do
|
||||
expect(guest).to receive(:capability).with(:choose_addressable_ip_addr, any_args).and_return("OTHER")
|
||||
subject.enable(machine, folders, options)
|
||||
expect(folders["/first/path"][:smb_host]).to eq("ADDR")
|
||||
expect(folders["/second/path"][:smb_host]).to eq("OTHER")
|
||||
end
|
||||
end
|
||||
|
||||
context "with owner and group set" do
|
||||
let(:folders){ {"/first/path" => {owner: "smbowner"}, "/second/path" => {group: "smbgroup"}} }
|
||||
|
||||
it "should not update set owner or group" do
|
||||
subject.enable(machine, folders, options)
|
||||
expect(folders["/first/path"][:owner]).to eq("smbowner")
|
||||
expect(folders["/first/path"][:group]).to eq("sshuser")
|
||||
expect(folders["/second/path"][:owner]).to eq("sshuser")
|
||||
expect(folders["/second/path"][:group]).to eq("smbgroup")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".cleanup" do
|
||||
context "without supporting capability" do
|
||||
it "does nothing" do
|
||||
subject.cleanup(machine, options)
|
||||
end
|
||||
end
|
||||
|
||||
context "with supporting capability" do
|
||||
let(:host_caps){ [:smb_cleanup] }
|
||||
|
||||
it "runs cleanup" do
|
||||
expect(host).to receive(:capability).with(:smb_cleanup, any_args)
|
||||
subject.cleanup(machine, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,17 +19,50 @@ SMB is built-in to Windows machines and provides a higher performance
|
|||
alternative to some other mechanisms such as VirtualBox shared folders.
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Windows only!</strong> SMB is currently only supported
|
||||
when the host machine is Windows. The guest machine can be Windows
|
||||
or Linux.
|
||||
SMB is currently only supported when the host machine is Windows or
|
||||
macOS. The guest machine can be Windows, Linux, or macOS.
|
||||
</div>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use the SMB synced folder type, the machine running Vagrant must be
|
||||
a Windows machine with PowerShell version 3 or later installed. In addition to this, the command prompt executing Vagrant
|
||||
must have administrative privileges. Vagrant requires these privileges in
|
||||
order to create new network folder shares.
|
||||
### Windows Host
|
||||
|
||||
To use the SMB synced folder type on a Windows host, the machine must have
|
||||
PowerShell version 3 or later installed. In addition, when Vagrant attempts
|
||||
to create new SMB shares, or remove existing SMB shares, Administrator
|
||||
privileges will be required. Vagrant will request these privileges using UAC.
|
||||
|
||||
### macOS Host
|
||||
|
||||
To use the SMB synced folder type on a macOS host, file sharing must be enabled
|
||||
for the local account. Enable SMB file sharing by following the instructions
|
||||
below:
|
||||
|
||||
* Open "System Preferences"
|
||||
* Click "Sharing"
|
||||
* Check the "On" checkbox next to "File Sharing"
|
||||
* Click "Options"
|
||||
* Check "Share files and folders using SMB"
|
||||
* Check the "On" checkbox next to your username within "Windows File Sharing"
|
||||
* Click "Done"
|
||||
|
||||
When Vagrant attempts to create new SMB shares, or remove existing SMB shares,
|
||||
root access will be required. Vagrant will request these privileges using
|
||||
`sudo` to run the `/usr/sbin/sharing` command. Adding the following to
|
||||
the system's `sudoers` configuration will allow Vagrant to manage SMB shares
|
||||
without requiring a password each time:
|
||||
|
||||
```
|
||||
Cmnd_Alias VAGRANT_SMB_ADD = /usr/sbin/sharing -a * -S * -s * -g * -n *
|
||||
Cmnd_Alias VAGRANT_SMB_REMOVE = /usr/sbin/sharing -r *
|
||||
Cmnd_Alias VAGRANT_SMB_LIST = /usr/sbin/sharing -l
|
||||
Cmnd_Alias VAGRANT_SMB_PLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist
|
||||
Cmnd_Alias VAGRANT_SMB_DLOAD = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smbd.plist
|
||||
Cmnd_Alias VAGRANT_SMB_DSTART = /bin/launchctl start com.apple.smbd
|
||||
%admin ALL=(root) NOPASSWD: VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST, VAGRANT_SMB_PLOAD, VAGRANT_SMB_DLOAD VAGRANT_SMB_DSTART
|
||||
```
|
||||
|
||||
### Guests
|
||||
|
||||
The destination machine must be able to mount SMB filesystems. On Linux
|
||||
the package to do this is usually called `smbfs` or `cifs`. Vagrant knows
|
||||
|
@ -75,18 +108,6 @@ shell. Note that you should research if this is the right option for you.
|
|||
net config server /autodisconnect:-1
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
Because SMB is a relatively new synced folder type in Vagrant, it still
|
||||
has some rough edges. Hopefully, future versions of Vagrant will address
|
||||
these.
|
||||
|
||||
The primary limitation of SMB synced folders at the moment is that they are
|
||||
never pruned or cleaned up. Once the folder share is defined, Vagrant never
|
||||
removes it. To clean up SMB synced folder shares, periodically run
|
||||
`net share` in a command prompt, find the shares you do not want, then
|
||||
run `net share NAME /delete` for each, where NAME is the name of the share.
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "wrong fs type" Error
|
||||
|
|
Loading…
Reference in New Issue