From 31163da0d544585f741b143e7dd8697677c6f091 Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Tue, 20 Jan 2015 11:45:57 -0800 Subject: [PATCH] Use winrm-fs for file uploads Replace the Vagrant native winrm file upload functionality with the winrm-fs gem. --- plugins/communicators/winrm/file_manager.rb | 163 -------------------- plugins/communicators/winrm/shell.rb | 8 +- vagrant.gemspec | 3 +- 3 files changed, 7 insertions(+), 167 deletions(-) delete mode 100644 plugins/communicators/winrm/file_manager.rb diff --git a/plugins/communicators/winrm/file_manager.rb b/plugins/communicators/winrm/file_manager.rb deleted file mode 100644 index 71586b811..000000000 --- a/plugins/communicators/winrm/file_manager.rb +++ /dev/null @@ -1,163 +0,0 @@ -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 - } - } - Write-Host "should upload file $dest_file_path" - 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 - end -end diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb index 2a116ec30..4b9542185 100644 --- a/plugins/communicators/winrm/shell.rb +++ b/plugins/communicators/winrm/shell.rb @@ -9,7 +9,7 @@ Vagrant::Util::SilenceWarnings.silence! do require "winrm" end -require_relative "file_manager" +require "winrm-fs/file_manager" module VagrantPlugins module CommunicatorWinRM @@ -67,11 +67,13 @@ module VagrantPlugins end def upload(from, to) - FileManager.new(self).upload(from, to) + file_manager = WinRM::FS::FileManager.new(session) + file_manager.upload(from, to) end def download(from, to) - FileManager.new(self).download(from, to) + file_manager = WinRM::FS::FileManager.new(session) + file_manager.download(from, to) end protected diff --git a/vagrant.gemspec b/vagrant.gemspec index d2b939852..8fa73e7ea 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -28,7 +28,8 @@ Gem::Specification.new do |s| s.add_dependency "rb-kqueue", "~> 0.2.0" s.add_dependency "rest-client", ">= 1.6.0", "< 2.0" s.add_dependency "wdm", "~> 0.1.0" - s.add_dependency "winrm", "~> 1.1.3" + s.add_dependency "winrm", "~> 1.3.0" + s.add_dependency "winrm-fs", "~> 0.1.0" # We lock this down to avoid compilation issues. s.add_dependency "nokogiri", "= 1.6.3.1"