Merge pull request #3519 from mitchellh/windows-guest-file-manager
communicators/winrm: Windows guest file manager
This commit is contained in:
commit
fbcc6d25b4
|
@ -21,6 +21,10 @@ module VagrantPlugins
|
|||
class WinRMNotReady < WinRMError
|
||||
error_key(:winrm_not_ready)
|
||||
end
|
||||
|
||||
class WinRMFileTransferError < WinRMError
|
||||
error_key(:winrm_file_transfer_error)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
require "log4r"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
|
||||
# Manages the file system on the remote guest allowing for file tranfer
|
||||
# between the guest and host.
|
||||
class FileManager
|
||||
|
||||
def initialize(shell)
|
||||
@logger = Log4r::Logger.new("vagrant::communication::filemanager")
|
||||
@shell = shell
|
||||
end
|
||||
|
||||
# Uploads the given file or directory from the host to the guest (recursively).
|
||||
#
|
||||
# @param [String] The source file or directory path on the host
|
||||
# @param [String] The destination file or directory path on the host
|
||||
def upload(host_src_file_path, guest_dest_file_path)
|
||||
@logger.debug("Upload: #{host_src_file_path} -> #{guest_dest_file_path}")
|
||||
if File.directory?(host_src_file_path)
|
||||
upload_directory(host_src_file_path, guest_dest_file_path)
|
||||
else
|
||||
upload_file(host_src_file_path, guest_dest_file_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Downloads the given file from the guest to the host.
|
||||
# NOTE: This currently only supports single file download
|
||||
#
|
||||
# @param [String] The source file path on the guest
|
||||
# @param [String] The destination file path on the host
|
||||
def download(guest_src_file_path, host_dest_file_path)
|
||||
@logger.debug("#{guest_src_file_path} -> #{host_dest_file_path}")
|
||||
|
||||
output = @shell.powershell("[System.convert]::ToBase64String([System.IO.File]::ReadAllBytes(\"#{guest_src_file_path}\"))")
|
||||
contents = output[:data].map!{|line| line[:stdout]}.join.gsub("\\n\\r", '')
|
||||
out = Base64.decode64(contents)
|
||||
IO.binwrite(host_dest_file_path, out)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Recursively uploads the given directory from the host to the guest
|
||||
#
|
||||
# @param [String] The source file or directory path on the host
|
||||
# @param [String] The destination file or directory path on the host
|
||||
def upload_directory(host_src_file_path, guest_dest_file_path)
|
||||
glob_patt = File.join(host_src_file_path, '**/*')
|
||||
Dir.glob(glob_patt).select { |f| !File.directory?(f) }.each do |host_file_path|
|
||||
guest_file_path = guest_file_path(host_src_file_path, guest_dest_file_path, host_file_path)
|
||||
upload_file(host_file_path, guest_file_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Uploads the given file, but only if the target file doesn't exist
|
||||
# or its MD5 checksum doens't match the host's source checksum.
|
||||
#
|
||||
# @param [String] The source file path on the host
|
||||
# @param [String] The destination file path on the guest
|
||||
def upload_file(host_src_file_path, guest_dest_file_path)
|
||||
if should_upload_file?(host_src_file_path, guest_dest_file_path)
|
||||
tmp_file_path = upload_to_temp_file(host_src_file_path)
|
||||
decode_temp_file(tmp_file_path, guest_dest_file_path)
|
||||
else
|
||||
@logger.debug("Up to date: #{guest_dest_file_path}")
|
||||
end
|
||||
end
|
||||
|
||||
# Uploads the given file to a new temp file on the guest
|
||||
#
|
||||
# @param [String] The source file path on the host
|
||||
# @return [String] The temp file path on the guest
|
||||
def upload_to_temp_file(host_src_file_path)
|
||||
tmp_file_path = File.join(guest_temp_dir, "winrm-upload-#{rand()}")
|
||||
@logger.debug("Uploading '#{host_src_file_path}' to temp file '#{tmp_file_path}'")
|
||||
|
||||
base64_host_file = Base64.encode64(IO.binread(host_src_file_path)).gsub("\n",'')
|
||||
base64_host_file.chars.to_a.each_slice(8000-tmp_file_path.size) do |chunk|
|
||||
out = @shell.cmd("echo #{chunk.join} >> \"#{tmp_file_path}\"")
|
||||
raise_upload_error_if_failed(out, host_src_file_path, tmp_file_path)
|
||||
end
|
||||
|
||||
tmp_file_path
|
||||
end
|
||||
|
||||
# Moves and decodes the given file temp file on the guest to its
|
||||
# permanent location
|
||||
#
|
||||
# @param [String] The source base64 encoded temp file path on the guest
|
||||
# @param [String] The destination file path on the guest
|
||||
def decode_temp_file(guest_tmp_file_path, guest_dest_file_path)
|
||||
@logger.debug("Decoding temp file '#{guest_tmp_file_path}' to '#{guest_dest_file_path}'")
|
||||
out = @shell.powershell <<-EOH
|
||||
$tmp_file_path = [System.IO.Path]::GetFullPath('#{guest_tmp_file_path}')
|
||||
$dest_file_path = [System.IO.Path]::GetFullPath('#{guest_dest_file_path}')
|
||||
|
||||
if (Test-Path $dest_file_path) {
|
||||
rm $dest_file_path
|
||||
}
|
||||
else {
|
||||
$dest_dir = ([System.IO.Path]::GetDirectoryName($dest_file_path))
|
||||
New-Item -ItemType directory -Force -Path $dest_dir
|
||||
}
|
||||
|
||||
$base64_string = Get-Content $tmp_file_path
|
||||
$bytes = [System.Convert]::FromBase64String($base64_string)
|
||||
[System.IO.File]::WriteAllBytes($dest_file_path, $bytes)
|
||||
EOH
|
||||
raise_upload_error_if_failed(out, guest_tmp_file_path, guest_dest_file_path)
|
||||
end
|
||||
|
||||
# Checks to see if the target file on the guest is missing or out of date.
|
||||
#
|
||||
# @param [String] The source file path on the host
|
||||
# @param [String] The destination file path on the guest
|
||||
# @return [Boolean] True if the file is missing or out of date
|
||||
def should_upload_file?(host_src_file_path, guest_dest_file_path)
|
||||
local_md5 = Digest::MD5.file(host_src_file_path).hexdigest
|
||||
cmd = <<-EOH
|
||||
$dest_file_path = [System.IO.Path]::GetFullPath('#{guest_dest_file_path}')
|
||||
|
||||
if (Test-Path $dest_file_path) {
|
||||
$crypto_provider = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
|
||||
try {
|
||||
$file = [System.IO.File]::Open($dest_file_path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read)
|
||||
$guest_md5 = ([System.BitConverter]::ToString($crypto_provider.ComputeHash($file))).Replace("-","").ToLower()
|
||||
}
|
||||
finally {
|
||||
$file.Dispose()
|
||||
}
|
||||
if ($guest_md5 -eq '#{local_md5}') {
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
exit 1
|
||||
EOH
|
||||
@shell.powershell(cmd)[:exitcode] == 1
|
||||
end
|
||||
|
||||
# Creates a guest file path equivalent from a host file path
|
||||
#
|
||||
# @param [String] The base host directory we're going to copy from
|
||||
# @param [String] The base guest directory we're going to copy to
|
||||
# @param [String] A full path to a file on the host underneath host_base_dir
|
||||
# @return [String] The guest file path equivalent
|
||||
def guest_file_path(host_base_dir, guest_base_dir, host_file_path)
|
||||
rel_path = File.dirname(host_file_path[host_base_dir.length, host_file_path.length])
|
||||
File.join(guest_base_dir, rel_path, File.basename(host_file_path))
|
||||
end
|
||||
|
||||
def guest_temp_dir
|
||||
@guest_temp ||= (@shell.cmd('echo %TEMP%'))[:data][0][:stdout].chomp
|
||||
end
|
||||
|
||||
def raise_upload_error_if_failed(out, from, to)
|
||||
raise Errors::WinRMFileTransferError,
|
||||
:from => from,
|
||||
:to => to,
|
||||
:message => out.inspect if out[:exitcode] != 0
|
||||
end
|
||||
|
||||
end #class
|
||||
end
|
||||
end
|
|
@ -9,6 +9,8 @@ Vagrant::Util::SilenceWarnings.silence! do
|
|||
require "winrm"
|
||||
end
|
||||
|
||||
require_relative "file_manager"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
class WinRMShell
|
||||
|
@ -62,31 +64,11 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def upload(from, to)
|
||||
@logger.debug("Uploading: #{from} to #{to}")
|
||||
file_name = (cmd("echo %TEMP%\\winrm-upload-#{rand()}"))[:data][0][:stdout].chomp
|
||||
powershell <<-EOH
|
||||
if(Test-Path #{to}) {
|
||||
rm #{to}
|
||||
}
|
||||
EOH
|
||||
Base64.encode64(IO.binread(from)).gsub("\n",'').chars.to_a.each_slice(8000-file_name.size) do |chunk|
|
||||
out = cmd("echo #{chunk.join} >> \"#{file_name}\"")
|
||||
end
|
||||
powershell <<-EOH
|
||||
mkdir $([System.IO.Path]::GetDirectoryName(\"#{to}\"))
|
||||
$base64_string = Get-Content \"#{file_name}\"
|
||||
$bytes = [System.Convert]::FromBase64String($base64_string)
|
||||
$new_file = [System.IO.Path]::GetFullPath(\"#{to}\")
|
||||
[System.IO.File]::WriteAllBytes($new_file,$bytes)
|
||||
EOH
|
||||
FileManager.new(self).upload(from, to)
|
||||
end
|
||||
|
||||
def download(from, to)
|
||||
@logger.debug("Downloading: #{from} to #{to}")
|
||||
output = powershell("[System.convert]::ToBase64String([System.IO.File]::ReadAllBytes(\"#{from}\"))")
|
||||
contents = output[:data].map!{|line| line[:stdout]}.join.gsub("\\n\\r", '')
|
||||
out = Base64.decode64(contents)
|
||||
IO.binwrite(to, out)
|
||||
FileManager.new(self).download(from, to)
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -20,3 +20,9 @@ en:
|
|||
The box is not able to report an address for WinRM to connect to yet.
|
||||
WinRM cannot access this Vagrant environment. Please wait for the
|
||||
Vagrant environment to be running and try again.
|
||||
winrm_file_transfer_error: |-
|
||||
Failed to transfer a file between the host and guest
|
||||
|
||||
From: %{from}
|
||||
To: %{to}
|
||||
Message: %{message}
|
||||
|
|
Loading…
Reference in New Issue