Chris Roberts ef5eca16d2 Fix SMB information generation when using net share
Adds the final line of output to tests and properly simulates
failure. Fix with type casting to prevent calling methods on
nil and drop the final entry as it's not valid.

Fixes #9806
2018-06-11 12:58:35 -07:00

193 lines
6.7 KiB
Ruby

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
# Required options for mounting a share hosted on Windows
# NOTE: Windows deprecated smb 1.0 so a minimum of 2.0 must be enabled
def self.smb_mount_options(env)
["vers=2.0"]
end
def self.smb_validate_password(env, machine, username, password)
script_path = File.expand_path("../../scripts/check_credentials.ps1", __FILE__)
args = []
args << "-username" << "'#{username.gsub("'", "''")}'"
args << "-password" << "'#{password.gsub("'", "''")}'"
r = Vagrant::Util::PowerShell.execute(script_path, *args)
r.exit_code == 0
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"].to_s.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
shares = get_smbshares || get_netshares
if shares.nil?
raise SyncedFolderSMB::Errors::SMBListFailed
end
@@logger.debug("local share listing: #{shares}")
shares
end
# Get current SMB share list using Get-SmbShare
#
# @return [Hash]
def self.get_smbshares
result = Vagrant::Util::PowerShell.execute_cmd("Get-SmbShare|Format-List")
if result.nil?
return nil
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
shares
end
# Get current SMB share list using net.exe
#
# @return [Hash]
def self.get_netshares
result = Vagrant::Util::PowerShell.execute_cmd("net share")
if result.nil?
return nil
end
result.sub!(/^.+?\-+/m, "")
share_names = result.strip.split("\n").map do |line|
line.strip.split(/\s+/).first
end
# Last item is command completion notification so remove it
share_names.pop
shares = {}
share_names.each do |share_name|
shares[share_name] = {}
result = Vagrant::Util::PowerShell.execute_cmd("net share #{share_name}")
next if result.nil?
result.each_line do |line|
key, value = line.strip.split(/\s+/, 2)
next if key == "Share name"
key = "Description" if key == "Remark"
shares[share_name][key] = value
end
end
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