diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 8fe7d9e71..b706145df 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -816,6 +816,22 @@ module Vagrant error_key(:unimplemented_provider_action) end + class UploadInvalidCompressionType < VagrantError + error_key(:upload_invalid_compression_type) + end + + class UploadMissingExtractCapability < VagrantError + error_key(:upload_missing_extract_capability) + end + + class UploadMissingTempCapability < VagrantError + error_key(:upload_missing_temp_capability) + end + + class UploadSourceMissing < VagrantError + error_key(:upload_source_missing) + end + class VagrantInterrupt < VagrantError error_key(:interrupted) end @@ -968,6 +984,10 @@ module Vagrant error_key(:power_off, "vagrant.actions.vm.export") end + class WinRMInvalidCommunicator < VagrantError + error_key(:winrm_invalid_communicator) + end + class WSLVagrantVersionMismatch < VagrantError error_key(:wsl_vagrant_version_mismatch) end diff --git a/plugins/commands/upload/command.rb b/plugins/commands/upload/command.rb new file mode 100644 index 000000000..a6a408990 --- /dev/null +++ b/plugins/commands/upload/command.rb @@ -0,0 +1,236 @@ +require 'optparse' +require "rubygems/package" + +module VagrantPlugins + module CommandUpload + class Command < Vagrant.plugin("2", :command) + + VALID_COMPRESS_TYPES = [:tgz, :zip].freeze + + def self.synopsis + "upload to machine via communicator" + end + + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant upload [options] [destination] [name|id]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("-t", "--temporary", "Upload source to temporary directory") do |t| + options[:temporary] = t + end + + o.on("-c", "--compress", "Use gzip compression for upload") do |c| + options[:compress] = c + end + + o.on("-C", "--compression-type=TYPE", "Type of compression to use (#{VALID_COMPRESS_TYPES.join(", ")})") do |c| + options[:compression_type] = c.to_sym + options[:compress] = true + end + end + + argv = parse_options(opts) + return if !argv + + case argv.size + when 3 + source, destination, guest = argv + when 2, 1 + source = argv[0] + if @env.active_machines.map(&:first).map(&:to_s).include?(argv[1]) + guest = argv[1] + else + destination = argv[1] + end + else + raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp + end + + # NOTE: We do this to handle paths on Windows like: "..\space dir\" + # because the final separater acts to escape the quote and ends up + # in the source value. + source = source.sub(/["']$/, "") + destination ||= File.basename(source) + + if File.file?(source) + type = :file + elsif File.directory?(source) + type = :directory + else + raise Vagrant::Errors::UploadSourceMissing, + source: source + end + + with_target_vms(guest, single_target: true) do |machine| + if options[:temporary] + if !machine.guest.capability?(:create_tmp_path) + raise Vagrant::Errors::UploadMissingTempCapability + end + extension = File.extname(source) if type == :file + destination = machine.guest.capability(:create_tmp_path, type: type, extension: extension) + end + + if options[:compress] + compression_setup!(machine, options) + @env.ui.info(I18n.t("vagrant.commands.upload.compress", + source: source, + type: options[:compression_type] + )) + destination_decompressed = destination + destination = machine.guest.capability(:create_tmp_path, type: :file, extension: ".#{options[:compression_type]}") + source_display = source + source = options[:compression_type] == :zip ? compress_source_zip(source) : compress_source_tgz(source) + end + + @env.ui.info(I18n.t("vagrant.commands.upload.start", + source: source, + destination: destination + )) + + # If the source is a directory, attach a `/.` to the end so we + # upload the contents to the destination instead of within a + # folder at the destination + if File.directory?(source) && !source.end_with?(".") + upload_source = File.join(source, ".") + else + upload_source = source + end + + machine.communicate.upload(upload_source, destination) + + if options[:compress] + @env.ui.info(I18n.t("vagrant.commands.upload.decompress", + destination: destination_decompressed, + type: options[:compression_type] + )) + machine.guest.capability(options[:decompression_method], destination, destination_decompressed, type: type) + destination = destination_decompressed + FileUtils.rm(source) + source = source_display + end + end + + @env.ui.info(I18n.t("vagrant.commands.upload.complete", + source: source, + destination: destination + )) + + # Success, exit status 0 + 0 + end + + # Setup compression options and validate host and guest have capability + # to handle compression + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [Hash] options Command options + def compression_setup!(machine, options) + if !options[:compression_type] + if machine.guest.capability_host_chain.first[0] == :windows + options[:compression_type] = :zip + else + options[:compression_type] = :tgz + end + end + if !VALID_COMPRESS_TYPES.include?(options[:compression_type]) + raise Vagrant::Errors::UploadInvalidCompressionType, + type: options[:compression_type], + valid_types: VALID_COMPRESS_TYPES.join(", ") + end + options[:decompression_method] = "decompress_#{options[:compression_type]}".to_sym + if !machine.guest.capability?(options[:decompression_method]) + raise Vagrant::Errors::UploadMissingExtractCapability, + type: options[:compression_type] + end + end + + # Compress path using zip into temporary file + # + # @param [String] path Path to compress + # @return [String] path to compressed file + def compress_source_zip(path) + require "zip" + zipfile = Tempfile.create(["vagrant", ".zip"]) + zipfile.close + if File.file?(path) + source_items = [path] + else + source_items = Dir.glob(File.join(path, "**", "**", "*")) + end + c_dir = nil + Zip::File.open(zipfile.path, Zip::File::CREATE) do |zip| + source_items.each do |source_item| + next if File.directory?(source_item) + trim_item = source_item.sub(path, "").sub(%r{^[/\\]}, "") + dirname = File.dirname(trim_item) + zip.mkdir dirname if c_dir != dirname + c_dir = dirname + zip.get_output_stream(trim_item) do |f| + source_file = File.open(source_item, "rb") + while data = source_file.read(2048) + f.write(data) + end + end + end + end + zipfile.path + end + + # Compress path using tar and gzip into temporary file + # + # @param [String] path Path to compress + # @return [String] path to compressed file + def compress_source_tgz(path) + tarfile = Tempfile.create(["vagrant", ".tar"]) + tarfile.close + tarfile = File.open(tarfile.path, "wb+") + tgzfile = Tempfile.create(["vagrant", ".tgz"]) + tgzfile.close + tgzfile = File.open(tgzfile.path, "wb") + tar = Gem::Package::TarWriter.new(tarfile) + tgz = Zlib::GzipWriter.new(tgzfile) + if File.file?(path) + tar.add_file(File.basename(path), File.stat(path).mode) do |io| + File.open(path, "rb") do |file| + while bytes = file.read(4096) + io.write(bytes) + end + end + end + else + Dir.glob(File.join(path, "**/**/*")).each do |item| + rel_path = item.sub(path, "") + item_mode = File.stat(item).mode + + if File.directory?(item) + tar.mkdir(rel_path, item_mode) + else + tar.add_file(rel_path, item_mode) do |io| + File.open(item, "rb") do |file| + while bytes = file.read(4096) + io.write(bytes) + end + end + end + end + end + end + tar.close + tarfile.rewind + while bytes = tarfile.read(4096) + tgz.write bytes + end + tgz.close + tgzfile.close + tarfile.close + File.delete(tarfile.path) + tgzfile.path + end + end + end +end diff --git a/plugins/commands/upload/plugin.rb b/plugins/commands/upload/plugin.rb new file mode 100644 index 000000000..ec1c64804 --- /dev/null +++ b/plugins/commands/upload/plugin.rb @@ -0,0 +1,17 @@ +require "vagrant" + +module VagrantPlugins + module CommandUpload + class Plugin < Vagrant.plugin("2") + name "upload command" + description <<-DESC + The `upload` command uploads files to guest via communicator + DESC + + command("upload") do + require File.expand_path("../command", __FILE__) + Command + end + end + end +end diff --git a/plugins/commands/winrm/command.rb b/plugins/commands/winrm/command.rb new file mode 100644 index 000000000..dd19109db --- /dev/null +++ b/plugins/commands/winrm/command.rb @@ -0,0 +1,69 @@ +require 'optparse' + +require "vagrant/util/safe_puts" + +module VagrantPlugins + module CommandWinRM + class Command < Vagrant.plugin("2", :command) + include Vagrant::Util::SafePuts + + def self.synopsis + "executes commands on a machine via WinRM" + end + + def execute + options = { + command: [], + shell: :powershell + } + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant winrm [options] [name|id]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("-c", "--command COMMAND", "Execute a WinRM command directly") do |c| + options[:command] << c + end + + o.on("-e", "--elevated", "Run with elevated credentials") do |e| + options[:elevated] = true + end + + o.on("-s", "--shell SHELL", [:powershell, :cmd], "Use specified shell (powershell, cmd)") do |s| + options[:shell] = s + end + end + + argv = parse_options(opts) + return if !argv + + with_target_vms(argv, single_target: true) do |machine| + if machine.config.vm.communicator != :winrm + raise Vagrant::Errors::WinRMInvalidCommunicator + end + + opts = { + shell: options[:shell], + elevated: !!options[:elevated] + } + + options[:command].each do |cmd| + begin + machine.communicate.execute(cmd, opts) do |type, data| + io = type == :stderr ? $stderr : $stdout + safe_puts(data, io: io, printer: :print) + end + rescue VagrantPlugins::CommunicatorWinRM::Errors::WinRMBadExitStatus + return 1 + end + end + end + + # Success, exit status 0 + 0 + end + end + end +end diff --git a/plugins/commands/winrm/plugin.rb b/plugins/commands/winrm/plugin.rb new file mode 100644 index 000000000..5a578ff4f --- /dev/null +++ b/plugins/commands/winrm/plugin.rb @@ -0,0 +1,17 @@ +require "vagrant" + +module VagrantPlugins + module CommandWinRM + class Plugin < Vagrant.plugin("2") + name "winrm command" + description <<-DESC + The `winrm` command executes commands on a machine via WinRM + DESC + + command("winrm") do + require File.expand_path("../command", __FILE__) + Command + end + end + end +end diff --git a/plugins/commands/winrm_config/command.rb b/plugins/commands/winrm_config/command.rb new file mode 100644 index 000000000..995eccf55 --- /dev/null +++ b/plugins/commands/winrm_config/command.rb @@ -0,0 +1,125 @@ +require 'optparse' +require "vagrant/util/safe_puts" +require_relative "../../communicators/winrm/helper" + +module VagrantPlugins + module CommandWinRMConfig + class Command < Vagrant.plugin("2", :command) + include Vagrant::Util::SafePuts + + def self.synopsis + "outputs WinRM configuration to connect to the machine" + end + + def convert_win_paths(paths) + paths.map! { |path| Vagrant::Util::Platform.format_windows_path(path, :disable_unc) } + end + + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant winrm-config [options] [name|id]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("--host NAME", "Name the host for the config") do |h| + options[:host] = h + end + end + + argv = parse_options(opts) + return if !argv + + with_target_vms(argv) do |machine| + winrm_info = CommunicatorWinRM::Helper.winrm_info(machine) + raise Vagrant::Errors::WinRMNotRead if winrm_info.nil? + + rdp_info = get_rdp_info(machine) || {} + + variables = { + host_key: options[:host] || machine.name || "vagrant", + rdp_host: rdp_info[:host] || winrm_info[:host], + rdp_port: rdp_info[:port], + rdp_user: rdp_info[:username], + rdp_pass: rdp_info[:password], + winrm_host: winrm_info[:host], + winrm_port: winrm_info[:port], + winrm_user: machine.config.winrm.username, + winrm_password: machine.config.winrm.password + } + + template = "commands/winrm_config/config" + config = Vagrant::Util::TemplateRenderer.render(template, variables) + machine.ui.machine("winrm-config", config) + safe_puts(config) + safe_puts + end + + # Success, exit status 0 + 0 + end + + protected + + # Generate RDP information for machine + # + # @param [Vagrant::Machine] machine Guest machine + # @return [Hash, nil] + def get_rdp_info(machine) + rdp_info = {} + if machine.provider.capability?(:rdp_info) + rdp_info = machine.provider.capability(:rdp_info) + rdp_info ||= {} + end + + ssh_info = machine.ssh_info || {} + + if !rdp_info[:username] + username = ssh_info[:username] + if machine.config.vm.communicator == :winrm + username = machine.config.winrm.username + end + rdp_info[:username] = username + end + + if !rdp_info[:password] + password = ssh_info[:password] + if machine.config.vm.communicator == :winrm + password = machine.config.winrm.password + end + rdp_info[:password] = password + end + + rdp_info[:host] ||= ssh_info[:host] + rdp_info[:port] ||= machine.config.rdp.port + rdp_info[:username] ||= machine.config.rdp.username + + if rdp_info[:host] == "127.0.0.1" + # We need to find a forwarded port... + search_port = machine.config.rdp.search_port + ports = nil + if machine.provider.capability?(:forwarded_ports) + ports = machine.provider.capability(:forwarded_ports) + else + ports = {}.tap do |result| + machine.config.vm.networks.each do |type, netopts| + next if type != :forwarded_port + next if !netopts[:host] + result[netopts[:host]] = netopts[:guest] + end + end + end + + ports = ports.invert + port = ports[search_port] + rdp_info[:port] = port + return nil if !port + end + + rdp_info + end + end + end +end diff --git a/plugins/commands/winrm_config/plugin.rb b/plugins/commands/winrm_config/plugin.rb new file mode 100644 index 000000000..2b0b8e37e --- /dev/null +++ b/plugins/commands/winrm_config/plugin.rb @@ -0,0 +1,17 @@ +require "vagrant" + +module VagrantPlugins + module CommandWinRMConfig + class Plugin < Vagrant.plugin("2") + name "winrm-config command" + description <<-DESC + The `winrm-config` command dumps WinRM configuration information + DESC + + command("winrm-config") do + require File.expand_path("../command", __FILE__) + Command + end + end + end +end diff --git a/plugins/guests/bsd/cap/file_system.rb b/plugins/guests/bsd/cap/file_system.rb new file mode 100644 index 000000000..c14f7b376 --- /dev/null +++ b/plugins/guests/bsd/cap/file_system.rb @@ -0,0 +1,77 @@ +module VagrantPlugins + module GuestBSD + module Cap + class FileSystem + # Create a temporary file or directory on the guest + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [Hash] opts Path options + # @return [String] path to temporary file or directory + def self.create_tmp_path(machine, opts) + template = "vagrant" + cmd = ["mktemp"] + if opts[:type] == :directory + cmd << "-d" + end + cmd << "-t" + cmd << template + tmp_path = "" + machine.communicate.execute(cmd.join(" ")) do |type, data| + if type == :stdout + tmp_path << data + end + end + tmp_path.strip + end + + # Decompress tgz file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_tgz(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + if opts[:type] == :directory + cmds << "mkdir -p '#{destination}'" + else + cmds << "mkdir -p '#{File.dirname(destination)}'" + end + cmds += [ + "tar -C '#{extract_dir}' -xzf '#{compressed_file}'", + "mv '#{extract_dir}'/* '#{destination}'", + "rm -f '#{compressed_file}'", + "rm -rf '#{extract_dir}'" + ] + cmds.each{ |cmd| comm.execute(cmd) } + true + end + + # Decompress zip file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_zip(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + if opts[:type] == :directory + cmds << "mkdir -p '#{destination}'" + else + cmds << "mkdir -p '#{File.dirname(destination)}'" + end + cmds += [ + "unzip '#{compressed_file}' -d '#{extract_dir}'", + "mv '#{extract_dir}'/* '#{destination}'", + "rm -f '#{compressed_file}'", + "rm -rf '#{extract_dir}'" + ] + cmds.each{ |cmd| comm.execute(cmd) } + true + end + end + end + end +end diff --git a/plugins/guests/bsd/plugin.rb b/plugins/guests/bsd/plugin.rb index 368765cdf..9a726cfdc 100644 --- a/plugins/guests/bsd/plugin.rb +++ b/plugins/guests/bsd/plugin.rb @@ -11,6 +11,21 @@ module VagrantPlugins Guest end + guest_capability(:bsd, :create_tmp_path) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:bsd, :decompress_tgz) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:bsd, :decompress_zip) do + require_relative "cap/file_system" + Cap::FileSystem + end + guest_capability(:bsd, :halt) do require_relative "cap/halt" Cap::Halt diff --git a/plugins/guests/linux/cap/file_system.rb b/plugins/guests/linux/cap/file_system.rb new file mode 100644 index 000000000..abff30e37 --- /dev/null +++ b/plugins/guests/linux/cap/file_system.rb @@ -0,0 +1,79 @@ +module VagrantPlugins + module GuestLinux + module Cap + class FileSystem + # Create a temporary file or directory on the guest + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [Hash] opts Path options + # @return [String] path to temporary file or directory + def self.create_tmp_path(machine, opts) + template = "vagrant-XXXXXX" + if opts[:extension] + template << opts[:extension].to_s + end + cmd = ["mktemp", "--tmpdir"] + if opts[:type] == :directory + cmd << "-d" + end + cmd << template + tmp_path = "" + machine.communicate.execute(cmd.join(" ")) do |type, data| + if type == :stdout + tmp_path << data + end + end + tmp_path.strip + end + + # Decompress tgz file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_tgz(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + if opts[:type] == :directory + cmds << "mkdir -p '#{destination}'" + else + cmds << "mkdir -p '#{File.dirname(destination)}'" + end + cmds += [ + "tar -C '#{extract_dir}' -xzf '#{compressed_file}'", + "mv '#{extract_dir}'/* '#{destination}'", + "rm -f '#{compressed_file}'", + "rm -rf '#{extract_dir}'" + ] + cmds.each{ |cmd| comm.execute(cmd) } + true + end + + # Decompress zip file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_zip(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + if opts[:type] == :directory + cmds << "mkdir -p '#{destination}'" + else + cmds << "mkdir -p '#{File.dirname(destination)}'" + end + cmds += [ + "unzip '#{compressed_file}' -d '#{extract_dir}'", + "mv '#{extract_dir}'/* '#{destination}'", + "rm -f '#{compressed_file}'", + "rm -rf '#{extract_dir}'" + ] + cmds.each{ |cmd| comm.execute(cmd) } + true + end + end + end + end +end diff --git a/plugins/guests/linux/plugin.rb b/plugins/guests/linux/plugin.rb index cb0a53c19..e4752571e 100644 --- a/plugins/guests/linux/plugin.rb +++ b/plugins/guests/linux/plugin.rb @@ -16,6 +16,21 @@ module VagrantPlugins Cap::ChooseAddressableIPAddr end + guest_capability(:linux, :create_tmp_path) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:linux, :decompress_tgz) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:linux, :decompress_zip) do + require_relative "cap/file_system" + Cap::FileSystem + end + guest_capability(:linux, :halt) do require_relative "cap/halt" Cap::Halt diff --git a/plugins/guests/solaris/cap/file_system.rb b/plugins/guests/solaris/cap/file_system.rb new file mode 100644 index 000000000..d697c9db8 --- /dev/null +++ b/plugins/guests/solaris/cap/file_system.rb @@ -0,0 +1,77 @@ +module VagrantPlugins + module GuestSolaris + module Cap + class FileSystem + # Create a temporary file or directory on the guest + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [Hash] opts Path options + # @return [String] path to temporary file or directory + def self.create_tmp_path(machine, opts) + template = "vagrant-XXXXXX" + cmd = ["mktemp"] + if opts[:type] == :directory + cmd << "-d" + end + cmd << "-t" + cmd << template + tmp_path = "" + machine.communicate.execute(cmd.join(" ")) do |type, data| + if type == :stdout + tmp_path << data + end + end + tmp_path.strip + end + + # Decompress tgz file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_tgz(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + if opts[:type] == :directory + cmds << "mkdir -p '#{destination}'" + else + cmds << "mkdir -p '#{File.dirname(destination)}'" + end + cmds += [ + "tar xzf '#{compressed_file}' -C '#{extract_dir}'", + "mv '#{extract_dir}'/* '#{destination}'", + "rm -f '#{compressed_file}'", + "rm -rf '#{extract_dir}'" + ] + cmds.each{ |cmd| comm.execute(cmd) } + true + end + + # Decompress zip file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_zip(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + if opts[:type] == :directory + cmds << "mkdir -p '#{destination}'" + else + cmds << "mkdir -p '#{File.dirname(destination)}'" + end + cmds += [ + "unzip '#{compressed_file}' -d '#{extract_dir}'", + "mv '#{extract_dir}'/* '#{destination}'", + "rm -f '#{compressed_file}'", + "rm -rf '#{extract_dir}'" + ] + cmds.each{ |cmd| comm.execute(cmd) } + true + end + end + end + end +end diff --git a/plugins/guests/solaris/plugin.rb b/plugins/guests/solaris/plugin.rb index 83c373100..b8c16df12 100644 --- a/plugins/guests/solaris/plugin.rb +++ b/plugins/guests/solaris/plugin.rb @@ -16,6 +16,21 @@ module VagrantPlugins Guest end + guest_capability(:solaris, :create_tmp_path) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:solaris, :decompress_tgz) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:solaris, :decompress_zip) do + require_relative "cap/file_system" + Cap::FileSystem + end + guest_capability(:solaris, :change_host_name) do require_relative "cap/change_host_name" Cap::ChangeHostName diff --git a/plugins/guests/windows/cap/file_system.rb b/plugins/guests/windows/cap/file_system.rb new file mode 100644 index 000000000..85a7d4a2f --- /dev/null +++ b/plugins/guests/windows/cap/file_system.rb @@ -0,0 +1,65 @@ +module VagrantPlugins + module GuestWindows + module Cap + class FileSystem + # Create a temporary file or directory on the guest + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [Hash] opts Path options + # @return [String] path to temporary file or directory + def self.create_tmp_path(machine, opts) + comm = machine.communicate + path = "" + cmd = "Write-Output ([System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), " \ + "[System.IO.Path]::GetRandomFileName())) | Out-String -Width 2048" + comm.execute(cmd, shell: :powershell) do |type, data| + if type == :stdout + path << data + end + end + path.strip! + if opts[:extension] + path << opts[:extension].to_s + end + if opts[:type] == :directory + comm.execute("[System.IO.Directory]::CreateDirectory('#{path}')") + end + path + end + + # Decompress zip file on guest to given location + # + # @param [Vagrant::Machine] machine Vagrant guest machine + # @param [String] compressed_file Path to compressed file on guest + # @param [String] destination Path for decompressed files on guest + def self.decompress_zip(machine, compressed_file, destination, opts={}) + comm = machine.communicate + extract_dir = create_tmp_path(machine, type: :directory) + cmds = [] + destination = destination.tr("/", "\\") + if opts[:type] == :directory + cmds << "New-Item -ItemType Directory -Force -Path \"#{destination}\"" + else + d_parts = destination.split("\\") + d_parts.pop + parent_dir = d_parts.join("\\") + "\\" + cmds << "New-Item -ItemType Directory -Force -Path \"#{parent_dir}\"" + end + cmd = "$f = \"#{compressed_file}\"; $d = \"#{extract_dir}\"; " + cmd << '$s = New-Object -ComObject "Shell.Application"; $z = $s.NameSpace($f); ' + cmd << 'foreach($i in $z.items()){ $s.Namespace($d).copyhere($i); }' + cmds << cmd + cmds += [ + "Move-Item -Force -Path \"#{extract_dir}\\*\" -Destination \"#{destination}\\\"", + "Remove-Item -Path \"#{compressed_file}\" -Force", + "Remove-Item -Path \"#{extract_dir}\" -Recurse -Force" + ] + cmds.each do |cmd| + comm.execute(cmd, shell: :powershell) + end + true + end + end + end + end +end diff --git a/plugins/guests/windows/plugin.rb b/plugins/guests/windows/plugin.rb index f7a71e959..76124f6b4 100644 --- a/plugins/guests/windows/plugin.rb +++ b/plugins/guests/windows/plugin.rb @@ -34,6 +34,16 @@ module VagrantPlugins Cap::Halt end + guest_capability(:windows, :create_tmp_path) do + require_relative "cap/file_system" + Cap::FileSystem + end + + guest_capability(:windows, :decompress_zip) do + require_relative "cap/file_system" + Cap::FileSystem + end + guest_capability(:windows, :mount_virtualbox_shared_folder) do require_relative "cap/mount_shared_folder" Cap::MountSharedFolder diff --git a/templates/commands/winrm_config/config.erb b/templates/commands/winrm_config/config.erb new file mode 100644 index 000000000..9eb269eb1 --- /dev/null +++ b/templates/commands/winrm_config/config.erb @@ -0,0 +1,11 @@ +Host <%= host_key %> + HostName <%= winrm_host %> + User <%= winrm_user %> + Password <%= winrm_password %> + Port <%= winrm_port %> + <% if rdp_port -%> + RDPHostName <%= rdp_host %> + RDPPort <%= rdp_port %> + RDPUser <%= rdp_user %> + RDPPassword <%= rdp_pass %> + <% end -%> diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 530f8f5c3..020805632 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1462,6 +1462,8 @@ en: your current setup. Please verify you have all the proper prerequisites for using this shared folder type and try again. + test_key: |- + test value triggers_run_fail: |- Trigger run failed triggers_guest_not_running: |- @@ -1489,6 +1491,24 @@ en: '%{provider}', but this provider doesn't support this action. This is probably a bug in either the provider or the plugin calling this action, and should be reported. + upload_invalid_compression_type: |- + The compression type requested for upload (`%{type}`) is not a + supported value. Try uploading again using a valid compression + type. + + Supported types: %{valid_types} + upload_missing_extract_capability: |- + The guest does not provide extraction capability for the requested + compression type (`%{type}`). Try a different compression type or + upload without compression. + upload_missing_temp_capability: |- + The guest does not provide temporary path capabilities. Please + try the upload again without requesting a temporary path. + upload_source_missing: |- + The source path provided for upload cannot be found. Please validate + the source location for upload an try again. + + Source Path: %{source} vagrantfile_exists: |- `Vagrantfile` already exists in this directory. Remove it before running `vagrant init`. @@ -1685,7 +1705,9 @@ en: vm_not_running: |- VM must be running to open SSH connection. Run `vagrant up` to start the virtual machine. - test_key: "test value" + winrm_invalid_communicator: |- + The winrm command requires a WinRM communicator to be used when connecting + to the guest. Please update your configuration and try the command again. wsl_vagrant_version_mismatch: |- Vagrant cannot currently enable access to manage machines within the Windows environment because the version of Vagrant installed on Windows does not @@ -2025,6 +2047,18 @@ en: up: upping: |- Bringing machine '%{name}' up with '%{provider}' provider... + upload: + compress: |- + Compressing %{source} to %{type} for upload... + complete: |- + Upload has completed successfully! + + Source: %{source} + Destination: %{destination} + decompress: |- + Decompressing %{type} upload to %{destination}... + start: |- + Uploading %{source} to %{destination} validate: success: |- Vagrantfile validated successfully. diff --git a/test/unit/plugins/commands/upload/command_test.rb b/test/unit/plugins/commands/upload/command_test.rb new file mode 100644 index 000000000..c8077d710 --- /dev/null +++ b/test/unit/plugins/commands/upload/command_test.rb @@ -0,0 +1,242 @@ +require File.expand_path("../../../../base", __FILE__) +require Vagrant.source_root.join("plugins/commands/upload/command") + +describe VagrantPlugins::CommandUpload::Command do + include_context "unit" + include_context "virtualbox" + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:guest) { double("guest", capability_host_chain: guest_chain) } + let(:host) { double("host", capability_host_chain: host_chain) } + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:communicator) { double("communicator") } + let(:host_chain){ [[]] } + let(:guest_chain){ [[]] } + + let(:argv) { [] } + let(:config) { + double("config") + } + + subject { described_class.new(argv, iso_env) } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + allow(machine).to receive(:config).and_return(config) + allow(subject).to receive(:with_target_vms) + end + + it "should raise invalid usage error by default" do + expect { subject.execute }.to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "when three arguments are provided" do + let(:argv) { ["source", "destination", "guest"] } + + before { allow(File).to receive(:file?).and_return(true) } + + it "should use third argument as name of guest" do + expect(subject).to receive(:with_target_vms).with("guest", any_args) + subject.execute + end + + it "should use first argument as source and second as destination" do + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + expect(communicator).to receive(:upload).with("source", "destination") + subject.execute + end + end + + context "when two arguments are provided" do + let(:argv) { ["source", "ambiguous"] } + let(:active_machines) { [] } + + before do + allow(File).to receive(:file?).and_return(true) + allow(iso_env).to receive(:active_machines).and_return(active_machines) + end + + it "should use the the second argument as destination when not a machine name" do + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + expect(communicator).to receive(:upload).with("source", "ambiguous") + subject.execute + end + + context "when active machine matches second argument" do + let(:active_machines) { [["ambiguous"]] } + + it "should use second argument as guest name and generate destination" do + allow(subject).to receive(:with_target_vms).with("ambiguous", any_args) { |&block| block.call machine } + expect(communicator).to receive(:upload).with("source", "source") + subject.execute + end + end + end + + context "when single argument is provided" do + let(:argv) { ["item"] } + + before do + allow(File).to receive(:file?) + allow(File).to receive(:directory?) + end + + it "should check if source is a file" do + expect(File).to receive(:file?).with("item").and_return(true) + subject.execute + end + + it "should check if source is a directory" do + expect(File).to receive(:directory?).with("item").and_return(true) + subject.execute + end + + it "should raise error if source is not a directory or file" do + expect { subject.execute }.to raise_error(Vagrant::Errors::UploadSourceMissing) + end + + context "when source path ends with double quote" do + let(:argv) { [".\\item\""] } + + it "should remove trailing quote" do + expect(File).to receive(:file?).with(".\\item").and_return(true) + subject.execute + end + end + + context "when source path ends with single quote" do + let(:argv) { ['.\item\''] } + + it "should remove trailing quote" do + expect(File).to receive(:file?).with(".\\item").and_return(true) + subject.execute + end + end + + context "when source is a directory" do + before do + allow(File).to receive(:file?).with("item").and_return(false) + allow(File).to receive(:directory?).with("item").and_return(true) + allow(communicator).to receive(:upload) + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + end + + it "should upload the directory" do + expect(communicator).to receive(:upload).with(/item/, anything) + subject.execute + end + + it "should append separator and dot to source path for upload" do + expect(communicator).to receive(:upload).with("item/.", anything) + subject.execute + end + end + + context "when source is a file" do + before do + allow(File).to receive(:file?).with("item").and_return(true) + allow(communicator).to receive(:upload) + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + allow(machine).to receive(:guest).and_return(guest) + allow(machine).to receive(:env).and_return(double("env", host: host)) + allow(guest).to receive(:capability?).and_return(true) + allow(guest).to receive(:capability) + end + + it "should upload the file" do + expect(communicator).to receive(:upload).with("item", anything) + subject.execute + end + + it "should name destination after the source" do + expect(communicator).to receive(:upload).with("item", "item") + subject.execute + end + + context "when temporary option is set" do + before { argv << "-t" } + + it "should get temporary path for destination from guest" do + expect(guest).to receive(:capability).with(:create_tmp_path, any_args).and_return("TMP_PATH") + expect(communicator).to receive(:upload).with("item", "TMP_PATH") + subject.execute + end + end + + context "when compress option is set" do + before do + argv << "-c" + allow(guest).to receive(:capability).with(:create_tmp_path, any_args).and_return("TMP") + allow(subject).to receive(:compress_source_zip).and_return("COMPRESS_SOURCE") + allow(subject).to receive(:compress_source_tgz).and_return("COMPRESS_SOURCE") + allow(FileUtils).to receive(:rm).with("COMPRESS_SOURCE") + end + + it "should check for guest decompression" do + expect(guest).to receive(:capability?).with(:decompress_tgz).and_return(true) + subject.execute + end + + it "should compress the source" do + expect(subject).to receive(:compress_source_tgz).with("item").and_return("COMPRESS_SOURCE") + subject.execute + end + + it "should cleanup compressed source" do + expect(FileUtils).to receive(:rm).with("COMPRESS_SOURCE") + subject.execute + end + + it "should upload the compressed source" do + expect(communicator).to receive(:upload).with("COMPRESS_SOURCE", anything) + subject.execute + end + + it "should upload compressed source to temporary location" do + expect(communicator).to receive(:upload).with("COMPRESS_SOURCE", "TMP") + subject.execute + end + + it "should have guest decompress file" do + expect(guest).to receive(:capability).with(:decompress_tgz, "TMP", any_args) + subject.execute + end + + it "should provide destination for guest decompression of file" do + expect(guest).to receive(:capability).with(:decompress_tgz, "TMP", "item", any_args) + subject.execute + end + + it "should provide the destination type for guest decompression" do + expect(guest).to receive(:capability).with(:decompress_tgz, "TMP", "item", hash_including(type: :file)) + subject.execute + end + + context "with compression type set to zip" do + before { argv << "-C" << "zip" } + + it "should test guest for decompression capability" do + expect(guest).to receive(:capability?).with(:decompress_zip).and_return(true) + subject.execute + end + + it "should compress source using zip" do + expect(subject).to receive(:compress_source_zip) + subject.execute + end + + it "should have guest decompress file using zip" do + expect(guest).to receive(:capability).with(:decompress_zip, any_args) + subject.execute + end + end + end + end + end +end diff --git a/test/unit/plugins/commands/winrm/command_test.rb b/test/unit/plugins/commands/winrm/command_test.rb new file mode 100644 index 000000000..5481296b8 --- /dev/null +++ b/test/unit/plugins/commands/winrm/command_test.rb @@ -0,0 +1,95 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/winrm/command") + +describe VagrantPlugins::CommandWinRM::Command do + include_context "unit" + include_context "virtualbox" + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + 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(:communicator) { double("communicator") } + + let(:argv) { [] } + let(:config) { + double("config", + vm: double("vm-config", communicator: communicator_name)) + } + let(:communicator_name) { :winrm } + + subject { described_class.new(argv, iso_env) } + + before do + allow(machine).to receive(:communicate).and_return(communicator) + allow(machine).to receive(:config).and_return(config) + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + allow(communicator).to receive(:execute) + end + + it "should exit successfully when no command is provided" do + expect(subject.execute).to eq(0) + end + + context "with command provided" do + let(:argv){ ["-c", "dir"] } + + it "should execute the command via the communicator" do + expect(communicator).to receive(:execute).with("dir", any_args) + subject.execute + end + + it "should execute with default shell" do + expect(communicator).to receive(:execute).with(anything, hash_including(shell: :powershell)) + subject.execute + end + + it "should execute without elevated privileges" do + expect(communicator).to receive(:execute).with(anything, hash_including(elevated: false)) + subject.execute + end + + context "with elevated option set" do + let(:argv) { ["-c", "dir", "-e"] } + + it "should execute with elevated privileges" do + expect(communicator).to receive(:execute).with(anything, hash_including(elevated: true)) + subject.execute + end + end + + context "with shell option set" do + let(:argv) { ["-c", "dir", "-s", "cmd"] } + + it "should execute with custom shell" do + expect(communicator).to receive(:execute).with(anything, hash_including(shell: :cmd)) + subject.execute + end + end + end + + context "with multiple command provided" do + let(:argv) { ["-c", "dir", "-c", "dir2"] } + + it "should execute multiple commands via the communicator" do + expect(communicator).to receive(:execute).with("dir", any_args) + expect(communicator).to receive(:execute).with("dir2", any_args) + subject.execute + end + end + + context "with invalid communicator configured" do + let(:communicator_name) { :ssh } + + it "should raise an error" do + expect { subject.execute }.to raise_error(Vagrant::Errors::WinRMInvalidCommunicator) + end + end +end diff --git a/test/unit/plugins/commands/winrm_config/command_test.rb b/test/unit/plugins/commands/winrm_config/command_test.rb new file mode 100644 index 000000000..95d3be329 --- /dev/null +++ b/test/unit/plugins/commands/winrm_config/command_test.rb @@ -0,0 +1,143 @@ +require File.expand_path("../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/winrm_config/command") +require Vagrant.source_root.join("plugins/communicators/winrm/helper") + +describe VagrantPlugins::CommandWinRMConfig::Command do + include_context "unit" + include_context "virtualbox" + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + 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(:argv) { [] } + let(:winrm_info) {{ + host: "testhost.vagrant.dev", + port: 1234 + }} + let(:config) { + double("config", + winrm: double("winrm-config", username: "vagrant", password: "vagrant"), + rdp: rdp_config, + vm: double("vm-config", communicator: :winrm) + ) + } + + let(:rdp_config) { double("rdp-config", port: 9876) } + + subject { described_class.new(argv, iso_env) } + + before do + allow(machine).to receive(:config).and_return(config) + allow(VagrantPlugins::CommunicatorWinRM::Helper).to receive(:winrm_info).and_return(winrm_info) + allow(subject).to receive(:with_target_vms) { |&block| block.call machine } + end + + describe "execute" do + it "prints out the winrm config for the given machine" do + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + + expect(output).to eq(<<-WINRMCONFIG) +Host #{machine.name} + HostName testhost.vagrant.dev + User vagrant + Password vagrant + Port 1234 + RDPHostName testhost.vagrant.dev + RDPPort 9876 + RDPUser vagrant + RDPPassword vagrant + WINRMCONFIG + end + + context "with host option set" do + let(:argv) { ["--host", "my-host"]} + + it "should use custom host name in config output" do + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + + expect(output).to eq(<<-WINRMCONFIG) +Host my-host + HostName testhost.vagrant.dev + User vagrant + Password vagrant + Port 1234 + RDPHostName testhost.vagrant.dev + RDPPort 9876 + RDPUser vagrant + RDPPassword vagrant + WINRMCONFIG + end + end + + context "when no RDP port is configured" do + let(:rdp_config) { double("rdp-config", port: nil) } + + it "should not include any RDP configuration information" do + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + expect(output).not_to include("RDP") + end + end + + context "when provider has rdp_info capability" do + let(:rdp_info) { + {host: "provider-host", port: 9999, username: "pvagrant", password: "pvagrant"} + } + + before do + allow(machine.provider).to receive(:capability?).with(:rdp_info).and_return(true) + allow(machine.provider).to receive(:capability).with(:rdp_info).and_return(rdp_info) + end + + it "should use provider RDP information" do + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + expect(output).to include("RDPPort 9999") + expect(output).to include("RDPHostName provider-host") + expect(output).to include("RDPUser pvagrant") + expect(output).to include("RDPPassword pvagrant") + end + + context "when provider rdp_info does not include host" do + before { rdp_info[:host] = nil } + + it "should use winrm host" do + output = "" + allow(subject).to receive(:safe_puts) do |data| + output += data if data + end + + subject.execute + expect(output).to include("RDPHostName testhost.vagrant.dev") + end + end + end + end +end diff --git a/test/unit/plugins/guests/bsd/cap/file_system_test.rb b/test/unit/plugins/guests/bsd/cap/file_system_test.rb new file mode 100644 index 000000000..7e7d528d6 --- /dev/null +++ b/test/unit/plugins/guests/bsd/cap/file_system_test.rb @@ -0,0 +1,127 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestBSD::Cap::FileSystem" do + let(:caps) do + VagrantPlugins::GuestBSD::Plugin + .components + .guest_capabilities[:bsd] + end + + let(:machine) { double("machine", communicate: comm) } + let(:comm) { double("comm") } + + before { allow(comm).to receive(:execute) } + + describe ".create_tmp_path" do + let(:cap) { caps.get(:create_tmp_path) } + let(:opts) { {} } + + it "should generate path on guest" do + expect(comm).to receive(:execute).with(/mktemp/) + cap.create_tmp_path(machine, opts) + end + + it "should capture path generated on guest" do + expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, "TMP_PATH") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + it "should strip newlines on path" do + expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, "TMP_PATH\n") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + context "when type is a directory" do + before { opts[:type] = :directory } + + it "should create guest path as a directory" do + expect(comm).to receive(:execute).with(/-d/) + cap.create_tmp_path(machine, opts) + end + end + end + + describe ".decompress_tgz" do + let(:cap) { caps.get(:decompress_tgz) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_tgz(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with tar" do + expect(comm).to receive(:execute).with(/tar/) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/rm .*#{comp}/) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/rm .*TMP_DIR/) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/mkdir -p .*to'/) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/mkdir -p .*destination'/) + end + end + end + + describe ".decompress_zip" do + let(:cap) { caps.get(:decompress_zip) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_zip(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with zip" do + expect(comm).to receive(:execute).with(/zip/) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/rm .*#{comp}/) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/rm .*TMP_DIR/) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/mkdir -p .*to'/) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/mkdir -p .*destination'/) + end + end + end +end diff --git a/test/unit/plugins/guests/linux/cap/file_system_test.rb b/test/unit/plugins/guests/linux/cap/file_system_test.rb new file mode 100644 index 000000000..aa7c0340c --- /dev/null +++ b/test/unit/plugins/guests/linux/cap/file_system_test.rb @@ -0,0 +1,127 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestLinux::Cap::FileSystem" do + let(:caps) do + VagrantPlugins::GuestLinux::Plugin + .components + .guest_capabilities[:linux] + end + + let(:machine) { double("machine", communicate: comm) } + let(:comm) { double("comm") } + + before { allow(comm).to receive(:execute) } + + describe ".create_tmp_path" do + let(:cap) { caps.get(:create_tmp_path) } + let(:opts) { {} } + + it "should generate path on guest" do + expect(comm).to receive(:execute).with(/mktemp/) + cap.create_tmp_path(machine, opts) + end + + it "should capture path generated on guest" do + expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, "TMP_PATH") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + it "should strip newlines on path" do + expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, "TMP_PATH\n") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + context "when type is a directory" do + before { opts[:type] = :directory } + + it "should create guest path as a directory" do + expect(comm).to receive(:execute).with(/-d/) + cap.create_tmp_path(machine, opts) + end + end + end + + describe ".decompress_tgz" do + let(:cap) { caps.get(:decompress_tgz) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_tgz(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with tar" do + expect(comm).to receive(:execute).with(/tar/) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/rm .*#{comp}/) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/rm .*TMP_DIR/) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/mkdir -p .*to'/) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/mkdir -p .*destination'/) + end + end + end + + describe ".decompress_zip" do + let(:cap) { caps.get(:decompress_zip) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_zip(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with zip" do + expect(comm).to receive(:execute).with(/zip/) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/rm .*#{comp}/) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/rm .*TMP_DIR/) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/mkdir -p .*to'/) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/mkdir -p .*destination'/) + end + end + end +end diff --git a/test/unit/plugins/guests/solaris/cap/file_system_test.rb b/test/unit/plugins/guests/solaris/cap/file_system_test.rb new file mode 100644 index 000000000..6f7288aad --- /dev/null +++ b/test/unit/plugins/guests/solaris/cap/file_system_test.rb @@ -0,0 +1,127 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestSolaris::Cap::FileSystem" do + let(:caps) do + VagrantPlugins::GuestSolaris::Plugin + .components + .guest_capabilities[:solaris] + end + + let(:machine) { double("machine", communicate: comm) } + let(:comm) { double("comm") } + + before { allow(comm).to receive(:execute) } + + describe ".create_tmp_path" do + let(:cap) { caps.get(:create_tmp_path) } + let(:opts) { {} } + + it "should generate path on guest" do + expect(comm).to receive(:execute).with(/mktemp/) + cap.create_tmp_path(machine, opts) + end + + it "should capture path generated on guest" do + expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, "TMP_PATH") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + it "should strip newlines on path" do + expect(comm).to receive(:execute).with(/mktemp/).and_yield(:stdout, "TMP_PATH\n") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + context "when type is a directory" do + before { opts[:type] = :directory } + + it "should create guest path as a directory" do + expect(comm).to receive(:execute).with(/-d/) + cap.create_tmp_path(machine, opts) + end + end + end + + describe ".decompress_tgz" do + let(:cap) { caps.get(:decompress_tgz) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_tgz(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with tar" do + expect(comm).to receive(:execute).with(/tar/) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/rm .*#{comp}/) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/rm .*TMP_DIR/) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/mkdir -p .*to'/) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/mkdir -p .*destination'/) + end + end + end + + describe ".decompress_zip" do + let(:cap) { caps.get(:decompress_zip) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_zip(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with zip" do + expect(comm).to receive(:execute).with(/zip/) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/rm .*#{comp}/) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/rm .*TMP_DIR/) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/mkdir -p .*to'/) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/mkdir -p .*destination'/) + end + end + end +end diff --git a/test/unit/plugins/guests/windows/cap/file_system_test.rb b/test/unit/plugins/guests/windows/cap/file_system_test.rb new file mode 100644 index 000000000..ca093399e --- /dev/null +++ b/test/unit/plugins/guests/windows/cap/file_system_test.rb @@ -0,0 +1,85 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestWindows::Cap::FileSystem" do + let(:caps) do + VagrantPlugins::GuestWindows::Plugin + .components + .guest_capabilities[:windows] + end + + let(:machine) { double("machine", communicate: comm) } + let(:comm) { double("comm") } + + before { allow(comm).to receive(:execute) } + + describe ".create_tmp_path" do + let(:cap) { caps.get(:create_tmp_path) } + let(:opts) { {} } + + it "should generate path on guest" do + expect(comm).to receive(:execute).with(/GetRandomFileName/, any_args) + cap.create_tmp_path(machine, opts) + end + + it "should capture path generated on guest" do + expect(comm).to receive(:execute).with(/Write-Output/, any_args).and_yield(:stdout, "TMP_PATH") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + it "should strip newlines on path" do + expect(comm).to receive(:execute).with(/Write-Output/, any_args).and_yield(:stdout, "TMP_PATH\r\n") + expect(cap.create_tmp_path(machine, opts)).to eq("TMP_PATH") + end + + context "when type is a directory" do + before { opts[:type] = :directory } + + it "should create guest path as a directory" do + expect(comm).to receive(:execute).with(/CreateDirectory/, any_args) + cap.create_tmp_path(machine, opts) + end + end + end + + describe ".decompress_zip" do + let(:cap) { caps.get(:decompress_zip) } + let(:comp) { "compressed_file" } + let(:dest) { "path/to/destination" } + let(:opts) { {} } + + before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") } + after{ cap.decompress_zip(machine, comp, dest, opts) } + + it "should create temporary directory for extraction" do + expect(cap).to receive(:create_tmp_path) + end + + it "should extract file with zip" do + expect(comm).to receive(:execute).with(/copyhere/, any_args) + end + + it "should extract file to temporary directory" do + expect(comm).to receive(:execute).with(/TMP_DIR/, any_args) + end + + it "should remove compressed file from guest" do + expect(comm).to receive(:execute).with(/Remove-Item .*#{comp}/, any_args) + end + + it "should remove extraction directory from guest" do + expect(comm).to receive(:execute).with(/Remove-Item .*TMP_DIR/, any_args) + end + + it "should create parent directories for destination" do + expect(comm).to receive(:execute).with(/New-Item .*Directory .*to\\"/, any_args) + end + + context "when type is directory" do + before { opts[:type] = :directory } + + it "should create destination directory" do + expect(comm).to receive(:execute).with(/New-Item .*Directory .*destination"/, any_args) + end + end + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index d67d1e83c..0d804e446 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -27,6 +27,7 @@ Gem::Specification.new do |s| s.add_dependency "net-scp", "~> 1.2.0" s.add_dependency "rb-kqueue", "~> 0.2.0" s.add_dependency "rest-client", ">= 1.6.0", "< 3.0" + s.add_dependency "rubyzip", "~> 1.2.2" s.add_dependency "wdm", "~> 0.1.0" s.add_dependency "winrm", "~> 2.1" s.add_dependency "winrm-fs", "~> 1.0" diff --git a/website/source/docs/cli/upload.html.md b/website/source/docs/cli/upload.html.md new file mode 100644 index 000000000..42898da6d --- /dev/null +++ b/website/source/docs/cli/upload.html.md @@ -0,0 +1,30 @@ +--- +layout: "docs" +page_title: "vagrant upload - Command-Line Interface" +sidebar_current: "cli-upload" +description: |- + The "vagrant upload" command is used to upload files from the host + to a guest machine. +--- + +# Upload + +**Command: `vagrant upload source [destination] [name|id]`** + +This command uploads files and directories from the host to the guest +machine. + +## Options + +* `destination` - Path on the guest machine to upload file or directory. + +* `source` - Path to file or diretory on host to upload to guest machine. + +* `--compress` - Compress the file or directory before uploading to guest machine. + +* `--compression-type type` - Type of compression to use when compressing + file or directory for upload. Defaults to `zip` for Windows guests and + `tgz` for non-Windows guests. Valid values: `tgz`, `zip`. + +* `--temporary` - Create a temporary location on the guest machine and upload + files to that location. diff --git a/website/source/docs/cli/winrm.html.md b/website/source/docs/cli/winrm.html.md new file mode 100644 index 000000000..67eb0ff3b --- /dev/null +++ b/website/source/docs/cli/winrm.html.md @@ -0,0 +1,26 @@ +--- +layout: "docs" +page_title: "vagrant winrm - Command-Line Interface" +sidebar_current: "cli-winrm" +description: |- + The "vagrant winrm" command is used execute commands on the remote + machine via WinRM +--- + +# WinRM + +**Command: `vagrant winrm [name|id]`** + +Executes the provided command(s) on the guest machine using the +WinRM communicator. Commands are provided with the `--command` +option and multiple `--command` flags may be provided for +executing multiple commands. This command requires the guest +machine to be configured with the WinRM communicator. + +## Options + +* `--command COMMAND` - Command to execute. + +* `--elevated` - Run command(s) with elevated credentials. + +* `--shell (cmd|powershell)` - Shell to execute commands. Defaults to `powershell`. diff --git a/website/source/docs/cli/winrm_config.html.md b/website/source/docs/cli/winrm_config.html.md new file mode 100644 index 000000000..76a44b2cd --- /dev/null +++ b/website/source/docs/cli/winrm_config.html.md @@ -0,0 +1,20 @@ +--- +layout: "docs" +page_title: "vagrant winrm-config - Command-Line Interface" +sidebar_current: "cli-winrm_config" +description: |- + The "vagrant winrm-config" command is used to output the WinRM configuration + used to connect to the guest machine. +--- + +# WinRM Config + +**Command: `vagrant winrm-config [name|id]`** + +This will output the WinRM configuration used for connecting to +the guest machine. It requires that the WinRM communicator is in +use for the guest machine. + +## Options + +* `--host NAME` - Name of the host for the outputted configuration.