diff --git a/.travis.yml b/.travis.yml index d09bd961d..ccb5c188c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,10 @@ addons: - bsdtar rvm: - - 2.3.6 - - 2.4.3 + - 2.3.7 + - 2.4.4 - 2.5.0 + - 2.5.1 branches: only: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7626f643c..c3f992d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,23 @@ IMPROVEMENTS: - commands/suspend: Introduce flag for suspending all machines [GH-9829] - commands/global-status: Improve message about removing stale entries [GH-9856] +- provider/hyper-v: Update implementation. Include support for modifications on reload [GH-9872] +- provisioners/ansible_local: Improve installation from PPA on Ubuntu guests. + The compatibility is maintained only for active long-term support (LTS) versions, + i.e. Ubuntu 12.04 (Precise Pangolin) is no longer supported. [GH-9879] BUG FIXES: +- communicator/ssh: Update ssh private key file permission handling on Windows [GH-9923, GH-9900] - core: Display plugin commands in help [GH-9808] - core: Ensure guestpath or name is set with synced_folder option and dont set guestpath if not provided [GH-9692] +- guest/debian: Fix netplan generation when using DHCP [GH-9855] +- guest/debain: Update priority of network configuration file when using networkd [GH-9867] - guest/ubuntu: Update netplan config generation to detect NetworkManager [GH-9824] +- guest/ubuntu: Fix failing Ansible installation from PPA on Bionic Beaver (18.04 LTS) [GH-9796] +- host/windows: Prevent processing of last SMB line when using net share [GH-9917] +- provisioner/chef: Prevent node_name set on configuration with chef_apply [GH-9916] +- provisioner/salt: Remove usage of masterless? config attribute [GH-9833] ## 2.1.1 (May 7, 2018) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index d980521c6..c775b5f37 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -65,12 +65,6 @@ require 'i18n' # there are issues with ciphers not being properly loaded. require 'openssl' -# If we are on Windows, load in File helpers -if Vagrant::Util::Platform.windows? - require "ffi-win32-extensions" - require "win32/file/security" -end - # Always make the version available require 'vagrant/version' global_logger = Log4r::Logger.new("vagrant::global") diff --git a/lib/vagrant/util/powershell.rb b/lib/vagrant/util/powershell.rb index 4ffa0a75b..3347e6d36 100644 --- a/lib/vagrant/util/powershell.rb +++ b/lib/vagrant/util/powershell.rb @@ -20,12 +20,16 @@ module Vagrant if !defined?(@_powershell_executable) @_powershell_executable = "powershell" - # Try to use WSL interoperability if PowerShell is not symlinked to - # the container. - if Which.which(@_powershell_executable).nil? && Platform.wsl? - @_powershell_executable += ".exe" + if Which.which(@_powershell_executable).nil? + # Try to use WSL interoperability if PowerShell is not symlinked to + # the container. + if Platform.wsl? + @_powershell_executable += ".exe" - if Which.which(@_powershell_executable).nil? + if Which.which(@_powershell_executable).nil? + @_powershell_executable = nil + end + else @_powershell_executable = nil end end @@ -41,19 +45,30 @@ module Vagrant # Execute a powershell script. # # @param [String] path Path to the PowerShell script to execute. + # @param [Array] args Command arguments + # @param [Hash] opts Options passed to execute + # @option opts [Hash] :env Custom environment variables # @return [Subprocess::Result] def self.execute(path, *args, **opts, &block) validate_install! if opts.delete(:sudo) || opts.delete(:runas) powerup_command(path, args, opts) else + if mpath = opts.delete(:module_path) + m_env = opts.fetch(:env, {}) + m_env["PSModulePath"] = "$env:PSModulePath+';#{mpath}'" + opts[:env] = m_env + end + if env = opts.delete(:env) + env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " + end command = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", - "&('#{path}')", + "#{resize_console}#{env}&('#{path}')", args ].flatten @@ -68,10 +83,20 @@ module Vagrant # Execute a powershell command. # # @param [String] command PowerShell command to execute. + # @param [Hash] opts Extra options + # @option opts [Hash] :env Custom environment variables # @return [nil, String] Returns nil if exit code is non-zero. # Returns stdout string if exit code is zero. - def self.execute_cmd(command) + def self.execute_cmd(command, **opts) validate_install! + if mpath = opts.delete(:module_path) + m_env = opts.fetch(:env, {}) + m_env["PSModulePath"] = "$env:PSModulePath+';#{mpath}'" + opts[:env] = m_env + end + if env = opts.delete(:env) + env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " + end c = [ executable, "-NoLogo", @@ -79,7 +104,7 @@ module Vagrant "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", - command + "#{resize_console}#{env}#{command}" ].flatten.compact r = Subprocess.execute(*c) @@ -91,9 +116,18 @@ module Vagrant # # @param [String] command PowerShell command to execute. # @param [Hash] opts A collection of options for subprocess::execute + # @option opts [Hash] :env Custom environment variables # @param [Block] block Ruby block def self.execute_inline(*command, **opts, &block) validate_install! + if mpath = opts.delete(:module_path) + m_env = opts.fetch(:env, {}) + m_env["PSModulePath"] = "$env:PSModulePath+';#{mpath}'" + opts[:env] = m_env + end + if env = opts.delete(:env) + env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " + end c = [ executable, "-NoLogo", @@ -101,7 +135,7 @@ module Vagrant "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", - command + "#{resize_console}#{env}#{command}" ].flatten.compact c << opts @@ -220,6 +254,19 @@ module Vagrant def self.reset! instance_variables.each(&method(:remove_instance_variable)) end + + # @private + # This is a helper method that provides the PowerShell command to resize + # the "console" to prevent output wrapping or truncating. An environment + # variable guard is provided to disable the behavior in cases where it + # may cause unexpected results (VAGRANT_POWERSHELL_RESIZE_DISABLE) + def self.resize_console + if ENV["VAGRANT_POWERSHELL_RESIZE_DISABLE"] + "" + else + "$host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(512,50); " + end + end end end end diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index 3d3d5a4b8..1ae8b9047 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -194,17 +194,9 @@ module VagrantPlugins f.write(priv) end - # Adjust private key file permissions - if Vagrant::Util::Platform.windows? - begin - priv_path = @machine.data_dir.join("private_key").to_s - File.set_permissions(priv_path, Etc.getlogin => File::FULL) - rescue => e - @logger.warn("Error encountered during private key permissions set - " \ - "#{e.class}: #{e.message}") - end - else - @machine.data_dir.join("private_key").chmod(0600) + # Adjust private key file permissions if host provides capability + if @machine.env.host.capability?(:set_ssh_key_permissions) + @machine.env.host.capability(:set_ssh_key_permissions, @machine.data_dir.join("private_key")) end # Remove the old key if it exists diff --git a/plugins/guests/debian/cap/configure_networks.rb b/plugins/guests/debian/cap/configure_networks.rb index e8bf24e95..35ffba2d2 100644 --- a/plugins/guests/debian/cap/configure_networks.rb +++ b/plugins/guests/debian/cap/configure_networks.rb @@ -37,7 +37,9 @@ module VagrantPlugins ethernets = {}.tap do |e_nets| networks.each do |network| e_config = {}.tap do |entry| - if network[:ip] + if network[:type].to_s == "dhcp" + entry["dhcp4"] = true + else mask = network[:netmask] if mask && IPAddr.new(network[:ip]).ipv4? begin @@ -47,8 +49,6 @@ module VagrantPlugins end end entry["addresses"] = [[network[:ip], mask].compact.join("/")] - else - entry["dhcp4"] = true end if network[:gateway] entry["gateway4"] = network[:gateway] @@ -107,7 +107,7 @@ module VagrantPlugins end remote_path = upload_tmp_file(comm, net_conf.join("\n")) - dest_path = "#{NETWORKD_DIRECTORY}/10-vagrant-#{dev_name}.network" + dest_path = "#{NETWORKD_DIRECTORY}/50-vagrant-#{dev_name}.network" comm.sudo(["mkdir -p #{NETWORKD_DIRECTORY}", "mv -f '#{remote_path}' '#{dest_path}'", "chown root:root '#{dest_path}'", diff --git a/plugins/hosts/bsd/cap/ssh.rb b/plugins/hosts/bsd/cap/ssh.rb new file mode 100644 index 000000000..9d38c8bea --- /dev/null +++ b/plugins/hosts/bsd/cap/ssh.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module HostBSD + module Cap + class SSH + # Set the ownership and permissions for SSH + # private key + # + # @param [Vagrant::Environment] env + # @param [Pathname] key_path + def self.set_ssh_key_permissions(env, key_path) + key_path.chmod(0600) + end + end + end + end +end diff --git a/plugins/hosts/bsd/plugin.rb b/plugins/hosts/bsd/plugin.rb index 359d4f7ad..ce79ede31 100644 --- a/plugins/hosts/bsd/plugin.rb +++ b/plugins/hosts/bsd/plugin.rb @@ -35,6 +35,11 @@ module VagrantPlugins require_relative "cap/nfs" Cap::NFS end + + host_capability("bsd", "set_ssh_key_permissions") do + require_relative "cap/ssh" + Cap::SSH + end end end end diff --git a/plugins/hosts/linux/cap/ssh.rb b/plugins/hosts/linux/cap/ssh.rb new file mode 100644 index 000000000..c3a17a5f7 --- /dev/null +++ b/plugins/hosts/linux/cap/ssh.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module HostLinux + module Cap + class SSH + # Set the ownership and permissions for SSH + # private key + # + # @param [Vagrant::Environment] env + # @param [Pathname] key_path + def self.set_ssh_key_permissions(env, key_path) + key_path.chmod(0600) + end + end + end + end +end diff --git a/plugins/hosts/linux/plugin.rb b/plugins/hosts/linux/plugin.rb index 8b89fc522..a4c6311bf 100644 --- a/plugins/hosts/linux/plugin.rb +++ b/plugins/hosts/linux/plugin.rb @@ -47,6 +47,11 @@ module VagrantPlugins require_relative "cap/nfs" Cap::NFS end + + host_capability("linux", "set_ssh_key_permissions") do + require_relative "cap/ssh" + Cap::SSH + end end end end diff --git a/plugins/hosts/windows/cap/smb.rb b/plugins/hosts/windows/cap/smb.rb index e767eb202..c0ed42a49 100644 --- a/plugins/hosts/windows/cap/smb.rb +++ b/plugins/hosts/windows/cap/smb.rb @@ -38,7 +38,7 @@ module VagrantPlugins m_id = machine_id(machine) prune_shares = existing_shares.map do |share_name, share_info| - if share_info["Description"].start_with?("vgt-#{m_id}-") + if share_info["Description"].to_s.start_with?("vgt-#{m_id}-") @@logger.info("removing smb share name=#{share_name} id=#{m_id}") share_name else @@ -159,6 +159,8 @@ module VagrantPlugins share_names = result.strip.split("\n").map do |line| line.strip.split(/\s+/).first end + # Last item is command completion notification so remove it + share_names.pop shares = {} share_names.each do |share_name| shares[share_name] = {} diff --git a/plugins/hosts/windows/cap/ssh.rb b/plugins/hosts/windows/cap/ssh.rb new file mode 100644 index 000000000..63415bcb5 --- /dev/null +++ b/plugins/hosts/windows/cap/ssh.rb @@ -0,0 +1,26 @@ +module VagrantPlugins + module HostWindows + module Cap + class SSH + # Set the ownership and permissions for SSH + # private key + # + # @param [Vagrant::Environment] env + # @param [Pathname] key_path + def self.set_ssh_key_permissions(env, key_path) + script_path = Host.scripts_path.join("set_ssh_key_permissions.ps1") + result = Vagrant::Util::PowerShell.execute( + script_path.to_s, "-KeyPath", key_path.to_s, + module_path: Host.modules_path.to_s + ) + if result.exit_code != 0 + raise Vagrant::Errors::PowerShellError, + script: script_path, + stderr: result.stderr + end + result + end + end + end + end +end diff --git a/plugins/hosts/windows/host.rb b/plugins/hosts/windows/host.rb index 4491f629a..5658f9b91 100644 --- a/plugins/hosts/windows/host.rb +++ b/plugins/hosts/windows/host.rb @@ -8,6 +8,16 @@ module VagrantPlugins def detect?(env) Vagrant::Util::Platform.windows? end + + # @return [Pathname] Path to scripts directory + def self.scripts_path + Pathname.new(File.expand_path("../scripts", __FILE__)) + end + + # @return [Pathname] Path to modules directory + def self.modules_path + scripts_path.join("utils") + end end end end diff --git a/plugins/hosts/windows/plugin.rb b/plugins/hosts/windows/plugin.rb index 78d9239e1..5668141fb 100644 --- a/plugins/hosts/windows/plugin.rb +++ b/plugins/hosts/windows/plugin.rb @@ -55,6 +55,11 @@ module VagrantPlugins require_relative "cap/configured_ip_addresses" Cap::ConfiguredIPAddresses end + + host_capability("windows", "set_ssh_key_permissions") do + require_relative "cap/ssh" + Cap::SSH + end end end end diff --git a/plugins/hosts/windows/scripts/set_ssh_key_permissions.ps1 b/plugins/hosts/windows/scripts/set_ssh_key_permissions.ps1 new file mode 100644 index 000000000..9fc5a1d20 --- /dev/null +++ b/plugins/hosts/windows/scripts/set_ssh_key_permissions.ps1 @@ -0,0 +1,17 @@ +#Requires -Modules VagrantSSH + +param( + [Parameter(Mandatory=$true)] + [string] $KeyPath, + [Parameter(Mandatory=$false)] + [string] $Principal=$null +) + +$ErrorActionPreference = "Stop" + +try { + Set-SSHKeyPermissions -SSHKeyPath $KeyPath -Principal $Principal +} catch { + Write-Error "Failed to set permissions on key: ${PSItem}" + exit 1 +} diff --git a/plugins/hosts/windows/scripts/utils/VagrantSSH/VagrantSSH.psm1 b/plugins/hosts/windows/scripts/utils/VagrantSSH/VagrantSSH.psm1 new file mode 100644 index 000000000..5d49a93db --- /dev/null +++ b/plugins/hosts/windows/scripts/utils/VagrantSSH/VagrantSSH.psm1 @@ -0,0 +1,26 @@ +# Vagrant SSH capability functions + +function Set-SSHKeyPermissions { + param ( + [parameter(Mandatory=$true)] + [string] $SSHKeyPath, + [parameter(Mandatory=$false)] + [string] $Principal=$null + ) + + if(!$Principal) { + $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + } + + # Create the new ACL we want to apply + $NewAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule( + $Principal, "FullControl", "None", "None", "Allow") + $ACL = Get-ACL "${SSHKeyPath}" + # Disable inherited rules + $ACL.SetAccessRuleProtection($true, $false) + # Scrub all existing ACLs from the file + $ACL.Access | %{$ACL.RemoveAccessRule($_)} + # Apply the new ACL + $ACL.SetAccessRule($NewAccessRule) + Set-ACL "${SSHKeyPath}" $ACL +} diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index f5e9b25c6..a42c9966a 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -140,6 +140,8 @@ module VagrantPlugins end b2.use Provision + b2.use Configure + b2.use SetName b2.use NetSetVLan b2.use NetSetMac b2.use StartInstance @@ -288,6 +290,7 @@ module VagrantPlugins autoload :Export, action_root.join("export") autoload :CheckEnabled, action_root.join("check_enabled") + autoload :Configure, action_root.join("configure") autoload :DeleteVM, action_root.join("delete_vm") autoload :Import, action_root.join("import") autoload :Package, action_root.join("package") @@ -304,6 +307,7 @@ module VagrantPlugins autoload :SnapshotDelete, action_root.join("snapshot_delete") autoload :SnapshotRestore, action_root.join("snapshot_restore") autoload :SnapshotSave, action_root.join("snapshot_save") + autoload :SetName, action_root.join("set_name") end end end diff --git a/plugins/providers/hyperv/action/configure.rb b/plugins/providers/hyperv/action/configure.rb new file mode 100644 index 000000000..30831ac12 --- /dev/null +++ b/plugins/providers/hyperv/action/configure.rb @@ -0,0 +1,104 @@ +require "fileutils" + +require "log4r" + +module VagrantPlugins + module HyperV + module Action + class Configure + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::configure") + end + + def call(env) + switches = env[:machine].provider.driver.execute(:get_switches) + if switches.empty? + raise Errors::NoSwitches + end + + switch = nil + env[:machine].config.vm.networks.each do |type, opts| + next if type != :public_network && type != :private_network + + if opts[:bridge] + @logger.debug("Looking for switch with name or ID: #{opts[:bridge]}") + switch = switches.find{ |s| + s["Name"].downcase == opts[:bridge].to_s.downcase || + s["Id"].downcase == opts[:bridge].to_s.downcase + } + if switch + @logger.debug("Found switch - Name: #{switch["Name"]} ID: #{switch["Id"]}") + switch = switch["Id"] + break + end + end + end + + # If we already configured previously don't prompt for switch + sentinel = env[:machine].data_dir.join("action_configure") + + if !switch && !sentinel.file? + if switches.length > 1 + env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") + switches.each_index do |i| + switch = switches[i] + env[:ui].detail("#{i+1}) #{switch["Name"]}") + end + env[:ui].detail(" ") + + switch = nil + while !switch + switch = env[:ui].ask("What switch would you like to use? ") + next if !switch + switch = switch.to_i - 1 + switch = nil if switch < 0 || switch >= switches.length + end + switch = switches[switch]["Id"] + else + switch = switches.first["Id"] + @logger.debug("Only single switch available so using that.") + end + end + + options = { + "VMID" => env[:machine].id, + "SwitchID" => switch, + "Memory" => env[:machine].provider_config.memory, + "MaxMemory" => env[:machine].provider_config.maxmemory, + "Processors" => env[:machine].provider_config.cpus, + "AutoStartAction" => env[:machine].provider_config.auto_start_action, + "AutoStopAction" => env[:machine].provider_config.auto_stop_action, + "EnableCheckpoints" => env[:machine].provider_config.enable_checkpoints, + "VirtualizationExtensions" => !!env[:machine].provider_config.enable_virtualization_extensions, + } + options.delete_if{|_,v| v.nil? } + + env[:ui].detail("Configuring the VM...") + env[:machine].provider.driver.execute(:configure_vm, options) + + # Create the sentinel + if !sentinel.file? + sentinel.open("w") do |f| + f.write(Time.now.to_i.to_s) + end + end + + if !env[:machine].provider_config.vm_integration_services.empty? + env[:ui].detail("Setting VM Integration Services") + + env[:machine].provider_config.vm_integration_services.each do |key, value| + state = value ? "enabled" : "disabled" + env[:ui].output("#{key} is #{state}") + end + + env[:machine].provider.driver.set_vm_integration_services( + env[:machine].provider_config.vm_integration_services) + end + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 434d991ad..25229e3d0 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -1,11 +1,13 @@ require "fileutils" - require "log4r" module VagrantPlugins module HyperV module Action class Import + + VALID_HD_EXTENSIONS = [".vhd".freeze, ".vhdx".freeze].freeze + def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::hyperv::import") @@ -14,160 +16,64 @@ module VagrantPlugins def call(env) vm_dir = env[:machine].box.directory.join("Virtual Machines") hd_dir = env[:machine].box.directory.join("Virtual Hard Disks") - memory = env[:machine].provider_config.memory - maxmemory = env[:machine].provider_config.maxmemory - cpus = env[:machine].provider_config.cpus - vmname = env[:machine].provider_config.vmname - differencing_disk = env[:machine].provider_config.differencing_disk - auto_start_action = env[:machine].provider_config.auto_start_action - auto_stop_action = env[:machine].provider_config.auto_stop_action - enable_virtualization_extensions = env[:machine].provider_config.enable_virtualization_extensions - vm_integration_services = env[:machine].provider_config.vm_integration_services - - env[:ui].output("Configured Dynamic memory allocation, maxmemory is #{maxmemory}") if maxmemory - env[:ui].output("Configured startup memory is #{memory}") if memory - env[:ui].output("Configured cpus number is #{cpus}") if cpus - env[:ui].output("Configured enable virtualization extensions is #{enable_virtualization_extensions}") if enable_virtualization_extensions - env[:ui].output("Configured vmname is #{vmname}") if vmname - env[:ui].output("Configured differencing disk instead of cloning") if differencing_disk - env[:ui].output("Configured automatic start action is #{auto_start_action}") if auto_start_action - env[:ui].output("Configured automatic stop action is #{auto_stop_action}") if auto_stop_action if !vm_dir.directory? || !hd_dir.directory? - raise Errors::BoxInvalid + @logger.error("Required virtual machine directory not found!") + raise Errors::BoxInvalid, name: env[:machine].name + end + + valid_config_ext = [".xml"] + if env[:machine].provider.driver.has_vmcx_support? + valid_config_ext << ".vmcx" end config_path = nil - config_type = nil - vm_dir.each_child do |f| - if f.extname.downcase == '.xml' - @logger.debug("Found XML config...") - config_path = f - config_type = 'xml' + vm_dir.each_child do |file| + if valid_config_ext.include?(file.extname.downcase) + config_path = file break end end - vmcx_support = env[:machine].provider.driver.execute("has_vmcx_support.ps1", {})['result'] - if vmcx_support - vm_dir.each_child do |f| - if f.extname.downcase == '.vmcx' - @logger.debug("Found VMCX config and support...") - config_path = f - config_type = 'vmcx' - break - end - end + if !config_path + @logger.error("Failed to locate box configuration path") + raise Errors::BoxInvalid, name: env[:machine].name + else + @logger.info("Found box configuration path: #{config_path}") end image_path = nil - image_ext = nil - image_filename = nil - hd_dir.each_child do |f| - if %w{.vhd .vhdx}.include?(f.extname.downcase) - image_path = f - image_ext = f.extname.downcase - image_filename = File.basename(f, image_ext) + hd_dir.each_child do |file| + if VALID_HD_EXTENSIONS.include?(file.extname.downcase) + image_path = file break end end - if !config_path || !image_path - raise Errors::BoxInvalid + if !image_path + @logger.error("Failed to locate box image path") + raise Errors::BoxInvalid, name: env[:machine].name + else + @logger.info("Found box image path: #{image_path}") end env[:ui].output("Importing a Hyper-V instance") + dest_path = env[:machine].data_dir.join("Virtual Hard Disks").join(image_path.basename).to_s - switches = env[:machine].provider.driver.execute("get_switches.ps1", {}) - raise Errors::NoSwitches if switches.empty? - - switch = nil - env[:machine].config.vm.networks.each do |type, opts| - next if type != :public_network && type != :private_network - - switchToFind = opts[:bridge] - - if switchToFind - @logger.debug("Looking for switch with name: #{switchToFind}") - switch = switches.find { |s| s["Name"].downcase == switchToFind.downcase }["Id"] - @logger.debug("Found switch: #{switch}") - end - end - - if switch.nil? - if switches.length > 1 - env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") - switches.each_index do |i| - switch = switches[i] - env[:ui].detail("#{i+1}) #{switch["Name"]}") - end - env[:ui].detail(" ") - - switch = nil - while !switch - switch = env[:ui].ask("What switch would you like to use? ") - next if !switch - switch = switch.to_i - 1 - switch = nil if switch < 0 || switch >= switches.length - end - switch = switches[switch]["Id"] - else - switch = switches[0]["Id"] - end - end - - env[:ui].detail("Cloning virtual hard drive...") - source_path = image_path.to_s - dest_path = env[:machine].data_dir.join("Virtual Hard Disks").join("#{image_filename}#{image_ext}").to_s - - # Still hard copy the disk of old XML configurations - if config_type == 'xml' - if differencing_disk - env[:machine].provider.driver.execute("clone_vhd.ps1", {Source: source_path, Destination: dest_path}) - else - FileUtils.mkdir_p(env[:machine].data_dir.join("Virtual Hard Disks")) - FileUtils.cp(source_path, dest_path) - end - end - image_path = dest_path - - # We have to normalize the paths to be Windows paths since - # we're executing PowerShell. options = { - vm_config_file: config_path.to_s.gsub("/", "\\"), - vm_config_type: config_type, - source_path: source_path.to_s, - dest_path: dest_path, - data_path: env[:machine].data_dir.to_s.gsub("/", "\\") + "VMConfigFile" => config_path.to_s.gsub("/", "\\"), + "DestinationPath" => dest_path.to_s.gsub("/", "\\"), + "DataPath" => env[:machine].data_dir.to_s.gsub("/", "\\"), + "LinkedClone" => !!env[:machine].provider_config.linked_clone, + "SourcePath" => image_path.to_s.gsub("/", "\\"), + "VMName" => env[:machine].provider_config.vmname, } - options[:switchid] = switch if switch - options[:memory] = memory if memory - options[:maxmemory] = maxmemory if maxmemory - options[:cpus] = cpus if cpus - options[:vmname] = vmname if vmname - options[:auto_start_action] = auto_start_action if auto_start_action - options[:auto_stop_action] = auto_stop_action if auto_stop_action - options[:differencing_disk] = differencing_disk if differencing_disk - options[:enable_virtualization_extensions] = "True" if enable_virtualization_extensions and enable_virtualization_extensions == true + env[:ui].detail("Creating and registering the VM...") server = env[:machine].provider.driver.import(options) - env[:ui].detail("Setting VM Integration Services") - vm_integration_services.each do |key, value| - state = false - if value === true - state = "enabled" - elsif value === false - state = "disabled" - end - env[:ui].output("#{key} is #{state}") if state - end - - vm_integration_services[:VmId] = server["id"] - env[:machine].provider.driver.set_vm_integration_services(vm_integration_services) - - env[:ui].detail("Successfully imported a VM with name: #{server['name']}") + env[:ui].detail("Successfully imported VM") env[:machine].id = server["id"] @app.call(env) end diff --git a/plugins/providers/hyperv/action/set_name.rb b/plugins/providers/hyperv/action/set_name.rb new file mode 100644 index 000000000..91a82695a --- /dev/null +++ b/plugins/providers/hyperv/action/set_name.rb @@ -0,0 +1,43 @@ +require "log4r" + +module VagrantPlugins + module HyperV + module Action + class SetName + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::set_name") + end + + def call(env) + name = env[:machine].provider_config.vmname + + # If we already set the name before, then don't do anything + sentinel = env[:machine].data_dir.join("action_set_name") + if !name && sentinel.file? + @logger.info("Default name was already set before, not doing it again.") + return @app.call(env) + end + + # If no name was manually set, then use a default + if !name + prefix = "#{env[:root_path].basename.to_s}_#{env[:machine].name}" + prefix.gsub!(/[^-a-z0-9_]/i, "") + + # milliseconds + random number suffix to allow for simultaneous + # `vagrant up` of the same box in different dirs + name = prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}" + end + + env[:machine].provider.driver.set_name(name) + + # Create the sentinel + sentinel.open("w") do |f| + f.write(Time.now.to_i.to_s) + end + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 9a4296701..3403ef682 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -3,18 +3,49 @@ require "vagrant" module VagrantPlugins module HyperV class Config < Vagrant.plugin("2", :config) - attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] - attr_accessor :memory # Memory size in mb @return [Integer] - attr_accessor :maxmemory # Maximal memory size in mb enables dynamical memory allocation @return [Integer] - attr_accessor :cpus # Number of cpu's @return [Integer] - attr_accessor :vmname # Name that will be shoen in Hyperv Manager @return [String] - attr_accessor :vlan_id # VLAN ID for network interface for the virtual machine. @return [Integer] - attr_accessor :mac # MAC address for network interface for the virtual machine. @return [String] - attr_accessor :differencing_disk # Create differencing disk instead of cloning whole VHD [Boolean] - attr_accessor :auto_start_action #action on automatic start of VM. Values: Nothing, StartIfRunning, Start - attr_accessor :auto_stop_action #action on automatic stop of VM. Values: ShutDown, TurnOff, Save - attr_accessor :enable_virtualization_extensions # Enable virtualization extensions (nested virtualization). Values: true, false - attr_accessor :vm_integration_services # Options for VMServiceIntegration [Hash] + # Allowed automatic start actions for VM + ALLOWED_AUTO_START_ACTIONS = [ + "Nothing".freeze, + "StartIfRunning".freeze, + "Start".freeze + ].freeze + + # Allowed automatic stop actions for VM + ALLOWED_AUTO_STOP_ACTIONS = [ + "ShutDown".freeze, + "TurnOff".freeze, + "Save".freeze + ].freeze + + # @return [Integer] Seconds to wait for an IP address when booting + attr_accessor :ip_address_timeout + # @return [Integer] Memory size in MB + attr_accessor :memory + # @return [Integer] Maximum memory size in MB. Enables dynamic memory. + attr_accessor :maxmemory + # @return [Integer] Number of CPUs + attr_accessor :cpus + # @return [String] Name of the VM (Shown in the Hyper-V Manager) + attr_accessor :vmname + # @return [Integer] VLAN ID for network interface + attr_accessor :vlan_id + # @return [String] MAC address for network interface + attr_accessor :mac + # @return [Boolean] Create linked clone instead of full clone + # @note **DEPRECATED** use #linked_clone instead + attr_accessor :differencing_disk + # @return [Boolean] Create linked clone instead of full clone + attr_accessor :linked_clone + # @return [String] Automatic action on start of host. Default: Nothing (Nothing, StartIfRunning, Start) + attr_accessor :auto_start_action + # @return [String] Automatic action on stop of host. Default: ShutDown (ShutDown, TurnOff, Save) + attr_accessor :auto_stop_action + # @return [Boolean] Enable automatic checkpoints. Default: false + attr_accessor :enable_checkpoints + # @return [Boolean] Enable virtualization extensions + attr_accessor :enable_virtualization_extensions + # @return [Hash] Options for VMServiceIntegration + attr_accessor :vm_integration_services def initialize @ip_address_timeout = UNSET_VALUE @@ -24,21 +55,23 @@ module VagrantPlugins @vmname = UNSET_VALUE @vlan_id = UNSET_VALUE @mac = UNSET_VALUE + @linked_clone = UNSET_VALUE @differencing_disk = UNSET_VALUE @auto_start_action = UNSET_VALUE @auto_stop_action = UNSET_VALUE @enable_virtualization_extensions = UNSET_VALUE - @vm_integration_services = { - guest_service_interface: UNSET_VALUE, - heartbeat: UNSET_VALUE, - key_value_pair_exchange: UNSET_VALUE, - shutdown: UNSET_VALUE, - time_synchronization: UNSET_VALUE, - vss: UNSET_VALUE - } + @enable_checkpoints = UNSET_VALUE + @vm_integration_services = {} end def finalize! + if @differencing_disk != UNSET_VALUE + @_differencing_disk_deprecation = true + end + @linked_clone = false if @linked_clone == UNSET_VALUE + @differencing_disk = false if @differencing_disk == UNSET_VALUE + @linked_clone ||= @differencing_disk + @differencing_disk ||= @linked_clone if @ip_address_timeout == UNSET_VALUE @ip_address_timeout = 120 end @@ -48,20 +81,46 @@ module VagrantPlugins @vmname = nil if @vmname == UNSET_VALUE @vlan_id = nil if @vlan_id == UNSET_VALUE @mac = nil if @mac == UNSET_VALUE - @differencing_disk = false if @differencing_disk == UNSET_VALUE - @auto_start_action = nil if @auto_start_action == UNSET_VALUE - @auto_stop_action = nil if @auto_stop_action == UNSET_VALUE - @enable_virtualization_extensions = false if @enable_virtualization_extensions == UNSET_VALUE # TODO will this work? - @vm_integration_services.each { |key, value| - @vm_integration_services[key] = nil if value == UNSET_VALUE - } - @vm_integration_services = nil if @vm_integration_services.length == 0 + @auto_start_action = "Nothing" if @auto_start_action == UNSET_VALUE + @auto_stop_action = "ShutDown" if @auto_stop_action == UNSET_VALUE + @enable_virtualization_extensions = false if @enable_virtualization_extensions == UNSET_VALUE + if @enable_checkpoints == UNSET_VALUE + @enable_checkpoints = false + else + @enable_checkpoints = !!@enable_checkpoints + end end def validate(machine) errors = _detected_errors + if @_differencing_disk_deprecation && machine + machine.ui.warn I18n.t("vagrant_hyperv.config.differencing_disk_deprecation") + end + + if !vm_integration_services.is_a?(Hash) + errors << I18n.t("vagrant_hyperv.config.invalid_integration_services_type", + received: vm_integration_services.class) + else + vm_integration_services.each do |key, value| + if ![true, false].include?(value) + errors << I18n.t("vagrant_hyperv.config.invalid_integration_services_entry", + entry_name: name, entry_value: value) + end + end + end + + if !ALLOWED_AUTO_START_ACTIONS.include?(auto_start_action) + errors << I18n.t("vagrant_hyperv.config.invalid_auto_start_action", action: auto_start_action, + allowed_actions: ALLOWED_AUTO_START_ACTIONS.join(", ")) + end + + if !ALLOWED_AUTO_STOP_ACTIONS.include?(auto_stop_action) + errors << I18n.t("vagrant_hyperv.config.invalid_auto_stop_action", action: auto_stop_action, + allowed_actions: ALLOWED_AUTO_STOP_ACTIONS.join(", ")) + end + {"Hyper-V" => errors} end end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 359e14b12..04efa3eb1 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -10,19 +10,41 @@ module VagrantPlugins ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m + # Name mapping for integration services for nicer keys + INTEGRATION_SERVICES_MAP = { + guest_service_interface: "Guest Service Interface", + heartbeat: "Heartbeat", + key_value_pair_exchange: "Key-Value Pair Exchange", + shutdown: "Shutdown", + time_synchronization: "Time Synchronization", + vss: "VSS", + } + + # @return [String] VM ID attr_reader :vm_id def initialize(id) @vm_id = id end - def execute(path, options) - r = execute_powershell(path, options) - if r.exit_code != 0 - raise Errors::PowerShellError, - script: path, - stderr: r.stderr + # @return [Boolean] Supports VMCX + def has_vmcx_support? + !!execute(:has_vmcx_support)["result"] + end + + # Execute a PowerShell command and process the results + # + # @param [String] path Path to PowerShell script + # @param [Hash] options Options to pass to command + # + # @return [Object, nil] If the command returned JSON content + # it will be parsed and returned, otherwise + # nil will be returned + def execute(path, options={}) + if path.is_a?(Symbol) + path = "#{path}.ps1" end + r = execute_powershell(path, options) # We only want unix-style line endings within Vagrant r.stdout.gsub!("\r\n", "\n") @@ -40,104 +62,185 @@ module VagrantPlugins stderr: data["error"] end + if r.exit_code != 0 + raise Errors::PowerShellError, + script: path, + stderr: r.stderr + end + # Nothing return nil if !output_match return JSON.parse(output_match[1]) end + # Fetch current state of the VM + # + # @return [Hash] def get_current_state - execute('get_vm_status.ps1', { VmId: vm_id }) + execute(:get_vm_status, VmId: vm_id) end - def delete_vm - execute('delete_vm.ps1', { VmId: vm_id }) - end + # Delete the VM + # + # @return [nil] + def delete_vm + execute(:delete_vm, VmId: vm_id) + end + # Export the VM to the given path + # + # @param [String] path Path for export + # @return [nil] def export(path) - execute('export_vm.ps1', {VmId: vm_id, Path: path}) + execute(:export_vm, VmId: vm_id, Path: path) end - def read_guest_ip - execute('get_network_config.ps1', { VmId: vm_id }) - end + # Get the IP address of the VM + # + # @return [Hash] + def read_guest_ip + execute(:get_network_config, VmId: vm_id) + end + # Get the MAC address of the VM + # + # @return [Hash] def read_mac_address - execute('get_network_mac.ps1', { VmId: vm_id }) + execute(:get_network_mac, VmId: vm_id) end - def resume - execute('resume_vm.ps1', { VmId: vm_id }) - end + # Resume the VM from suspension + # + # @return [nil] + def resume + execute(:resume_vm, VmId: vm_id) + end - def start - execute('start_vm.ps1', { VmId: vm_id }) - end + # Start the VM + # + # @return [nil] + def start + execute(:start_vm, VmId: vm_id ) + end - def stop - execute('stop_vm.ps1', { VmId: vm_id }) - end + # Stop the VM + # + # @return [nil] + def stop + execute(:stop_vm, VmId: vm_id) + end - def suspend - execute("suspend_vm.ps1", { VmId: vm_id }) - end + # Suspend the VM + # + # @return [nil] + def suspend + execute(:suspend_vm, VmId: vm_id) + end - def import(options) - config_type = options.delete(:vm_config_type) - if config_type === "vmcx" - execute('import_vm_vmcx.ps1', options) - else - options.delete(:data_path) - options.delete(:source_path) - options.delete(:differencing_disk) - execute('import_vm_xml.ps1', options) - end - end + # Import a new VM + # + # @param [Hash] options Configuration options + # @return [Hash] New VM ID + def import(options) + execute(:import_vm, options) + end - def net_set_vlan(vlan_id) - execute("set_network_vlan.ps1", { VmId: vm_id, VlanId: vlan_id }) - end + # Set the VLAN ID + # + # @param [String] vlan_id VLAN ID + # @return [nil] + def net_set_vlan(vlan_id) + execute(:set_network_vlan, VmId: vm_id, VlanId: vlan_id) + end - def net_set_mac(mac_addr) - execute("set_network_mac.ps1", { VmId: vm_id, Mac: mac_addr }) - end + # Set the VM adapter MAC address + # + # @param [String] mac_addr MAC address + # @return [nil] + def net_set_mac(mac_addr) + execute(:set_network_mac, VmId: vm_id, Mac: mac_addr) + end - def create_snapshot(snapshot_name) - execute("create_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } ) - end + # Create a new snapshot with the given name + # + # @param [String] snapshot_name Name of the new snapshot + # @return [nil] + def create_snapshot(snapshot_name) + execute(:create_snapshot, VmId: vm_id, SnapName: snapshot_name) + end - def restore_snapshot(snapshot_name) - execute("restore_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } ) - end + # Restore the given snapshot + # + # @param [String] snapshot_name Name of snapshot to restore + # @return [nil] + def restore_snapshot(snapshot_name) + execute(:restore_snapshot, VmId: vm_id, SnapName: snapshot_name) + end - def list_snapshots() - snaps = execute("list_snapshots.ps1", { VmID: vm_id } ) - snaps.map { |s| s['Name'] } - end + # Get list of current snapshots + # + # @return [Array] snapshot names + def list_snapshots + snaps = execute(:list_snapshots, VmID: vm_id) + snaps.map { |s| s['Name'] } + end - def delete_snapshot(snapshot_name) - execute("delete_snapshot.ps1", {VmID: vm_id, SnapName: snapshot_name}) - end + # Delete snapshot with the given name + # + # @param [String] snapshot_name Name of snapshot to delete + # @return [nil] + def delete_snapshot(snapshot_name) + execute(:delete_snapshot, VmID: vm_id, SnapName: snapshot_name) + end + # Enable or disable VM integration services + # + # @param [Hash] config Integration services to enable or disable + # @return [nil] + # @note Keys in the config hash will be remapped if found in the + # INTEGRATION_SERVICES_MAP. If they are not, the name will + # be passed directly. This allows new integration services + # to configurable even if Vagrant is not aware of them. def set_vm_integration_services(config) - execute("set_vm_integration_services.ps1", config) + config.each_pair do |srv_name, srv_enable| + args = {VMID: vm_id, Name: INTEGRATION_SERVICES_MAP.fetch(srv_name.to_sym, srv_name).to_s} + args[:Enable] = true if srv_enable + execute(:set_vm_integration_services, args) + end + end + + # Set the name of the VM + # + # @param [String] vmname Name of the VM + # @return [nil] + def set_name(vmname) + execute(:set_name, VMID: vm_id, VMName: vmname) end protected def execute_powershell(path, options, &block) lib_path = Pathname.new(File.expand_path("../scripts", __FILE__)) + mod_path = lib_path.join("utils").to_s.gsub("/", "\\") path = lib_path.join(path).to_s.gsub("/", "\\") options = options || {} ps_options = [] options.each do |key, value| + next if value == false ps_options << "-#{key}" + # If the value is a TrueClass assume switch + next if value == true ps_options << "'#{value}'" end # Always have a stop error action for failures ps_options << "-ErrorAction" << "Stop" - opts = { notify: [:stdout, :stderr, :stdin] } + # Include our module path so we can nicely load helper modules + opts = { + notify: [:stdout, :stderr, :stdin], + env: {"PSModulePath" => "$env:PSModulePath+';#{mod_path}'"} + } Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block) end end diff --git a/plugins/providers/hyperv/scripts/check_hyperv.ps1 b/plugins/providers/hyperv/scripts/check_hyperv.ps1 index d9992b1e9..1d1775314 100644 --- a/plugins/providers/hyperv/scripts/check_hyperv.ps1 +++ b/plugins/providers/hyperv/scripts/check_hyperv.ps1 @@ -1,8 +1,6 @@ -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +#Requires -Modules VagrantMessages -$check = $(-Not (-Not (Get-Command "Hyper-V\Get-VMSwitch" -errorAction SilentlyContinue))) +$check = $(-Not (-Not (Get-Command "Hyper-V\Get-VMSwitch" -ErrorAction SilentlyContinue))) $result = @{ result = $check } diff --git a/plugins/providers/hyperv/scripts/clone_vhd.ps1 b/plugins/providers/hyperv/scripts/clone_vhd.ps1 index fc83a2c06..829366635 100644 --- a/plugins/providers/hyperv/scripts/clone_vhd.ps1 +++ b/plugins/providers/hyperv/scripts/clone_vhd.ps1 @@ -1,4 +1,6 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$Source, @@ -6,4 +8,11 @@ Param( [string]$Destination ) -Hyper-V\New-VHD -Path $Destination -ParentPath $Source -ErrorAction Stop +$ErrorActionPreference = "Stop" + +try { + Hyper-V\New-VHD -Path $Destination -ParentPath $Source +} catch { + Write-Error-Message "Failed to clone drive: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/configure_vm.ps1 b/plugins/providers/hyperv/scripts/configure_vm.ps1 new file mode 100644 index 000000000..908bb1f24 --- /dev/null +++ b/plugins/providers/hyperv/scripts/configure_vm.ps1 @@ -0,0 +1,95 @@ +#Requires -Modules VagrantVM, VagrantMessages + +param( + [parameter (Mandatory=$true)] + [Guid] $VMID, + [parameter (Mandatory=$false)] + [string] $SwitchID=$null, + [parameter (Mandatory=$false)] + [string] $Memory=$null, + [parameter (Mandatory=$false)] + [string] $MaxMemory=$null, + [parameter (Mandatory=$false)] + [string] $Processors=$null, + [parameter (Mandatory=$false)] + [string] $AutoStartAction=$null, + [parameter (Mandatory=$false)] + [string] $AutoStopAction=$null, + [parameter (Mandatory=$false)] + [switch] $VirtualizationExtensions, + [parameter (Mandatory=$false)] + [switch] $EnableCheckpoints +) + +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VMID +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 +} + +if($Processors) { + try { + Set-VagrantVMCPUS -VM $VM -CPUCount ($Processors -as [int]) + } catch { + Write-Error-Message "Failed to configure CPUs: ${PSItem}" + exit 1 + } +} + +if($Memory -or $MaxMemory) { + try { + Set-VagrantVMMemory -VM $VM -Memory $Memory -MaxMemory $MaxMemory + } catch { + Write-Error-Message "Failed to configure memory: ${PSItem}" + exit 1 + } +} + +if($AutoStartAction -or $AutoStopAction) { + try { + Set-VagrantVMAutoActions -VM $VM -AutoStartAction $AutoStartAction -AutoStopAction $AutoStopAction + } catch { + Write-Error-Message "Failed to configure automatic actions: ${PSItem}" + exit 1 + } +} + +if($VirtualizationExtensions) { + $virtex = $true +} else { + $virtex = $false +} + +try { + Set-VagrantVMVirtExtensions -VM $VM -Enabled $virtex +} catch { + Write-Error-Message "Failed to configure virtualization extensions: ${PSItem}" + exit 1 +} + +if($SwitchID) { + try { + $SwitchName = Get-VagrantVMSwitch -NameOrID $SwitchID + Set-VagrantVMSwitch -VM $VM -SwitchName $SwitchName + } catch { + Write-Error-Message "Failed to configure network adapter: ${PSItem}" + } +} + +if($EnableCheckpoints) { + $checkpoints = "Standard" + $CheckpointAction = "enable" +} else { + $checkpoints = "Disabled" + $CheckpointAction = "disable" +} + +try { + Hyper-V\Set-VM -VM $VM -CheckpointType $checkpoints +} catch { + Write-Error-Message "Failed to ${CheckpointAction} checkpoints on VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/create_snapshot.ps1 b/plugins/providers/hyperv/scripts/create_snapshot.ps1 index 080c47d7a..775f91898 100644 --- a/plugins/providers/hyperv/scripts/create_snapshot.ps1 +++ b/plugins/providers/hyperv/scripts/create_snapshot.ps1 @@ -1,8 +1,17 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [string]$SnapName ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Checkpoint-VM $VM -SnapshotName $SnapName +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Checkpoint-VM $VM -SnapshotName $SnapName +} catch { + Write-Error-Message "Failed to create snapshot: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/delete_snapshot.ps1 b/plugins/providers/hyperv/scripts/delete_snapshot.ps1 index 54c2c33b7..bba44b588 100644 --- a/plugins/providers/hyperv/scripts/delete_snapshot.ps1 +++ b/plugins/providers/hyperv/scripts/delete_snapshot.ps1 @@ -1,8 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [string]$SnapName ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Remove-VMSnapshot $VM -Name $SnapName +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Remove-VMSnapshot $VM -Name $SnapName +} catch { + Write-Error-Message "Failed to delete snapshot: ${PSItem}" +} diff --git a/plugins/providers/hyperv/scripts/delete_vm.ps1 b/plugins/providers/hyperv/scripts/delete_vm.ps1 index ce83cb6a7..53e160b16 100644 --- a/plugins/providers/hyperv/scripts/delete_vm.ps1 +++ b/plugins/providers/hyperv/scripts/delete_vm.ps1 @@ -1,7 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Remove-VM $VM -Force +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Remove-VM $VM -Force +} catch { + Write-Error-Message "Failed to delete VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/export_vm.ps1 b/plugins/providers/hyperv/scripts/export_vm.ps1 index 207275981..263c12be2 100644 --- a/plugins/providers/hyperv/scripts/export_vm.ps1 +++ b/plugins/providers/hyperv/scripts/export_vm.ps1 @@ -1,15 +1,29 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [Parameter(Mandatory=$true)] [string]$Path ) -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$vm | Hyper-V\Export-VM -Path $Path +$ErrorActionPreference = "Stop" + +try { + $vm = Hyper-V\Get-VM -Id $VmId + $vm | Hyper-V\Export-VM -Path $Path +} catch { + Write-Error-Message "Failed to export VM: ${PSItem}" + exit 1 +} # Prepare directory structure for box import -$name = $vm.Name -Move-Item $Path/$name/* $Path -Remove-Item -Path $Path/Snapshots -Force -Recurse -Remove-Item -Path $Path/$name -Force \ No newline at end of file +try { + $name = $vm.Name + Move-Item $Path/$name/* $Path + Remove-Item -Path $Path/Snapshots -Force -Recurse + Remove-Item -Path $Path/$name -Force +} catch { + Write-Error-Message "Failed to format exported box: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/file_sync.ps1 b/plugins/providers/hyperv/scripts/file_sync.ps1 index bca5e41d6..ffe340407 100644 --- a/plugins/providers/hyperv/scripts/file_sync.ps1 +++ b/plugins/providers/hyperv/scripts/file_sync.ps1 @@ -1,22 +1,23 @@ +#Requires -Modules VagrantMessages #------------------------------------------------------------------------- # Copyright (c) Microsoft Open Technologies, Inc. # All Rights Reserved. Licensed under the MIT License. #-------------------------------------------------------------------------- param ( - [string]$vm_id = $(throw "-vm_id is required."), - [string]$guest_ip = $(throw "-guest_ip is required."), - [string]$username = $(throw "-guest_username is required."), - [string]$password = $(throw "-guest_password is required."), - [string]$host_path = $(throw "-host_path is required."), - [string]$guest_path = $(throw "-guest_path is required.") - ) - -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } + [parameter (Mandatory=$true)] + [string]$vm_id, + [parameter (Mandatory=$true)] + [string]$guest_ip, + [parameter (Mandatory=$true)] + [string]$username, + [parameter (Mandatory=$true)] + [string]$password, + [parameter (Mandatory=$true)] + [string]$host_path, + [parameter (Mandatory=$true)] + [string]$guest_path +) function Get-file-hash($source_path, $delimiter) { $source_files = @() @@ -120,4 +121,3 @@ $resultHash = @{ } $result = ConvertTo-Json $resultHash Write-Output-Message $result - diff --git a/plugins/providers/hyperv/scripts/get_network_config.ps1 b/plugins/providers/hyperv/scripts/get_network_config.ps1 index 5f9186a2c..959501add 100644 --- a/plugins/providers/hyperv/scripts/get_network_config.ps1 +++ b/plugins/providers/hyperv/scripts/get_network_config.ps1 @@ -1,56 +1,68 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ErrorActionPreference = "Stop" -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$networks = Hyper-V\Get-VMNetworkAdapter -VM $vm -foreach ($network in $networks) { - if ($network.IpAddresses.Length -gt 0) { - foreach ($ip_address in $network.IpAddresses) { - if ($ip_address.Contains(".") -And [string]::IsNullOrEmpty($ip4_address)) { - $ip4_address = $ip_address - } elseif ($ip_address.Contains(":") -And [string]::IsNullOrEmpty($ip6_address)) { - $ip6_address = $ip_address +try { + $vm = Hyper-V\Get-VM -Id $VmId +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 +} + +try { + $networks = Hyper-V\Get-VMNetworkAdapter -VM $vm + foreach ($network in $networks) { + if ($network.IpAddresses.Length -gt 0) { + foreach ($ip_address in $network.IpAddresses) { + if ($ip_address.Contains(".") -And [string]::IsNullOrEmpty($ip4_address)) { + $ip4_address = $ip_address + } elseif ($ip_address.Contains(":") -And [string]::IsNullOrEmpty($ip6_address)) { + $ip6_address = $ip_address + } } } } -} -# If no address was found in the network settings, check for -# neighbor with mac address and see if an IP exists -if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) { - $macaddresses = $vm | select -ExpandProperty NetworkAdapters | select MacAddress - foreach ($macaddr in $macaddresses) { - $macaddress = $macaddr.MacAddress -replace '(.{2})(?!$)', '${1}-' - $addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress - if ($ip_address) { - $ip_address = $addr.IPAddress - if ($ip_address.Contains(".")) { - $ip4_address = $ip_address - } elseif ($ip_address.Contains(":")) { - $ip6_address = $ip_address + # If no address was found in the network settings, check for + # neighbor with mac address and see if an IP exists + if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) { + $macaddresses = $vm | select -ExpandProperty NetworkAdapters | select MacAddress + foreach ($macaddr in $macaddresses) { + $macaddress = $macaddr.MacAddress -replace '(.{2})(?!$)', '${1}-' + $addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress + if ($ip_address) { + $ip_address = $addr.IPAddress + if ($ip_address.Contains(".")) { + $ip4_address = $ip_address + } elseif ($ip_address.Contains(":")) { + $ip6_address = $ip_address + } } } } -} -if (-Not ([string]::IsNullOrEmpty($ip4_address))) { - $guest_ipaddress = $ip4_address -} elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) { - $guest_ipaddress = $ip6_address -} - -if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) { - $resultHash = @{ - ip = $guest_ipaddress + if (-Not ([string]::IsNullOrEmpty($ip4_address))) { + $guest_ipaddress = $ip4_address + } elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) { + $guest_ipaddress = $ip6_address } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result -} else { - Write-Error-Message "Failed to determine IP address" + + if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) { + $resultHash = @{ + ip = $guest_ipaddress + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result + } else { + Write-Error-Message "Failed to determine IP address" + exit 1 + } +} catch { + Write-Error-Message "Unexpected error while detecting network configuration: ${PSItem}" + exit 1 } diff --git a/plugins/providers/hyperv/scripts/get_network_mac.ps1 b/plugins/providers/hyperv/scripts/get_network_mac.ps1 index 9424917df..88d4c0306 100644 --- a/plugins/providers/hyperv/scripts/get_network_mac.ps1 +++ b/plugins/providers/hyperv/scripts/get_network_mac.ps1 @@ -1,28 +1,32 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId - ) +) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ErrorActionPreference = "Stop" -$ip_address = "" -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$networks = Hyper-V\Get-VMNetworkAdapter -VM $vm -foreach ($network in $networks) { - if ($network.MacAddress -gt 0) { - $mac_address = $network.MacAddress - if (-Not ([string]::IsNullOrEmpty($mac_address))) { - # We found our mac address! - break +try { + $ip_address = "" + $vm = Hyper-V\Get-VM -Id $VmId + $networks = Hyper-V\Get-VMNetworkAdapter -VM $vm + foreach ($network in $networks) { + if ($network.MacAddress -gt 0) { + $mac_address = $network.MacAddress + if (-Not ([string]::IsNullOrEmpty($mac_address))) { + # We found our mac address! + break + } + } } - } -} - -$resultHash = @{ - mac = "$mac_address" + $resultHash = @{ + mac = "$mac_address" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} catch { + Write-Error-Message "Unexpected error while fetching MAC: ${PSItem}" + exit 1 } -$result = ConvertTo-Json $resultHash -Write-Output-Message $result \ No newline at end of file diff --git a/plugins/providers/hyperv/scripts/get_switches.ps1 b/plugins/providers/hyperv/scripts/get_switches.ps1 index 85c0f12fe..83632aaa8 100644 --- a/plugins/providers/hyperv/scripts/get_switches.ps1 +++ b/plugins/providers/hyperv/scripts/get_switches.ps1 @@ -1,11 +1,9 @@ +#Requires -Modules VagrantMessages # This will have a SwitchType property. As far as I know the values are: # # 0 - Private # 1 - Internal # -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) $Switches = @(Hyper-V\Get-VMSwitch ` | Select-Object Name,SwitchType,NetAdapterInterfaceDescription,Id) diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 index 43c8595c9..90c05cbcc 100644 --- a/plugins/providers/hyperv/scripts/get_vm_status.ps1 +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -1,12 +1,10 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) - # Make sure the exception type is loaded try { @@ -37,7 +35,7 @@ try { $State = "not_created" $Status = $State } - else + else { throw; } diff --git a/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 b/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 index 79dac42d7..bf7d89cda 100644 --- a/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 +++ b/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 @@ -1,6 +1,4 @@ -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +#Requires -Modules VagrantMessages # Windows version 10 and up have support for binary format $check = [System.Environment]::OSVersion.Version.Major -ge 10 diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 new file mode 100644 index 000000000..06fc65622 --- /dev/null +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -0,0 +1,37 @@ +#Requires -Modules VagrantVM, VagrantMessages + +param( + [parameter (Mandatory=$true)] + [string] $VMConfigFile, + [parameter (Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [switch] $LinkedClone, + [parameter (Mandatory=$false)] + [string] $VMName=$null +) + +$ErrorActionPreference = "Stop" + +try { + if($LinkedClone) { + $linked = $true + } else { + $linked = $false + } + + $VM = New-VagrantVM -VMConfigFile $VMConfigFile -DestinationPath $DestinationPath ` + -DataPath $DataPath -SourcePath $SourcePath -LinkedClone $linked -VMName $VMName + + $Result = @{ + id = $VM.Id.Guid; + } + Write-Output-Message (ConvertTo-Json $Result) +} catch { + Write-Error-Message "${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 b/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 deleted file mode 100644 index 682cabfbd..000000000 --- a/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 +++ /dev/null @@ -1,165 +0,0 @@ -Param( - [Parameter(Mandatory=$true)] - [string]$vm_config_file, - [Parameter(Mandatory=$true)] - [string]$source_path, - [Parameter(Mandatory=$true)] - [string]$dest_path, - [Parameter(Mandatory=$true)] - [string]$data_path, - - [string]$switchid=$null, - [string]$memory=$null, - [string]$maxmemory=$null, - [string]$cpus=$null, - [string]$vmname=$null, - [string]$auto_start_action=$null, - [string]$auto_stop_action=$null, - [string]$differencing_disk=$null, - [string]$enable_virtualization_extensions=$False -) - -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) - -$VmProperties = @{ - Path = $vm_config_file - SnapshotFilePath = Join-Path $data_path 'Snapshots' - VhdDestinationPath = Join-Path $data_path 'Virtual Hard Disks' - VirtualMachinePath = $data_path -} - -$vmConfig = (Hyper-V\Compare-VM -Copy -GenerateNewID @VmProperties) - -$generation = $vmConfig.VM.Generation - -if (!$vmname) { - # Get the name of the vm - $vm_name = $vmconfig.VM.VMName -} else { - $vm_name = $vmname -} - -if (!$cpus) { - # Get the processorcount of the VM - $processors = (Hyper-V\Get-VMProcessor -VM $vmConfig.VM).Count -}else { - $processors = $cpus -} - -function GetUniqueName($name) { - Hyper-V\Get-VM | ForEach-Object -Process { - if ($name -eq $_.Name) { - $name = $name + "_1" - } - } - return $name -} - -do { - $name = $vm_name - $vm_name = GetUniqueName $name -} while ($vm_name -ne $name) - -if (!$memory) { - $configMemory = Hyper-V\Get-VMMemory -VM $vmConfig.VM - $dynamicmemory = $configMemory.DynamicMemoryEnabled - - $MemoryMaximumBytes = ($configMemory.Maximum) - $MemoryStartupBytes = ($configMemory.Startup) - $MemoryMinimumBytes = ($configMemory.Minimum) -} else { - if (!$maxmemory){ - $dynamicmemory = $False - $MemoryMaximumBytes = ($memory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } else { - $dynamicmemory = $True - $MemoryMaximumBytes = ($maxmemory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } -} - -if (!$switchid) { - $switchname = (Hyper-V\Get-VMNetworkAdapter -VM $vmConfig.VM).SwitchName -} else { - $switchname = $(Hyper-V\Get-VMSwitch -Id $switchid).Name -} - -# Enable nested virtualization if configured -if ($enable_virtualization_extensions -eq "True") { - Hyper-V\Set-VMProcessor -VM $vmConfig.VM -ExposeVirtualizationExtensions $true -} - -$vmNetworkAdapter = Hyper-V\Get-VMNetworkAdapter -VM $vmConfig.VM -Hyper-V\Connect-VMNetworkAdapter -VMNetworkAdapter $vmNetworkAdapter -SwitchName $switchname -Hyper-V\Set-VM -VM $vmConfig.VM -NewVMName $vm_name -Hyper-V\Set-VM -VM $vmConfig.VM -ErrorAction "Stop" -Hyper-V\Set-VM -VM $vmConfig.VM -ProcessorCount $processors - -if ($dynamicmemory) { - Hyper-V\Set-VM -VM $vmConfig.VM -DynamicMemory - Hyper-V\Set-VM -VM $vmConfig.VM -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes $MemoryMaximumBytes -MemoryStartupBytes $MemoryStartupBytes -} else { - Hyper-V\Set-VM -VM $vmConfig.VM -StaticMemory - Hyper-V\Set-VM -VM $vmConfig.VM -MemoryStartupBytes $MemoryStartupBytes -} - -if ($notes) { - Hyper-V\Set-VM -VM $vmConfig.VM -Notes $notes -} - -if ($auto_start_action) { - Hyper-V\Set-VM -VM $vmConfig.VM -AutomaticStartAction $auto_start_action -} - -if ($auto_stop_action) { - Hyper-V\Set-VM -VM $vmConfig.VM -AutomaticStopAction $auto_stop_action -} - -# Only set EFI secure boot for Gen 2 machines, not gen 1 -if ($generation -ne 1) { - Hyper-V\Set-VMFirmware -VM $vmConfig.VM -EnableSecureBoot (Hyper-V\Get-VMFirmware -VM $vmConfig.VM).SecureBoot -} - -$report = Hyper-V\Compare-VM -CompatibilityReport $vmConfig - -# Stop if there are incompatibilities -if($report.Incompatibilities.Length -gt 0){ - Write-Error-Message $(ConvertTo-Json $($report.Incompatibilities | Select -ExpandProperty Message)) - exit 0 -} - -if($differencing_disk){ - # Get all controller on the VM, first scsi, then IDE if it is a Gen 1 device - $controllers = Hyper-V\Get-VMScsiController -VM $vmConfig.VM - if($generation -eq 1){ - $controllers = @($controllers) + @(Hyper-V\Get-VMIdeController -VM $vmConfig.VM) - } - - foreach($controller in $controllers){ - foreach($drive in $controller.Drives){ - if([System.IO.Path]::GetFileName($drive.Path) -eq [System.IO.Path]::GetFileName($source_path)){ - # Remove the old disk and replace it with a differencing version - $path = $drive.Path - Hyper-V\Remove-VMHardDiskDrive $drive - Hyper-V\New-VHD -Path $dest_path -ParentPath $source_path -ErrorAction Stop - Hyper-V\Add-VMHardDiskDrive -VM $vmConfig.VM -Path $dest_path - } - } - } -} - -Hyper-V\Import-VM -CompatibilityReport $vmConfig - -$vm_id = (Hyper-V\Get-VM $vm_name).id.guid -$resultHash = @{ - name = $vm_name - id = $vm_id -} - -$result = ConvertTo-Json $resultHash -Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/import_vm_xml.ps1 b/plugins/providers/hyperv/scripts/import_vm_xml.ps1 deleted file mode 100644 index 655380c0e..000000000 --- a/plugins/providers/hyperv/scripts/import_vm_xml.ps1 +++ /dev/null @@ -1,221 +0,0 @@ -Param( - [Parameter(Mandatory=$true)] - [string]$vm_config_file, - [Parameter(Mandatory=$true)] - [string]$dest_path, - - [string]$switchname=$null, - [string]$memory=$null, - [string]$maxmemory=$null, - [string]$cpus=$null, - [string]$vmname=$null, - [string]$auto_start_action=$null, - [string]$auto_stop_action=$null, - [string]$enable_virtualization_extensions=$False -) - -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) - -[xml]$vmconfig = Get-Content -Path $vm_config_file - -$generation = [int]($vmconfig.configuration.properties.subtype.'#text')+1 - -if (!$vmname) { - # Get the name of the vm - $vm_name = $vmconfig.configuration.properties.name.'#text' -}else { - $vm_name = $vmname -} - -if (!$cpus) { - # Get the name of the vm - $processors = $vmconfig.configuration.settings.processors.count.'#text' -}else { - $processors = $cpus -} - -function GetUniqueName($name) { - Hyper-V\Get-VM | ForEach-Object -Process { - if ($name -eq $_.Name) { - $name = $name + "_1" - } - } - return $name -} - -do { - $name = $vm_name - $vm_name = GetUniqueName $name -} while ($vm_name -ne $name) - -if (!$memory) { - $xmlmemory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank - if ($xmlmemory.dynamic_memory_enabled."#text" -eq "True") { - $dynamicmemory = $True - } - else { - $dynamicmemory = $False - } - # Memory values need to be in bytes - $MemoryMaximumBytes = ($xmlmemory.limit."#text" -as [int]) * 1MB - $MemoryStartupBytes = ($xmlmemory.size."#text" -as [int]) * 1MB - $MemoryMinimumBytes = ($xmlmemory.reservation."#text" -as [int]) * 1MB -} -else { - if (!$maxmemory){ - $dynamicmemory = $False - $MemoryMaximumBytes = ($memory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } - else { - $dynamicmemory = $True - $MemoryMaximumBytes = ($maxmemory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } -} - - -if (!$switchname) { - # Get the name of the virtual switch - $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" -} - -if ($generation -eq 1) { - # Determine boot device - Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { - "Floppy" { $bootdevice = "Floppy" } - "HardDrive" { $bootdevice = "IDE" } - "Optical" { $bootdevice = "CD" } - "Network" { $bootdevice = "LegacyNetworkAdapter" } - "Default" { $bootdevice = "IDE" } - } #switch -} else { - # Determine boot device - Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { - "HardDrive" { $bootdevice = "VHD" } - "Optical" { $bootdevice = "CD" } - "Network" { $bootdevice = "NetworkAdapter" } - "Default" { $bootdevice = "VHD" } - } #switch -} - -# Determine secure boot options -$secure_boot_enabled = (Select-Xml -xml $vmconfig -XPath "//secure_boot_enabled").Node."#text" - -# Define a hash map of parameter values for New-VM - -$vm_params = @{ - Name = $vm_name - NoVHD = $True - MemoryStartupBytes = $MemoryStartupBytes - SwitchName = $switchname - BootDevice = $bootdevice - ErrorAction = "Stop" -} - -# Generation parameter was added in ps v4 -if((get-command Hyper-V\New-VM).Parameters.Keys.Contains("generation")) { - $vm_params.Generation = $generation -} - -# Create the VM using the values in the hash map - -$vm = Hyper-V\New-VM @vm_params - -$notes = (Select-Xml -xml $vmconfig -XPath "//notes").node.'#text' - -# Set-VM parameters to configure new VM with old values - -$more_vm_params = @{ - ProcessorCount = $processors - MemoryStartupBytes = $MemoryStartupBytes -} - -If ($dynamicmemory) { - $more_vm_params.Add("DynamicMemory",$True) - $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) - $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) -} else { - $more_vm_params.Add("StaticMemory",$True) -} - -if ($notes) { - $more_vm_params.Add("Notes",$notes) -} - -if ($auto_start_action) { - $more_vm_params.Add("AutomaticStartAction",$auto_start_action) -} - -if ($auto_stop_action) { - $more_vm_params.Add("AutomaticStopAction",$auto_stop_action) -} - -# Set the values on the VM -$vm | Hyper-V\Set-VM @more_vm_params -Passthru - -# Add drives to the virtual machine -$controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" - -# Only set EFI secure boot for Gen 2 machines, not gen 1 -if ($generation -ne 1) { - # Set EFI secure boot - if ($secure_boot_enabled -eq "True") { - Hyper-V\Set-VMFirmware -VM $vm -EnableSecureBoot On - } else { - Hyper-V\Set-VMFirmware -VM $vm -EnableSecureBoot Off - } -} - -# Enable nested virtualization if configured -if ($enable_virtualization_extensions -eq "True") { - Hyper-V\Set-VMProcessor -VM $vm -ExposeVirtualizationExtensions $true -} - -# A regular expression pattern to pull the number from controllers -[regex]$rx="\d" - -foreach ($controller in $controllers) { - $node = $controller.Node - - # Check for SCSI - if ($node.ParentNode.ChannelInstanceGuid) { - $ControllerType = "SCSI" - } else { - $ControllerType = "IDE" - } - - $drives = $node.ChildNodes | where {$_.pathname."#text"} - foreach ($drive in $drives) { - #if drive type is ISO then set DVD Drive accordingly - $driveType = $drive.type."#text" - - $addDriveParam = @{ - ControllerNumber = $rx.Match($controller.node.name).value - Path = $dest_path - } - - if ($drive.pool_id."#text") { - $ResourcePoolName = $drive.pool_id."#text" - $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) - } - - if ($drivetype -eq 'VHD') { - $addDriveParam.add("ControllerType",$ControllerType) - $vm | Hyper-V\Add-VMHardDiskDrive @AddDriveparam - } - } -} - -$vm_id = (Hyper-V\Get-VM $vm_name).id.guid -$resultHash = @{ - name = $vm_name - id = $vm_id -} - -$result = ConvertTo-Json $resultHash -Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/list_snapshots.ps1 b/plugins/providers/hyperv/scripts/list_snapshots.ps1 index d5bd05811..7e4b85583 100644 --- a/plugins/providers/hyperv/scripts/list_snapshots.ps1 +++ b/plugins/providers/hyperv/scripts/list_snapshots.ps1 @@ -1,12 +1,19 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$Snapshots = @(Hyper-V\Get-VMSnapshot $VM | Select-Object Name) -$result = ConvertTo-json $Snapshots +$ErrorActionPreference = "Stop" -Write-Host "===Begin-Output===" -Write-Host $result -Write-Host "===End-Output===" +try { + $VM = Hyper-V\Get-VM -Id $VmId + $Snapshots = @(Hyper-V\Get-VMSnapshot $VM | Select-Object Name) +} catch { + Write-Error-Message "Failed to get snapshot list: ${PSItem}" + exit 1 +} + +$result = ConvertTo-json $Snapshots +Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/restore_snapshot.ps1 b/plugins/providers/hyperv/scripts/restore_snapshot.ps1 index 406804394..192e56075 100644 --- a/plugins/providers/hyperv/scripts/restore_snapshot.ps1 +++ b/plugins/providers/hyperv/scripts/restore_snapshot.ps1 @@ -1,8 +1,17 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [string]$SnapName ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Restore-VMSnapshot $VM -Name $SnapName -Confirm:$false +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Restore-VMSnapshot $VM -Name $SnapName -Confirm:$false +} catch { + Write-Error-Message "Failed to restore snapshot: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/resume_vm.ps1 b/plugins/providers/hyperv/scripts/resume_vm.ps1 index 8cbca00d4..f30e05b73 100644 --- a/plugins/providers/hyperv/scripts/resume_vm.ps1 +++ b/plugins/providers/hyperv/scripts/resume_vm.ps1 @@ -1,7 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Resume-VM $VM +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Resume-VM $VM +} catch { + Write-Error-Message "Failed to resume VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/set_name.ps1 b/plugins/providers/hyperv/scripts/set_name.ps1 new file mode 100644 index 000000000..ac97fad48 --- /dev/null +++ b/plugins/providers/hyperv/scripts/set_name.ps1 @@ -0,0 +1,24 @@ +#Requires -Modules VagrantMessages + +param ( + [parameter (Mandatory=$true)] + [Guid] $VMID, + [parameter (Mandatory=$true)] + [string] $VMName +) + +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VMID +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 +} + +try { + Hyper-V\Set-VM -VM $VM -NewVMName $VMName +} catch { + Write-Error-Message "Failed to assign new VM name ${VMName}: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/set_network_mac.ps1 b/plugins/providers/hyperv/scripts/set_network_mac.ps1 index 4ef157f38..3942eb621 100644 --- a/plugins/providers/hyperv/scripts/set_network_mac.ps1 +++ b/plugins/providers/hyperv/scripts/set_network_mac.ps1 @@ -1,18 +1,18 @@ -param ( - [string]$VmId = $(throw "-VmId is required."), - [string]$Mac = $(throw "-Mac ") - ) +#Requires -Modules VagrantMessages -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +param ( + [parameter (Mandatory=$true)] + [string]$VmId, + [parameter (Mandatory=$true)] + [string]$Mac +) + +$ErrorActionPreference = "Stop" try { - $vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" - Hyper-V\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac -ErrorAction "stop" -} -catch { - Write-Error-Message "Failed to set VM's MAC address $_" + $vm = Hyper-V\Get-VM -Id $VmId + Hyper-V\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac +} catch { + Write-Error-Message "Failed to set VM MAC address: ${PSItem}" + exit 1 } diff --git a/plugins/providers/hyperv/scripts/set_network_vlan.ps1 b/plugins/providers/hyperv/scripts/set_network_vlan.ps1 index b385dc9c5..26dc8941d 100644 --- a/plugins/providers/hyperv/scripts/set_network_vlan.ps1 +++ b/plugins/providers/hyperv/scripts/set_network_vlan.ps1 @@ -1,7 +1,11 @@ +#Requires -Modules VagrantMessages + param ( - [string]$VmId = $(throw "-VmId is required."), - [int]$VlanId = $(throw "-VlanId ") - ) + [parameter (Mandatory=$true)] + [string]$VmId, + [parameter (Mandatory=$true)] + [int]$VlanId +) # Include the following modules $presentDir = Split-Path -parent $PSCommandPath diff --git a/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 b/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 index fd0d30063..458ddbaf3 100644 --- a/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 +++ b/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 @@ -1,37 +1,27 @@ +#Requires -Modules VagrantVM, VagrantMessages + param ( - [string] $VmId, - [string] $guest_service_interface = $null, - [string] $heartbeat = $null, - [string] $key_value_pair_exchange = $null, - [string] $shutdown = $null, - [string] $time_synchronization = $null, - [string] $vss = $null + [parameter (Mandatory=$true)] + [string] $VMID, + [parameter (Mandatory=$true)] + [string] $Name, + [parameter (Mandatory=$false)] + [switch] $Enable ) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ErrorActionPreference = "Stop" -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" - -# Set the service based on value -function VmSetService -{ - param ([string] $Name, [string] $Value, [Microsoft.HyperV.PowerShell.VirtualMachine] $Vm) - - if ($Value -ne $null){ - if($Value -eq "true"){ - Hyper-V\Enable-VMIntegrationService -VM $Vm -Name $Name - } - if($Value -eq "false"){ - Hyper-V\Disable-VMIntegrationService -VM $Vm -Name $Name - } - } +try { + $VM = Hyper-V\Get-VM -Id $VMID +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 } -VmSetService -Name "Guest Service Interface" -Value $guest_service_interface -Vm $vm -VmSetService -Name "Heartbeat" -Value $heartbeat -Vm $vm -VmSetService -Name "Key-Value Pair Exchange" -Value $key_value_pair_exchange -Vm $vm -VmSetService -Name "Shutdown" -Value $shutdown -Vm $vm -VmSetService -Name "Time Synchronization" -Value $time_synchronization -Vm $vm -VmSetService -Name "VSS" -Value $vss -Vm $vm \ No newline at end of file +try { + Set-VagrantVMService -VM $VM -Name $Name -Enable $Enable +} catch { + if($Enable){ $action = "enable" } else { $action = "disable" } + Write-Error-Message "Failed to ${action} VM integration service ${Name}: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/start_vm.ps1 b/plugins/providers/hyperv/scripts/start_vm.ps1 index bfa67bc92..8380e1c8f 100644 --- a/plugins/providers/hyperv/scripts/start_vm.ps1 +++ b/plugins/providers/hyperv/scripts/start_vm.ps1 @@ -1,27 +1,26 @@ -param ( - [string]$VmId = $(throw "-VmId is required.") - ) +#Requires -Modules VagrantMessages -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +param ( + [parameter (Mandatory=$true)] + [string]$VmId +) + +$ErrorActionPreference = "Stop" try { - $vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" - Hyper-V\Start-VM $vm -ErrorAction "stop" - $state = $vm.state - $status = $vm.status - $name = $vm.name - $resultHash = @{ - state = "$state" - status = "$status" - name = "$name" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result + $vm = Hyper-V\Get-VM -Id $VmId + Hyper-V\Start-VM $vm + $state = $vm.state + $status = $vm.status + $name = $vm.name + $resultHash = @{ + state = "$state" + status = "$status" + name = "$name" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} catch { + Write-Error-Message "Failed to start VM ${PSItem}" + exit 1 } -catch { - Write-Error-Message "Failed to start a VM $_" -} \ No newline at end of file diff --git a/plugins/providers/hyperv/scripts/stop_vm.ps1 b/plugins/providers/hyperv/scripts/stop_vm.ps1 index 524a45fba..5cf88e636 100644 --- a/plugins/providers/hyperv/scripts/stop_vm.ps1 +++ b/plugins/providers/hyperv/scripts/stop_vm.ps1 @@ -1,8 +1,17 @@ +#Requires -Modules VagrantMessages + Param( [Parameter(Mandatory=$true)] [string]$VmId ) -# Shuts down virtual machine regardless of any unsaved application data -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Stop-VM $VM -Force +$ErrorActionPreference = "Stop" + +try{ + # Shuts down virtual machine regardless of any unsaved application data + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Stop-VM $VM -Force +} catch { + Write-Error-Message "Failed to stop VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/suspend_vm.ps1 b/plugins/providers/hyperv/scripts/suspend_vm.ps1 index 2f1d69da8..43993ac15 100644 --- a/plugins/providers/hyperv/scripts/suspend_vm.ps1 +++ b/plugins/providers/hyperv/scripts/suspend_vm.ps1 @@ -1,7 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Suspend-VM $VM +$ErrorActionPreference = "Stop" + +try{ + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Suspend-VM $VM +} catch { + Write-Error-Message "Failed to suspend VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 b/plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 new file mode 100644 index 000000000..2f934b6c0 --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +function Write-Error-Message { + param ( + [parameter (Mandatory=$true,Position=0)] + [string] $Message + ) + $error_message = @{ + error = $Message + } + Write-Host "===Begin-Error===" + Write-Host (ConvertTo-Json $error_message) + Write-Host "===End-Error===" +} + +function Write-Output-Message { + param ( + [parameter (Mandatory=$true,Position=0)] + [string] $Message + ) + Write-Host "===Begin-Output===" + Write-Host $Message + Write-Host "===End-Output===" +} diff --git a/plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 b/plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 new file mode 100644 index 000000000..885d3c6b1 --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 @@ -0,0 +1,616 @@ +# Vagrant VM creation functions + +function New-VagrantVM { + param ( + [parameter(Mandatory=$true)] + [string] $VMConfigFile, + [parameter(Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [bool] $LinkedClone = $false, + [parameter(Mandatory=$false)] + [string] $VMName + ) + if([IO.Path]::GetExtension($VMConfigFile).ToLower() -eq ".xml") { + return New-VagrantVMXML @PSBoundParameters + } else { + return New-VagrantVMVMCX @PSBoundParameters + } +<# +.SYNOPSIS + +Create a new Vagrant Hyper-V VM by cloning original. This +is the general use function with will call the specialized +function based on the extension of the configuration file. + +.DESCRIPTION + +Using an existing Hyper-V VM a new Hyper-V VM is created +by cloning the original. + +.PARAMETER VMConfigFile +Path to the original Hyper-V VM configuration file. + +.PARAMETER DestinationPath +Path to new Hyper-V VM hard drive. + +.PARAMETER DataPath +Directory path of the original Hyper-V VM to be cloned. + +.PARAMETER SourcePath +Path to the original Hyper-V VM hard drive. + +.PARAMETER LinkedClone +New Hyper-V VM should be linked clone instead of complete copy. + +.PARAMETER VMName +Name of the new Hyper-V VM. + +.INPUTS + +None. + +.OUTPUTS + +VirtualMachine. The cloned Hyper-V VM. +#> +} + +function New-VagrantVMVMCX { + param ( + [parameter(Mandatory=$true)] + [string] $VMConfigFile, + [parameter(Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [bool] $LinkedClone = $false, + [parameter(Mandatory=$false)] + [string] $VMName + ) + + $NewVMConfig = @{ + Path = $VMConfigFile; + SnapshotFilePath = Join-Path $DataPath "Snapshots"; + VhdDestinationPath = Join-Path $DataPath "Virtual Hard Disks"; + VirtualMachinePath = $DataPath; + } + $VMConfig = (Hyper-V\Compare-VM -Copy -GenerateNewID @NewVMConfig) + $VM = $VMConfig.VM + $Gen = $VM.Generation + + # Set VM name if name has been provided + if($VMName) { + Hyper-V\Set-VM -VM $VM -NewVMName $VMName + } + + # Set EFI secure boot on machines after Gen 1 + if($Gen -gt 1) { + Hyper-V\Set-VMFirmware -VM $VM -EnableSecureBoot (Hyper-V\Get-VMFirmware -VM $VM).SecureBoot + } + + # Verify new VM + $Report = Hyper-V\Compare-VM -CompatibilityReport $VMConfig + if($Report.Incompatibilities.Length -gt 0){ + throw $(ConvertTo-Json $($Report.Incompatibilities | Select -ExpandProperty Message)) + } + + if($LinkedClone) { + $Controllers = Hyper-V\Get-VMScsiController -VM $VM + if($Gen -eq 1){ + $Controllers = @($Controllers) + @(Hyper-V\Get-VMIdeController -VM $VM) + } + foreach($Controller in $Controllers) { + foreach($Drive in $Controller.Drives) { + if([System.IO.Path]::GetFileName($Drive.Path) -eq [System.IO.Path]::GetFileName($SourcePath)) { + $Path = $Drive.Path + Hyper-V\Remove-VMHardDiskDrive $Drive + Hyper-V\New-VHD -Path $DestinationPath -ParentPath $SourcePath + Hyper-V\AddVMHardDiskDrive -VM $VM -Path $DestinationPath + break + } + } + } + + } + return Hyper-V\Import-VM -CompatibilityReport $VMConfig +<# +.SYNOPSIS + +Create a new Vagrant Hyper-V VM by cloning original (VMCX based). + +.DESCRIPTION + +Using an existing Hyper-V VM a new Hyper-V VM is created +by cloning the original. + +.PARAMETER VMConfigFile +Path to the original Hyper-V VM configuration file. + +.PARAMETER DestinationPath +Path to new Hyper-V VM hard drive. + +.PARAMETER DataPath +Directory path of the original Hyper-V VM to be cloned. + +.PARAMETER SourcePath +Path to the original Hyper-V VM hard drive. + +.PARAMETER LinkedClone +New Hyper-V VM should be linked clone instead of complete copy. + +.PARAMETER VMName +Name of the new Hyper-V VM. + +.INPUTS + +None. + +.OUTPUTS + +VirtualMachine. The cloned Hyper-V VM. +#> +} + +function New-VagrantVMXML { + param ( + [parameter(Mandatory=$true)] + [string] $VMConfigFile, + [parameter(Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [bool] $LinkedClone = $false, + [parameter(Mandatory=$false)] + [string] $VMName + ) + + $DestinationDirectory = [System.IO.Path]::GetDirectoryName($DestinationPath) + New-Item -ItemType Directory -Force -Path $DestinationDirectory + + if($LinkedClone){ + Hyper-V\New-VHD -Path $DestinationPath -ParentPath $SourcePath -ErrorAction Stop + } else { + Copy-Item $SourcePath -Destination $DestinationPath -ErrorAction Stop + } + + [xml]$VMConfig = Get-Content -Path $VMConfigFile + $Gen = [int]($VMConfig.configuration.properties.subtype."#text") + 1 + if(!$VMName) { + $VMName = $VMConfig.configuration.properties.name."#text" + } + + # Determine boot device + if($Gen -eq 1) { + Switch ((Select-Xml -xml $VMConfig -XPath "//boot").node.device0."#text") { + "Floppy" { $BootDevice = "Floppy" } + "HardDrive" { $BootDevice = "IDE" } + "Optical" { $BootDevice = "CD" } + "Network" { $BootDevice = "LegacyNetworkAdapter" } + "Default" { $BootDevice = "IDE" } + } + } else { + Switch ((Select-Xml -xml $VMConfig -XPath "//boot").node.device0."#text") { + "HardDrive" { $BootDevice = "VHD" } + "Optical" { $BootDevice = "CD" } + "Network" { $BootDevice = "NetworkAdapter" } + "Default" { $BootDevice = "VHD" } + } + } + + # Determine if secure boot is enabled + $SecureBoot = (Select-Xml -XML $VMConfig -XPath "//secure_boot_enabled").Node."#text" + + $NewVMConfig = @{ + Name = $VMName; + NoVHD = $true; + BootDevice = $BootDevice; + } + + # Generation parameter in PS4 so validate before using + if((Get-Command Hyper-V\New-VM).Parameters.Keys.Contains("generation")) { + $NewVMConfig.Generation = $Gen + } + + # Create new VM instance + $VM = Hyper-V\New-VM @NewVMConfig + + # Configure secure boot + if($Gen -gt 1) { + if($SecureBoot -eq "True") { + Hyper-V\Set-VMFirmware -VM $VM -EnableSecureBoot On + } else { + Hyper-V\Set-VMFirmware -VM $VM -EnableSecureBoot Off + } + } + + # Configure drives + [regex]$DriveNumberMatcher = "\d" + $Controllers = Select-Xml -XML $VMConfig -XPath "//*[starts-with(name(.),'controller')]" + + foreach($Controller in $Controllers) { + $Node = $Controller.Node + if($Node.ParentNode.ChannelInstanceGuid) { + $ControllerType = "SCSI" + } else { + $ControllerType = "IDE" + } + $Drives = $Node.ChildNodes | where {$_.pathname."#text"} + foreach($Drive in $Drives) { + $DriveType = $Drive.type."#text" + if($DriveType -ne "VHD") { + continue + } + + $NewDriveConfig = @{ + ControllerNumber = $DriveNumberMatcher.Match($Controller.node.name).value; + Path = $DestinationPath; + ControllerType = $ControllerType; + } + if($Drive.pool_id."#text") { + $NewDriveConfig.ResourcePoolname = $Drive.pool_id."#text" + } + $VM | Hyper-V\Add-VMHardDiskDrive @NewDriveConfig + } + } + + # Apply original VM configuration to new VM instance + + $processors = $VMConfig.configuration.settings.processors.count."#text" + $notes = (Select-Xml -XML $VMConfig -XPath "//notes").node."#text" + $memory = (Select-Xml -XML $VMConfig -XPath "//memory").node.Bank + if ($memory.dynamic_memory_enabled."#text" -eq "True") { + $dynamicmemory = $True + } + else { + $dynamicmemory = $False + } + # Memory values need to be in bytes + $MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB + $MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB + $MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB + + $Config = @{ + ProcessorCount = $processors; + MemoryStartupBytes = $MemoryStartupBytes + } + if($dynamicmemory) { + $Config.DynamicMemory = $true + $Config.MemoryMinimumBytes = $MemoryMinimumBytes + $Config.MemoryMaximumBytes = $MemoryMaximumBytes + } else { + $Config.StaticMemory = $true + } + if($notes) { + $Config.Notes = $notes + } + Hyper-V\Set-VM -VM $VM @Config + + return $VM +<# +.SYNOPSIS + +Create a new Vagrant Hyper-V VM by cloning original (XML based). + +.DESCRIPTION + +Using an existing Hyper-V VM a new Hyper-V VM is created +by cloning the original. + +.PARAMETER VMConfigFile +Path to the original Hyper-V VM configuration file. + +.PARAMETER DestinationPath +Path to new Hyper-V VM hard drive. + +.PARAMETER DataPath +Directory path of the original Hyper-V VM to be cloned. + +.PARAMETER SourcePath +Path to the original Hyper-V VM hard drive. + +.PARAMETER LinkedClone +New Hyper-V VM should be linked clone instead of complete copy. + +.PARAMETER VMName +Name of the new Hyper-V VM. + +.INPUTS + +None. + +.OUTPUTS + +VirtualMachine. The cloned Hyper-V VM. +#> +} + +# Vagrant VM configuration functions + +function Set-VagrantVMMemory { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [int] $Memory, + [parameter (Mandatory=$false)] + [int] $MaxMemory + ) + + $ConfigMemory = Hyper-V\Get-VMMemory -VM $VM + + if(!$Memory) { + $MemoryStartupBytes = ($ConfigMemory.Startup) + $MemoryMinimumBytes = ($ConfigMemory.Minimum) + $MemoryMaximumBytes = ($ConfigMemory.Maximum) + } else { + $MemoryStartupBytes = $Memory * 1MB + $MemoryMinimumBytes = $Memory * 1MB + $MemoryMaximumBytes = $Memory * 1MB + } + + if($MaxMemory) { + $DynamicMemory = $true + $MemoryMaximumBytes = $MaxMemory * 1MB + } + + if($DynamicMemory) { + Hyper-V\Set-VM -VM $VM -DynamicMemory + Hyper-V\Set-VM -VM $VM -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes ` + $MemoryMaximumBytes -MemoryStartupBytes $MemoryStartupBytes + } else { + Hyper-V\Set-VM -VM $VM -StaticMemory + Hyper-V\Set-VM -VM $VM -MemoryStartupBytes $MemoryStartupBytes + } + return $VM +<# +.SYNOPSIS + +Configure VM memory settings. + +.DESCRIPTION + +Adjusts the VM memory settings. If MaxMemory is defined, dynamic memory +is enabled on the VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.Parameter Memory + +Memory to allocate to the given VM in MB. + +.Parameter MaxMemory + +Maximum memory to allocate to the given VM in MB. When this value is +provided dynamic memory is enabled for the VM. The Memory value or +the currently configured memory of the VM will be used as the minimum +and startup memory value. + +.Output + +VirtualMachine. +#> +} + +function Set-VagrantVMCPUS { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [int] $CPUCount + ) + + if($CPUCount) { + Hyper-V\Set-VM -VM $VM -ProcessorCount $CPUCount + } + return $VM +<# +.SYNOPSIS + +Configure VM CPU count. + +.DESCRIPTION + +Configure the number of CPUs on the given VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER CPUCount + +Number of CPUs. + +.Output + +VirtualMachine. +#> +} + +function Set-VagrantVMVirtExtensions { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [bool] $Enabled=$false + ) + + Hyper-V\Set-VMProcessor -VM $VM -ExposeVirtualizationExtensions $Enabled + return $VM +<# +.SYNOPSIS + +Enable virtualization extensions on VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER Enabled + +Enable virtualization extensions on given VM. + +.OUTPUT + +VirtualMachine. +#> +} + +function Set-VagrantVMAutoActions { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [string] $AutoStartAction="Nothing", + [parameter (Mandatory=$false)] + [string] $AutoStopAction="ShutDown" + ) + + Hyper-V\Set-VM -VM $VM -AutomaticStartAction $AutoStartAction + Hyper-V\Set-VM -VM $VM -AutomaticStopAction $AutoStopAction + return $VM +<# +.SYNOPSIS + +Configure automatic start and stop actions for VM + +.DESCRIPTION + +Configures the automatic start and automatic stop actions for +the given VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER AutoStartAction + +Action the VM should automatically take when the host is started. + +.PARAMETER AutoStopAction + +Action the VM should automatically take when the host is stopped. + +.OUTPUT + +VirtualMachine. +#> +} + +function Set-VagrantVMService { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$true)] + [string] $Name, + [parameter (Mandatory=$true)] + [bool] $Enable + ) + + if($Enable) { + Hyper-V\Enable-VMIntegrationService -VM $VM -Name $Name + } else { + Hyper-V\Disable-VMIntegrationService -VM $VM -Name $Name + } + return $VM +<# +.SYNOPSIS + +Enable or disable Hyper-V VM integration services. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER Name + +Name of the integration service. + +.PARAMETER Enable + +Enable or disable the service. + +.OUTPUT + +VirtualMachine. +#> +} + +# Vagrant networking functions + +function Get-VagrantVMSwitch { + param ( + [parameter (Mandatory=$true)] + [string] $NameOrID + ) + $SwitchName = $(Hyper-V\Get-VMSwitch -Id $NameOrID).Name + if(!$SwitchName) { + $SwitchName = $(Hyper-V\Get-VMSwitch -Name $NameOrID).Name + } + if(!$SwitchName) { + throw "Failed to locate switch with name or ID: ${NameOrID}" + } + return $SwitchName +<# +.SYNOPSIS + +Get name of VMSwitch. + +.DESCRIPTION + +Find VMSwitch by name or ID and return name. + +.PARAMETER NameOrID + +Name or ID of VMSwitch. + +.OUTPUT + +Name of VMSwitch. +#> +} + +function Set-VagrantVMSwitch { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$true)] + [String] $SwitchName + ) + $Adapter = Hyper-V\Get-VMNetworkAdapter -VM $VM + Hyper-V\Connect-VMNetworkAdapter -VMNetworkAdapter $Adapter -SwitchName $SwitchName + return $VM +<# +.SYNOPSIS + +Configure VM to use given switch. + +.DESCRIPTION + +Configures VM adapter to use the the VMSwitch with the given name. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER SwitchName + +Name of the VMSwitch. + +.OUTPUT + +VirtualMachine. +#> +} diff --git a/plugins/providers/hyperv/scripts/utils/create_session.ps1 b/plugins/providers/hyperv/scripts/utils/create_session.ps1 deleted file mode 100644 index fcf0f7f10..000000000 --- a/plugins/providers/hyperv/scripts/utils/create_session.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -function Get-Remote-Session($guest_ip, $username, $password) { - $secstr = convertto-securestring -AsPlainText -Force -String $password - $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr - New-PSSession -ComputerName $guest_ip -Credential $cred -ErrorAction "stop" -} - -function Create-Remote-Session($guest_ip, $username, $password) { - $count = 0 - $session_error = "" - $session = "" - do { - $count++ - try { - $session = Get-Remote-Session $guest_ip $username $password - $session_error = "" - } - catch { - Start-Sleep -s 1 - $session_error = $_ - $session = "" - } - } - while (!$session -and $count -lt 20) - - return @{ - session = $session - error = $session_error - } -} diff --git a/plugins/providers/hyperv/scripts/utils/write_messages.ps1 b/plugins/providers/hyperv/scripts/utils/write_messages.ps1 deleted file mode 100644 index ce0d8d8cf..000000000 --- a/plugins/providers/hyperv/scripts/utils/write_messages.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -function Write-Error-Message($message) { - $error_message = @{ - error = "$message" - } - Write-Host "===Begin-Error===" - $result = ConvertTo-json $error_message - Write-Host $result - Write-Host "===End-Error===" -} - -function Write-Output-Message($message) { - Write-Host "===Begin-Output===" - Write-Host $message - Write-Host "===End-Output===" -} diff --git a/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb index 3629239d9..d319ac071 100644 --- a/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb +++ b/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb @@ -18,11 +18,17 @@ module VagrantPlugins private def self.ansible_apt_install(machine) - machine.communicate.sudo "apt-get update -y -qq" - machine.communicate.sudo "apt-get install -y -qq software-properties-common python-software-properties" - machine.communicate.sudo "add-apt-repository ppa:ansible/ansible -y" - machine.communicate.sudo "apt-get update -y -qq" - machine.communicate.sudo "apt-get install -y -qq ansible" + unless machine.communicate.test("test -x \"$(which add-apt-repository)\"") + machine.communicate.sudo """ + apt-get update -y -qq && \ + apt-get install -y -qq software-properties-common + """ + end + machine.communicate.sudo """ + add-apt-repository ppa:ansible/ansible -y && \ + apt-get update -y -qq && \ + apt-get install -y -qq ansible + """ end end diff --git a/plugins/provisioners/chef/provisioner/base.rb b/plugins/provisioners/chef/provisioner/base.rb index 28c30bb64..607909a4e 100644 --- a/plugins/provisioners/chef/provisioner/base.rb +++ b/plugins/provisioners/chef/provisioner/base.rb @@ -24,7 +24,7 @@ module VagrantPlugins @logger = Log4r::Logger.new("vagrant::provisioners::chef") - if !present?(@config.node_name) + if @config.respond_to?(:node_name) && !present?(@config.node_name) # First attempt to get the node name from the hostname, and if that # is not present, generate/retrieve a random hostname. hostname = @machine.config.vm.hostname diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index 33884f7f3..efa5500df 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -385,7 +385,7 @@ module VagrantPlugins @machine.env.ui.info "Calling state.highstate... (this may take a while)" if @config.install_master - unless @config.masterless? + unless @config.masterless @machine.communicate.sudo("salt '*' saltutil.sync_all") end options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_salt_args}" @@ -397,7 +397,7 @@ module VagrantPlugins else if @machine.config.vm.communicator == :winrm opts = { elevated: true } - unless @config.masterless? + unless @config.masterless @machine.communicate.execute("C:\\salt\\salt-call.bat saltutil.sync_all", opts) end # TODO: something equivalent to { error_key: :ssh_bad_exit_status_muted }? @@ -408,7 +408,7 @@ module VagrantPlugins end end else - unless @config.masterless? + unless @config.masterless @machine.communicate.sudo("salt-call saltutil.sync_all") end options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}" diff --git a/plugins/synced_folders/smb/scripts/mount_share.ps1 b/plugins/synced_folders/smb/scripts/mount_share.ps1 deleted file mode 100644 index fa1f98133..000000000 --- a/plugins/synced_folders/smb/scripts/mount_share.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -param ( - [string]$share_name = $(throw "-share_name is required."), - [string]$guest_path = $(throw "-guest_path is required."), - [string]$guest_ip = $(throw "-guest_ip is required."), - [string]$username = $(throw "-username is required."), - [string]$password = $(throw "-password is required."), - [string]$host_ip = $(throw "-host_ip is required."), - [string]$host_share_username = $(throw "-host_share_username is required."), - [string]$host_share_password = $(throw "-host_share_password is required.") - ) - -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\create_session.ps1" -$modules += $presentDir + "\utils\write_messages.ps1" - -forEach ($module in $modules) { . $module } - -try { - function Mount-File($share_name, $guest_path, $host_path, $host_share_username, $host_share_password) { - try { - # TODO: Check for folder exist. - # Use net use and prompt for password - $guest_path = $guest_path.replace("/", "\") - # Map a network drive to the guest machine - $result = net use * $host_path /user:$host_share_username $host_share_password /persistent:yes - $mapped_drive = (($result -match "\w:") -split (" "))[1] - Write-Host cmd /c mklink /d $guest_path $mapped_drive - # If a folder exist remove it. - if (Test-Path $guest_path) { - $junction = Get-Item $guest_path - $junction.Delete() - } - cmd /c mklink /d $guest_path $mapped_drive - } catch { - return $_ - } - } - - $response = Create-Remote-Session $guest_ip $username $password - - if (!$response["session"] -and $response["error"]) { - Write-Error-Message $response["error"] - return - } - $host_path = "\\$host_ip\$share_name" - $host_share_username = "$host_ip\$host_share_username" - $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Mount-File} -ArgumentList $share_name, $guest_path, $host_path, $host_share_username, $host_share_password -ErrorAction "stop" - Remove-PSSession -Id $response["session"].Id - Write-Error-Message $result -} -catch { - Write-Error-Message "Failed to mount files VM $_" - return -} diff --git a/plugins/synced_folders/smb/scripts/ps_version.ps1 b/plugins/synced_folders/smb/scripts/ps_version.ps1 deleted file mode 100644 index 89fbab28e..000000000 --- a/plugins/synced_folders/smb/scripts/ps_version.ps1 +++ /dev/null @@ -1 +0,0 @@ -Write-Output $PSVersionTable.PSVersion.Major diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index d81875297..d4deb6153 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -11,6 +11,34 @@ en: message_not_running: |- Hyper-V machine isn't running. Can't SSH in! + config: + invalid_auto_start_action: |- + The requested auto start action for the Hyper-V VM is not a + valid action. Please provide a valid action and run the command + again. + + Received: %{action} + Allowed: %{allowed_actions} + invalid_auto_stop_action: |- + The requested auto stop action for the Hyper-V VM is not a + valid action. Please provide a valid action and run the command + again. + + Received: %{action} + Allowed: %{allowed_actions} + invalid_integration_services_type: |- + Invalid type provided for `vm_integration_services`. Type received + is `%{received}` but `Hash` was expected. + invalid_integration_services_entry: |- + The `%{entry_name}` entry in the `vm_integration_services` is set + to an unexpected value. + + Received: %{entry_value} + Allowed: true, false + differencing_disk_deprecation: |- + The `differencing_disk` configuration option is deprecated and should + no longer be used. The `linked_clone` configuration option should + be used instead. errors: admin_required: |- The Hyper-V provider requires that Vagrant be run with diff --git a/test/unit/plugins/communicators/ssh/communicator_test.rb b/test/unit/plugins/communicators/ssh/communicator_test.rb index 1b244b8a3..a236c1931 100644 --- a/test/unit/plugins/communicators/ssh/communicator_test.rb +++ b/test/unit/plugins/communicators/ssh/communicator_test.rb @@ -34,9 +34,12 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do double("machine", config: config, provider: provider, - ui: ui + ui: ui, + env: env ) end + let(:env){ double("env", host: host) } + let(:host){ double("host") } # SSH information of the machine let(:machine_ssh_info){ {host: '10.1.2.3', port: 22} } # Subject instance to test @@ -89,6 +92,10 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do allow(communicator).to receive(:retryable).and_return(connection) end + before do + allow(host).to receive(:capability?).and_return(false) + end + describe ".wait_for_ready" do before(&connection_setup) context "with no static config (default scenario)" do @@ -208,41 +215,14 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do expect(private_key_file).to receive(:write).with(new_private_key) end - it "should set private key file as user readable only" do - expect(private_key_file).to receive(:chmod).with(0600) + it "should call the set_ssh_key_permissions host capability" do + expect(host).to receive(:capability?).with(:set_ssh_key_permissions).and_return(true) + expect(host).to receive(:capability).with(:set_ssh_key_permissions, private_key_file) end it "should remove the default public key" do expect(guest).to receive(:capability).with(:remove_public_key, any_args) end - - context "on windows platform" do - let(:owner){ "owner" } - - before do - allow(private_key_file).to receive(:to_s).and_return("PRIVATE_KEY_PATH") - allow(File).to receive(:set_permissions) - allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true) - allow(Etc).to receive(:getlogin).and_return(owner) - stub_const('File::FULL', :full) - end - - it "should get set new permissions on private key file" do - expect(File).to receive(:set_permissions).with("PRIVATE_KEY_PATH", any_args) - end - - it "should proceed when error is encountered" do - expect(File).to receive(:set_permissions).and_raise(StandardError) - end - - context "with multiple permissions on file" do - - it "should delete all non-owner permissions" do - expect(File).to receive(:set_permissions).with("PRIVATE_KEY_PATH", - owner => :full) - end - end - end end end end diff --git a/test/unit/plugins/hosts/bsd/cap/ssh_test.rb b/test/unit/plugins/hosts/bsd/cap/ssh_test.rb new file mode 100644 index 000000000..1b571a907 --- /dev/null +++ b/test/unit/plugins/hosts/bsd/cap/ssh_test.rb @@ -0,0 +1,15 @@ +require_relative "../../../../base" + +require_relative "../../../../../../plugins/hosts/bsd/cap/ssh" + +describe VagrantPlugins::HostBSD::Cap::SSH do + let(:subject){ VagrantPlugins::HostBSD::Cap::SSH } + + let(:env){ double("env") } + let(:key_path){ double("key_path") } + + it "should set file as user only read/write" do + expect(key_path).to receive(:chmod).with(0600) + subject.set_ssh_key_permissions(env, key_path) + end +end diff --git a/test/unit/plugins/hosts/linux/cap/ssh_test.rb b/test/unit/plugins/hosts/linux/cap/ssh_test.rb new file mode 100644 index 000000000..1dd67b5e9 --- /dev/null +++ b/test/unit/plugins/hosts/linux/cap/ssh_test.rb @@ -0,0 +1,15 @@ +require_relative "../../../../base" + +require_relative "../../../../../../plugins/hosts/linux/cap/ssh" + +describe VagrantPlugins::HostLinux::Cap::SSH do + let(:subject){ VagrantPlugins::HostLinux::Cap::SSH } + + let(:env){ double("env") } + let(:key_path){ double("key_path") } + + it "should set file as user only read/write" do + expect(key_path).to receive(:chmod).with(0600) + subject.set_ssh_key_permissions(env, key_path) + end +end diff --git a/test/unit/plugins/hosts/windows/cap/smb_test.rb b/test/unit/plugins/hosts/windows/cap/smb_test.rb index d482ad0dd..d791c53d9 100644 --- a/test/unit/plugins/hosts/windows/cap/smb_test.rb +++ b/test/unit/plugins/hosts/windows/cap/smb_test.rb @@ -31,7 +31,7 @@ Share name Resource Remark vgt-CUSTOM_ID-1 /a/path vgt-CUSTOM_ID-1 vgt-CUSTOM_ID-2 /other/path vgt-CUSTOM_ID-2 my-share /my/path Not Vagran... - +The command completed successfully. EOF } let(:netshare1){ <<-EOF diff --git a/test/unit/plugins/hosts/windows/cap/ssh_test.rb b/test/unit/plugins/hosts/windows/cap/ssh_test.rb new file mode 100644 index 000000000..e258574e5 --- /dev/null +++ b/test/unit/plugins/hosts/windows/cap/ssh_test.rb @@ -0,0 +1,38 @@ +require_relative "../../../../base" + +require_relative "../../../../../../plugins/hosts/windows/cap/ssh" + +describe VagrantPlugins::HostWindows::Cap::SSH do + let(:subject){ VagrantPlugins::HostWindows::Cap::SSH } + let(:result){ Vagrant::Util::Subprocess::Result.new(exit_code, stdout, stderr) } + let(:exit_code){ 0 } + let(:stdout){ "" } + let(:stderr){ "" } + + let(:key_path){ double("keypath", to_s: "keypath") } + let(:env){ double("env") } + + before do + allow(Vagrant::Util::PowerShell).to receive(:execute).and_return(result) + end + + it "should execute PowerShell script" do + expect(Vagrant::Util::PowerShell).to receive(:execute).with( + /set_ssh_key_permissions.ps1/, "-KeyPath", key_path.to_s, any_args + ).and_return(result) + subject.set_ssh_key_permissions(env, key_path) + end + + it "should return the result" do + + expect(subject.set_ssh_key_permissions(env, key_path)).to eq(result) + end + + context "when command fails" do + let(:exit_code){ 1 } + + it "should raise an error" do + expect{ subject.set_ssh_key_permissions(env, key_path) }.to raise_error(Vagrant::Errors::PowerShellError) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/check_enabled_test.rb b/test/unit/plugins/providers/hyperv/action/check_enabled_test.rb new file mode 100644 index 000000000..288d61b1f --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/check_enabled_test.rb @@ -0,0 +1,27 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/check_enabled") + +describe VagrantPlugins::HyperV::Action::CheckEnabled do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:subject){ described_class.new(app, env) } + + before{ allow(ui).to receive(:output) } + + it "should continue when Hyper-V is enabled" do + expect(driver).to receive(:execute).and_return("result" => true) + expect(app).to receive(:call) + subject.call(env) + end + + it "should raise error when Hyper-V is not enabled" do + expect(driver).to receive(:execute).and_return("result" => false) + expect(app).not_to receive(:call) + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellFeaturesDisabled) + end +end diff --git a/test/unit/plugins/providers/hyperv/action/configure_test.rb b/test/unit/plugins/providers/hyperv/action/configure_test.rb new file mode 100644 index 000000000..9b2d8222b --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/configure_test.rb @@ -0,0 +1,125 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/configure") + +describe VagrantPlugins::HyperV::Action::Configure do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, config: config, provider_config: provider_config, data_dir: data_dir, id: "machineID") } + let(:data_dir){ double("data_dir") } + let(:config){ double("config", vm: vm) } + let(:vm){ double("vm", networks: networks) } + let(:networks){ [] } + let(:switches){ [ + {"Name" => "Switch1", "Id" => "ID1"}, + {"Name" => "Switch2", "Id" => "ID2"} + ]} + let(:sentinel){ double("sentinel") } + let(:provider_config){ + double("provider_config", + memory: "1024", + maxmemory: "1024", + cpus: 1, + auto_start_action: "Nothing", + auto_stop_action: "Save", + enable_checkpoints: false, + enable_virtualization_extensions: false, + vm_integration_services: vm_integration_services + ) + } + let(:vm_integration_services){ {} } + + let(:subject){ described_class.new(app, env) } + + before do + allow(driver).to receive(:execute) + allow(app).to receive(:call) + expect(driver).to receive(:execute).with(:get_switches).and_return(switches) + allow(ui).to receive(:detail) + allow(ui).to receive(:output) + allow(ui).to receive(:ask).and_return("1") + allow(data_dir).to receive(:join).and_return(sentinel) + allow(sentinel).to receive(:file?).and_return(false) + allow(sentinel).to receive(:open) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + context "with missing switch sentinel file" do + it "should prompt for switch to use" do + expect(ui).to receive(:ask) + subject.call(env) + end + + it "should write sentinel file" do + expect(sentinel).to receive(:open) + subject.call(env) + end + end + + context "with existing switch sentinel file" do + before{ allow(sentinel).to receive(:file?).twice.and_return(true) } + + it "should not prompt for switch to use" do + expect(ui).not_to receive(:ask) + subject.call(env) + end + + it "should not write sentinel file" do + expect(sentinel).not_to receive(:open) + subject.call(env) + end + end + + context "with bridge defined in networks" do + context "with valid bridge switch name" do + let(:networks){ [[:public_network, {bridge: "Switch1"}]] } + + it "should not prompt for switch" do + expect(ui).not_to receive(:ask) + subject.call(env) + end + end + + context "with valid bridge switch ID" do + let(:networks){ [[:public_network, {bridge: "ID1"}]] } + + it "should not prompt for switch" do + expect(ui).not_to receive(:ask) + subject.call(env) + end + end + + context "with invalid bridge switch name" do + let(:networks){ [[:public_network, {bridge: "UNKNOWN"}]] } + + it "should prompt for switch" do + expect(ui).to receive(:ask) + subject.call(env) + end + end + end + + context "with integration services enabled" do + let(:vm_integration_services){ {service: true} } + + it "should call the driver to set the services" do + expect(driver).to receive(:set_vm_integration_services) + subject.call(env) + end + end + + context "without available switches" do + let(:switches){ [] } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::NoSwitches) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/delete_vm_test.rb b/test/unit/plugins/providers/hyperv/action/delete_vm_test.rb new file mode 100644 index 000000000..dda24c90c --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/delete_vm_test.rb @@ -0,0 +1,29 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/delete_vm") + +describe VagrantPlugins::HyperV::Action::DeleteVM do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(ui).to receive(:info) + allow(driver).to receive(:delete_vm) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to delete the vm" do + expect(driver).to receive(:delete_vm) + subject.call(env) + end +end diff --git a/test/unit/plugins/providers/hyperv/action/export_test.rb b/test/unit/plugins/providers/hyperv/action/export_test.rb new file mode 100644 index 000000000..257225219 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/export_test.rb @@ -0,0 +1,42 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/export") + +describe VagrantPlugins::HyperV::Action::Export do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, state: state) } + let(:state){ double("state", id: machine_state) } + let(:machine_state){ :off } + + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(ui).to receive(:info) + allow(ui).to receive(:clear_line) + allow(ui).to receive(:report_progress) + allow(driver).to receive(:export) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to perform the export" do + expect(driver).to receive(:export) + subject.call(env) + end + + context "with invalid machine state" do + let(:machine_state){ :on } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(Vagrant::Errors::VMPowerOffToPackage) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/import_test.rb b/test/unit/plugins/providers/hyperv/action/import_test.rb new file mode 100644 index 000000000..9c66bb0c2 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/import_test.rb @@ -0,0 +1,130 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/import") + +describe VagrantPlugins::HyperV::Action::Import do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config, box: box, data_dir: data_dir, name: "machname") } + let(:provider_config){ + double("provider_config", + linked_clone: false, + vmname: "VMNAME" + ) + } + let(:box){ double("box", directory: box_directory) } + let(:box_directory){ double("box_directory") } + let(:data_dir){ double("data_dir") } + let(:vm_dir){ double("vm_dir") } + let(:hd_dir){ double("hd_dir") } + + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(box_directory).to receive(:join).with("Virtual Machines").and_return(vm_dir) + allow(box_directory).to receive(:join).with("Virtual Hard Disks").and_return(hd_dir) + allow(vm_dir).to receive(:directory?).and_return(true) + allow(vm_dir).to receive(:each_child).and_yield(Pathname.new("file.txt")) + allow(hd_dir).to receive(:directory?).and_return(true) + allow(hd_dir).to receive(:each_child).and_yield(Pathname.new("file.txt")) + allow(driver).to receive(:has_vmcx_support?).and_return(true) + allow(data_dir).to receive(:join).and_return(data_dir) + allow(data_dir).to receive(:to_s).and_return("DATA_DIR_PATH") + allow(driver).to receive(:import).and_return("id" => "VMID") + allow(machine).to receive(:id=) + allow(ui).to receive(:output) + allow(ui).to receive(:detail) + end + + context "with missing virtual machines directory" do + before{ expect(vm_dir).to receive(:directory?).and_return(false) } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with missing hard disks directory" do + before{ expect(hd_dir).to receive(:directory?).and_return(false) } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with missing configuration file" do + before do + allow(hd_dir).to receive(:each_child).and_yield(Pathname.new("image.vhd")) + end + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with missing image file" do + before do + allow(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.xml")) + end + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with image and config files" do + before do + allow(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.xml")) + allow(hd_dir).to receive(:each_child).and_yield(Pathname.new("image.vhd")) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should request import via the driver" do + expect(driver).to receive(:import).and_return("id" => "VMID") + subject.call(env) + end + + it "should set the machine ID after import" do + expect(machine).to receive(:id=).with("VMID") + subject.call(env) + end + + context "with no vmcx support" do + before do + expect(driver).to receive(:has_vmcx_support?).and_return(false) + end + + it "should match XML config file" do + subject.call(env) + end + + it "should not match VMCX config file" do + expect(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.vmcx")) + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with vmcx support" do + before do + expect(driver).to receive(:has_vmcx_support?).and_return(true) + end + + it "should match XML config file" do + subject.call(env) + end + + it "should match VMCX config file" do + expect(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.vmcx")) + subject.call(env) + end + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/is_windows_test.rb b/test/unit/plugins/providers/hyperv/action/is_windows_test.rb new file mode 100644 index 000000000..6ed9aaf68 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/is_windows_test.rb @@ -0,0 +1,37 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/is_windows") + +describe VagrantPlugins::HyperV::Action::IsWindows do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, config: config) } + let(:config){ double("config", vm: vm) } + let(:vm){ double("vm", guest: :windows) } + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(env).to receive(:[]=) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should update the env with the result" do + expect(env).to receive(:[]=).with(:result, true) + subject.call(env) + end + + it "should set the result to false when not windows" do + expect(vm).to receive(:guest).and_return(:linux) + expect(env).to receive(:[]=).with(:result, false) + subject.call(env) + end + +end diff --git a/test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb b/test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb new file mode 100644 index 000000000..01bc3d6ef --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb @@ -0,0 +1,40 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/net_set_mac") + +describe VagrantPlugins::HyperV::Action::NetSetMac do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config) } + let(:provider_config){ double("provider_config", mac: mac) } + let(:mac){ "ADDRESS" } + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:info) + allow(driver).to receive(:net_set_mac) + allow(app).to receive(:call) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to set the MAC address" do + expect(driver).to receive(:net_set_mac).with(mac) + subject.call(env) + end + + context "with no MAC address provided" do + let(:mac){ nil } + + it "should not call driver to set the MAC address" do + expect(driver).not_to receive(:net_set_mac) + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb b/test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb new file mode 100644 index 000000000..c8b128d73 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb @@ -0,0 +1,40 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/net_set_vlan") + +describe VagrantPlugins::HyperV::Action::NetSetVLan do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config) } + let(:provider_config){ double("provider_config", vlan_id: vlan_id) } + let(:vlan_id){ "VID" } + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:info) + allow(driver).to receive(:net_set_vlan) + allow(app).to receive(:call) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to set the vlan id" do + expect(driver).to receive(:net_set_vlan).with(vlan_id) + subject.call(env) + end + + context "with no vlan id provided" do + let(:vlan_id){ nil } + + it "should not call driver to set the vlan id" do + expect(driver).not_to receive(:net_set_vlan) + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb b/test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb new file mode 100644 index 000000000..1ec91639a --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb @@ -0,0 +1,39 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/read_guest_ip") + +describe VagrantPlugins::HyperV::Action::ReadGuestIP do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(env).to receive(:[]=) + allow(machine).to receive(:id) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + context "with machine ID set" do + before{ allow(machine).to receive(:id).and_return("VMID") } + + it "should request guest IP from the driver" do + expect(driver).to receive(:read_guest_ip).and_return("ip" => "ADDRESS") + subject.call(env) + end + + it "should set the host information into the env" do + expect(env).to receive(:[]=).with(:machine_ssh_info, host: "ADDRESS") + expect(driver).to receive(:read_guest_ip).and_return("ip" => "ADDRESS") + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/read_state_test.rb b/test/unit/plugins/providers/hyperv/action/read_state_test.rb new file mode 100644 index 000000000..50602f67d --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/read_state_test.rb @@ -0,0 +1,56 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/read_state") + +describe VagrantPlugins::HyperV::Action::ReadState do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine, machine_state_id: state_id} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:state_id){ nil } + + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(env).to receive(:[]=) + allow(machine).to receive(:id) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should set machine state into the env as not created" do + expect(env).to receive(:[]=).with(:machine_state_id, :not_created) + subject.call(env) + end + + context "with machine ID set" do + before{ allow(machine).to receive(:id).and_return("VMID") } + + it "should request machine state from the driver" do + expect(driver).to receive(:get_current_state).and_return("state" => "running") + subject.call(env) + end + + it "should set machine state into the env" do + expect(driver).to receive(:get_current_state).and_return("state" => "running") + expect(env).to receive(:[]=).with(:machine_state_id, :running) + subject.call(env) + end + + context "with machine state ID as not_created" do + let(:state_id){ :not_created } + + it "should clear the machine ID" do + expect(driver).to receive(:get_current_state).and_return("state" => "not_created") + expect(machine).to receive(:id=).with(nil) + subject.call(env) + end + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/set_name_test.rb b/test/unit/plugins/providers/hyperv/action/set_name_test.rb new file mode 100644 index 000000000..9708140e8 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/set_name_test.rb @@ -0,0 +1,61 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/set_name") + +describe VagrantPlugins::HyperV::Action::SetName do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine, root_path: Pathname.new("path")} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config, data_dir: data_dir, name: "machname") } + let(:data_dir){ double("data_dir") } + let(:provider_config){ double("provider_config", vmname: vmname) } + let(:vmname){ "VMNAME" } + let(:sentinel){ double("sentinel") } + + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:info) + allow(driver).to receive(:set_name) + allow(app).to receive(:call) + allow(data_dir).to receive(:join).and_return(sentinel) + allow(sentinel).to receive(:file?).and_return(false) + allow(sentinel).to receive(:open) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to set the name" do + expect(driver).to receive(:set_name) + subject.call(env) + end + + it "should use the configured name when setting" do + expect(driver).to receive(:set_name).with(vmname) + subject.call(env) + end + + it "should write sentinel after name is set" do + expect(sentinel).to receive(:open) + subject.call(env) + end + + context "when no name is provided in the config" do + let(:vmname){ nil } + + it "should generate a name based on path and machine" do + expect(driver).to receive(:set_name).with(/^#{env[:root_path].to_s}_#{machine.name}_.+/) + subject.call(env) + end + + it "should not set name if sentinel exists" do + expect(sentinel).to receive(:file?).and_return(true) + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb b/test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb new file mode 100644 index 000000000..4d5173236 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb @@ -0,0 +1,40 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/wait_for_ip_address") + +describe VagrantPlugins::HyperV::Action::WaitForIPAddress do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config) } + let(:provider_config){ double("provider_config", ip_address_timeout: ip_address_timeout) } + let(:ip_address_timeout){ 1 } + + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:output) + allow(ui).to receive(:detail) + allow(driver).to receive(:read_guest_ip).and_return("ip" => "127.0.0.1") + allow(app).to receive(:call) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should set a timeout for waiting" do + expect(Timeout).to receive(:timeout).with(ip_address_timeout) + subject.call(env) + end + + it "should retry until it receives a valid address" do + expect(driver).to receive(:read_guest_ip).and_return("ip" => "ADDRESS") + expect(driver).to receive(:read_guest_ip).and_return("ip" => "127.0.0.1") + expect(subject).to receive(:sleep) + subject.call(env) + end +end diff --git a/test/unit/plugins/providers/hyperv/config_test.rb b/test/unit/plugins/providers/hyperv/config_test.rb index 467fc7f97..23328cf3b 100644 --- a/test/unit/plugins/providers/hyperv/config_test.rb +++ b/test/unit/plugins/providers/hyperv/config_test.rb @@ -3,6 +3,10 @@ require_relative "../../../base" require Vagrant.source_root.join("plugins/providers/hyperv/config") describe VagrantPlugins::HyperV::Config do + + let(:machine){ double("machine", ui: ui) } + let(:ui){ double("ui") } + describe "#ip_address_timeout" do it "can be set" do subject.ip_address_timeout = 180 @@ -30,7 +34,7 @@ describe VagrantPlugins::HyperV::Config do expect(subject.mac).to eq("001122334455") end end - + describe "#vmname" do it "can be set" do subject.vmname = "test" @@ -62,4 +66,159 @@ describe VagrantPlugins::HyperV::Config do expect(subject.cpus).to eq(2) end end + + describe "#vmname" do + it "can be set" do + subject.vmname = "custom" + subject.finalize! + expect(subject.vmname).to eq("custom") + end + end + + describe "#differencing_disk" do + it "is false by default" do + subject.finalize! + expect(subject.differencing_disk).to eq(false) + end + + it "can be set" do + subject.differencing_disk = true + subject.finalize! + expect(subject.differencing_disk).to eq(true) + end + + it "should set linked_clone" do + subject.differencing_disk = true + subject.finalize! + expect(subject.differencing_disk).to eq(true) + expect(subject.linked_clone).to eq(true) + end + + it "should provide a deprecation warning when set" do + expect(ui).to receive(:warn) + subject.differencing_disk = true + subject.finalize! + subject.validate(machine) + end + end + + describe "#linked_clone" do + it "is false by default" do + subject.finalize! + expect(subject.linked_clone).to eq(false) + end + + it "can be set" do + subject.linked_clone = true + subject.finalize! + expect(subject.linked_clone).to eq(true) + end + + it "should set differencing_disk" do + subject.linked_clone = true + subject.finalize! + expect(subject.linked_clone).to eq(true) + expect(subject.differencing_disk).to eq(true) + end + end + + describe "#auto_start_action" do + it "should be Nothing by default" do + subject.finalize! + expect(subject.auto_start_action).to eq("Nothing") + end + + it "can be set" do + subject.auto_start_action = "Start" + subject.finalize! + expect(subject.auto_start_action).to eq("Start") + end + + it "does not accept invalid values" do + subject.auto_start_action = "Invalid" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + end + + describe "#auto_stop_action" do + it "should be ShutDown by default" do + subject.finalize! + expect(subject.auto_stop_action).to eq("ShutDown") + end + + it "can be set" do + subject.auto_stop_action = "Save" + subject.finalize! + expect(subject.auto_stop_action).to eq("Save") + end + + it "does not accept invalid values" do + subject.auto_stop_action = "Invalid" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + end + + describe "#enable_checkpoints" do + it "is false by default" do + subject.finalize! + expect(subject.enable_checkpoints).to eq(false) + end + + it "can be set" do + subject.enable_checkpoints = true + subject.finalize! + expect(subject.enable_checkpoints).to eq(true) + end + end + + describe "#enable_virtualization_extensions" do + it "is false by default" do + subject.finalize! + expect(subject.enable_virtualization_extensions).to eq(false) + end + + it "can be set" do + subject.enable_virtualization_extensions = true + subject.finalize! + expect(subject.enable_virtualization_extensions).to eq(true) + end + end + + describe "#vm_integration_services" do + it "is empty by default" do + subject.finalize! + expect(subject.vm_integration_services).to be_empty + end + + it "accepts new entries" do + subject.vm_integration_services["entry"] = "value" + subject.finalize! + expect(subject.vm_integration_services["entry"]).to eq("value") + end + + it "does not accept non-Hash types" do + subject.vm_integration_services = "value" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + + it "accepts boolean values within Hash" do + subject.vm_integration_services["custom"] = true + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).to be_empty + end + + it "does not accept non-boolean values within Hash" do + subject.vm_integration_services["custom"] = "value" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + end end diff --git a/test/unit/plugins/providers/hyperv/driver_test.rb b/test/unit/plugins/providers/hyperv/driver_test.rb new file mode 100644 index 000000000..fa99484f0 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/driver_test.rb @@ -0,0 +1,176 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/driver") + +describe VagrantPlugins::HyperV::Driver do + def generate_result(obj) + "===Begin-Output===\n" + + JSON.dump(obj) + + "\n===End-Output===" + end + + def generate_error(msg) + "===Begin-Error===\n#{JSON.dump(error: msg)}\n===End-Error===\n" + end + + let(:result){ + Vagrant::Util::Subprocess::Result.new( + result_exit, result_stdout, result_stderr) } + let(:subject){ described_class.new(vm_id) } + let(:vm_id){ 1 } + let(:result_stdout){ "" } + let(:result_stderr){ "" } + let(:result_exit){ 0 } + + context "public methods" do + before{ allow(subject).to receive(:execute_powershell).and_return(result) } + + describe "#execute" do + it "should convert symbol into path string" do + expect(subject).to receive(:execute_powershell).with(kind_of(String), any_args) + .and_return(result) + subject.execute(:thing) + end + + it "should append extension when converting symbol" do + expect(subject).to receive(:execute_powershell).with("thing.ps1", any_args) + .and_return(result) + subject.execute(:thing) + end + + context "when command returns non-zero exit code" do + let(:result_exit){ 1 } + + it "should raise an error" do + expect{ subject.execute(:thing) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellError) + end + end + + context "when command stdout matches error pattern" do + let(:result_stdout){ generate_error("Error Message") } + + it "should raise an error" do + expect{ subject.execute(:thing) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellError) + end + end + + context "with valid JSON output" do + let(:result_stdout){ generate_result(:custom => "value") } + + it "should return parsed JSON data" do + expect(subject.execute(:thing)).to eq("custom" => "value") + end + end + + context "with invalid JSON output" do + let(:result_stdout){ "value" } + it "should return nil" do + expect(subject.execute(:thing)).to be_nil + end + end + end + + describe "#has_vmcx_support?" do + context "when support is available" do + let(:result_stdout){ generate_result(:result => true) } + + it "should be true" do + expect(subject.has_vmcx_support?).to eq(true) + end + end + + context "when support is not available" do + let(:result_stdout){ generate_result(:result => false) } + + it "should be false" do + expect(subject.has_vmcx_support?).to eq(false) + end + end + end + + describe "#set_vm_integration_services" do + it "should map known integration services names automatically" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Name]).to eq("Shutdown") + end + subject.set_vm_integration_services(shutdown: true) + end + + it "should set enable when value is true" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Enable]).to eq(true) + end + subject.set_vm_integration_services(shutdown: true) + end + + it "should not set enable when value is false" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Enable]).to be_nil + end + subject.set_vm_integration_services(shutdown: false) + end + + it "should pass unknown key names directly through" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Name]).to eq("CustomKey") + end + subject.set_vm_integration_services(CustomKey: true) + end + end + end + + describe "#execute_powershell" do + before{ allow(Vagrant::Util::PowerShell).to receive(:execute) } + + it "should call the PowerShell module to execute" do + expect(Vagrant::Util::PowerShell).to receive(:execute) + subject.send(:execute_powershell, "path", {}) + end + + it "should modify the path separators" do + expect(Vagrant::Util::PowerShell).to receive(:execute) + .with("\\path\\to\\script.ps1", any_args) + subject.send(:execute_powershell, "/path/to/script.ps1", {}) + end + + it "should include ErrorAction option as Stop" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).to include("-ErrorAction") + expect(args).to include("Stop") + end + subject.send(:execute_powershell, "path", {}) + end + + it "should automatically include module path" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + opts = args.detect{|i| i.is_a?(Hash)} + expect(opts[:env]).not_to be_nil + expect(opts[:env]["PSModulePath"]).to include("$env:PSModulePath+") + end + subject.send(:execute_powershell, "path", {}) + end + + it "should covert hash options into arguments" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).to include("-Custom") + expect(args).to include("'Value'") + end + subject.send(:execute_powershell, "path", "Custom" => "Value") + end + + it "should treat keys with `true` value as switches" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).to include("-Custom") + expect(args).not_to include("'true'") + end + subject.send(:execute_powershell, "path", "Custom" => true) + end + + it "should not include keys with `false` value" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).not_to include("-Custom") + end + subject.send(:execute_powershell, "path", "Custom" => false) + end + end +end diff --git a/test/unit/plugins/provisioners/chef/provisioner/base_test.rb b/test/unit/plugins/provisioners/chef/provisioner/base_test.rb index 10536f035..9afa712b8 100644 --- a/test/unit/plugins/provisioners/chef/provisioner/base_test.rb +++ b/test/unit/plugins/provisioners/chef/provisioner/base_test.rb @@ -39,16 +39,21 @@ describe VagrantPlugins::Chef::Provisioner::Base do it "defaults to hostname if given" do machine.config.vm.hostname = "by.hostname" - instance = described_class.new(machine, OpenStruct.new) + instance = described_class.new(machine, OpenStruct.new(node_name: nil)) expect(instance.config.node_name).to eq("by.hostname") end it "generates a random name if no hostname or node_name is given" do - config = OpenStruct.new(node_name: nil) machine.config.vm.hostname = nil - instance = described_class.new(machine, OpenStruct.new) + instance = described_class.new(machine, OpenStruct.new(node_name: nil)) expect(instance.config.node_name).to match(/vagrant\-.+/) end + + it "does not set node_name if configuration does not define it" do + expect(config).to receive(:respond_to?).with(:node_name).and_return(false) + expect(config).not_to receive(:node_name) + described_class.new(machine, config) + end end describe "#encrypted_data_bag_secret_key_path" do diff --git a/test/unit/plugins/provisioners/salt/provisioner_test.rb b/test/unit/plugins/provisioners/salt/provisioner_test.rb index ad9d0753e..e6bfece89 100644 --- a/test/unit/plugins/provisioners/salt/provisioner_test.rb +++ b/test/unit/plugins/provisioners/salt/provisioner_test.rb @@ -55,7 +55,6 @@ describe VagrantPlugins::Salt::Provisioner do it "passes along extra cli flags" do allow(config).to receive(:run_highstate).and_return(true) allow(config).to receive(:verbose).and_return(true) - allow(config).to receive(:masterless?).and_return(false) allow(config).to receive(:masterless).and_return(false) allow(config).to receive(:minion_id).and_return(nil) allow(config).to receive(:log_level).and_return(nil) @@ -74,7 +73,6 @@ describe VagrantPlugins::Salt::Provisioner do it "has no additional cli flags if not included" do allow(config).to receive(:run_highstate).and_return(true) allow(config).to receive(:verbose).and_return(true) - allow(config).to receive(:masterless?).and_return(false) allow(config).to receive(:masterless).and_return(false) allow(config).to receive(:minion_id).and_return(nil) allow(config).to receive(:log_level).and_return(nil) @@ -95,7 +93,6 @@ describe VagrantPlugins::Salt::Provisioner do it "passes along extra cli flags" do allow(config).to receive(:run_highstate).and_return(true) allow(config).to receive(:verbose).and_return(true) - allow(config).to receive(:masterless?).and_return(true) allow(config).to receive(:masterless).and_return(true) allow(config).to receive(:minion_id).and_return(nil) allow(config).to receive(:log_level).and_return(nil) @@ -115,7 +112,6 @@ describe VagrantPlugins::Salt::Provisioner do it "has no additional cli flags if not included" do allow(config).to receive(:run_highstate).and_return(true) allow(config).to receive(:verbose).and_return(true) - allow(config).to receive(:masterless?).and_return(true) allow(config).to receive(:masterless).and_return(true) allow(config).to receive(:minion_id).and_return(nil) allow(config).to receive(:log_level).and_return(nil) diff --git a/test/unit/vagrant/util/powershell_test.rb b/test/unit/vagrant/util/powershell_test.rb index d5760fc28..0733f7783 100644 --- a/test/unit/vagrant/util/powershell_test.rb +++ b/test/unit/vagrant/util/powershell_test.rb @@ -4,6 +4,9 @@ require 'vagrant/util/powershell' describe Vagrant::Util::PowerShell do include_context "unit" + + after{ described_class.reset! } + describe ".version" do before do allow(described_class).to receive(:executable) @@ -13,7 +16,6 @@ describe Vagrant::Util::PowerShell do after do described_class.version - described_class.reset! end it "should execute powershell command" do @@ -41,4 +43,272 @@ describe Vagrant::Util::PowerShell do end end end + + describe ".executable" do + before{ allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(false) } + + context "when found in PATH" do + before{ expect(Vagrant::Util::Which).to receive(:which).with("powershell").and_return(true) } + + it "should return powershell string" do + expect(described_class.executable).to eq("powershell") + end + end + + context "when not found in PATH" do + before{ expect(Vagrant::Util::Which).to receive(:which).with("powershell").and_return(nil) } + + it "should return nil" do + expect(described_class.executable).to be_nil + end + + context "when within WSL" do + before{ expect(Vagrant::Util::Platform).to receive(:wsl?).and_return(true) } + + it "should check PATH with .exe extension" do + expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe") + described_class.executable + end + + it "should return powershell.exe when found" do + expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return(true) + expect(described_class.executable).to eq("powershell.exe") + end + + it "should return nil when not found" do + expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return(nil) + expect(described_class.executable).to be_nil + end + end + end + end + + describe ".available?" do + context "when powershell executable is available" do + before{ expect(described_class).to receive(:executable).and_return("powershell") } + + it "should be true" do + expect(described_class.available?).to be(true) + end + end + + context "when powershell executable is not available" do + before{ expect(described_class).to receive(:executable).and_return(nil) } + + it "should be false" do + expect(described_class.available?).to be(false) + end + end + end + + describe ".execute" do + before do + allow(described_class).to receive(:validate_install!) + allow(Vagrant::Util::Subprocess).to receive(:execute) + end + + it "should validate installation before use" do + expect(described_class).to receive(:validate_install!) + described_class.execute("command") + end + + it "should include command to execute" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("custom-command") + end + described_class.execute("custom-command") + end + + it "should automatically include console resize" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("BufferSize") + end + described_class.execute("custom-command") + end + + it "should accept custom environment" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("$env:TEST_KEY=test-value") + end + described_class.execute("custom-command", env: {"TEST_KEY" => "test-value"}) + end + + it "should define a custom module path" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("$env:PSModulePath+';C:\\My-Path'") + end + described_class.execute("custom-command", module_path: "C:\\My-Path") + end + end + + describe ".execute_cmd" do + let(:result) do + Vagrant::Util::Subprocess::Result.new( + exit_code, stdout, stderr) + end + let(:exit_code){ 0 } + let(:stdout){ "" } + let(:stderr){ "" } + + before do + allow(described_class).to receive(:validate_install!) + allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result) + end + + it "should validate installation before use" do + expect(described_class).to receive(:validate_install!) + described_class.execute_cmd("command") + end + + it "should include command to execute" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("custom-command") + result + end + described_class.execute_cmd("custom-command") + end + + it "should automatically include console resize" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("BufferSize") + result + end + described_class.execute_cmd("custom-command") + end + + it "should accept custom environment" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("$env:TEST_KEY=test-value") + result + end + described_class.execute_cmd("custom-command", env: {"TEST_KEY" => "test-value"}) + end + + it "should define a custom module path" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("$env:PSModulePath+';C:\\My-Path'") + result + end + described_class.execute_cmd("custom-command", module_path: "C:\\My-Path") + end + + context "with command output" do + let(:stdout){ "custom-output" } + + it "should return stdout" do + expect(described_class.execute_cmd("cmd")).to eq(stdout) + end + end + + context "with failed command" do + let(:exit_code){ 1 } + + it "should return nil" do + expect(described_class.execute_cmd("cmd")).to be_nil + end + end + end + + describe ".execute_inline" do + let(:result) do + Vagrant::Util::Subprocess::Result.new( + exit_code, stdout, stderr) + end + let(:exit_code){ 0 } + let(:stdout){ "" } + let(:stderr){ "" } + + before do + allow(described_class).to receive(:validate_install!) + allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result) + end + + it "should validate installation before use" do + expect(described_class).to receive(:validate_install!) + described_class.execute_inline("command") + end + + it "should include command to execute" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("custom-command") + result + end + described_class.execute_inline("custom-command") + end + + it "should automatically include console resize" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("BufferSize") + result + end + described_class.execute_inline("custom-command") + end + + it "should accept custom environment" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("$env:TEST_KEY=test-value") + result + end + described_class.execute_inline("custom-command", env: {"TEST_KEY" => "test-value"}) + end + + it "should define a custom module path" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.to_s.include?("custom-command") } + expect(comm.to_s).to include("$env:PSModulePath+';C:\\My-Path'") + result + end + described_class.execute_inline("custom-command", module_path: "C:\\My-Path") + end + + it "should return a result instance" do + expect(described_class.execute_inline("cmd")).to eq(result) + end + end + + describe ".validate_install!" do + before do + allow(described_class).to receive(:available?).and_return(true) + end + + context "with version under minimum required" do + before{ expect(described_class).to receive(:version).and_return("2.1").at_least(:once) } + + it "should raise an error" do + expect{ described_class.validate_install! }.to raise_error(Vagrant::Errors::PowerShellInvalidVersion) + end + end + + context "with version above minimum required" do + before{ expect(described_class).to receive(:version).and_return("3.1").at_least(:once) } + + it "should return true" do + expect(described_class.validate_install!).to be(true) + end + end + + end + + describe ".resize_console" do + it "should return command string" do + expect(described_class.resize_console).to include("BufferSize") + end + + it "should return empty string when disabled" do + with_temp_env("VAGRANT_POWERSHELL_RESIZE_DISABLE" => "1") do + expect(described_class.resize_console).to be_empty + end + end + end end diff --git a/vagrant.gemspec b/vagrant.gemspec index 4096fd684..edc12f032 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -28,8 +28,6 @@ Gem::Specification.new do |s| s.add_dependency "rb-kqueue", "~> 0.2.0" s.add_dependency "rest-client", ">= 1.6.0", "< 3.0" s.add_dependency "wdm", "~> 0.1.0" - s.add_dependency "win32-file", "~> 0.8.1" - s.add_dependency "win32-file-security", "~> 1.0.10" s.add_dependency "winrm", "~> 2.1" s.add_dependency "winrm-fs", "~> 1.0" s.add_dependency "winrm-elevated", "~> 1.1" diff --git a/website/source/docs/hyperv/configuration.html.md b/website/source/docs/hyperv/configuration.html.md index 2fed569e9..178fadf10 100644 --- a/website/source/docs/hyperv/configuration.html.md +++ b/website/source/docs/hyperv/configuration.html.md @@ -12,41 +12,45 @@ description: |- The Vagrant Hyper-V provider has some provider-specific configuration options you may set. A complete reference is shown below: - * `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. - Defaults is taken from box image XML. - * `cpus` (integer) - Number of virtual CPU given to machine. - Defaults is taken from box image XML. - * `memory` (integer) - Number of MegaBytes allocated to VM at startup. - Defaults is taken from box image XML. - * `maxmemory` (integer) - Number of MegaBytes maximal allowed to allocate for VM - This parameter is switch on Dynamic Allocation of memory. - Defaults is taken from box image XML. - * `vlan_id` (integer) - Number of Vlan ID for your guest network interface - Defaults is not defined, vlan configuration will be untouched if not set. - * `mac` (string) - MAC address for your guest network interface - Default is not defined, MAC address will be dynamically assigned by Hyper-V if not set. - * `ip_address_timeout` (integer) - The time in seconds to wait for the - virtual machine to report an IP address. This defaults to 120 seconds. - This may have to be increased if your VM takes longer to boot. - * `differencing_disk` (boolean) - Switch to use differencing disk instead of cloning whole VHD. - * `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. - This allows Hyper-V to be nested and run inside another Hyper-VM VM. It requires Windows 10 - 1511 (build 10586) or newer. - Default is not defined. This will be disabled if not set. - * `auto_start_action` (Nothing, StartIfRunning, Start) - Action on automatic start of VM when booting OS - * `auto_stop_action` (ShutDown, TurnOff, Save) - Action on automatic stop of VM when shutting down OS. - * `vm_integration_services` (Hash) - Hash to set the state of integration services. - - Example: - - ```ruby - config.vm.provider "hyperv" do |h| - h.vm_integration_services = { - guest_service_interface: true, - heartbeat: true, - key_value_pair_exchange: true, - shutdown: true, - time_synchronization: true, - vss: true - } - end - ``` \ No newline at end of file +* `auto_start_action` (Nothing, StartIfRunning, Start) - Automatic start action for VM on host startup. Default: Nothing. +* `auto_stop_action` (ShutDown, TurnOff, Save) - Automatic stop action for VM on host shutdown. Default: ShutDown. +* `cpus` (integer) - Number of virtual CPUs allocated to VM at startup. +* `differencing_disk` (boolean) - **Deprecated** Use differencing disk instead of cloning entire VHD (use `linked_clone` instead) Default: false. +* `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. Default: false +* `enable_checkpoints` (boolean) Enable automatic checkpoints of the VM. Default: false +* `ip_address_timeout` (integer) - Number of seconds to wait for the VM to report an IP address. Default: 120. +* `linked_clone` (boolean) - Use differencing disk instead of cloning entire VHD. Default: false +* `mac` (string) - MAC address for the guest network interface +* `maxmemory` (integer) - Maximum number of megabytes allowed to be allocated for the VM. When set Dynamic Memory Allocation will be enabled. +* `memory` (integer) - Number of megabytes allocated to VM at startup. If `maxmemory` is set, this will be amount of memory allocated at startup. +* `vlan_id` (integer) - VLAN ID for the guest network interface. +* `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. Default: Generated name. +* `vm_integration_services` (Hash) - Hash to set the state of integration services. (Note: Unknown key values will be passed directly.) + * `guest_service_interface` (boolean) + * `heartbeat` (boolean) + * `key_value_pair_exchange` (boolean) + * `shutdown` (boolean) + * `time_synchronization` (boolean) + * `vss` (boolean) + +## VM Integration Services + +The `vm_integration_services` configuration option consists of a simple Hash. The key values are the +names of VM integration services to enable or disable for the VM. Vagrant includes an internal +mapping of known services which allows them to be provided in a "snake case" format. When a provided +key is unknown, the key value is used "as-is" without any modifications. + +For example, if a new `CustomVMSRV` VM integration service was added and Vagrant is not aware of this +new service name, it can be provided as the key value explicitly: + +```ruby +config.vm.provider "hyperv" do |h| + h.vm_integration_services = { + guest_service_interface: true, + CustomVMSRV: true + } +end +``` + +This example would enable the `GuestServiceInterface` (which Vagrant is aware) and `CustomVMSRV` (which +Vagrant is _not_ aware) VM integration services. diff --git a/website/source/docs/hyperv/index.html.md b/website/source/docs/hyperv/index.html.md index ff860ef77..95706f377 100644 --- a/website/source/docs/hyperv/index.html.md +++ b/website/source/docs/hyperv/index.html.md @@ -24,7 +24,7 @@ To enable Hyper-V, go to "Programs and Features", click on "Turn Windows features on or off" and check the box next to "Hyper-V". Or install via PowerShell with: -Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All. +Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All See official documentation [here](https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v). diff --git a/website/source/docs/provisioning/ansible_local.html.md b/website/source/docs/provisioning/ansible_local.html.md index d25318b93..14df9d53f 100644 --- a/website/source/docs/provisioning/ansible_local.html.md +++ b/website/source/docs/provisioning/ansible_local.html.md @@ -73,7 +73,7 @@ This section lists the _specific_ options for the Ansible Local provisioner. In - `install_mode` (`:default`, `:pip`, or `:pip_args_only`) - Select the way to automatically install Ansible on the guest system. - `:default`: Ansible is installed from the operating system package manager. This mode doesn't support `version` selection. For many platforms (e.g Debian, FreeBSD, OpenSUSE) the official package repository is used, except for the following Linux distributions: - - On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository. + - On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository. The compatibility is maintained only for active long-term support (LTS) versions. - On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository. - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](/docs/provisioning/ansible_common.html#version) option. diff --git a/website/source/docs/triggers/configuration.html.md b/website/source/docs/triggers/configuration.html.md index d66938741..bd1581220 100644 --- a/website/source/docs/triggers/configuration.html.md +++ b/website/source/docs/triggers/configuration.html.md @@ -21,6 +21,7 @@ The trigger class takes various options. - `provision` - `reload` - `resume` + - `suspend` - `up` * `ignore` (symbol, array) - Symbol or array of symbols corresponding to the action that a trigger should not fire on. diff --git a/website/source/docs/vagrant-cloud/api.html.md.erb b/website/source/docs/vagrant-cloud/api.html.md.erb index 999e5cc29..693d42fe3 100644 --- a/website/source/docs/vagrant-cloud/api.html.md.erb +++ b/website/source/docs/vagrant-cloud/api.html.md.erb @@ -1234,7 +1234,7 @@ Response body is identical to [Reading a provider](#read-a-provider). * `provider` * `name` - The name of the provider. - * `url` - A valid URL to download this provider. If omitted, you must [upload](#TODO) the Vagrant box image for this provider to Vagrant Cloud before the provider can be used. + * `url` - A valid URL to download this provider. If omitted, you must [upload](#upload-a-provider) the Vagrant box image for this provider to Vagrant Cloud before the provider can be used. #### Example Request diff --git a/website/source/docs/vagrantfile/ssh_settings.html.md b/website/source/docs/vagrantfile/ssh_settings.html.md index 9653f6f1d..1529cebb5 100644 --- a/website/source/docs/vagrantfile/ssh_settings.html.md +++ b/website/source/docs/vagrantfile/ssh_settings.html.md @@ -155,18 +155,26 @@ is: config.ssh.export_command_template = 'export %ENV_KEY%="%ENV_VALUE%"' ``` +
+ `config.ssh.sudo_command` - The command to use when executing a command with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by the command that is being executed. +
+ `config.ssh.compression` - If `false`, this setting will not include the compression setting when ssh'ing into a machine. If this is not set, it will default to `true` and `Compression=yes` will be enabled with ssh. +
+ `config.ssh.dsa_authentication` - If `false`, this setting will not include `DSAAuthentication` when ssh'ing into a machine. If this is not set, it will default to `true` and `DSAAuthentication=yes` will be used with ssh. +
+ `config.ssh.extra_args` - This settings value is passed directly into the ssh executable. This allows you to pass any arbitrary commands to do things such as reverse tunneling down into the ssh program. These options can either be diff --git a/website/source/downloads.html.erb b/website/source/downloads.html.erb index c3f6f6c81..46f766889 100644 --- a/website/source/downloads.html.erb +++ b/website/source/downloads.html.erb @@ -28,6 +28,9 @@ description: |- which has been signed using HashiCorp's GPG key. You can also download older versions of Vagrant from the releases service.

+

+ Check out the v<%= latest_version %> CHANGELOG for information on the latest release. +