Skip to content

Commit

Permalink
Merge pull request hashicorp#9872 from chrisroberts/e-hyperv-2
Browse files Browse the repository at this point in the history
Hyper-V provider overhaul
  • Loading branch information
chrisroberts authored Jun 4, 2018
2 parents 6787459 + 5e68c89 commit 120fa07
Show file tree
Hide file tree
Showing 56 changed files with 2,897 additions and 971 deletions.
53 changes: 44 additions & 9 deletions lib/vagrant/util/powershell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ def self.executable
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
Expand All @@ -41,19 +45,26 @@ def self.available?
# Execute a powershell script.
#
# @param [String] path Path to the PowerShell script to execute.
# @param [Array<String>] 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
env = opts.delete(:env)
if 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

Expand All @@ -68,18 +79,24 @@ def self.execute(path, *args, **opts, &block)
# 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!
env = opts.delete(:env)
if env
env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; "
end
c = [
executable,
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
command
"#{resize_console}#{env}#{command}"
].flatten.compact

r = Subprocess.execute(*c)
Expand All @@ -91,17 +108,22 @@ def self.execute_cmd(command)
#
# @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!
env = opts.delete(:env)
if env
env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; "
end
c = [
executable,
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
command
"#{resize_console}#{env}#{command}"
].flatten.compact
c << opts

Expand Down Expand Up @@ -220,6 +242,19 @@ def self.powerup_command(path, args, opts)
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
4 changes: 4 additions & 0 deletions plugins/providers/hyperv/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ def self.action_start
end

b2.use Provision
b2.use Configure
b2.use SetName
b2.use NetSetVLan
b2.use NetSetMac
b2.use StartInstance
Expand Down Expand Up @@ -288,6 +290,7 @@ def self.action_snapshot_save
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")
Expand All @@ -304,6 +307,7 @@ def self.action_snapshot_save
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
104 changes: 104 additions & 0 deletions plugins/providers/hyperv/action/configure.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 120fa07

Please sign in to comment.