Merge 31e577aeb3
into b231abe114
This commit is contained in:
commit
4160535fdd
|
@ -388,6 +388,10 @@ module Vagrant
|
|||
error_key(:hyperv_virtualbox_error)
|
||||
end
|
||||
|
||||
class HypervNotSupported < VagrantError
|
||||
error_key(:hyperv_not_supported)
|
||||
end
|
||||
|
||||
class ForwardPortAdapterNotFound < VagrantError
|
||||
error_key(:forward_port_adapter_not_found)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
module Vagrant
|
||||
module Util
|
||||
module HypervDaemons
|
||||
HYPERV_DAEMON_SERVICES = %i[kvp vss fcopy]
|
||||
|
||||
def hyperv_daemons_activate(machine)
|
||||
result = HYPERV_DAEMON_SERVICES.map do |service|
|
||||
hyperv_daemon_activate machine, service
|
||||
end
|
||||
result.all?
|
||||
end
|
||||
|
||||
def hyperv_daemon_activate(machine, service)
|
||||
comm = machine.communicate
|
||||
service_name = hyperv_service_name(machine, service)
|
||||
return false unless comm.test("systemctl enable #{service_name}",
|
||||
sudo: true)
|
||||
|
||||
return false unless comm.test("systemctl restart #{service_name}",
|
||||
sudo: true)
|
||||
|
||||
hyperv_daemon_running machine, service
|
||||
end
|
||||
|
||||
def hyperv_daemons_running(machine)
|
||||
result = HYPERV_DAEMON_SERVICES.map do |service|
|
||||
hyperv_daemon_running machine, service
|
||||
end
|
||||
result.all?
|
||||
end
|
||||
|
||||
def hyperv_daemon_running(machine, service)
|
||||
comm = machine.communicate
|
||||
service_name = hyperv_service_name(machine, service)
|
||||
comm.test("systemctl -q is-active #{service_name}")
|
||||
end
|
||||
|
||||
def hyperv_daemons_installed(machine)
|
||||
result = HYPERV_DAEMON_SERVICES.map do |service|
|
||||
hyperv_daemon_installed machine, service
|
||||
end
|
||||
result.all?
|
||||
end
|
||||
|
||||
def hyperv_daemon_installed(machine, service)
|
||||
comm = machine.communicate
|
||||
daemon_name = hyperv_daemon_name(service)
|
||||
comm.test("which #{daemon_name}")
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def hyperv_service_name(machine, service)
|
||||
is_deb = machine.communicate.test("which apt-get")
|
||||
separator = is_deb ? '-' : '_'
|
||||
['hv', service.to_s, 'daemon'].join separator
|
||||
end
|
||||
|
||||
def hyperv_daemon_name(service)
|
||||
['hv', service.to_s, 'daemon'].join '_'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -487,11 +487,11 @@ module Vagrant
|
|||
#
|
||||
# @param [Pathname, String] path Path to convert
|
||||
# @return [String]
|
||||
def windows_path(path)
|
||||
def windows_path(path, *args)
|
||||
path = cygwin_windows_path(path)
|
||||
path = wsl_to_windows_path(path)
|
||||
if windows? || wsl?
|
||||
path = windows_unc_path(path)
|
||||
path = windows_unc_path(path) if !args.include?(:disable_unc)
|
||||
end
|
||||
path
|
||||
end
|
||||
|
@ -651,6 +651,17 @@ module Vagrant
|
|||
@_wsl_windows_appdata_local
|
||||
end
|
||||
|
||||
# Fetch the Windows temp directory
|
||||
#
|
||||
# @return [String, Nil]
|
||||
def windows_temp
|
||||
if !@_windows_temp
|
||||
result = Vagrant::Util::PowerShell.execute_cmd("(Get-Item Env:TEMP).Value")
|
||||
@_windows_temp = result.gsub("\"", "").strip
|
||||
end
|
||||
@_windows_temp
|
||||
end
|
||||
|
||||
# Confirm Vagrant versions installed within the WSL and the Windows system
|
||||
# are the same. Raise error if they do not match.
|
||||
def wsl_validate_matching_vagrant_versions!
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
module VagrantPlugins
|
||||
module GuestArch
|
||||
module Cap
|
||||
class HypervDaemons
|
||||
def self.hyperv_daemons_installed(machine)
|
||||
machine.communicate.test("pacman -Q hyperv")
|
||||
end
|
||||
|
||||
def self.hyperv_daemons_install(machine)
|
||||
comm = machine.communicate
|
||||
comm.sudo <<-EOH.gsub(/^ {12}/, "")
|
||||
pacman --noconfirm -Syy &&
|
||||
pacman --noconfirm -S hyperv
|
||||
EOH
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,6 +45,16 @@ module VagrantPlugins
|
|||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
guest_capability(:arch, :hyperv_daemons_installed) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:arch, :hyperv_daemons_install) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ module VagrantPlugins
|
|||
"rm -f '#{compressed_file}'",
|
||||
"rm -rf '#{extract_dir}'"
|
||||
]
|
||||
cmds.each{ |cmd| comm.execute(cmd) }
|
||||
cmds.each{ |cmd| comm.execute(cmd, sudo: opts[:sudo] || false) }
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -68,7 +68,7 @@ module VagrantPlugins
|
|||
"rm -f '#{compressed_file}'",
|
||||
"rm -rf '#{extract_dir}'"
|
||||
]
|
||||
cmds.each{ |cmd| comm.execute(cmd) }
|
||||
cmds.each{ |cmd| comm.execute(cmd, sudo: opts[:sudo] || false) }
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
module VagrantPlugins
|
||||
module GuestDebian
|
||||
module Cap
|
||||
class HypervDaemons
|
||||
def self.hyperv_daemons_installed(machine)
|
||||
machine.communicate.test('dpkg -s linux-cloud-tools-common', sudo: true)
|
||||
end
|
||||
|
||||
def self.hyperv_daemons_install(machine)
|
||||
comm = machine.communicate
|
||||
comm.sudo <<-EOH.gsub(/^ {12}/, "")
|
||||
DEBIAN_FRONTEND=noninteractive apt-get update -y &&
|
||||
apt-get install -y -o Dpkg::Options::="--force-confdef" linux-cloud-tools-common
|
||||
EOH
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,6 +35,16 @@ module VagrantPlugins
|
|||
require_relative "cap/smb"
|
||||
Cap::SMB
|
||||
end
|
||||
|
||||
guest_capability(:debian, :hyperv_daemons_installed) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:debian, :hyperv_daemons_install) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,7 +46,7 @@ module VagrantPlugins
|
|||
"rm -f '#{compressed_file}'",
|
||||
"rm -rf '#{extract_dir}'"
|
||||
]
|
||||
cmds.each{ |cmd| comm.execute(cmd) }
|
||||
cmds.each{ |cmd| comm.execute(cmd, sudo: opts[:sudo] || false) }
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -70,9 +70,45 @@ module VagrantPlugins
|
|||
"rm -f '#{compressed_file}'",
|
||||
"rm -rf '#{extract_dir}'"
|
||||
]
|
||||
cmds.each{ |cmd| comm.execute(cmd) }
|
||||
cmds.each{ |cmd| comm.execute(cmd, sudo: opts[:sudo] || false) }
|
||||
true
|
||||
end
|
||||
|
||||
# Create directories at given locations on guest
|
||||
#
|
||||
# @param [Vagrant::Machine] machine Vagrant guest machine
|
||||
# @param [array] paths to create on guest
|
||||
def self.create_directories(machine, dirs, opts={})
|
||||
return [] if dirs.empty?
|
||||
|
||||
remote_fn = create_tmp_path(machine, {})
|
||||
tmp = Tempfile.new('hv_dirs')
|
||||
begin
|
||||
tmp.binmode
|
||||
tmp.write dirs.join("\n") + "\n"
|
||||
tmp.close
|
||||
machine.communicate.upload(tmp.path, remote_fn)
|
||||
ensure
|
||||
tmp.close
|
||||
tmp.unlink
|
||||
end
|
||||
created_paths = []
|
||||
machine.communicate.execute("bash -c 'while IFS= read -r line
|
||||
do
|
||||
if [ ! -z \"${line}\" ] && [ ! -d \"${line}\" ]; then
|
||||
if [ -f \"${line}\" ]; then
|
||||
rm \"${line}\"
|
||||
fi
|
||||
mkdir -p -v \"${line}\" || true
|
||||
fi
|
||||
done < #{remote_fn}'
|
||||
", sudo: opts[:sudo] || false) do |type, data|
|
||||
if type == :stdout && /^.*\'(?<dir>.*)\'/ =~ data
|
||||
created_paths << dir.strip
|
||||
end
|
||||
end
|
||||
created_paths
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
require "vagrant/util/hyperv_daemons"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestLinux
|
||||
module Cap
|
||||
class HypervDaemons
|
||||
extend Vagrant::Util::HypervDaemons
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -117,10 +117,30 @@ module VagrantPlugins
|
|||
Cap::RSync
|
||||
end
|
||||
|
||||
guest_capability(:linux, :create_directories) do
|
||||
require_relative "cap/file_system"
|
||||
Cap::FileSystem
|
||||
end
|
||||
|
||||
guest_capability(:linux, :unmount_virtualbox_shared_folder) do
|
||||
require_relative "cap/mount_virtualbox_shared_folder"
|
||||
Cap::MountVirtualBoxSharedFolder
|
||||
end
|
||||
|
||||
guest_capability(:linux, :hyperv_daemons_running) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:linux, :hyperv_daemons_activate) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:linux, :hyperv_daemons_installed) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
module VagrantPlugins
|
||||
module GuestRedHat
|
||||
module Cap
|
||||
class HypervDaemons
|
||||
def self.hyperv_daemons_installed(machine)
|
||||
machine.communicate.test("rpm -q hyperv-daemons")
|
||||
end
|
||||
|
||||
def self.hyperv_daemons_install(machine)
|
||||
comm = machine.communicate
|
||||
comm.sudo <<-EOH.gsub(/^ {12}/, "")
|
||||
if command -v dnf; then
|
||||
dnf -y install hyperv-daemons
|
||||
else
|
||||
yum -y install hyperv-daemons
|
||||
fi
|
||||
EOH
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -40,6 +40,16 @@ module VagrantPlugins
|
|||
require_relative "cap/rsync"
|
||||
Cap::RSync
|
||||
end
|
||||
|
||||
guest_capability(:redhat, :hyperv_daemons_installed) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:redhat, :hyperv_daemons_install) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ module VagrantPlugins
|
|||
"rm -f '#{compressed_file}'",
|
||||
"rm -rf '#{extract_dir}'"
|
||||
]
|
||||
cmds.each{ |cmd| comm.execute(cmd) }
|
||||
cmds.each{ |cmd| comm.execute(cmd, sudo: opts[:sudo] || false) }
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -68,7 +68,7 @@ module VagrantPlugins
|
|||
"rm -f '#{compressed_file}'",
|
||||
"rm -rf '#{extract_dir}'"
|
||||
]
|
||||
cmds.each{ |cmd| comm.execute(cmd) }
|
||||
cmds.each{ |cmd| comm.execute(cmd, sudo: opts[:sudo] || false) }
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'json'
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestWindows
|
||||
module Cap
|
||||
|
@ -59,6 +61,47 @@ module VagrantPlugins
|
|||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Create directories at given locations on guest
|
||||
#
|
||||
# @param [Vagrant::Machine] machine Vagrant guest machine
|
||||
# @param [array] paths to create on guest
|
||||
def self.create_directories(machine, dirs, opts={})
|
||||
return [] if dirs.empty?
|
||||
|
||||
remote_fn = create_tmp_path(machine, {})
|
||||
tmp = Tempfile.new('hv_dirs')
|
||||
begin
|
||||
tmp.write dirs.join("\n") + "\n"
|
||||
tmp.close
|
||||
machine.communicate.upload(tmp.path, remote_fn)
|
||||
ensure
|
||||
tmp.close
|
||||
tmp.unlink
|
||||
end
|
||||
created_paths = []
|
||||
cmd = <<-EOH.gsub(/^ {6}/, "")
|
||||
$files = Get-Content #{remote_fn}
|
||||
foreach ($file in $files) {
|
||||
if (-Not (Test-Path($file))) {
|
||||
ConvertTo-Json (New-Item $file -type directory -Force | Select-Object FullName)
|
||||
} else {
|
||||
if (-Not ((Get-Item $file) -is [System.IO.DirectoryInfo])) {
|
||||
# Remove the file
|
||||
Remove-Item -Path $file -Force
|
||||
ConvertTo-Json (New-Item $file -type directory -Force | Select-Object FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
EOH
|
||||
machine.communicate.execute(cmd, shell: :powershell) do |type, data|
|
||||
if type == :stdout
|
||||
obj = JSON.parse(data)
|
||||
created_paths << obj["FullName"].strip unless obj["FullName"].nil?
|
||||
end
|
||||
end
|
||||
created_paths
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
require "vagrant/util/hyperv_daemons"
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestWindows
|
||||
module Cap
|
||||
class HypervDaemons
|
||||
HYPERV_DAEMON_SERVICES = %i[kvp vss fcopy]
|
||||
HYPERV_DAEMON_SERVICE_NAMES = {kvp: "vmickvpexchange", vss: "vmicvss", fcopy: "vmicguestinterface" }
|
||||
|
||||
# https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=netframework-4.8
|
||||
STOPPED = 1
|
||||
START_PENDING = 2
|
||||
STOP_PENDING = 3
|
||||
RUNNING = 4
|
||||
CONTINUE_PENDING = 5
|
||||
PAUSE_PENDING = 6
|
||||
PAUSED = 7
|
||||
|
||||
MANUAL_MODE = 3
|
||||
DISABLED_MODE = 4
|
||||
|
||||
def self.hyperv_daemons_activate(machine)
|
||||
result = HYPERV_DAEMON_SERVICES.map do |service|
|
||||
hyperv_daemon_activate machine, service
|
||||
end
|
||||
result.all?
|
||||
end
|
||||
|
||||
def self.hyperv_daemon_activate(machine, service)
|
||||
comm = machine.communicate
|
||||
service_name = hyperv_service_name(machine, service)
|
||||
daemon_service = service_info(comm, service_name)
|
||||
return false if daemon_service.nil?
|
||||
|
||||
if daemon_service["StartType"] == DISABLED_MODE
|
||||
return false unless enable_service(comm, service_name)
|
||||
end
|
||||
|
||||
return false unless restart_service(comm, service_name)
|
||||
hyperv_daemon_running machine, service
|
||||
end
|
||||
|
||||
def self.hyperv_daemons_running(machine)
|
||||
result = HYPERV_DAEMON_SERVICES.map do |service|
|
||||
hyperv_daemon_running machine, service.to_sym
|
||||
end
|
||||
result.all?
|
||||
end
|
||||
|
||||
def self.hyperv_daemon_running(machine, service)
|
||||
comm = machine.communicate
|
||||
service_name = hyperv_service_name(machine, service)
|
||||
daemon_service = service_info(comm, service_name)
|
||||
return daemon_service["Status"] == RUNNING unless daemon_service.nil?
|
||||
false
|
||||
end
|
||||
|
||||
def self.hyperv_daemons_installed(machine)
|
||||
result = HYPERV_DAEMON_SERVICES.map do |service|
|
||||
hyperv_daemon_installed machine, service.to_sym
|
||||
end
|
||||
result.all?
|
||||
end
|
||||
|
||||
def self.hyperv_daemon_installed(machine, service)
|
||||
# Windows guest should have Hyper-V service installed
|
||||
true
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.service_info(comm, service)
|
||||
cmd = "ConvertTo-Json (Get-Service -Name #{service})"
|
||||
result = []
|
||||
comm.execute(cmd, shell: :powershell) do |type, data|
|
||||
if type == :stdout
|
||||
result << JSON.parse(data)
|
||||
end
|
||||
end
|
||||
result[0] || {}
|
||||
end
|
||||
|
||||
def self.restart_service(comm, service)
|
||||
cmd = "Restart-Service -Name #{service} -Force"
|
||||
comm.execute(cmd, shell: :powershell)
|
||||
true
|
||||
end
|
||||
|
||||
def self.enable_service(comm, service)
|
||||
cmd = "Set-Service -Name #{service} -StartupType #{MANUAL_MODE}"
|
||||
comm.execute(cmd, shell: :powershell)
|
||||
true
|
||||
end
|
||||
|
||||
def self.hyperv_service_name(machine, service)
|
||||
hyperv_daemon_name(service)
|
||||
end
|
||||
|
||||
def self.hyperv_daemon_name(service)
|
||||
HYPERV_DAEMON_SERVICE_NAMES[service]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,6 +44,11 @@ module VagrantPlugins
|
|||
Cap::FileSystem
|
||||
end
|
||||
|
||||
guest_capability(:windows, :create_directories) 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
|
||||
|
@ -99,6 +104,21 @@ module VagrantPlugins
|
|||
Cap::PublicKey
|
||||
end
|
||||
|
||||
guest_capability(:windows, :hyperv_daemons_running) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:windows, :hyperv_daemons_activate) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
guest_capability(:windows, :hyperv_daemons_installed) do
|
||||
require_relative "cap/hyperv_daemons"
|
||||
Cap::HypervDaemons
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.init!
|
||||
|
|
|
@ -161,6 +161,15 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
# This is the action that is called to sync folders to a running
|
||||
# machine without a reboot.
|
||||
def self.action_sync_folders
|
||||
Vagrant::Action::Builder.new.tap do |b|
|
||||
b.use SyncedFolderCleanup
|
||||
b.use SyncedFolders
|
||||
end
|
||||
end
|
||||
|
||||
def self.action_up
|
||||
Vagrant::Action::Builder.new.tap do |b|
|
||||
b.use CheckEnabled
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
require 'optparse'
|
||||
|
||||
require "vagrant/action/builtin/mixin_synced_folders"
|
||||
|
||||
require_relative "../sync_helper"
|
||||
|
||||
module VagrantPlugins
|
||||
module HyperV
|
||||
module Command
|
||||
class Sync < Vagrant.plugin("2", :command)
|
||||
include Vagrant::Action::Builtin::MixinSyncedFolders
|
||||
|
||||
def self.synopsis
|
||||
"syncs synced folders to remote machine"
|
||||
end
|
||||
|
||||
def execute
|
||||
opts = OptionParser.new do |o|
|
||||
o.banner = "Usage: vagrant sync [vm-name]"
|
||||
o.separator ""
|
||||
o.separator "This command forces any synced folders to sync."
|
||||
o.separator "Hyper-V currently does not provider an automatic sync so a manual command is used."
|
||||
o.separator ""
|
||||
end
|
||||
|
||||
# Parse the options and return if we don't have any target.
|
||||
argv = parse_options(opts)
|
||||
return if !argv
|
||||
|
||||
# Go through each machine and perform the rsync
|
||||
error = false
|
||||
with_target_vms(argv) do |machine|
|
||||
if !machine.communicate.ready?
|
||||
machine.ui.error(I18n.t("vagrant_hyperv.sync.communicator_not_ready"))
|
||||
error = true
|
||||
next
|
||||
end
|
||||
|
||||
# Determine the rsync synced folders for this machine
|
||||
folders = synced_folders(machine, cached: true)[:hyperv]
|
||||
next if !folders || folders.empty?
|
||||
|
||||
# short guestpaths first, so we don't step on ourselves
|
||||
folders = folders.sort_by do |id, data|
|
||||
if data[:guestpath]
|
||||
data[:guestpath].length
|
||||
else
|
||||
# A long enough path to just do this at the end.
|
||||
10000
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate the owner and group
|
||||
ssh_info = machine.ssh_info
|
||||
|
||||
# Sync them!
|
||||
folders.each do |id, data|
|
||||
next unless data[:guestpath]
|
||||
|
||||
SyncHelper.sync_single machine, ssh_info, data
|
||||
end
|
||||
end
|
||||
|
||||
return error ? 1 : 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,196 @@
|
|||
require "log4r"
|
||||
require 'optparse'
|
||||
require "thread"
|
||||
|
||||
require "vagrant/action/builtin/mixin_synced_folders"
|
||||
require "vagrant/util/busy"
|
||||
require "vagrant/util/platform"
|
||||
|
||||
require "listen"
|
||||
|
||||
require_relative '../sync_helper'
|
||||
|
||||
module VagrantPlugins
|
||||
module HyperV
|
||||
module Command
|
||||
class SyncAuto < Vagrant.plugin("2", :command)
|
||||
include Vagrant::Action::Builtin::MixinSyncedFolders
|
||||
|
||||
def self.synopsis
|
||||
"syncs synced folders automatically when files change"
|
||||
end
|
||||
|
||||
def execute
|
||||
@logger = Log4r::Logger.new("vagrant::commands::sync-auto")
|
||||
|
||||
options = {}
|
||||
opts = OptionParser.new do |o|
|
||||
o.banner = "Usage: vagrant sync-auto [vm-name]"
|
||||
o.separator ""
|
||||
o.separator "Options:"
|
||||
o.separator ""
|
||||
|
||||
o.on("--[no-]poll", "Force polling filesystem (slow)") do |poll|
|
||||
options[:poll] = poll
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the options and return if we don't have any target.
|
||||
argv = parse_options(opts)
|
||||
return if !argv
|
||||
|
||||
# Build up the paths that we need to listen to.
|
||||
paths = {}
|
||||
ignores = []
|
||||
with_target_vms(argv) do |machine|
|
||||
next if machine.state.id == :not_created
|
||||
|
||||
cached = synced_folders(machine, cached: true)
|
||||
fresh = synced_folders(machine)
|
||||
diff = synced_folders_diff(cached, fresh)
|
||||
if !diff[:added].empty?
|
||||
machine.ui.warn(I18n.t("vagrant_hyperv.sync.auto_new_folders"))
|
||||
end
|
||||
|
||||
folders = cached[:hyperv]
|
||||
next if !folders || folders.empty?
|
||||
|
||||
# Get the SSH info for this machine so we can do an initial
|
||||
# sync to the VM.
|
||||
ssh_info = machine.ssh_info
|
||||
if ssh_info
|
||||
machine.ui.info(I18n.t("vagrant_hyperv.sync.auto_initial"))
|
||||
folders.each do |id, data|
|
||||
next unless data[:guestpath]
|
||||
|
||||
SyncHelper.sync_single machine, ssh_info, data
|
||||
end
|
||||
end
|
||||
|
||||
folders.each do |id, folder_opts|
|
||||
# If we marked this folder to not auto sync, then
|
||||
# don't do it.
|
||||
next if folder_opts.key?(:auto) && !folder_opts[:auto]
|
||||
|
||||
hostpath = folder_opts[:hostpath]
|
||||
expanded_hostpath = HyperV::SyncHelper.expand_path(hostpath, machine.env.root_path)
|
||||
paths[expanded_hostpath] ||= []
|
||||
paths[expanded_hostpath] << {
|
||||
id: id,
|
||||
machine: machine,
|
||||
opts: folder_opts,
|
||||
}
|
||||
|
||||
excludes = HyperV::SyncHelper.expand_excludes(hostpath, folder_opts[:exclude])
|
||||
excludes[:dirs].each do |dir|
|
||||
dir = dir.gsub File.join(expanded_hostpath, ''), ''
|
||||
dir = dir.gsub '.', '\.'
|
||||
ignores << Regexp.new("#{dir}.*")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Exit immediately if there is nothing to watch
|
||||
if paths.empty?
|
||||
@env.ui.info(I18n.t("vagrant_hyperv.sync.auto_no_paths"))
|
||||
return 1
|
||||
end
|
||||
|
||||
# Output to the user what paths we'll be watching
|
||||
paths.keys.sort.each do |path|
|
||||
paths[path].each do |path_opts|
|
||||
path_opts[:machine].ui.info(I18n.t(
|
||||
"vagrant_hyperv.sync.auto_path",
|
||||
path: path.to_s,
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
@logger.info("Listening to paths: #{paths.keys.sort.inspect}")
|
||||
@logger.info("Ignoring #{ignores.length} paths:")
|
||||
ignores.each do |ignore|
|
||||
@logger.info(" -- #{ignore.to_s}")
|
||||
end
|
||||
@logger.info("Listening via: #{Listen::Adapter.select.inspect}")
|
||||
callback = method(:callback).to_proc.curry[paths]
|
||||
listopts = { ignore: ignores, force_polling: !!options[:poll] }
|
||||
listener = Listen.to(*paths.keys, listopts, &callback)
|
||||
|
||||
# Create the callback that lets us know when we've been interrupted
|
||||
queue = Queue.new
|
||||
callback = lambda do
|
||||
# This needs to execute in another thread because Thread
|
||||
# synchronization can't happen in a trap context.
|
||||
Thread.new { queue << true }
|
||||
end
|
||||
|
||||
# Run the listener in a busy block so that we can cleanly
|
||||
# exit once we receive an interrupt.
|
||||
Vagrant::Util::Busy.busy(callback) do
|
||||
listener.start
|
||||
queue.pop
|
||||
listener.stop if listener.state != :stopped
|
||||
end
|
||||
|
||||
0
|
||||
end
|
||||
|
||||
# This is the callback that is called when any changes happen
|
||||
def callback(paths, modified, added, removed)
|
||||
@logger.info("File change callback called!")
|
||||
@logger.info(" - Modified: #{modified.inspect}")
|
||||
@logger.info(" - Added: #{added.inspect}")
|
||||
@logger.info(" - Removed: #{removed.inspect}")
|
||||
|
||||
tosync = []
|
||||
paths.each do |hostpath, folders|
|
||||
# Find out if this path should be synced
|
||||
found = catch(:done) do
|
||||
[modified, added, removed].each do |changed|
|
||||
changed.each do |listenpath|
|
||||
throw :done, true if listenpath.start_with?(hostpath)
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure to return false if all else fails so that we
|
||||
# don't sync to this machine.
|
||||
false
|
||||
end
|
||||
|
||||
# If it should be synced, store it for later
|
||||
tosync << folders if found
|
||||
end
|
||||
|
||||
# Sync all the folders that need to be synced
|
||||
tosync.each do |folders|
|
||||
folders.each do |opts|
|
||||
# Reload so we get the latest ID
|
||||
opts[:machine].reload
|
||||
if !opts[:machine].id || opts[:machine].id == ""
|
||||
# Skip since we can't get SSH info without an ID
|
||||
next
|
||||
end
|
||||
|
||||
ssh_info = opts[:machine].ssh_info
|
||||
begin
|
||||
start = Time.now
|
||||
SyncHelper.sync_single opts[:machine], ssh_info, opts[:opts]
|
||||
finish = Time.now
|
||||
@logger.info("Time spent in sync: #{finish - start} (in seconds)")
|
||||
rescue Vagrant::Errors::MachineGuestNotReady
|
||||
# Error communicating to the machine, probably a reload or
|
||||
# halt is happening. Just notify the user but don't fail out.
|
||||
opts[:machine].ui.error(I18n.t(
|
||||
"vagrant_hyperv.sync.communicator_not_ready_callback"))
|
||||
rescue Vagrant::Errors::VagrantError => e
|
||||
# Error auto sync folder, show an error
|
||||
opts[:machine].ui.error(I18n.t(
|
||||
"vagrant_hyperv.sync.auto_sync_error", message: e.to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,10 @@
|
|||
require 'fileutils'
|
||||
require 'find'
|
||||
require "json"
|
||||
require 'tempfile'
|
||||
|
||||
require "vagrant/util/powershell"
|
||||
require "vagrant/util/subprocess"
|
||||
|
||||
require_relative "plugin"
|
||||
|
||||
|
@ -120,7 +124,7 @@ module VagrantPlugins
|
|||
#
|
||||
# @return [nil]
|
||||
def start
|
||||
execute(:start_vm, VmId: vm_id )
|
||||
execute(:start_vm, VmId: vm_id)
|
||||
end
|
||||
|
||||
# Stop the VM
|
||||
|
@ -217,6 +221,36 @@ module VagrantPlugins
|
|||
execute(:set_name, VMID: vm_id, VMName: vmname)
|
||||
end
|
||||
|
||||
# Sync files
|
||||
#
|
||||
# @return [nil]
|
||||
def sync_files(vm_id, dirs, files, is_win_guest: true)
|
||||
network_info = read_guest_ip
|
||||
guest_ip = network_info["ip"]
|
||||
suffix = (0...8).map { ('a'..'z').to_a[rand(26)] }.join
|
||||
windows_temp = Vagrant::Util::Platform.windows_temp
|
||||
if Vagrant::Util::Platform.wsl?
|
||||
process = Vagrant::Util::Subprocess.execute(
|
||||
"wslpath", "-u", "-a", windows_temp)
|
||||
windows_temp = process.stdout.chomp if process.exit_code == 0
|
||||
end
|
||||
fn = File.join(windows_temp, ".hv_sync_files_#{suffix}")
|
||||
begin
|
||||
File.open(fn, 'w') do |file|
|
||||
file.write files.to_json
|
||||
end
|
||||
win_path = Vagrant::Util::Platform.windows_path(
|
||||
fn, :disable_unc)
|
||||
status = execute(:sync_files,
|
||||
vm_id: vm_id,
|
||||
guest_ip: guest_ip,
|
||||
file_list: win_path)
|
||||
status
|
||||
ensure
|
||||
FileUtils.rm_f(fn)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def execute_powershell(path, options, &block)
|
||||
|
|
|
@ -16,6 +16,11 @@ module VagrantPlugins
|
|||
Provider
|
||||
end
|
||||
|
||||
synced_folder(:hyperv) do
|
||||
require File.expand_path("../synced_folder", __FILE__)
|
||||
SyncedFolder
|
||||
end
|
||||
|
||||
config(:hyperv, :provider) do
|
||||
require_relative "config"
|
||||
init!
|
||||
|
@ -32,6 +37,16 @@ module VagrantPlugins
|
|||
Cap::SnapshotList
|
||||
end
|
||||
|
||||
command("hyperv-sync", primary: false) do
|
||||
require_relative "command/sync"
|
||||
Command::Sync
|
||||
end
|
||||
|
||||
command("hyperv-sync-auto", primary: false) do
|
||||
require_relative "command/sync_auto"
|
||||
Command::SyncAuto
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.init!
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
#Requires -Modules VagrantMessages
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) 2019 Microsoft
|
||||
# All Rights Reserved. Licensed under the MIT License.
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
param (
|
||||
[parameter (Mandatory=$true)]
|
||||
[string]$vm_id,
|
||||
[parameter (Mandatory=$true)]
|
||||
[string]$guest_ip,
|
||||
[parameter (Mandatory=$true)]
|
||||
[string]$file_list,
|
||||
[string]$path_separator
|
||||
)
|
||||
|
||||
function copy-file($machine, $file_list, $path_separator) {
|
||||
$files = Get-Content $file_list | ConvertFrom-Json
|
||||
$succeeded = @()
|
||||
$failed = @()
|
||||
foreach ($line in $files.PSObject.Properties) {
|
||||
$from = $sourceDir = $line.Name
|
||||
$to = $destDir = $line.Value
|
||||
Write-Host "Copying $from to $($machine) => $to..."
|
||||
Try {
|
||||
Hyper-V\Copy-VMFile -VM $machine -SourcePath $from -DestinationPath $to -CreateFullPath -FileSource Host -Force
|
||||
$succeeded += $from
|
||||
Write-Host "Copied $from to $($machine) => $to."
|
||||
} Catch {
|
||||
$failed += $from
|
||||
}
|
||||
}
|
||||
[hashtable]$return = @{}
|
||||
$return.succeeded = $succeeded
|
||||
$return.failed = $failed
|
||||
return $return
|
||||
}
|
||||
|
||||
$machine = Hyper-V\Get-VM -Id $vm_id
|
||||
|
||||
$status = copy-file $machine $file_list $path_separator
|
||||
|
||||
$resultHash = @{
|
||||
message = "OK"
|
||||
status = $status
|
||||
}
|
||||
$result = ConvertTo-Json $resultHash
|
||||
Write-OutputMessage $result
|
|
@ -0,0 +1,343 @@
|
|||
require "vagrant/util/platform"
|
||||
|
||||
module VagrantPlugins
|
||||
module HyperV
|
||||
class SyncHelper
|
||||
WINDOWS_SEPARATOR = "\\"
|
||||
UNIX_SEPARATOR = "/"
|
||||
|
||||
# Expands glob-style exclude string
|
||||
#
|
||||
# @param [String] path Path to operate on
|
||||
# @param [String] exclude Array of glob-style exclude strings
|
||||
# @return [Hash] Excluded directories and files
|
||||
def self.expand_excludes(path, exclude)
|
||||
excludes = ['.vagrant/']
|
||||
excludes += Array(exclude).map(&:to_s) if exclude
|
||||
excludes.uniq!
|
||||
|
||||
expanded_path = expand_path(path)
|
||||
excluded_dirs = []
|
||||
excluded_files = []
|
||||
excludes.map do |exclude|
|
||||
# Dir.glob accepts Unix style path only
|
||||
excluded_path = platform_join expanded_path, exclude, is_windows: false
|
||||
Dir.glob(excluded_path) do |e|
|
||||
if directory?(e)
|
||||
excluded_dirs << e
|
||||
else
|
||||
excluded_files << e
|
||||
end
|
||||
end
|
||||
end
|
||||
{dirs: excluded_dirs,
|
||||
files: excluded_files}
|
||||
end
|
||||
|
||||
def self.find_includes(path, exclude)
|
||||
expanded_path = expand_path(path)
|
||||
excludes = expand_excludes(path, exclude)
|
||||
included_dirs = []
|
||||
included_files = []
|
||||
Find.find(expanded_path) do |e|
|
||||
if directory?(e)
|
||||
path = File.join e, ''
|
||||
next if excludes[:dirs].include? path
|
||||
next if excludes[:dirs].select { |x| path.start_with? x }.any?
|
||||
|
||||
included_dirs << e
|
||||
else
|
||||
next if excludes[:files].include? e
|
||||
next if excludes[:dirs].select { |x| e.start_with? x }.any?
|
||||
|
||||
included_files << e
|
||||
end
|
||||
end
|
||||
{ dirs: included_dirs,
|
||||
files: included_files }
|
||||
end
|
||||
|
||||
def self.path_mapping(host_path, guest_path, includes, is_win_guest:)
|
||||
host_path = expand_path(host_path)
|
||||
platform_host_path = platform_path host_path, is_windows: !Vagrant::Util::Platform.wsl?
|
||||
win_host_path = Vagrant::Util::Platform.windows_path(host_path, :disable_unc)
|
||||
platform_guest_path = platform_path(guest_path, is_windows: is_win_guest)
|
||||
|
||||
dir_mappings = { hyperv: {}, platform: {} }
|
||||
file_mappings = { hyperv: {}, platform: {} }
|
||||
{ dirs: dir_mappings,
|
||||
files: file_mappings }.map do |sym, mapping|
|
||||
includes[sym].map do |e|
|
||||
guest_rel = e.gsub(host_path, '')
|
||||
guest_rel = trim_head guest_rel
|
||||
guest_rel = to_unix_path guest_rel
|
||||
|
||||
if guest_rel == ''
|
||||
file_host_path = win_host_path
|
||||
file_platform_host_path = platform_host_path
|
||||
target = platform_guest_path
|
||||
else
|
||||
file_host_path = platform_join(win_host_path, guest_rel)
|
||||
file_platform_host_path = platform_join(platform_host_path, guest_rel,
|
||||
is_windows: !Vagrant::Util::Platform.wsl?)
|
||||
guest_rel = guest_rel.split(UNIX_SEPARATOR)[0..-2].join(UNIX_SEPARATOR) if sym == :files
|
||||
target = platform_join(platform_guest_path, guest_rel, is_windows: is_win_guest)
|
||||
target = trim_tail target
|
||||
end
|
||||
# make sure the dir names are Windows-style for them to pass to Hyper-V
|
||||
mapping[:hyperv][file_host_path] = target
|
||||
mapping[:platform][file_platform_host_path] = target
|
||||
end
|
||||
end
|
||||
{ dirs: dir_mappings, files: file_mappings }
|
||||
end
|
||||
|
||||
# Syncs single folder to guest machine
|
||||
#
|
||||
# @param [Vagrant::Machine] path Path to operate on
|
||||
# @param [Hash] ssh_info
|
||||
# @param [Hash] opts Synced folder details
|
||||
def self.sync_single(machine, ssh_info, opts)
|
||||
is_win_guest = machine.guest.name == :windows
|
||||
host_path = opts[:hostpath]
|
||||
guest_path = opts[:guestpath]
|
||||
|
||||
includes = find_includes(host_path, opts[:exclude])
|
||||
if opts[:no_compression]
|
||||
# Copy file to guest directly for disk consumption saving
|
||||
guest_path_mapping = path_mapping(host_path, guest_path, includes, is_win_guest: is_win_guest)
|
||||
remove_directory machine, guest_path, is_win_guest: is_win_guest, sudo: true
|
||||
machine.guest.capability(:create_directories, guest_path_mapping[:dirs][:hyperv].values, sudo: true)
|
||||
if hyperv_copy? machine
|
||||
machine.provider.driver.sync_files(machine.id,
|
||||
guest_path_mapping[:dirs][:hyperv],
|
||||
guest_path_mapping[:files][:hyperv],
|
||||
is_win_guest: is_win_guest)
|
||||
else
|
||||
guest_path_mapping[:files][:platform].each do |host_path, guest_path|
|
||||
next unless file_exist? host_path
|
||||
|
||||
stat = file_stat host_path
|
||||
next if stat.symlink?
|
||||
|
||||
machine.communicate.upload(host_path, guest_path)
|
||||
end
|
||||
end
|
||||
else
|
||||
source_items = includes[:files]
|
||||
type = is_win_guest ? :zip : :tgz
|
||||
host_path = expand_path(host_path)
|
||||
source = send("compress_source_#{type}".to_sym, host_path, source_items)
|
||||
decompress_cap = type == :zip ? :decompress_zip : :decompress_tgz
|
||||
begin
|
||||
destination = machine.guest.capability(:create_tmp_path, extension: ".#{type}")
|
||||
upload_file(machine, source, destination, is_win_guest: is_win_guest)
|
||||
remove_directory machine, guest_path, is_win_guest: is_win_guest, sudo: true
|
||||
machine.guest.capability(decompress_cap, destination, platform_path(guest_path, is_windows: is_win_guest),
|
||||
type: :directory, sudo: true)
|
||||
ensure
|
||||
FileUtils.rm_f source if file_exist? source
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Compress path using zip into temporary file
|
||||
#
|
||||
# @param [String] path Path to compress
|
||||
# @return [String] path to compressed file
|
||||
def self.compress_source_zip(path, source_items)
|
||||
require "zip"
|
||||
zipfile = Tempfile.create(%w(vagrant .zip), format_windows_temp)
|
||||
zipfile.close
|
||||
c_dir = nil
|
||||
Zip::File.open(zipfile.path, Zip::File::CREATE) do |zip|
|
||||
source_items.each do |source_item|
|
||||
next unless file_exist? source_item
|
||||
next if directory?(source_item)
|
||||
|
||||
stat = file_stat(source_item)
|
||||
next if stat.symlink?
|
||||
|
||||
trim_item = source_item.sub(path, "").sub(%r{^[/\\]}, "")
|
||||
dirname = File.dirname(trim_item)
|
||||
begin
|
||||
zip.get_entry(dirname)
|
||||
rescue Errno::ENOENT
|
||||
zip.mkdir dirname if c_dir != dirname
|
||||
end
|
||||
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 self.compress_source_tgz(path, source_items)
|
||||
tmp_dir = format_windows_temp
|
||||
tarfile = Tempfile.create(%w(vagrant .tar), tmp_dir)
|
||||
tarfile.close
|
||||
tarfile = File.open(tarfile.path, "wb+")
|
||||
tgzfile = Tempfile.create(%w(vagrant .tgz), tmp_dir)
|
||||
tgzfile.close
|
||||
tgzfile = File.open(tgzfile.path, "wb")
|
||||
tar = Gem::Package::TarWriter.new(tarfile)
|
||||
tgz = Zlib::GzipWriter.new(tgzfile)
|
||||
source_items.each do |item|
|
||||
next unless file_exist? item
|
||||
|
||||
rel_path = item.sub(path, "")
|
||||
stat = file_stat(item)
|
||||
item_mode = stat.mode
|
||||
|
||||
if directory?(item)
|
||||
tar.mkdir(rel_path, item_mode)
|
||||
elsif stat.symlink?
|
||||
tar.add_symlink(rel_path, File.readlink(item), 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
|
||||
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
|
||||
|
||||
def self.remove_directory(machine, guestpath, is_win_guest: false, sudo: false)
|
||||
comm = machine.communicate
|
||||
if is_win_guest
|
||||
guestpath = to_windows_path guestpath
|
||||
cmd = <<-EOH.gsub(/^ {6}/, "")
|
||||
if (Test-Path(\"#{guestpath}\")) {
|
||||
Remove-Item -Path \"#{guestpath}\" -Recurse -Force
|
||||
}
|
||||
EOH
|
||||
comm.execute(cmd, shell: :powershell)
|
||||
else
|
||||
guestpath = to_unix_path guestpath
|
||||
if comm.test("test -d '#{guestpath}'")
|
||||
comm.execute("rm -rf '#{guestpath}'", sudo: sudo)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.format_windows_temp
|
||||
windows_temp = Vagrant::Util::Platform.windows_temp
|
||||
if Vagrant::Util::Platform.wsl?
|
||||
process = Vagrant::Util::Subprocess.execute(
|
||||
"wslpath", "-u", "-a", windows_temp)
|
||||
windows_temp = process.stdout.chomp if process.exit_code == 0
|
||||
end
|
||||
windows_temp
|
||||
end
|
||||
|
||||
def self.upload_file(machine, source, dest, is_win_guest:)
|
||||
begin
|
||||
# try Hyper-V guest integration service first as WinRM upload is slower
|
||||
if hyperv_copy? machine
|
||||
separator = is_win_guest ? WINDOWS_SEPARATOR: UNIX_SEPARATOR
|
||||
parts = dest.split(separator)
|
||||
filename = parts[-1]
|
||||
dest_dir = parts[0..-2].join(separator)
|
||||
|
||||
windows_temp = format_windows_temp
|
||||
source_copy = platform_join windows_temp, filename, is_windows: !Vagrant::Util::Platform.wsl?
|
||||
FileUtils.mv source, source_copy
|
||||
source = source_copy
|
||||
win_source_path = Vagrant::Util::Platform.windows_path(source, :disable_unc)
|
||||
hyperv_copy machine, win_source_path, dest_dir
|
||||
else
|
||||
machine.communicate.upload(source, dest)
|
||||
end
|
||||
ensure
|
||||
FileUtils.rm_f source
|
||||
end
|
||||
end
|
||||
|
||||
def self.hyperv_copy?(machine)
|
||||
machine.guest.capability?(:hyperv_daemons_running) && machine.guest.capability(:hyperv_daemons_running)
|
||||
end
|
||||
|
||||
def self.hyperv_copy(machine, source, dest_dir)
|
||||
vm_id = machine.id
|
||||
ps_cmd = <<-EOH.gsub(/^ {6}/, "")
|
||||
$machine = Hyper-V\\Get-VM -Id \"#{vm_id}\"
|
||||
Hyper-V\\Copy-VMFile -VM $machine -SourcePath \"#{source}\" -DestinationPath \"#{dest_dir}\" -CreateFullPath -FileSource Host -Force
|
||||
EOH
|
||||
Vagrant::Util::PowerShell.execute_cmd(ps_cmd)
|
||||
end
|
||||
|
||||
def self.platform_join(string, *smth, is_windows: true)
|
||||
joined = [string, *smth].join is_windows ? WINDOWS_SEPARATOR : UNIX_SEPARATOR
|
||||
if is_windows
|
||||
to_windows_path joined
|
||||
else
|
||||
to_unix_path joined
|
||||
end
|
||||
end
|
||||
|
||||
def self.platform_path(path, is_windows: true)
|
||||
win_path = to_windows_path path
|
||||
linux_path = to_unix_path path
|
||||
is_windows ? win_path : linux_path
|
||||
end
|
||||
|
||||
def self.expand_path(*path)
|
||||
# stub for unit test
|
||||
File.expand_path(*path)
|
||||
end
|
||||
|
||||
def self.directory?(path)
|
||||
# stub for unit test
|
||||
File.directory? path
|
||||
end
|
||||
|
||||
def self.file_exist?(path)
|
||||
# stub for unit test
|
||||
File.exist? path
|
||||
end
|
||||
|
||||
def self.file_stat(path)
|
||||
# stub for unit test
|
||||
File.stat path
|
||||
end
|
||||
|
||||
def self.to_windows_path(path)
|
||||
path.tr UNIX_SEPARATOR, WINDOWS_SEPARATOR
|
||||
end
|
||||
|
||||
def self.to_unix_path(path)
|
||||
path.tr WINDOWS_SEPARATOR, UNIX_SEPARATOR
|
||||
end
|
||||
|
||||
def self.trim_head(path)
|
||||
path.start_with?(WINDOWS_SEPARATOR, UNIX_SEPARATOR) ? path[1..-1] : path
|
||||
end
|
||||
|
||||
def self.trim_tail(path)
|
||||
path.end_with?(WINDOWS_SEPARATOR, UNIX_SEPARATOR) ? path[0..-2] : path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
require "fileutils"
|
||||
require "vagrant/util/platform"
|
||||
|
||||
require_relative 'sync_helper'
|
||||
|
||||
module VagrantPlugins
|
||||
module HyperV
|
||||
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
|
||||
def usable?(machine, raise_errors=false)
|
||||
# These synced folders only work if the provider if VirtualBox
|
||||
return false if machine.provider_name != :hyperv
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def prepare(machine, folders, _opts)
|
||||
# Nothing is necessary to do before VM boot.
|
||||
end
|
||||
|
||||
def enable(machine, folders, _opts)
|
||||
machine.ui.warn I18n.t("vagrant_hyperv.share_folders.no_daemons") unless configure_hv_daemons(machine)
|
||||
|
||||
# short guestpaths first, so we don't step on ourselves
|
||||
folders = folders.sort_by do |id, data|
|
||||
if data[:guestpath]
|
||||
data[:guestpath].length
|
||||
else
|
||||
# A long enough path to just do this at the end.
|
||||
10000
|
||||
end
|
||||
end
|
||||
|
||||
# Go through each folder and mount
|
||||
machine.ui.output(I18n.t("vagrant_hyperv.share_folders.syncing"))
|
||||
folders.each do |id, data|
|
||||
if data[:guestpath]
|
||||
# Guest path specified, so sync the folder to specified point
|
||||
machine.ui.detail(I18n.t("vagrant_hyperv.share_folders.syncing_entry",
|
||||
guestpath: data[:guestpath],
|
||||
hostpath: data[:hostpath]))
|
||||
|
||||
# Dup the data so we can pass it to the guest API
|
||||
SyncHelper.sync_single machine, machine.ssh_info, data.dup
|
||||
else
|
||||
# If no guest path is specified, then automounting is disabled
|
||||
machine.ui.detail(I18n.t("vagrant_hyperv.share_folders.nosync_entry",
|
||||
hostpath: data[:hostpath]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def disable(machine, folders, _opts) end
|
||||
|
||||
def cleanup(machine, opts) end
|
||||
|
||||
protected
|
||||
|
||||
def configure_hv_daemons(machine)
|
||||
return false unless machine.guest.capability?(:hyperv_daemons_running)
|
||||
|
||||
unless machine.guest.capability(:hyperv_daemons_running)
|
||||
installed = machine.guest.capability(:hyperv_daemons_installed)
|
||||
unless installed
|
||||
can_install = machine.guest.capability?(:hyperv_daemons_install)
|
||||
unless can_install
|
||||
machine.ui.warn I18n.t("vagrant_hyperv.daemons.unable_to_install")
|
||||
return false
|
||||
end
|
||||
|
||||
machine.ui.info I18n.t("vagrant_hyperv.daemons.installing")
|
||||
machine.guest.capability(:hyperv_daemons_install)
|
||||
end
|
||||
|
||||
can_activate = machine.guest.capability?(:hyperv_daemons_activate)
|
||||
unless can_activate
|
||||
machine.ui.warn I18n.t("vagrant_hyperv.daemons.unable_to_activate")
|
||||
return false
|
||||
end
|
||||
|
||||
machine.ui.info I18n.t("vagrant_hyperv.daemons.activating")
|
||||
activated = machine.guest.capability(:hyperv_daemons_activate)
|
||||
unless activated
|
||||
machine.ui.warn I18n.t("vagrant_hyperv.daemons.activation_failed")
|
||||
return false
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# This is here so that we can stub it for tests
|
||||
def driver(machine)
|
||||
machine.provider.driver
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,6 +10,52 @@ en:
|
|||
VM not created. Moving on...
|
||||
message_not_running: |-
|
||||
Hyper-V machine isn't running. Can't SSH in!
|
||||
share_folders:
|
||||
syncing: Syncing shared folders...
|
||||
syncing_entry: "%{guestpath} => %{hostpath}"
|
||||
nosync_entry: "Sync disabled: %{hostpath}"
|
||||
no_daemons: |-
|
||||
Hyper-V daemons are not running on the guest operation system.
|
||||
This may impact the performance.
|
||||
daemons:
|
||||
installing: |-
|
||||
Installing Hyper-V daemons...
|
||||
activating: |-
|
||||
Activating Hyper-V daemons...
|
||||
unable_to_install: |-
|
||||
Hyper-V daemons cannot be installed in the guest operation system.
|
||||
unable_to_activate: |-
|
||||
Hyper-V daemons cannot be activated in the guest operation system
|
||||
as no Hyper-V daemons activation approach is provided on the guest
|
||||
operation system.
|
||||
activation_failed: |-
|
||||
Failed to activate in the guest operation system.
|
||||
sync:
|
||||
auto_initial: |-
|
||||
Doing an initial sync...
|
||||
auto_new_folders: |-
|
||||
New synced folders were added to the Vagrantfile since running
|
||||
`vagrant reload`. If these new synced folders are backed by Hyper-V
|
||||
auto sync, they won't be automatically synced until a
|
||||
`vagrant reload` is run.
|
||||
auto_no_paths: |-
|
||||
There are no paths to watch! This is either because you have no
|
||||
synced folders, or any synced folders you have have specified `auto`
|
||||
to be false.
|
||||
auto_path: "Watching: %{path}"
|
||||
auto_sync_error: |-
|
||||
There was an error while syncing. The error is shown below.
|
||||
This may not be critical since syncing sometimes fails, but if this
|
||||
message repeats, then please fix the issue:
|
||||
|
||||
%{message}
|
||||
communicator_not_ready: |-
|
||||
The machine is reporting that it is not ready to communicate with it.
|
||||
Verify that this machine is properly running.
|
||||
communicator_not_ready_callback: |-
|
||||
Failed to connect to remote machine. This is usually caused by the
|
||||
machine rebooting or being halted. Please make sure the machine is
|
||||
running, and modify a file to try again.
|
||||
|
||||
config:
|
||||
invalid_auto_start_action: |-
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe "VagrantPlugins::GuestArch::Cap::HypervDaemons" do
|
||||
let(:caps) do
|
||||
VagrantPlugins::GuestArch::Plugin
|
||||
.components
|
||||
.guest_capabilities[:arch]
|
||||
end
|
||||
|
||||
let(:machine) { double("machine") }
|
||||
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(comm)
|
||||
end
|
||||
|
||||
after do
|
||||
comm.verify_expectations!
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_installed" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_install) }
|
||||
|
||||
it "checks whether hyperv package is installed" do
|
||||
cap.hyperv_daemons_installed(machine)
|
||||
expect(comm.received_commands[0]).to match(/pacman -Q hyperv/)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_install" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_install) }
|
||||
let(:cmd) do
|
||||
<<-EOH.gsub(/^ {12}/, "")
|
||||
pacman --noconfirm -Syy &&
|
||||
pacman --noconfirm -S hyperv
|
||||
EOH
|
||||
end
|
||||
|
||||
it "installs hyperv package" do
|
||||
cap.hyperv_daemons_install(machine)
|
||||
expect(comm.received_commands[0]).to match("pacman --noconfirm -Syy &&\npacman --noconfirm -S hyperv\n")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,40 +45,45 @@ describe "VagrantPlugins::GuestBSD::Cap::FileSystem" 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
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
it "should extract file with tar" do
|
||||
expect(comm).to receive(:execute).with(/tar/)
|
||||
end
|
||||
it "should create temporary directory for extraction" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/)
|
||||
end
|
||||
it "should extract file with tar" do
|
||||
expect(comm).to receive(:execute).with(/tar/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/)
|
||||
end
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)
|
||||
end
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/)
|
||||
end
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
context "when type is directory" do
|
||||
before { opts[:type] = :directory }
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create destination directory" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)
|
||||
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'/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -87,40 +92,45 @@ describe "VagrantPlugins::GuestBSD::Cap::FileSystem" 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
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
it "should extract file with zip" do
|
||||
expect(comm).to receive(:execute).with(/zip/)
|
||||
end
|
||||
it "should create temporary directory for extraction" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/)
|
||||
end
|
||||
it "should extract file with zip" do
|
||||
expect(comm).to receive(:execute).with(/zip/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/)
|
||||
end
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)
|
||||
end
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/)
|
||||
end
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
context "when type is directory" do
|
||||
before { opts[:type] = :directory }
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create destination directory" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)
|
||||
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'/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe "VagrantPlugins::GuestDebian::Cap::HypervDaemons" do
|
||||
let(:caps) do
|
||||
VagrantPlugins::GuestDebian::Plugin
|
||||
.components
|
||||
.guest_capabilities[:debian]
|
||||
end
|
||||
|
||||
let(:machine) { double("machine") }
|
||||
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(comm)
|
||||
end
|
||||
|
||||
after do
|
||||
comm.verify_expectations!
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_installed" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_installed) }
|
||||
|
||||
it "checks whether linux-cloud-tools-common package is installed" do
|
||||
cap.hyperv_daemons_installed(machine)
|
||||
expect(comm.received_commands[0]).to match(/dpkg -s linux-cloud-tools-common/)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_install" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_install) }
|
||||
let(:cmd) do
|
||||
<<-EOH.gsub(/^ {12}/, "")
|
||||
DEBIAN_FRONTEND=noninteractive apt-get update -y &&
|
||||
apt-get install -y -o Dpkg::Options::="--force-confdef" linux-cloud-tools-common
|
||||
EOH
|
||||
end
|
||||
|
||||
it "install linux-cloud-tools-common package" do
|
||||
cap.hyperv_daemons_install(machine)
|
||||
expect(comm.received_commands[0]).to match("DEBIAN_FRONTEND=noninteractive apt-get update -y &&\napt-get install -y -o Dpkg::Options::=\"--force-confdef\" linux-cloud-tools-common\n")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,40 +45,45 @@ describe "VagrantPlugins::GuestLinux::Cap::FileSystem" 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
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
it "should extract file with tar" do
|
||||
expect(comm).to receive(:execute).with(/tar/)
|
||||
end
|
||||
it "should create temporary directory for extraction" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/)
|
||||
end
|
||||
it "should extract file with tar" do
|
||||
expect(comm).to receive(:execute).with(/tar/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/)
|
||||
end
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)
|
||||
end
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/)
|
||||
end
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
context "when type is directory" do
|
||||
before { opts[:type] = :directory }
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create destination directory" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)
|
||||
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'/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -87,40 +92,121 @@ describe "VagrantPlugins::GuestLinux::Cap::FileSystem" 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)
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
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/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/, sudo: sudo_flag)
|
||||
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'/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should extract file with zip" do
|
||||
expect(comm).to receive(:execute).with(/zip/)
|
||||
end
|
||||
describe ".create_directories" do
|
||||
let(:cap) { caps.get(:create_directories) }
|
||||
let(:dirs) { %w(dir1 dir2) }
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/)
|
||||
end
|
||||
before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") }
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/)
|
||||
end
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)
|
||||
end
|
||||
after { expect(cap.create_directories(machine, dirs, opts)).to eql(dirs) }
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/)
|
||||
end
|
||||
context "passes directories to be create" do
|
||||
let(:temp_file) do
|
||||
double("temp_file").tap do |temp_file|
|
||||
allow(temp_file).to receive(:binmode)
|
||||
allow(temp_file).to receive(:close)
|
||||
allow(temp_file).to receive(:path).and_return("temp_path")
|
||||
allow(temp_file).to receive(:unlink)
|
||||
end
|
||||
end
|
||||
let(:exec_block) do
|
||||
Proc.new do |arg, &proc|
|
||||
lines = arg.split("\n")
|
||||
expect(lines[lines.length - 2]).to match(/TMP_DIR/)
|
||||
dirs.each do |dir|
|
||||
proc.call :stdout, "mkdir: created directory '#{dir}'\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when type is directory" do
|
||||
before { opts[:type] = :directory }
|
||||
before do
|
||||
allow(Tempfile).to receive(:new).and_return(temp_file)
|
||||
allow(temp_file).to receive(:write)
|
||||
allow(temp_file).to receive(:close)
|
||||
allow(comm).to receive(:upload)
|
||||
allow(comm).to receive(:execute, &exec_block)
|
||||
end
|
||||
|
||||
it "should create destination directory" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)
|
||||
it "creates temporary file on guest" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "creates a temporary file to write dir list" do
|
||||
expect(Tempfile).to receive(:new).and_return(temp_file)
|
||||
end
|
||||
|
||||
it "writes dir list to a local temporary file" do
|
||||
expect(temp_file).to receive(:write).with(dirs.join("\n") + "\n")
|
||||
end
|
||||
|
||||
it "uploads the local temporary file with dir list to guest" do
|
||||
expect(comm).to receive(:upload).with("temp_path", "TMP_DIR")
|
||||
end
|
||||
|
||||
it "executes bash script to create directories on guest" do
|
||||
expect(comm).to receive(:execute, &exec_block).with(/bash -c .*/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
|
||||
context "passes empty dir list" do
|
||||
let(:dirs) { [] }
|
||||
|
||||
after { expect(cap.create_directories(machine, dirs, opts)).to eql([]) }
|
||||
|
||||
it "does nothing" do
|
||||
expect(cap).to receive(:create_tmp_path).never
|
||||
expect(Tempfile).to receive(:new).never
|
||||
expect(comm).to receive(:upload).never
|
||||
expect(comm).to receive(:execute).never
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe "VagrantPlugins::GuestLinux::Cap::HypervDaemons" do
|
||||
let(:caps) do
|
||||
VagrantPlugins::GuestLinux::Plugin
|
||||
.components
|
||||
.guest_capabilities[:linux]
|
||||
end
|
||||
|
||||
let(:hyperv_services) do
|
||||
%w[kvp vss fcopy]
|
||||
end
|
||||
|
||||
let(:machine) { double("machine") }
|
||||
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(comm)
|
||||
end
|
||||
|
||||
after do
|
||||
comm.verify_expectations!
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_running" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_running) }
|
||||
|
||||
before do
|
||||
comm.stub_command("which apt-get", exit_code: 0)
|
||||
hyperv_services.each do |service|
|
||||
name = ['hv', service, 'daemon'].join('-')
|
||||
comm.stub_command("systemctl -q is-active #{name}", exit_code: 0)
|
||||
end
|
||||
expect(cap.hyperv_daemons_running(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks whether guest OS is apt based" do
|
||||
expect(comm.received_commands[0]).to match(/which apt-get/)
|
||||
end
|
||||
|
||||
it "checks daemon service status by systemctl" do
|
||||
hyperv_services.each_with_index do |service, idx|
|
||||
name = ['hv', service, 'daemon'].join('-')
|
||||
expect(comm.received_commands[idx + 1]).to match(/systemctl -q is-active #{name}/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_installed" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_installed) }
|
||||
|
||||
before do
|
||||
hyperv_services.each do |service|
|
||||
comm.stub_command("which #{['hv', service, 'daemon'].join('_')}", exit_code: 0)
|
||||
end
|
||||
|
||||
expect(cap.hyperv_daemons_installed(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks whether hyperv daemons exist on the path" do
|
||||
hyperv_services.each_with_index do |service, idx|
|
||||
name = ['hv', service, 'daemon'].join('_')
|
||||
expect(comm.received_commands[idx]).to match(/which #{name}/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_activate" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_activate) }
|
||||
let(:hyperv_service_names) { [] }
|
||||
|
||||
before do
|
||||
comm.stub_command("which apt-get", exit_code: apt_get? ? 0 : 1)
|
||||
hyperv_services.each do |service|
|
||||
name = ['hv', service, 'daemon'].join(service_separator)
|
||||
comm.stub_command("systemctl enable #{name}", exit_code: 0)
|
||||
comm.stub_command("systemctl restart #{name}", exit_code: 0)
|
||||
comm.stub_command("systemctl -q is-active #{name}", exit_code: 0)
|
||||
hyperv_service_names << name
|
||||
end
|
||||
expect(cap.hyperv_daemons_activate(machine)).to be_truthy
|
||||
end
|
||||
|
||||
{ debian: { apt_get?: true,
|
||||
service_separator: "-" },
|
||||
linux: { apt_get?: false,
|
||||
service_separator: "_" } }.map do |guest_type, guest_opts|
|
||||
|
||||
context guest_type do
|
||||
let(:apt_get?) { guest_opts[:apt_get?] }
|
||||
let(:service_separator) { guest_opts[:service_separator] }
|
||||
|
||||
it "checks whether guest OS is apt based" do
|
||||
expect(comm.received_commands[0]).to match(/which apt-get/)
|
||||
end
|
||||
|
||||
it "checks whether hyperv daemons are activated on Debian/Ubuntu" do
|
||||
pos = 1
|
||||
hyperv_service_names.each do |name|
|
||||
expect(comm.received_commands[pos]).to match(/systemctl enable #{name}/)
|
||||
expect(comm.received_commands[pos + 1]).to match(/systemctl restart #{name}/)
|
||||
expect(comm.received_commands[pos + 2]).to match(/systemctl -q is-active #{name}/)
|
||||
pos += 3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe "VagrantPlugins::GuestRedHat::Cap::HypervDaemons" do
|
||||
let(:caps) do
|
||||
VagrantPlugins::GuestRedHat::Plugin
|
||||
.components
|
||||
.guest_capabilities[:redhat]
|
||||
end
|
||||
|
||||
let(:machine) { double("machine") }
|
||||
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(comm)
|
||||
end
|
||||
|
||||
after do
|
||||
comm.verify_expectations!
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_installed" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_installed) }
|
||||
|
||||
it "checks whether hyperv-daemons package is installed" do
|
||||
cap.hyperv_daemons_installed(machine)
|
||||
expect(comm.received_commands[0]).to match(/rpm -q hyperv-daemons/)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".hyperv_daemons_install" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_install) }
|
||||
let(:cmd) do
|
||||
<<-EOH.gsub(/^ {12}/, "")
|
||||
if command -v dnf; then
|
||||
dnf -y install hyperv-daemons
|
||||
else
|
||||
yum -y install hyperv-daemons
|
||||
fi
|
||||
EOH
|
||||
end
|
||||
|
||||
it "install hyperv-daemons package" do
|
||||
cap.hyperv_daemons_install(machine)
|
||||
expect(comm.received_commands[0]).to match(/if command -v dnf; then\n dnf -y install hyperv-daemons\nelse\n yum -y install hyperv-daemons\nfi\n/)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,40 +45,45 @@ describe "VagrantPlugins::GuestSolaris::Cap::FileSystem" 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
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
it "should extract file with tar" do
|
||||
expect(comm).to receive(:execute).with(/tar/)
|
||||
end
|
||||
it "should create temporary directory for extraction" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/)
|
||||
end
|
||||
it "should extract file with tar" do
|
||||
expect(comm).to receive(:execute).with(/tar/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/)
|
||||
end
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)
|
||||
end
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/)
|
||||
end
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
context "when type is directory" do
|
||||
before { opts[:type] = :directory }
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create destination directory" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)
|
||||
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'/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -87,40 +92,45 @@ describe "VagrantPlugins::GuestSolaris::Cap::FileSystem" 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
|
||||
[false, true].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
let(:opts) { {sudo: sudo_flag} }
|
||||
|
||||
it "should extract file with zip" do
|
||||
expect(comm).to receive(:execute).with(/zip/)
|
||||
end
|
||||
it "should create temporary directory for extraction" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/)
|
||||
end
|
||||
it "should extract file with zip" do
|
||||
expect(comm).to receive(:execute).with(/zip/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/)
|
||||
end
|
||||
it "should extract file to temporary directory" do
|
||||
expect(comm).to receive(:execute).with(/TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/)
|
||||
end
|
||||
it "should remove compressed file from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*#{comp}/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/)
|
||||
end
|
||||
it "should remove extraction directory from guest" do
|
||||
expect(comm).to receive(:execute).with(/rm .*TMP_DIR/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
context "when type is directory" do
|
||||
before { opts[:type] = :directory }
|
||||
it "should create parent directories for destination" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*to'/, sudo: sudo_flag)
|
||||
end
|
||||
|
||||
it "should create destination directory" do
|
||||
expect(comm).to receive(:execute).with(/mkdir -p .*destination'/)
|
||||
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'/, sudo: sudo_flag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require 'json'
|
||||
require_relative "../../../../base"
|
||||
|
||||
describe "VagrantPlugins::GuestWindows::Cap::FileSystem" do
|
||||
|
@ -82,4 +83,88 @@ describe "VagrantPlugins::GuestWindows::Cap::FileSystem" do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".create_directories" do
|
||||
let(:cap) { caps.get(:create_directories) }
|
||||
let(:dirs) { %w(dir1 dir2) }
|
||||
|
||||
before { allow(cap).to receive(:create_tmp_path).and_return("TMP_DIR") }
|
||||
after { expect(cap.create_directories(machine, dirs)).to eql(dirs) }
|
||||
|
||||
context "passes directories to be create" do
|
||||
let(:temp_file) do
|
||||
double("temp_file").tap do |temp_file|
|
||||
allow(temp_file).to receive(:close)
|
||||
allow(temp_file).to receive(:path).and_return("temp_path")
|
||||
allow(temp_file).to receive(:unlink)
|
||||
end
|
||||
end
|
||||
let(:sudo_block) do
|
||||
Proc.new do |arg, &proc|
|
||||
lines = arg.split("\n")
|
||||
expect(lines[0]).to match(/TMP_DIR/)
|
||||
dirs.each do |dir|
|
||||
proc.call :stdout, { FullName: dir }.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:cmd) do
|
||||
<<-EOH.gsub(/^ {6}/, "")
|
||||
$files = Get-Content TMP_DIR
|
||||
foreach ($file in $files) {
|
||||
if (-Not (Test-Path($file))) {
|
||||
ConvertTo-Json (New-Item $file -type directory -Force | Select-Object FullName)
|
||||
} else {
|
||||
if (-Not ((Get-Item $file) -is [System.IO.DirectoryInfo])) {
|
||||
# Remove the file
|
||||
Remove-Item -Path $file -Force
|
||||
ConvertTo-Json (New-Item $file -type directory -Force | Select-Object FullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
EOH
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Tempfile).to receive(:new).and_return(temp_file)
|
||||
allow(temp_file).to receive(:write)
|
||||
allow(temp_file).to receive(:close)
|
||||
allow(comm).to receive(:upload)
|
||||
allow(comm).to receive(:execute, &sudo_block)
|
||||
end
|
||||
|
||||
it "creates temporary file on guest" do
|
||||
expect(cap).to receive(:create_tmp_path)
|
||||
end
|
||||
|
||||
it "creates a temporary file to write dir list" do
|
||||
expect(Tempfile).to receive(:new).and_return(temp_file)
|
||||
end
|
||||
|
||||
it "writes dir list to a local temporary file" do
|
||||
expect(temp_file).to receive(:write).with(dirs.join("\n") + "\n")
|
||||
end
|
||||
|
||||
it "uploads the local temporary file with dir list to guest" do
|
||||
expect(comm).to receive(:upload).with("temp_path", "TMP_DIR")
|
||||
end
|
||||
|
||||
it "executes bash script to create directories on guest" do
|
||||
expect(comm).to receive(:execute, &sudo_block).with(cmd, shell: :powershell)
|
||||
end
|
||||
end
|
||||
|
||||
context "passes empty dir list" do
|
||||
let(:dirs) { [] }
|
||||
|
||||
after { expect(cap.create_directories(machine, dirs)).to eql([]) }
|
||||
|
||||
it "does nothing" do
|
||||
expect(cap).to receive(:create_tmp_path).never
|
||||
expect(Tempfile).to receive(:new).never
|
||||
expect(comm).to receive(:upload).never
|
||||
expect(comm).to receive(:execute).never
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
require 'json'
|
||||
|
||||
require_relative "../../../../base"
|
||||
|
||||
require Vagrant.source_root.join("plugins/guests/windows/cap/hyperv_daemons")
|
||||
|
||||
describe VagrantPlugins::GuestWindows::Cap::HypervDaemons do
|
||||
HYPERV_DAEMON_SERVICES = %i[kvp vss fcopy]
|
||||
HYPERV_DAEMON_SERVICE_NAMES = {kvp: "vmickvpexchange", vss: "vmicvss", fcopy: "vmicguestinterface" }
|
||||
|
||||
STOPPED = 1
|
||||
RUNNING = 4
|
||||
|
||||
MANUAL_MODE = 3
|
||||
DISABLED_MODE = 4
|
||||
|
||||
include_context "unit"
|
||||
|
||||
let(:machine) do
|
||||
double("machine").tap do |machine|
|
||||
allow(machine).to receive(:communicate).and_return(comm)
|
||||
end
|
||||
end
|
||||
let(:comm) { double("comm") }
|
||||
|
||||
def name_for(service)
|
||||
HYPERV_DAEMON_SERVICE_NAMES[service]
|
||||
end
|
||||
|
||||
def service_status(name, running: true, disabled: false)
|
||||
{ "Name" => name,
|
||||
"Status" => running ? RUNNING : STOPPED,
|
||||
"StartType" => disabled ? DISABLED_MODE : MANUAL_MODE }
|
||||
end
|
||||
|
||||
context "test declared methods" do
|
||||
subject { described_class }
|
||||
|
||||
describe "#hyperv_daemon_running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon #{service}" do
|
||||
let(:service) { service }
|
||||
let(:service_name) { name_for(service) }
|
||||
|
||||
it "checks daemon is running" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).and_return(service_status(service_name, running: true))
|
||||
expect(subject.hyperv_daemon_running(machine, service)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks daemon is not running" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).and_return(service_status(service_name, running: false))
|
||||
expect(subject.hyperv_daemon_running(machine, service)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_running" do
|
||||
it "checks hyperv daemons are running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_running).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(subject.hyperv_daemons_running(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks hyperv daemons are not running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_running).with(machine, service).and_return(false)
|
||||
end
|
||||
expect(subject.hyperv_daemons_running(machine)).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemon_installed" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon #{service}" do
|
||||
let(:service) { service }
|
||||
|
||||
before { expect(subject.hyperv_daemon_installed(subject, service)).to be_truthy }
|
||||
|
||||
it "does not call communicate#execute" do
|
||||
expect(comm).to receive(:execute).never
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_installed" do
|
||||
it "checks hyperv daemons are running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_installed).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(subject.hyperv_daemons_installed(machine)).to be_truthy
|
||||
expect(comm).to receive(:execute).never
|
||||
end
|
||||
|
||||
it "checks hyperv daemons are not running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_installed).with(machine, service).and_return(false)
|
||||
end
|
||||
expect(subject.hyperv_daemons_installed(machine)).to be_falsy
|
||||
expect(comm).to receive(:execute).never
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemon_activate" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon #{service}" do
|
||||
let(:service) { service }
|
||||
let(:service_name) { name_for(service) }
|
||||
let(:service_disabled_status) { service_status(service_name, disabled: true, running: false) }
|
||||
let(:service_stopped_status) { service_status(service_name, running: false) }
|
||||
let(:service_running_status) { service_status(service_name) }
|
||||
|
||||
context "activate succeeds" do
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_truthy }
|
||||
|
||||
it "enables the service when service disabled" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_disabled_status)
|
||||
expect(subject).to receive(:enable_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:restart_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_running_status)
|
||||
end
|
||||
|
||||
it "only restarts the service when service enabled" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_running_status)
|
||||
expect(subject).to receive(:enable_service).never
|
||||
expect(subject).to receive(:restart_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_running_status)
|
||||
end
|
||||
end
|
||||
|
||||
context "activate fails" do
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_falsy }
|
||||
|
||||
it "enables the service when service disabled" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_disabled_status)
|
||||
expect(subject).to receive(:enable_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:restart_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_stopped_status)
|
||||
end
|
||||
|
||||
it "does not restart service when failed to enable it" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).and_return(service_disabled_status)
|
||||
expect(subject).to receive(:enable_service).with(comm, service_name).and_return(false)
|
||||
expect(subject).to receive(:restart_service).never
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_activate" do
|
||||
it "activates hyperv daemons" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_activate).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(subject.hyperv_daemons_activate(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "fails to activate hyperv daemons" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_activate).with(machine, service).and_return(false)
|
||||
end
|
||||
expect(subject.hyperv_daemons_activate(machine)).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemon_activate" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon #{service}" do
|
||||
let(:service) { service }
|
||||
let(:service_name) { name_for(service) }
|
||||
let(:service_disabled_status) { service_status(service_name, disabled: true, running: false) }
|
||||
let(:service_stopped_status) { service_status(service_name, running: false) }
|
||||
let(:service_running_status) { service_status(service_name) }
|
||||
|
||||
context "activate succeeds" do
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_truthy }
|
||||
|
||||
it "enables the service when service disabled" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_disabled_status)
|
||||
expect(subject).to receive(:enable_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:restart_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_running_status)
|
||||
end
|
||||
|
||||
it "only restarts the service when service enabled" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_running_status)
|
||||
expect(subject).to receive(:enable_service).never
|
||||
expect(subject).to receive(:restart_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_running_status)
|
||||
end
|
||||
end
|
||||
|
||||
context "activate fails" do
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_falsy }
|
||||
|
||||
it "enables the service when service disabled" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_disabled_status)
|
||||
expect(subject).to receive(:enable_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:restart_service).with(comm, service_name).and_return(true)
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).ordered.and_return(service_stopped_status)
|
||||
end
|
||||
|
||||
it "does not restart service when failed to enable it" do
|
||||
expect(subject).to receive(:service_info).
|
||||
with(comm, service_name).and_return(service_disabled_status)
|
||||
expect(subject).to receive(:enable_service).with(comm, service_name).and_return(false)
|
||||
expect(subject).to receive(:restart_service).never
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#service_info" do
|
||||
let(:service_name) { name_for(:kvp) }
|
||||
let(:status) { service_status(service_name) }
|
||||
|
||||
it "executes powershell script" do
|
||||
cmd = "ConvertTo-Json (Get-Service -Name #{service_name})"
|
||||
expect(comm).to receive(:execute).with(cmd, shell: :powershell) do |&proc|
|
||||
proc.call :stdout, status.to_json
|
||||
end
|
||||
expect(subject.send(:service_info, comm, service_name)).to eq(status)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#restart_service" do
|
||||
let(:service_name) { name_for(:kvp) }
|
||||
let(:status) { service_status(service_name) }
|
||||
|
||||
it "executes powershell script" do
|
||||
cmd = "Restart-Service -Name #{service_name} -Force"
|
||||
expect(comm).to receive(:execute).with(cmd, shell: :powershell)
|
||||
expect(subject.send(:restart_service, comm, service_name)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#enable_service" do
|
||||
let(:service_name) { name_for(:kvp) }
|
||||
let(:status) { service_status(service_name) }
|
||||
|
||||
it "executes powershell script" do
|
||||
cmd = "Set-Service -Name #{service_name} -StartupType #{MANUAL_MODE}"
|
||||
expect(comm).to receive(:execute).with(cmd, shell: :powershell)
|
||||
expect(subject.send(:enable_service, comm, service_name)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "calls through guest capabilities" do
|
||||
let(:caps) do
|
||||
VagrantPlugins::GuestWindows::Plugin.components.guest_capabilities[:windows]
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_running" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_running) }
|
||||
|
||||
it "checks hyperv daemons are running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(cap).to receive(:hyperv_daemon_running).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(cap.hyperv_daemons_running(machine)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_installed" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_installed) }
|
||||
|
||||
it "checks hyperv daemons are running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(cap).to receive(:hyperv_daemon_installed).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(cap.hyperv_daemons_installed(machine)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_activate" do
|
||||
let(:cap) { caps.get(:hyperv_daemons_activate) }
|
||||
|
||||
it "activates hyperv daemons" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(cap).to receive(:hyperv_daemon_activate).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(cap.hyperv_daemons_activate(machine)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,282 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/sync_helper")
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/command/sync_auto")
|
||||
|
||||
describe VagrantPlugins::HyperV::Command::SyncAuto do
|
||||
include_context "unit"
|
||||
|
||||
let(:argv) { [] }
|
||||
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(:hostpath_mappings) do
|
||||
{ Windows: { "/vagrant-sandbox": 'C:\Users\brian\code\vagrant-sandbox',
|
||||
"/Not-The-Same-Path": 'C:\Not\The\Same\Path',
|
||||
"/relative-dir": 'C:\Users\brian\code\relative-dir',
|
||||
"/vagrant-sandbox-other-dir": 'C:\Users\brian\code\vagrant-sandbox\other-dir' },
|
||||
WSL: { "/vagrant-sandbox": "/mnt/c/Users/brian/code/vagrant-sandbox",
|
||||
"/Not-The-Same-Path": "/mnt/c/Not/The/Same/Path",
|
||||
"/relative-dir": "/mnt/c/Users/brian/code/relative-dir",
|
||||
"/vagrant-sandbox-other-dir": "/mnt/c/Users/brian/code/vagrant-sandbox/other-dir" } }
|
||||
end
|
||||
let(:synced_folders_empty) { {} }
|
||||
let(:synced_folders_dupe) do
|
||||
{ "1234":
|
||||
{ type: "hyperv",
|
||||
exclude: [".git/"],
|
||||
guestpath: "/vagrant-sandbox" },
|
||||
"5678":
|
||||
{ type: "hyperv",
|
||||
exclude: [".git/"],
|
||||
guestpath: "/Not-The-Same-Path" },
|
||||
"0912":
|
||||
{ type: "hyperv",
|
||||
exclude: [".git/"],
|
||||
guestpath: "/relative-dir"} }
|
||||
end
|
||||
|
||||
let(:helper_class) { VagrantPlugins::HyperV::SyncHelper }
|
||||
|
||||
let(:paths) { {} }
|
||||
let(:ssh_info) { { username: "vagrant" }}
|
||||
|
||||
before do
|
||||
I18n.load_path << Vagrant.source_root.join("templates/locales/providers_hyperv.yml")
|
||||
I18n.reload!
|
||||
end
|
||||
|
||||
def machine_stub(name)
|
||||
double(name).tap do |m|
|
||||
allow(m).to receive(:id).and_return("foo")
|
||||
allow(m).to receive(:reload).and_return(nil)
|
||||
allow(m).to receive(:ssh_info).and_return(ssh_info)
|
||||
allow(m).to receive(:ui).and_return(iso_env.ui)
|
||||
allow(m).to receive(:provider).and_return(double("provider"))
|
||||
allow(m).to receive(:state).and_return(double("state", id: :not_created))
|
||||
allow(m).to receive(:env).and_return(iso_env)
|
||||
allow(m).to receive(:config).and_return(double("config"))
|
||||
|
||||
|
||||
allow(m.ui).to receive(:error).and_return(nil)
|
||||
end
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(argv, iso_env).tap
|
||||
end
|
||||
|
||||
|
||||
describe "#execute" do
|
||||
let (:machine) { machine_stub("m") }
|
||||
let (:excludes) { { dirs: {}, files: {} } }
|
||||
|
||||
let(:config_synced_folders) do
|
||||
{ "/vagrant":
|
||||
{ type: "hyperv",
|
||||
exclude: [".git/"],
|
||||
guestpath: "/vagrant-sandbox" },
|
||||
"/vagrant/other-dir":
|
||||
{ type: "hyperv",
|
||||
exclude: [".git/"],
|
||||
guestpath: "/vagrant-sandbox-other-dir" },
|
||||
"/vagrant/relative-dir":
|
||||
{ type: "hyperv",
|
||||
exclude: [".git/"],
|
||||
guestpath: "/relative-dir" } }
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
|
||||
allow(machine.ui).to receive(:info)
|
||||
allow(machine.state).to receive(:id).and_return(:created)
|
||||
allow(machine.provider).to receive(:capability?).and_return(false)
|
||||
allow(machine.config).to receive(:vm).and_return(true)
|
||||
allow(machine.config.vm).to receive(:synced_folders).and_return(config_synced_folders)
|
||||
allow(VagrantPlugins::HyperV::SyncHelper).to receive(:expand_excludes).and_return(excludes)
|
||||
allow(helper_class).to receive(:sync_single).and_return(true)
|
||||
allow(Vagrant::Util::Busy).to receive(:busy).and_return(true)
|
||||
allow(Listen).to receive(:to).and_return(true)
|
||||
end
|
||||
|
||||
%i[Windows WSL].each do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:host_type) { host_type }
|
||||
let(:hostpath_mapping) { hostpath_mappings[host_type] }
|
||||
let (:cached_folders) do
|
||||
{ hyperv: synced_folders_dupe.dup.tap do |folders|
|
||||
folders.values.each do |folder|
|
||||
folder[:hostpath] = hostpath_mapping[folder[:guestpath].to_sym]
|
||||
end
|
||||
end }
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:synced_folders).
|
||||
with(machine, cached: true).and_return(cached_folders)
|
||||
cached_folders[:hyperv].values.each do |folders|
|
||||
allow(VagrantPlugins::HyperV::SyncHelper).to receive(:expand_path).
|
||||
with(folders[:hostpath], machine.env.root_path).and_return(folders[:hostpath])
|
||||
end
|
||||
end
|
||||
|
||||
it "syncs all configured folders" do
|
||||
expect(helper_class).to receive(:sync_single)
|
||||
subject.execute
|
||||
end
|
||||
|
||||
it "watches all configured folders for changes" do
|
||||
expect(machine.ui).to receive(:info).
|
||||
with("Doing an initial sync...")
|
||||
cached_folders[:hyperv].values.each do |folder|
|
||||
expect(machine.ui).to receive(:info).with("Watching: #{folder[:hostpath]}")
|
||||
end
|
||||
subject.execute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(argv, iso_env).tap do |s|
|
||||
allow(s).to receive(:synced_folders).and_return(synced_folders_empty)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#callback" do
|
||||
it "syncs modified folders to the proper path" do
|
||||
paths["/foo"] = [
|
||||
{ machine: machine_stub("m1"), opts: double("opts_m1") },
|
||||
{ machine: machine_stub("m2"), opts: double("opts_m2") },
|
||||
]
|
||||
paths["/bar"] = [
|
||||
{ machine: machine_stub("m3"), opts: double("opts_m3") },
|
||||
]
|
||||
|
||||
paths["/foo"].each do |data|
|
||||
expect(helper_class).to receive(:sync_single).
|
||||
with(data[:machine], data[:machine].ssh_info, data[:opts]).
|
||||
once
|
||||
end
|
||||
|
||||
m = ["/foo/bar"]
|
||||
a = []
|
||||
r = []
|
||||
subject.callback(paths, m, a, r)
|
||||
end
|
||||
|
||||
it "syncs added folders to the proper path" do
|
||||
paths["/foo"] = [
|
||||
{ machine: machine_stub("m1"), opts: double("opts_m1") },
|
||||
{ machine: machine_stub("m2"), opts: double("opts_m2") },
|
||||
]
|
||||
paths["/bar"] = [
|
||||
{ machine: machine_stub("m3"), opts: double("opts_m3") },
|
||||
]
|
||||
|
||||
paths["/foo"].each do |data|
|
||||
expect(helper_class).to receive(:sync_single).
|
||||
with(data[:machine], data[:machine].ssh_info, data[:opts]).
|
||||
once
|
||||
end
|
||||
|
||||
m = []
|
||||
a = ["/foo/bar"]
|
||||
r = []
|
||||
subject.callback(paths, m, a, r)
|
||||
end
|
||||
|
||||
it "syncs removed folders to the proper path" do
|
||||
paths["/foo"] = [
|
||||
{ machine: machine_stub("m1"), opts: double("opts_m1") },
|
||||
{ machine: machine_stub("m2"), opts: double("opts_m2") },
|
||||
]
|
||||
paths["/bar"] = [
|
||||
{ machine: machine_stub("m3"), opts: double("opts_m3") },
|
||||
]
|
||||
|
||||
paths["/foo"].each do |data|
|
||||
expect(helper_class).to receive(:sync_single).
|
||||
with(data[:machine], data[:machine].ssh_info, data[:opts]).
|
||||
once
|
||||
end
|
||||
|
||||
m = []
|
||||
a = []
|
||||
r = ["/foo/bar"]
|
||||
subject.callback(paths, m, a, r)
|
||||
end
|
||||
|
||||
it "doesn't fail if guest error occurs" do
|
||||
paths["/foo"] = [
|
||||
{ machine: machine_stub("m1"), opts: double("opts_m1") },
|
||||
{ machine: machine_stub("m2"), opts: double("opts_m2") },
|
||||
]
|
||||
paths["/bar"] = [
|
||||
{ machine: machine_stub("m3"), opts: double("opts_m3") },
|
||||
]
|
||||
|
||||
paths["/foo"].each do |data|
|
||||
expect(helper_class).to receive(:sync_single).
|
||||
with(data[:machine], data[:machine].ssh_info, data[:opts]).
|
||||
and_raise(Vagrant::Errors::MachineGuestNotReady)
|
||||
end
|
||||
|
||||
m = []
|
||||
a = []
|
||||
r = ["/foo/bar"]
|
||||
expect { subject.callback(paths, m, a, r) }.
|
||||
to_not raise_error
|
||||
end
|
||||
|
||||
it "doesn't sync machines with no ID" do
|
||||
paths["/foo"] = [
|
||||
{ machine: machine_stub("m1"), opts: double("opts_m1") },
|
||||
]
|
||||
|
||||
paths["/foo"].each do |data|
|
||||
allow(data[:machine]).to receive(:id).and_return(nil)
|
||||
expect(helper_class).to_not receive(:sync_single)
|
||||
end
|
||||
|
||||
m = []
|
||||
a = []
|
||||
r = ["/foo/bar"]
|
||||
expect { subject.callback(paths, m, a, r) }.
|
||||
to_not raise_error
|
||||
end
|
||||
|
||||
context "on failure" do
|
||||
let(:machine) { machine_stub("m1") }
|
||||
let(:opts) { double("opts_m1") }
|
||||
let(:paths) { {"/foo" => [machine: machine, opts: opts]} }
|
||||
let(:args) { [paths, ["/foo/bar"], [], []] }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Vagrant::Errors::VagrantError).
|
||||
to receive(:translate_error)
|
||||
allow(machine.ui).to receive(:error)
|
||||
end
|
||||
|
||||
context "when sync command fails" do
|
||||
before do
|
||||
expect(helper_class).to receive(:sync_single).with(machine, machine.ssh_info, opts).
|
||||
and_raise(Vagrant::Errors::VagrantError)
|
||||
end
|
||||
|
||||
it "should notify on error" do
|
||||
expect(machine.ui).to receive(:error)
|
||||
subject.callback(*args)
|
||||
end
|
||||
|
||||
it "should not raise error" do
|
||||
expect { subject.callback(*args) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,82 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/sync_helper")
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/command/sync")
|
||||
|
||||
describe VagrantPlugins::HyperV::Command::Sync do
|
||||
include_context "unit"
|
||||
|
||||
let(:argv) { [] }
|
||||
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(:communicator) { double("comm") }
|
||||
|
||||
let(:synced_folders) { {} }
|
||||
|
||||
let(:helper_class) { VagrantPlugins::HyperV::SyncHelper }
|
||||
|
||||
subject do
|
||||
described_class.new(argv, iso_env).tap do |s|
|
||||
allow(s).to receive(:synced_folders).and_return(synced_folders)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
iso_env.machine_names.each do |name|
|
||||
m = iso_env.machine(name, iso_env.default_provider)
|
||||
allow(m).to receive(:communicate).and_return(communicator)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#execute" do
|
||||
context "with a single machine" do
|
||||
let(:ssh_info) {{
|
||||
private_key_path: [],
|
||||
username: "vagrant",
|
||||
}}
|
||||
let(:provider) { double("provider") }
|
||||
let(:capability) { double("capability") }
|
||||
|
||||
let(:machine) { iso_env.machine(iso_env.machine_names[0], iso_env.default_provider) }
|
||||
|
||||
before do
|
||||
allow(communicator).to receive(:ready?).and_return(true)
|
||||
allow(machine).to receive(:ssh_info).and_return(ssh_info)
|
||||
allow(machine).to receive(:provider).and_return(provider)
|
||||
allow(provider).to receive(:capability).and_return(capability)
|
||||
|
||||
synced_folders[:hyperv] = [
|
||||
[:one, {
|
||||
hostpath: 'C:\vagrant', guestpath: '/vagrant'
|
||||
}],
|
||||
[:two, {
|
||||
hostpath: 'C:\vagrant2', guestpath: '/vagrant2'
|
||||
}]
|
||||
]
|
||||
end
|
||||
|
||||
it "doesn't sync if communicator isn't ready and exits with 1" do
|
||||
allow(communicator).to receive(:ready?).and_return(false)
|
||||
|
||||
expect(helper_class).to receive(:sync_single).never
|
||||
|
||||
expect(subject.execute).to eql(1)
|
||||
end
|
||||
|
||||
it "syncs each folder and exits successfully" do
|
||||
synced_folders[:hyperv].each do |_, opts|
|
||||
expect(helper_class).to receive(:sync_single).
|
||||
with(machine, ssh_info, opts).
|
||||
ordered
|
||||
end
|
||||
|
||||
expect(subject.execute).to eql(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
require 'json'
|
||||
require_relative "../../../base"
|
||||
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/driver")
|
||||
|
@ -117,6 +118,88 @@ describe VagrantPlugins::HyperV::Driver do
|
|||
subject.set_vm_integration_services(CustomKey: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sync_files" do
|
||||
let(:dirs) { %w[dir1 dir2] }
|
||||
let(:files) { %w[file1 file2] }
|
||||
let(:guest_ip) do
|
||||
{}.tap do |ip|
|
||||
ip["ip"] = "guest_ip"
|
||||
end
|
||||
end
|
||||
let(:windows_path) { "WIN_PATH" }
|
||||
let(:windows_temp) { "TEMP_DIR" }
|
||||
let(:wsl_temp) { "WSL_TEMP" }
|
||||
let(:file_list) { double("file") }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:read_guest_ip).and_return(guest_ip)
|
||||
allow(Vagrant::Util::Platform).to receive(:windows_temp).and_return(windows_temp)
|
||||
allow(Vagrant::Util::Subprocess).to receive(:execute).
|
||||
with("wslpath", "-u", "-a", windows_temp).and_return(double(exit_code: 0, stdout: wsl_temp))
|
||||
allow(File).to receive(:open) do |fn, type, &proc|
|
||||
proc.call file_list
|
||||
|
||||
allow(Vagrant::Util::Platform).to receive(:windows_path).
|
||||
with(fn, :disable_unc).and_return(windows_path)
|
||||
allow(FileUtils).to receive(:rm_f).with(fn)
|
||||
end.and_return(file_list)
|
||||
allow(file_list).to receive(:write).with(files.to_json)
|
||||
allow(subject).to receive(:execute).with(:sync_files,
|
||||
vm_id: vm_id,
|
||||
guest_ip: guest_ip["ip"],
|
||||
file_list: windows_path)
|
||||
end
|
||||
|
||||
after { subject.sync_files vm_id, dirs, files, is_win_guest: false }
|
||||
|
||||
%i[Windows WSL].each do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:is_wsl) { host_type == :WSL }
|
||||
let(:temp_dir) { is_wsl ? wsl_temp : windows_temp }
|
||||
|
||||
before do
|
||||
allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(is_wsl)
|
||||
end
|
||||
|
||||
it "reads guest ip" do
|
||||
expect(subject).to receive(:read_guest_ip).and_return(guest_ip)
|
||||
end
|
||||
|
||||
it "gets Windows temporary dir where dir list is written" do
|
||||
expect(Vagrant::Util::Platform).to receive(:windows_temp).and_return(windows_temp)
|
||||
end
|
||||
|
||||
if host_type == :WSL
|
||||
it "converts Windows temporary dir to Unix style for WSL" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).
|
||||
with("wslpath", "-u", "-a", windows_temp).and_return(double(exit_code: 0, stdout: wsl_temp))
|
||||
end
|
||||
end
|
||||
|
||||
it "writes dir list to temporary file" do
|
||||
expect(File).to receive(:open) do |fn, type, &proc|
|
||||
expect(fn).to match(/#{temp_dir}\/\.hv_sync_files_.*/)
|
||||
expect(type).to eq('w')
|
||||
|
||||
proc.call file_list
|
||||
|
||||
expect(Vagrant::Util::Platform).to receive(:windows_path).
|
||||
with(fn, :disable_unc).and_return(windows_path)
|
||||
expect(FileUtils).to receive(:rm_f).with(fn)
|
||||
end.and_return(file_list)
|
||||
expect(file_list).to receive(:write).with(files.to_json)
|
||||
end
|
||||
|
||||
it "calls sync files powershell script" do
|
||||
expect(subject).to receive(:execute).with(:sync_files,
|
||||
vm_id: vm_id,
|
||||
guest_ip: guest_ip["ip"],
|
||||
file_list: windows_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#execute_powershell" do
|
||||
|
|
|
@ -0,0 +1,868 @@
|
|||
require 'find'
|
||||
require 'zip'
|
||||
|
||||
require_relative "../../../base"
|
||||
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/sync_helper")
|
||||
|
||||
describe VagrantPlugins::HyperV::SyncHelper do
|
||||
subject { described_class }
|
||||
|
||||
let(:vm_id) { "vm_id" }
|
||||
let(:guest) { double("guest") }
|
||||
let(:comm) { double("comm") }
|
||||
let(:machine) { double("machine", provider: provider, guest: guest, id: vm_id, communicate: comm) }
|
||||
let(:provider) { double("provider", driver: driver) }
|
||||
let(:driver) { double("driver") }
|
||||
let(:separators) { { Windows: '\\', WSL: "/" } }
|
||||
|
||||
def random_name
|
||||
(0...8).map { ('a'..'z').to_a[rand(26)] }.join
|
||||
end
|
||||
|
||||
def generate_random_file(files, path, separator, is_directory: true)
|
||||
prefix = is_directory ? "dir" : "file"
|
||||
fn = [path, "#{prefix}_#{random_name}"].join(separator)
|
||||
files << fn
|
||||
allow(subject).to receive(:directory?).with(fn).and_return(is_directory)
|
||||
fn
|
||||
end
|
||||
|
||||
def generate_test_data(paths, separator)
|
||||
files = []
|
||||
excludes = { dirs: [], files: [] }
|
||||
includes = { dirs: [], files: [] }
|
||||
paths.map do |dir|
|
||||
files << dir
|
||||
excluded = dir.end_with?('.vagrant', '.git')
|
||||
allow(VagrantPlugins::HyperV::SyncHelper).to receive(:directory?).with(dir).and_return(true)
|
||||
(0..10).map do
|
||||
fn = generate_random_file(files, dir, separator, is_directory: false)
|
||||
if excluded
|
||||
excludes[:files] << fn
|
||||
else
|
||||
includes[:files] << fn
|
||||
end
|
||||
end
|
||||
|
||||
sub_dir = generate_random_file(files, dir, separator, is_directory: true)
|
||||
if excluded
|
||||
excludes[:dirs] << dir
|
||||
excludes[:dirs] << sub_dir
|
||||
else
|
||||
includes[:dirs] << dir
|
||||
includes[:dirs] << sub_dir
|
||||
end
|
||||
|
||||
(0..10).map do
|
||||
fn = generate_random_file(files, sub_dir, separator, is_directory: false)
|
||||
if excluded
|
||||
excludes[:files] << fn
|
||||
else
|
||||
includes[:files] << fn
|
||||
end
|
||||
end
|
||||
end
|
||||
{ files: files,
|
||||
excludes: excludes,
|
||||
includes: includes }
|
||||
end
|
||||
|
||||
def convert_path(mapping, path, host_type, guest_type, is_file: true)
|
||||
win_path = path.gsub "/vagrant", 'C:\vagrant'
|
||||
win_path.tr! "/", '\\'
|
||||
|
||||
linux_path = path.gsub 'C:\vagrant', "/vagrant"
|
||||
linux_path.tr! '\\', "/"
|
||||
|
||||
dir_win_path = is_file ? win_path.split("\\")[0..-2].join("\\") : win_path
|
||||
dir_win_path = dir_win_path[0..-2] if dir_win_path.end_with? '\\', '/'
|
||||
|
||||
dir_linux_path = is_file ? linux_path.split("/")[0..-2].join("/") : linux_path
|
||||
dir_linux_path = dir_linux_path[0..-2] if dir_linux_path.end_with? '\\', '/'
|
||||
|
||||
guest_path =
|
||||
if host_type == :WSL
|
||||
if guest_type == :linux
|
||||
dir_linux_path
|
||||
else
|
||||
dir_win_path
|
||||
end
|
||||
else
|
||||
# windows
|
||||
if guest_type == :linux
|
||||
dir_linux_path
|
||||
else
|
||||
dir_win_path
|
||||
end
|
||||
end
|
||||
mapping[:hyperv][win_path] = guest_path
|
||||
mapping[:platform][host_type == :Windows ? win_path : linux_path] = guest_path
|
||||
end
|
||||
|
||||
describe "#expand_excludes" do
|
||||
let(:hostpath) { 'vagrant' }
|
||||
let(:expanded_hostpaths) do
|
||||
{ Windows: 'C:\vagrant', WSL: "/vagrant" }
|
||||
end
|
||||
let(:exclude) { [".git/"] }
|
||||
let(:exclude_dirs) { %w[.vagrant/ .git/] }
|
||||
|
||||
%i[Windows WSL].map do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:host_type) { host_type }
|
||||
let(:is_windows) { host_type == :Windows }
|
||||
let(:separator) { separators[host_type] }
|
||||
let(:expanded_hostpath) { expanded_hostpaths[host_type] }
|
||||
|
||||
before do
|
||||
allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(false)
|
||||
allow(subject).to receive(:expand_path).with(hostpath).and_return(expanded_hostpath)
|
||||
end
|
||||
|
||||
it "expands excludes into file and dir list" do
|
||||
files = []
|
||||
dirs = []
|
||||
exclude_dirs.map do |dir|
|
||||
fullpath = [expanded_hostpath, dir[0..-2]].join(separator)
|
||||
allow(subject).to receive(:platform_join).
|
||||
with(expanded_hostpath, dir, is_windows: false).and_return(fullpath)
|
||||
|
||||
ignore_paths = []
|
||||
allow(described_class).to receive(:directory?).with(fullpath).and_return(true)
|
||||
ignore_paths << fullpath
|
||||
dirs << fullpath
|
||||
|
||||
file = generate_random_file(ignore_paths, fullpath, separator, is_directory: false)
|
||||
files << file
|
||||
|
||||
subDir = generate_random_file(ignore_paths, fullpath, separator, is_directory: true)
|
||||
dirs << subDir
|
||||
|
||||
subDirFile = generate_random_file(ignore_paths, subDir, separator, is_directory: false)
|
||||
files << subDirFile
|
||||
|
||||
allow(Dir).to receive(:glob).with(fullpath) do |arg, &proc|
|
||||
ignore_paths.each do |path|
|
||||
proc.call path
|
||||
end
|
||||
end
|
||||
end
|
||||
excludes = subject.expand_excludes(hostpath, exclude)
|
||||
expect(excludes[:dirs]).to eq(dirs)
|
||||
expect(excludes[:files]).to eq(files)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sync_single" do
|
||||
let(:hostpath) { 'vagrant' }
|
||||
let(:expanded_hostpaths) do
|
||||
{ Windows: 'C:\vagrant', WSL: "/vagrant" }
|
||||
end
|
||||
let(:guestpaths) do
|
||||
{ windows: 'C:\vagrant', linux: "/vagrant" }
|
||||
end
|
||||
let(:remote_guestdirs) do
|
||||
{ windows: 'C:\Windows\tmp', linux: "/tmp" }
|
||||
end
|
||||
let(:paths) do
|
||||
{ Windows: %w[C:\vagrant C:\vagrant\.vagrant C:\vagrant\.git C:\vagrant\test],
|
||||
WSL: %w[/vagrant /vagrant/.vagrant /vagrant/.git /vagrant/test] }
|
||||
end
|
||||
let(:exclude) { [".git/"] }
|
||||
let(:ssh_info) { { username: "vagrant" } }
|
||||
let(:no_compression) { false }
|
||||
|
||||
%i[windows linux].map do |guest_type|
|
||||
context "#{guest_type} guest" do
|
||||
let(:guest_type) { guest_type }
|
||||
let(:is_win_guest) { guest_type == :windows }
|
||||
let(:guestpath) { guestpaths[guest_type] }
|
||||
let(:remote_guestdir) { remote_guestdirs[guest_type] }
|
||||
|
||||
before { allow(guest).to receive(:name).and_return(guest_type) }
|
||||
|
||||
%i[Windows WSL].map do |host_type|
|
||||
let(:host_type) { host_type }
|
||||
let(:separator) { separators[host_type] }
|
||||
let(:input_paths) { paths[host_type] }
|
||||
let(:expanded_hostpath) { expanded_hostpaths[host_type] }
|
||||
let(:test_data) { generate_test_data input_paths, separator }
|
||||
let(:test_includes) { test_data[:includes] }
|
||||
let(:folder_opts) do
|
||||
h = { hostpath: hostpath,
|
||||
guestpath: guestpath,
|
||||
exclude: exclude }
|
||||
h = !no_compression ? h : h.dup.merge({no_compression: no_compression})
|
||||
h
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:expand_path).
|
||||
with(hostpath).and_return(expanded_hostpath)
|
||||
end
|
||||
|
||||
after { subject.sync_single(machine, ssh_info, folder_opts) }
|
||||
|
||||
context "in #{host_type} environment" do
|
||||
before do
|
||||
allow(subject).to receive(:find_includes).with(hostpath, exclude).and_return(test_includes)
|
||||
end
|
||||
|
||||
context "with no compression" do
|
||||
let(:no_compression) { true }
|
||||
let(:dir_mappings) do
|
||||
mappings = { hyperv: {}, platform: {} }
|
||||
test_includes[:dirs].map do |dir|
|
||||
convert_path(mappings, dir, host_type, guest_type, is_file: false)
|
||||
end
|
||||
mappings
|
||||
end
|
||||
let(:files_mappings) do
|
||||
mappings = { hyperv: {}, platform: {} }
|
||||
test_includes[:files].map do |file|
|
||||
convert_path(mappings, file, host_type, guest_type, is_file: true)
|
||||
end
|
||||
mappings
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:path_mapping).
|
||||
with(hostpath, guestpath, test_includes, is_win_guest: is_win_guest).
|
||||
and_return({dirs: dir_mappings, files: files_mappings})
|
||||
allow(subject).to receive(:remove_directory).
|
||||
with(machine, guestpath, is_win_guest: is_win_guest, sudo: true)
|
||||
allow(guest).to receive(:capability).
|
||||
with(:create_directories, dir_mappings[:hyperv].values, sudo: true)
|
||||
allow(subject).to receive(:hyperv_copy?).with(machine).and_return(true)
|
||||
allow(driver).to receive(:sync_files).
|
||||
with(machine.id, dir_mappings[:hyperv], files_mappings[:hyperv], is_win_guest: is_win_guest)
|
||||
end
|
||||
|
||||
context "copy with Hyper-V daemons" do
|
||||
it "calls driver#sync_files to sync all files at once" do
|
||||
expect(driver).to receive(:sync_files).
|
||||
with(machine.id, dir_mappings[:hyperv], files_mappings[:hyperv], is_win_guest: is_win_guest)
|
||||
end
|
||||
end
|
||||
|
||||
context "copy with WinRM" do
|
||||
let(:stat) { double("stat", symlink?: false)}
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:hyperv_copy?).with(machine).and_return(false)
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:file_stat).and_return(stat)
|
||||
allow(comm).to receive(:upload)
|
||||
end
|
||||
|
||||
it "calls WinRM to upload files" do
|
||||
files_mappings[:platform].each do |host_path, guest_path|
|
||||
expect(subject).to receive(:file_exist?).ordered.with(host_path).and_return(true)
|
||||
expect(subject).to receive(:file_stat).ordered.with(host_path).and_return(stat)
|
||||
expect(comm).to receive(:upload).ordered.with(host_path, guest_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "removes destination dir and creates directory structure on guest" do
|
||||
expect(subject).to receive(:remove_directory).
|
||||
with(machine, guestpath, is_win_guest: is_win_guest, sudo: true)
|
||||
expect(guest).to receive(:capability).
|
||||
with(:create_directories, dir_mappings[:hyperv].values, sudo: true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with compression" do
|
||||
let(:compression_type) { guest_type == :windows ? :zip : :tgz }
|
||||
let(:remote_guestpath) { [remote_guestdir, "remote_#{compression_type}"].join separator }
|
||||
let(:archive_name) { [expanded_hostpath, "vagrant_tmp.#{compression_type}"].join separator }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:compress_source_zip).
|
||||
with(expanded_hostpath, test_includes[:files]).and_return(archive_name)
|
||||
allow(subject).to receive(:compress_source_tgz).
|
||||
with(expanded_hostpath, test_includes[:files]).and_return(archive_name)
|
||||
allow(guest).to receive(:capability).
|
||||
with(:create_tmp_path, extension: ".#{compression_type}").and_return(remote_guestpath)
|
||||
allow(subject).to receive(:upload_file).
|
||||
with(machine, archive_name, remote_guestpath, is_win_guest: is_win_guest)
|
||||
allow(subject).to receive(:remove_directory).
|
||||
with(machine, guestpath, is_win_guest: is_win_guest, sudo: true)
|
||||
allow(guest).to receive(:capability).
|
||||
with("decompress_#{compression_type}".to_sym, remote_guestpath, guestpath, type: :directory, sudo: true)
|
||||
allow(subject).to receive(:file_exist?).with(archive_name).and_return(true)
|
||||
allow(FileUtils).to receive(:rm_f).with(archive_name)
|
||||
end
|
||||
|
||||
it "compresses the host directory to archive" do
|
||||
expect(subject).to receive("compress_source_#{compression_type}".to_sym).
|
||||
with(expanded_hostpath, test_includes[:files]).and_return(archive_name)
|
||||
end
|
||||
|
||||
it "creates temporary path on guest" do
|
||||
expect(guest).to receive(:capability).
|
||||
with(:create_tmp_path, extension: ".#{compression_type}").and_return(remote_guestpath)
|
||||
end
|
||||
|
||||
it "uploads archive file to temporary path on guest" do
|
||||
allow(subject).to receive(:upload_file).
|
||||
with(machine, archive_name, remote_guestpath, is_win_guest: is_win_guest)
|
||||
end
|
||||
|
||||
it "removes destination dir and decompresses archive file at temporary path on guest" do
|
||||
expect(subject).to receive(:remove_directory).
|
||||
with(machine, guestpath, is_win_guest: is_win_guest, sudo: true)
|
||||
expect(guest).to receive(:capability).
|
||||
with("decompress_#{compression_type}".to_sym, remote_guestpath, guestpath, type: :directory, sudo: true)
|
||||
end
|
||||
|
||||
it "removes temporary archive file" do
|
||||
expect(FileUtils).to receive(:rm_f).with(archive_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find_includes" do
|
||||
let(:hostpath) { 'vagrant' }
|
||||
let(:expanded_hostpaths) do
|
||||
{ Windows: 'C:\vagrant', WSL: "/vagrant" }
|
||||
end
|
||||
let(:exclude) { [".git/"] }
|
||||
let(:paths) do
|
||||
{ Windows: %w[C:\vagrant C:\vagrant\.vagrant C:\vagrant\.git C:\vagrant\test],
|
||||
WSL: %w[/vagrant /vagrant/.vagrant /vagrant/.git /vagrant/test] }
|
||||
end
|
||||
|
||||
%i[windows linux].map do |guest_type|
|
||||
context "#{guest_type} guest" do
|
||||
%i[Windows WSL].map do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:host_type) { host_type }
|
||||
let(:separator) { separators[host_type] }
|
||||
let(:input_paths) { paths[host_type] }
|
||||
let(:expanded_hostpath) { expanded_hostpaths[host_type] }
|
||||
let(:test_data) { generate_test_data input_paths, separator }
|
||||
let(:test_files) { test_data[:files] }
|
||||
let(:test_includes) { test_data[:includes] }
|
||||
let(:test_excludes) { test_data[:excludes] }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:expand_path).
|
||||
with(hostpath).and_return(expanded_hostpath)
|
||||
allow(subject).to receive(:expand_excludes).
|
||||
with(hostpath, exclude).and_return(test_excludes)
|
||||
allow(Find).to receive(:find).with(expanded_hostpath) do |arg, &proc|
|
||||
test_files.map do |file|
|
||||
proc.call file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
expect(described_class.send(:find_includes, hostpath, exclude)).to eq(test_includes)
|
||||
end
|
||||
|
||||
it "expands host path to full path" do
|
||||
allow(subject).to receive(:expand_path).
|
||||
with(hostpath).and_return(expanded_hostpath)
|
||||
end
|
||||
|
||||
it "expands excluded files and directories for exclusion" do
|
||||
allow(subject).to receive(:expand_excludes).
|
||||
with(hostpath, exclude).and_return(test_excludes)
|
||||
end
|
||||
|
||||
it "locates all files in expanded host path" do
|
||||
expect(Find).to receive(:find).with(expanded_hostpath)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#path_mapping" do
|
||||
let(:hostpath) { 'vagrant' }
|
||||
let(:expanded_hostpaths) do
|
||||
{ Windows: 'C:\vagrant', WSL: "/vagrant" }
|
||||
end
|
||||
let(:guestpaths) do
|
||||
{ windows: 'C:\vagrant', linux: "/vagrant" }
|
||||
end
|
||||
let(:paths) do
|
||||
{ Windows: %w[C:\vagrant C:\vagrant\.vagrant C:\vagrant\.git C:\vagrant\test],
|
||||
WSL: %w[/vagrant /vagrant/.vagrant /vagrant/.git /vagrant/test] }
|
||||
end
|
||||
|
||||
%i[windows linux].map do |guest_type|
|
||||
context "maps dirs and files for copy on #{guest_type} guest" do
|
||||
let(:guest_type) { guest_type }
|
||||
let(:is_win_guest) { guest_type == :windows }
|
||||
|
||||
%i[Windows WSL].map do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:is_windows) { host_type == :Windows }
|
||||
let(:host_type) { host_type }
|
||||
let(:separator) { separators[host_type] }
|
||||
let(:input_paths) { paths[host_type] }
|
||||
let(:expanded_hostpath) { expanded_hostpaths[host_type] }
|
||||
let(:expanded_hostpath_windows) { expanded_hostpaths[:Windows] }
|
||||
let(:guestpath) { guestpaths[guest_type] }
|
||||
let(:test_data) { generate_test_data input_paths, separator }
|
||||
let(:includes) { test_data[:includes] }
|
||||
let(:dir_mappings) do
|
||||
mappings = { hyperv: {}, platform: {} }
|
||||
includes[:dirs].map do |dir|
|
||||
convert_path(mappings, dir, host_type, guest_type, is_file: false)
|
||||
end
|
||||
mappings
|
||||
end
|
||||
let(:files_mappings) do
|
||||
mappings = { hyperv: {}, platform: {} }
|
||||
includes[:files].map do |file|
|
||||
convert_path(mappings, file, host_type, guest_type, is_file: true)
|
||||
end
|
||||
mappings
|
||||
end
|
||||
let(:opts) do
|
||||
shared_folder.dup.tap do |opts|
|
||||
opts[:guestpath] = guestpath
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:expand_path).
|
||||
with(hostpath).and_return(expanded_hostpath)
|
||||
allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(!is_windows)
|
||||
allow(Vagrant::Util::Platform).to receive(:windows_path).
|
||||
with(expanded_hostpath, :disable_unc).and_return(expanded_hostpath_windows)
|
||||
end
|
||||
|
||||
after { expect(subject.path_mapping(hostpath, guestpath, includes, is_win_guest: is_win_guest)).to eq({ dirs: dir_mappings, files: files_mappings }) }
|
||||
|
||||
it "expands host path to full path" do
|
||||
expect(subject).to receive(:expand_path).
|
||||
with(hostpath).and_return(expanded_hostpath)
|
||||
end
|
||||
|
||||
it "formats expanded full path to windows path" do
|
||||
expect(Vagrant::Util::Platform).to receive(:windows_path).
|
||||
with(expanded_hostpath, :disable_unc).and_return(expanded_hostpath_windows)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#compress_source_zip" do
|
||||
let(:windows_temps) { { Windows: 'C:\Windows\tmp', WSL: "/mnt/c/Windows/tmp" } }
|
||||
let(:paths) do
|
||||
{ Windows: 'C:\vagrant', WSL: "/vagrant" }
|
||||
end
|
||||
let(:dir_items) { { Windows: 'C:\vagrant\dir1', WSL: '/vagrant/dir1' } }
|
||||
let(:source_items) { { Windows: %w(C:\vagrant\file1 C:\vagrant\file2 C:\vagrant\dir1 C:\vagrant\dir1\file2),
|
||||
WSL: %w(/vagrant/file1 /vagrant/file2 /vagrant/dir1 /vagrant/dir1/file2) } }
|
||||
|
||||
%i[Windows WSL].map do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:is_windows) { host_type == :Windows }
|
||||
let(:host_type) { host_type }
|
||||
let(:separator) { separators[host_type] }
|
||||
let(:windows_temp) { windows_temps[host_type] }
|
||||
let(:path) { paths[host_type] }
|
||||
let(:dir_item) { dir_items[host_type] }
|
||||
let(:platform_source_items) { source_items[host_type] }
|
||||
let(:zip_path) { [windows_temp, "vagrant_test_tmp.zip"].join separator }
|
||||
let(:zip_file) { double("zip_file", path: zip_path) }
|
||||
let(:zip) { double("zip") }
|
||||
let(:stat) { double("stat", symlink?: false)}
|
||||
let(:zip_output_stream) { double("zip_output_stream") }
|
||||
let(:source_file) { double("source_file") }
|
||||
let(:content) { "ABC" }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:format_windows_temp).and_return(windows_temp)
|
||||
allow(Tempfile).to receive(:create).and_return(zip_file)
|
||||
allow(zip_file).to receive(:close)
|
||||
allow(Zip::File).to receive(:open).with(zip_path, Zip::File::CREATE).and_yield(zip)
|
||||
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:file_stat).and_return(stat)
|
||||
allow(subject).to receive(:directory?).and_return(false)
|
||||
allow(subject).to receive(:directory?).with(dir_item).and_return(true)
|
||||
|
||||
allow(zip).to receive(:get_entry).with(/[\.|dir?]/).and_raise(Errno::ENOENT)
|
||||
allow(zip).to receive(:mkdir).with(/[\.|dir?]/)
|
||||
allow(zip).to receive(:get_output_stream).with(/.*file?/).and_yield(zip_output_stream)
|
||||
allow(File).to receive(:open).with(/.*/, "rb").and_return(source_file)
|
||||
allow(source_file).to receive(:read).with(2048).and_return(content, nil)
|
||||
allow(zip_output_stream).to receive(:write).with(content)
|
||||
end
|
||||
|
||||
after { expect(subject.compress_source_zip(path, platform_source_items)).to eq(zip_path) }
|
||||
|
||||
it "creates a temporary file for writing" do
|
||||
expect(subject).to receive(:format_windows_temp).and_return(windows_temp)
|
||||
expect(Tempfile).to receive(:create).and_return(zip_file)
|
||||
expect(Zip::File).to receive(:open).with(zip_path, Zip::File::CREATE).and_yield(zip)
|
||||
end
|
||||
|
||||
it "skips directory" do
|
||||
expect(File).to receive(:open).with(dir_item, "rb").never
|
||||
end
|
||||
|
||||
it "writes file content to zip archive" do
|
||||
expect(File).to receive(:open).with(/.*/, "rb").and_return(source_file)
|
||||
expect(source_file).to receive(:read).with(2048).and_return(content, nil)
|
||||
expect(zip_output_stream).to receive(:write).with(content)
|
||||
end
|
||||
|
||||
it "processes all files" do
|
||||
allow(zip).to receive(:get_output_stream).with(/.*file?/).exactly(platform_source_items.length - 1).times
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#compress_source_tgz" do
|
||||
let(:windows_temps) { { Windows: 'C:\Windows\tmp', WSL: "/mnt/c/Windows/tmp" } }
|
||||
let(:paths) do
|
||||
{ Windows: 'C:\vagrant', WSL: "/vagrant" }
|
||||
end
|
||||
let(:dir_items) { { Windows: 'C:\vagrant\dir1', WSL: '/vagrant/dir1' } }
|
||||
let(:symlink_items) { { Windows: 'C:\vagrant\file2', WSL: '/vagrant/file2' } }
|
||||
let(:source_items) { { Windows: %w(C:\vagrant\file1 C:\vagrant\file2 C:\vagrant\dir1 C:\vagrant\dir1\file2),
|
||||
WSL: %w(/vagrant/file1 /vagrant/file2 /vagrant/dir1 /vagrant/dir1/file2) } }
|
||||
|
||||
%i[Windows WSL].map do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:is_windows) { host_type == :Windows }
|
||||
let(:host_type) { host_type }
|
||||
let(:separator) { separators[host_type] }
|
||||
let(:windows_temp) { windows_temps[host_type] }
|
||||
let(:path) { paths[host_type] }
|
||||
let(:dir_item) { dir_items[host_type] }
|
||||
let(:symlink_item) { symlink_items[host_type] }
|
||||
let(:platform_source_items) { source_items[host_type] }
|
||||
let(:tar_path) { [windows_temp, "vagrant_test_tmp.tar"].join separator }
|
||||
let(:tgz_path) { [windows_temp, "vagrant_test_tmp.tgz"].join separator }
|
||||
let(:tar_file) { double("tar_file", path: tar_path) }
|
||||
let(:tgz_file) { double("tgz_file", path: tgz_path) }
|
||||
let(:tar) { double("tar") }
|
||||
let(:tgz) { double("tgz") }
|
||||
let(:file_mode) { 0744 }
|
||||
let(:stat) { double("stat", symlink?: false, mode: file_mode)}
|
||||
let(:stat_symlink) { double("stat_symlink", symlink?: true, mode: file_mode)}
|
||||
let(:tar_io) { double("tar_io") }
|
||||
let(:source_file) { double("source_file") }
|
||||
let(:content) { "ABC" }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:format_windows_temp).and_return(windows_temp)
|
||||
allow(Tempfile).to receive(:create).and_return(tar_file, tgz_file)
|
||||
allow(File).to receive(:open).with(tar_path, "wb+").and_return(tar_file)
|
||||
allow(File).to receive(:open).with(tgz_path, "wb").and_return(tgz_file)
|
||||
allow(Gem::Package::TarWriter).to receive(:new).with(tar_file).and_return(tar)
|
||||
allow(Zlib::GzipWriter).to receive(:new).with(tgz_file).and_return(tgz)
|
||||
|
||||
allow(File).to receive(:delete).with(tar_path)
|
||||
allow(tar_file).to receive(:close)
|
||||
allow(tar_file).to receive(:read)
|
||||
allow(tar_file).to receive(:rewind)
|
||||
allow(tgz_file).to receive(:close)
|
||||
|
||||
allow(tar).to receive(:mkdir)
|
||||
allow(tar).to receive(:add_symlink)
|
||||
allow(tar).to receive(:add_file).and_yield(tar_io)
|
||||
allow(tar).to receive(:close)
|
||||
allow(tgz).to receive(:mkdir)
|
||||
allow(tgz).to receive(:write)
|
||||
allow(tgz).to receive(:close)
|
||||
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:file_stat).and_return(stat)
|
||||
allow(subject).to receive(:directory?).and_return(false)
|
||||
allow(subject).to receive(:directory?).with(dir_item).and_return(true)
|
||||
allow(File).to receive(:open).with(/.*/, "rb").and_yield(source_file)
|
||||
allow(source_file).to receive(:read).and_return(content, nil)
|
||||
allow(tar_io).to receive(:write)
|
||||
end
|
||||
|
||||
after { expect(subject.compress_source_tgz(path, platform_source_items)).to eq(tgz_path) }
|
||||
|
||||
it "creates temporary tar/tgz for writing" do
|
||||
expect(subject).to receive(:format_windows_temp).and_return(windows_temp)
|
||||
expect(Tempfile).to receive(:create).and_return(tar_file, tgz_file)
|
||||
expect(File).to receive(:open).with(tar_path, "wb+").and_return(tar_file)
|
||||
expect(File).to receive(:open).with(tgz_path, "wb").and_return(tgz_file)
|
||||
expect(Gem::Package::TarWriter).to receive(:new).with(tar_file).and_return(tar)
|
||||
expect(Zlib::GzipWriter).to receive(:new).with(tgz_file).and_return(tgz)
|
||||
end
|
||||
|
||||
it "creates directories in tar" do
|
||||
expect(tar).to receive(:mkdir).with(dir_item.sub(path, ""), file_mode)
|
||||
end
|
||||
|
||||
it "writes file content to tar archive" do
|
||||
expect(File).to receive(:open).with(/.*/, "rb").and_yield(source_file)
|
||||
expect(source_file).to receive(:read).and_return(content, nil)
|
||||
expect(tar_io).to receive(:write).with(content)
|
||||
end
|
||||
|
||||
it "deletes tar file eventually" do
|
||||
expect(File).to receive(:delete).with(tar_path)
|
||||
end
|
||||
|
||||
it "processes all files" do
|
||||
expect(tar).to receive(:add_file).with(/.*file?/, file_mode).exactly(platform_source_items.length - 1).times
|
||||
end
|
||||
|
||||
it "does not treat symlink as normal file" do
|
||||
allow(subject).to receive(:file_stat).with(symlink_item).and_return(stat_symlink)
|
||||
expect(File).to receive(:readlink).with(symlink_item).and_return("/target")
|
||||
expect(tar).to receive(:add_symlink).with(/.*file?/, "/target", file_mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#remove_directory" do
|
||||
let(:windows_path) { 'C:\Windows\Temp' }
|
||||
let(:unix_path) { "C:/Windows/Temp" }
|
||||
|
||||
[true, false].each do |sudo_flag|
|
||||
context "sudo flag: #{sudo_flag}" do
|
||||
it "calls powershell script to remove directory" do
|
||||
allow(subject).to receive(:to_windows_path).with(unix_path).and_return(windows_path)
|
||||
expect(comm).to receive(:execute).with(/.*if \(Test-Path\(\"C:\\Windows\\Temp\"\).*\n.*Remove-Item -Path \"C:\\Windows\\Temp\".*/, shell: :powershell)
|
||||
subject.remove_directory machine, unix_path, is_win_guest: true, sudo: sudo_flag
|
||||
end
|
||||
|
||||
it "calls linux command to remove directory forcibly" do
|
||||
allow(subject).to receive(:to_unix_path).with(windows_path).and_return(unix_path)
|
||||
expect(comm).to receive(:test).with("test -d '#{unix_path}'").and_return(true)
|
||||
expect(comm).to receive(:execute).with("rm -rf '#{unix_path}'", sudo: sudo_flag)
|
||||
subject.remove_directory machine, windows_path, is_win_guest: false, sudo: sudo_flag
|
||||
end
|
||||
|
||||
it "does not call rm when directory does not exist" do
|
||||
allow(subject).to receive(:to_unix_path).with(windows_path).and_return(unix_path)
|
||||
expect(comm).to receive(:test).with("test -d '#{unix_path}'").and_return(false)
|
||||
expect(comm).to receive(:execute).never
|
||||
subject.remove_directory machine, windows_path, is_win_guest: false, sudo: sudo_flag
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#format_windows_temp" do
|
||||
let(:windows_temp) { 'C:\Windows\tmp' }
|
||||
let(:unix_temp) { "/mnt/c/Windows/tmp" }
|
||||
|
||||
before { allow(Vagrant::Util::Platform).to receive(:windows_temp).and_return(windows_temp) }
|
||||
|
||||
it "returns Windows style temporary directory" do
|
||||
allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(false)
|
||||
expect(subject.format_windows_temp).to eq(windows_temp)
|
||||
end
|
||||
|
||||
it "returns Unix style temporary directory in WSL" do
|
||||
allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(true)
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).
|
||||
with("wslpath", "-u", "-a", windows_temp).and_return(double(stdout: unix_temp, exit_code: 0))
|
||||
expect(subject.format_windows_temp).to eq(unix_temp)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#upload_file" do
|
||||
let(:sources) { { Windows: 'C:\vagrant\file1.zip', WSL: "/vagrant/file1.zip" } }
|
||||
let(:new_sources) { { Windows: 'C:\Windows\tmp\file2.zip', WSL: "/mnt/c/Windows/tmp/file2.zip" } }
|
||||
let(:windows_temps) { { Windows: 'C:\Windows\tmp', WSL: "/mnt/c/Windows/tmp" } }
|
||||
let(:dests) { { windows: 'C:\vagrant\file2.zip', linux: "/vagrant/file2.zip" } }
|
||||
let(:dest_dirs) { { windows: 'C:\vagrant', linux: "/vagrant" } }
|
||||
|
||||
%i[windows linux].map do |guest_type|
|
||||
context "#{guest_type} guest" do
|
||||
let(:guest_type) { guest_type }
|
||||
let(:dest) { dests[guest_type] }
|
||||
let(:dest_dir) { dest_dirs[guest_type] }
|
||||
|
||||
%i[Windows WSL].map do |host_type|
|
||||
context "in #{host_type} environment" do
|
||||
let(:host_type) { host_type }
|
||||
let(:is_windows) { host_type == :Windows }
|
||||
let(:source) { sources[host_type] }
|
||||
let(:new_source) { new_sources[host_type] }
|
||||
let(:new_source_windows) { new_sources[:Windows] }
|
||||
|
||||
context "uploads file by Hyper-V daemons when applicable" do
|
||||
let(:windows_temp) { windows_temps[host_type] }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:hyperv_copy?).with(machine).and_return(true)
|
||||
allow(subject).to receive(:format_windows_temp).and_return(windows_temp)
|
||||
allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(!is_windows)
|
||||
allow(Vagrant::Util::Platform).to receive(:windows_path).
|
||||
with(new_source, :disable_unc).and_return(new_source_windows)
|
||||
allow(FileUtils).to receive(:rm_f)
|
||||
allow(FileUtils).to receive(:mv)
|
||||
allow(subject).to receive(:hyperv_copy)
|
||||
end
|
||||
|
||||
after { subject.upload_file machine, source, dest, is_win_guest: guest_type == :windows }
|
||||
|
||||
if host_type != :Windows
|
||||
it "moves the source file to new path with the destination filename" do
|
||||
expect(FileUtils).to receive(:mv).with(source, new_source)
|
||||
expect(FileUtils).to receive(:rm_f).with(new_source)
|
||||
end
|
||||
end
|
||||
|
||||
it "calls Hyper-V cmdlet to copy file" do
|
||||
expect(subject).to receive(:hyperv_copy).with(machine, new_source_windows, dest_dir)
|
||||
end
|
||||
end
|
||||
|
||||
it "uploads file by WinRM when Hyper-V daemons are not applicable" do
|
||||
allow(subject).to receive(:hyperv_copy?).with(machine).and_return(false)
|
||||
expect(comm).to receive(:upload).with(source, dest)
|
||||
expect(subject).to receive(:hyperv_copy).never
|
||||
subject.upload_file machine, source, dest, is_win_guest: guest_type == :windows
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_copy?" do
|
||||
before do
|
||||
allow(guest).to receive(:capability?)
|
||||
allow(guest).to receive(:capability)
|
||||
end
|
||||
|
||||
it "does not leverage Hyper-V daemons when guest does not support Hyper-V daemons" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(false)
|
||||
expect(guest).to receive(:capability).never
|
||||
end
|
||||
|
||||
it "checks whether Hyper-V daemons are running" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(true)
|
||||
expect(subject.hyperv_copy?(machine)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_copy" do
|
||||
let(:source) { 'C:\Windows\test' }
|
||||
let(:dest_dir) { "/vagrant" }
|
||||
|
||||
it "calls Copy-VMFile cmdlet to copy file to guest" do
|
||||
expect(Vagrant::Util::PowerShell).to receive(:execute_cmd).with(/.*Hyper-V\\Get-VM -Id \"vm_id\"\n.*Hyper-V\\Copy-VMFile -VM \$machine -SourcePath \"C:\\Windows\\test\" -DestinationPath \"#{dest_dir}\".*/)
|
||||
subject.hyperv_copy(machine, source, dest_dir)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#platform_join" do
|
||||
it "produces Windows-style path" do
|
||||
expect(subject.platform_join("C:", "vagrant", ".vagrant", "", is_windows: true)).to eq("C:\\vagrant\\.vagrant\\")
|
||||
end
|
||||
|
||||
it "produces Unix-style path in WSL" do
|
||||
expect(subject.platform_join("/mnt", "vagrant", ".vagrant", "", is_windows: false)).to eq("/mnt/vagrant/.vagrant/")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#platform_path" do
|
||||
let(:windows_path) { 'C:\Windows\Temp' }
|
||||
let(:unix_path) { "C:/Windows/Temp" }
|
||||
|
||||
it "returns Windows style path in Windows" do
|
||||
expect(subject.platform_path(unix_path, is_windows: true)).to eq(windows_path)
|
||||
end
|
||||
|
||||
it "returns Unix style path in WSL" do
|
||||
expect(subject.platform_path(windows_path, is_windows: false)).to eq(unix_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_windows_path" do
|
||||
let(:windows_path) { 'C:\Windows\Temp' }
|
||||
let(:unix_path) { "C:/Windows/Temp" }
|
||||
|
||||
it "converts path with unix separator to Windows" do
|
||||
expect(subject.to_windows_path(unix_path)).to eq(windows_path)
|
||||
end
|
||||
|
||||
it "keeps the original input for Windows path" do
|
||||
expect(subject.to_windows_path(windows_path)).to eq(windows_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_unix_path" do
|
||||
let(:windows_path) { '\usr\bin\test' }
|
||||
let(:unix_path) { "/usr/bin/test" }
|
||||
|
||||
it "converts path with Windows separator to Unix" do
|
||||
expect(subject.to_unix_path(windows_path)).to eq(unix_path)
|
||||
end
|
||||
|
||||
it "keeps the original input for Unix path" do
|
||||
expect(subject.to_unix_path(unix_path)).to eq(unix_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#trim_head" do
|
||||
let(:windows_path_no_heading) { 'usr\bin\test' }
|
||||
let(:unix_path_no_heading) { "usr/bin/test" }
|
||||
let(:windows_path_with_heading) { '\usr\bin\test' }
|
||||
let(:unix_path_with_heading) { "/usr/bin/test" }
|
||||
|
||||
it "keeps Windows path with no heading" do
|
||||
expect(subject.trim_head(windows_path_no_heading)).to eq(windows_path_no_heading)
|
||||
end
|
||||
|
||||
it "keeps Unix path with no heading" do
|
||||
expect(subject.trim_head(unix_path_no_heading)).to eq(unix_path_no_heading)
|
||||
end
|
||||
|
||||
it "removes heading separator from Windows path" do
|
||||
expect(subject.trim_head(windows_path_with_heading)).to eq(windows_path_no_heading)
|
||||
end
|
||||
|
||||
it "removes heading separator from Unix path" do
|
||||
expect(subject.trim_head(unix_path_with_heading)).to eq(unix_path_no_heading)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#trim_tail" do
|
||||
let(:windows_path_no_tailing) { '\usr\bin\test' }
|
||||
let(:unix_path_no_tailing) { "/usr/bin/test" }
|
||||
let(:windows_path_with_tailing) { '\usr\bin\test\\' }
|
||||
let(:unix_path_with_tailing) { "/usr/bin/test/" }
|
||||
|
||||
it "keeps Windows path with no tailing" do
|
||||
expect(subject.trim_tail(windows_path_no_tailing)).to eq(windows_path_no_tailing)
|
||||
end
|
||||
|
||||
it "keeps Unix path with no tailing" do
|
||||
expect(subject.trim_tail(unix_path_no_tailing)).to eq(unix_path_no_tailing)
|
||||
end
|
||||
|
||||
it "removes tailing separator from Windows path" do
|
||||
expect(subject.trim_tail(windows_path_with_tailing)).to eq(windows_path_no_tailing)
|
||||
end
|
||||
|
||||
it "removes tailing separator from Unix path" do
|
||||
expect(subject.trim_tail(unix_path_with_tailing)).to eq(unix_path_no_tailing)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,136 @@
|
|||
require "vagrant"
|
||||
|
||||
require Vagrant.source_root.join("test/unit/base")
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/config")
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/errors")
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/sync_helper")
|
||||
require Vagrant.source_root.join("plugins/providers/hyperv/synced_folder")
|
||||
|
||||
describe VagrantPlugins::HyperV::SyncedFolder do
|
||||
include_context "unit"
|
||||
let(:guest) { double("guest") }
|
||||
let(:ui) { double("ui") }
|
||||
let(:ssh_info) { {username: "vagrant"} }
|
||||
let(:provider) { double("provider") }
|
||||
let(:machine) do
|
||||
double("machine").tap do |m|
|
||||
allow(m).to receive(:provider_config).and_return(VagrantPlugins::HyperV::Config.new)
|
||||
allow(m).to receive(:provider_name).and_return(:hyperv)
|
||||
allow(m).to receive(:guest).and_return(guest)
|
||||
allow(m).to receive(:provider).and_return(provider)
|
||||
allow(m).to receive(:ssh_info).and_return(ssh_info)
|
||||
allow(m).to receive(:ui).and_return(ui)
|
||||
end
|
||||
end
|
||||
let(:helper_class) { VagrantPlugins::HyperV::SyncHelper }
|
||||
|
||||
subject { described_class.new }
|
||||
|
||||
before do
|
||||
I18n.load_path << Vagrant.source_root.join("templates/locales/providers_hyperv.yml")
|
||||
I18n.reload!
|
||||
machine.provider_config.finalize!
|
||||
end
|
||||
|
||||
describe "#usable?" do
|
||||
it "should be with hyperv provider" do
|
||||
allow(machine).to receive(:provider_name).and_return(:hyperv)
|
||||
expect(subject).to be_usable(machine)
|
||||
end
|
||||
|
||||
it "should not be with another provider" do
|
||||
allow(machine).to receive(:provider_name).and_return(:vmware_fusion)
|
||||
expect(subject).not_to be_usable(machine)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#share_folders" do
|
||||
let(:folders) do
|
||||
{ 'folder1' => { hostpath: 'C:\vagrant', guestpath: '/vagrant' },
|
||||
'folder2' => { hostpath: 'C:\vagrant2', guestpath: '/vagrant2' },
|
||||
'ignored' => { hostpath: 'C:\vagrant3' } }
|
||||
end
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:configure_hv_daemons).and_return(true)
|
||||
allow(ui).to receive(:output)
|
||||
allow(ui).to receive(:info)
|
||||
allow(ui).to receive(:detail)
|
||||
allow(helper_class).to receive(:sync_single).
|
||||
with(machine, ssh_info,
|
||||
hostpath: 'C:\vagrant',
|
||||
guestpath: "/vagrant")
|
||||
allow(helper_class).to receive(:sync_single).
|
||||
with(machine, ssh_info,
|
||||
hostpath: 'C:\vagrant2',
|
||||
guestpath: "/vagrant2")
|
||||
end
|
||||
|
||||
it "should sync folders" do
|
||||
subject.send(:enable, machine, folders, {})
|
||||
end
|
||||
end
|
||||
|
||||
describe "#configure_hv_daemons" do
|
||||
before do
|
||||
allow(ui).to receive(:info)
|
||||
allow(ui).to receive(:warn)
|
||||
end
|
||||
|
||||
it "runs guest which does not support capability :hyperv_daemons_running" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(false)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_falsy
|
||||
end
|
||||
|
||||
it "runs guest which has all hyperv daemons running" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(true)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "runs guest which has hyperv daemons installed but not running" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(false)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_installed).and_return(true)
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_activate).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_activate).and_return(true)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "runs guest which has hyperv daemons installed but cannot activate" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(false)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_installed).and_return(true)
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_activate).and_return(false)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_falsy
|
||||
end
|
||||
|
||||
it "runs guest which has hyperv daemons installed but activate failed" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(false)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_installed).and_return(true)
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_activate).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_activate).and_return(false)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_falsy
|
||||
end
|
||||
|
||||
it "runs guest which has no hyperv daemons and unable to install" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(false)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_installed).and_return(false)
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_install).and_return(false)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_falsy
|
||||
end
|
||||
|
||||
it "runs guest which has hyperv daemons newly installed but failed to activate" do
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_running).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_running).and_return(false)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_installed).and_return(false)
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_install).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_install).and_return(true)
|
||||
allow(guest).to receive(:capability?).with(:hyperv_daemons_activate).and_return(true)
|
||||
allow(guest).to receive(:capability).with(:hyperv_daemons_activate).and_return(false)
|
||||
expect(subject.send(:configure_hv_daemons, machine)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,241 @@
|
|||
require File.expand_path("../../../base", __FILE__)
|
||||
|
||||
require "vagrant/util/hyperv_daemons"
|
||||
|
||||
describe Vagrant::Util::HypervDaemons do
|
||||
HYPERV_DAEMON_SERVICES = %i[kvp vss fcopy]
|
||||
|
||||
include_context "unit"
|
||||
|
||||
subject do
|
||||
klass = described_class
|
||||
Class.new do
|
||||
extend klass
|
||||
end
|
||||
end
|
||||
|
||||
let(:machine) do
|
||||
double("machine").tap do |machine|
|
||||
allow(machine).to receive(:communicate).and_return(comm)
|
||||
end
|
||||
end
|
||||
let(:comm) { double("comm") }
|
||||
|
||||
def name_for(service, separator)
|
||||
['hv', service.to_s, 'daemon'].join separator
|
||||
end
|
||||
|
||||
describe "#hyperv_daemon_running" do
|
||||
%i[debian linux].each do |guest_type|
|
||||
context "guest: #{guest_type}" do
|
||||
let(:guest_type) { guest_type }
|
||||
let(:is_debian) { guest_type == :debian }
|
||||
|
||||
before do
|
||||
allow(comm).to receive(:test).with("which apt-get").and_return(is_debian)
|
||||
end
|
||||
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon: #{service}" do
|
||||
let(:service) { service }
|
||||
let(:service_name) { name_for(service, is_debian ? '-' : '_') }
|
||||
|
||||
it "checks daemon service is running" do
|
||||
expect(comm).to receive(:test).with("systemctl -q is-active #{service_name}").and_return(true)
|
||||
expect(subject.hyperv_daemon_running(machine, service)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks daemon service is not running" do
|
||||
expect(comm).to receive(:test).with("systemctl -q is-active #{service_name}").and_return(false)
|
||||
expect(subject.hyperv_daemon_running(machine, service)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_running" do
|
||||
%i[debian linux].each do |guest_type|
|
||||
context "guest: #{guest_type}" do
|
||||
let(:guest_type) { guest_type }
|
||||
|
||||
it "checks hyperv daemons are running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_running).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(subject.hyperv_daemons_running(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks hyperv daemons are not running" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_running).with(machine, service).and_return(false)
|
||||
end
|
||||
expect(subject.hyperv_daemons_running(machine)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemon_installed" do
|
||||
%i[debian linux].each do |guest_type|
|
||||
context "guest: #{guest_type}" do
|
||||
let(:guest_type) { guest_type }
|
||||
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon: #{service}" do
|
||||
let(:service) { service }
|
||||
let(:daemon_name) { name_for(service, '_') }
|
||||
|
||||
it "checks daemon is installed" do
|
||||
allow(comm).to receive(:test).with("which #{daemon_name}").and_return(true)
|
||||
expect(subject.hyperv_daemon_installed(machine, service)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks daemon is not installed" do
|
||||
allow(comm).to receive(:test).with("which #{daemon_name}").and_return(false)
|
||||
expect(subject.hyperv_daemon_installed(machine, service)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_installed" do
|
||||
%i[debian linux].each do |guest_type|
|
||||
context "guest: #{guest_type}" do
|
||||
let(:guest_type) { guest_type }
|
||||
|
||||
it "checks hyperv daemons are installed" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_installed).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(subject.hyperv_daemons_installed(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "checks hyperv daemons are not installed" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_installed).with(machine, service).and_return(false)
|
||||
end
|
||||
expect(subject.hyperv_daemons_installed(machine)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemon_activate" do
|
||||
%i[debian linux].each do |guest_type|
|
||||
context "guest: #{guest_type}" do
|
||||
let(:guest_type) { guest_type }
|
||||
let(:is_debian) { guest_type == :debian }
|
||||
|
||||
before do
|
||||
allow(comm).to receive(:test).with("which apt-get").and_return(is_debian)
|
||||
end
|
||||
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
context "daemon: #{service}" do
|
||||
let(:service) { service }
|
||||
let(:service_name) { name_for(service, is_debian ? '-' : '_') }
|
||||
|
||||
before do
|
||||
allow(comm).to receive(:test).with("systemctl enable #{service_name}", sudo: true).and_return(true)
|
||||
allow(comm).to receive(:test).with("systemctl restart #{service_name}", sudo: true).and_return(true)
|
||||
allow(comm).to receive(:test).with("systemctl -q is-active #{service_name}").and_return(true)
|
||||
end
|
||||
|
||||
context "activation succeeds" do
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_truthy }
|
||||
|
||||
it "tests whether enabling service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl enable #{service_name}", sudo: true).and_return(true)
|
||||
end
|
||||
|
||||
it "tests whether restart service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl restart #{service_name}", sudo: true).and_return(true)
|
||||
end
|
||||
|
||||
it "checks whether service is active after restart" do
|
||||
expect(comm).to receive(:test).with("systemctl -q is-active #{service_name}").and_return(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "fails to enable" do
|
||||
before { allow(comm).to receive(:test).with("systemctl enable #{service_name}", sudo: true).and_return(false) }
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_falsy }
|
||||
|
||||
it "tests whether enabling service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl enable #{service_name}", sudo: true).and_return(false)
|
||||
end
|
||||
|
||||
it "does not try to restart service" do
|
||||
expect(comm).to receive(:test).with("systemctl restart #{service_name}", sudo: true).never
|
||||
end
|
||||
|
||||
it "does not check the service status" do
|
||||
expect(comm).to receive(:test).with("systemctl -q is-active #{service_name}").never
|
||||
end
|
||||
end
|
||||
|
||||
context "fails to restart" do
|
||||
before { allow(comm).to receive(:test).with("systemctl restart #{service_name}", sudo: true).and_return(false) }
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_falsy }
|
||||
|
||||
it "tests whether enabling service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl enable #{service_name}", sudo: true).and_return(true)
|
||||
end
|
||||
|
||||
it "tests whether restart service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl restart #{service_name}", sudo: true).and_return(false)
|
||||
end
|
||||
|
||||
it "does not check the service status" do
|
||||
expect(comm).to receive(:test).with("systemctl -q is-active #{service_name}").never
|
||||
end
|
||||
end
|
||||
|
||||
context "restarts the service but still not active" do
|
||||
before { allow(comm).to receive(:test).with("systemctl -q is-active #{service_name}").and_return(false) }
|
||||
after { expect(subject.hyperv_daemon_activate(machine, service)).to be_falsy }
|
||||
|
||||
it "tests whether enabling service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl enable #{service_name}", sudo: true).and_return(true)
|
||||
end
|
||||
|
||||
it "tests whether restart service succeeds" do
|
||||
expect(comm).to receive(:test).with("systemctl restart #{service_name}", sudo: true).and_return(true)
|
||||
end
|
||||
|
||||
it "checks whether service is active after restart" do
|
||||
expect(comm).to receive(:test).with("systemctl -q is-active #{service_name}").and_return(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hyperv_daemons_activate" do
|
||||
%i[debian linux].each do |guest_type|
|
||||
context "guest: #{guest_type}" do
|
||||
let(:guest_type) { guest_type }
|
||||
|
||||
it "activates hyperv daemons" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_activate).with(machine, service).and_return(true)
|
||||
end
|
||||
expect(subject.hyperv_daemons_activate(machine)).to be_truthy
|
||||
end
|
||||
|
||||
it "fails to activate hyperv daemons" do
|
||||
HYPERV_DAEMON_SERVICES.each do |service|
|
||||
expect(subject).to receive(:hyperv_daemon_activate).with(machine, service).and_return(false)
|
||||
end
|
||||
expect(subject.hyperv_daemons_activate(machine)).to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -533,5 +533,15 @@ EOF
|
|||
expect(subject.wsl_drvfs_path?("/home/vagrant/some/path")).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe ".windows_temp" do
|
||||
let(:temp_dir) { 'C:\Users\User\AppData\Local\Temp' }
|
||||
|
||||
it "should return windows temporary directory" do
|
||||
allow(Vagrant::Util::PowerShell).to receive(:execute_cmd).
|
||||
with("(Get-Item Env:TEMP).Value").and_return(temp_dir)
|
||||
expect(subject.windows_temp).to eql(temp_dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue