diff --git a/lib/vagrant/util/powershell.rb b/lib/vagrant/util/powershell.rb new file mode 100644 index 000000000..3e2206c3e --- /dev/null +++ b/lib/vagrant/util/powershell.rb @@ -0,0 +1,36 @@ +require_relative "subprocess" +require_relative "which" + +module Vagrant + module Util + # Executes PowerShell scripts. + # + # This is primarily a convenience wrapper around Subprocess that + # properly sets powershell flags for you. + class PowerShell + def self.available? + !!Which.which("powershell") + end + + # Execute a powershell script. + # + # @param [String] path Path to the PowerShell script to execute. + # @return [Subprocess::Result] + def self.execute(path, *args, **opts, &block) + command = [ + "powershell", + "-NoProfile", + "-ExecutionPolicy", "Bypass", + path, + args + ].flatten + + # Append on the options hash since Subprocess doesn't use + # Ruby 2.0 style options yet. + command << opts + + Subprocess.execute(*command, &block) + end + end + end +end diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index a801ad346..532c5b353 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -44,8 +44,8 @@ module VagrantPlugins def self.action_start Vagrant::Action::Builder.new.tap do |b| b.use StartInstance - b.use ShareFolders - b.use SyncFolders + #b.use ShareFolders + #b.use SyncFolders end end diff --git a/plugins/providers/hyperv/action/read_state.rb b/plugins/providers/hyperv/action/read_state.rb index b0bc82242..f0ad3456e 100644 --- a/plugins/providers/hyperv/action/read_state.rb +++ b/plugins/providers/hyperv/action/read_state.rb @@ -1,9 +1,5 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- -require "debugger" require "log4r" + module VagrantPlugins module HyperV module Action @@ -19,7 +15,7 @@ module VagrantPlugins options = { vm_id: env[:machine].id } response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) env[:machine_state_id] = response["state"].downcase.to_sym - rescue Error::SubprocessError => e + rescue Error::SubprocessError env[:machine].id = nil env[:ui].info "Could not find a machine, assuming it to be deleted or terminated." env[:machine_state_id] = :not_created @@ -29,7 +25,6 @@ module VagrantPlugins end @app.call(env) end - end end end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 8c8c5a0a6..8513e8c7e 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -32,6 +32,7 @@ module VagrantPlugins def validate(machine) errors = _detected_errors +=begin unless host_share.valid_config? errors << host_share.errors.flatten.join(" ") end @@ -39,6 +40,7 @@ module VagrantPlugins unless guest.valid_config? errors << guest.errors.flatten.join(" ") end +=end { "HyperV" => errors } end end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index be47fb5ce..9c0f2645e 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -1,13 +1,95 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- +require "json" + +require "vagrant/util/powershell" module VagrantPlugins module HyperV - module Driver - lib_path = Pathname.new(File.expand_path("../driver", __FILE__)) - autoload :Base, lib_path.join("base") + class Driver + attr_reader :vmid + + def initialize(id=nil) + @vmid = id + @output = nil + end + + def execute(path, options) + execute_powershell(path, options) do |type, data| + process_output(type, data) + end + if success? + JSON.parse(json_output[:success].join) unless json_output[:success].empty? + else + message = json_output[:error].join unless json_output[:error].empty? + raise Error::SubprocessError, message if message + end + end + + def raw_execute(command) + command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten + clear_output_buffer + Vagrant::Util::Subprocess.execute(*command) do |type, data| + process_output(type, data) + end + end + + protected + + def json_output + return @json_output if @json_output + json_success_begin = false + json_error_begin = false + success = [] + error = [] + @output.split("\n").each do |line| + json_error_begin = false if line.include?("===End-Error===") + json_success_begin = false if line.include?("===End-Output===") + message = "" + if json_error_begin || json_success_begin + message = line.gsub("\\'","\"") + end + success << message if json_success_begin + error << message if json_error_begin + json_success_begin = true if line.include?("===Begin-Output===") + json_error_begin = true if line.include?("===Begin-Error===") + end + @json_output = { :success => success, :error => error } + end + + def success? + @error_messages.empty? && json_output[:error].empty? + end + + def process_output(type, data) + if type == :stdout + @output = data.gsub("\r\n", "\n") + end + if type == :stdin + # $stdin.gets.chomp || "" + end + if type == :stderr + @error_messages = data.gsub("\r\n", "\n") + end + end + + def clear_output_buffer + @output = "" + @error_messages = "" + @json_output = nil + end + + def execute_powershell(path, options, &block) + lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) + path = lib_path.join(path).to_s.gsub("/", "\\") + options = options || {} + ps_options = [] + options.each do |key, value| + ps_options << "-#{key}" + ps_options << "'#{value}'" + end + clear_output_buffer + opts = { notify: [:stdout, :stderr, :stdin] } + Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block) + end end end end diff --git a/plugins/providers/hyperv/driver/base.rb b/plugins/providers/hyperv/driver/base.rb deleted file mode 100644 index 70d337a40..000000000 --- a/plugins/providers/hyperv/driver/base.rb +++ /dev/null @@ -1,105 +0,0 @@ -require "json" -require "vagrant/util/which" -require "vagrant/util/subprocess" - -module VagrantPlugins - module HyperV - module Driver - class Base - attr_reader :vmid - - def initialize(id=nil) - @vmid = id - check_power_shell - @output = nil - end - - def execute(path, options) - r = execute_powershell(path, options) do |type, data| - process_output(type, data) - end - if success? - JSON.parse(json_output[:success].join) unless json_output[:success].empty? - else - message = json_output[:error].join unless json_output[:error].empty? - raise Error::SubprocessError, message if message - end - end - - def raw_execute(command) - command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten - clear_output_buffer - Vagrant::Util::Subprocess.execute(*command) do |type, data| - process_output(type, data) - end - end - - protected - - def json_output - return @json_output if @json_output - json_success_begin = false - json_error_begin = false - success = [] - error = [] - @output.split("\n").each do |line| - json_error_begin = false if line.include?("===End-Error===") - json_success_begin = false if line.include?("===End-Output===") - message = "" - if json_error_begin || json_success_begin - message = line.gsub("\\'","\"") - end - success << message if json_success_begin - error << message if json_error_begin - json_success_begin = true if line.include?("===Begin-Output===") - json_error_begin = true if line.include?("===Begin-Error===") - end - @json_output = { :success => success, :error => error } - end - - def success? - @error_messages.empty? && json_output[:error].empty? - end - - def process_output(type, data) - if type == :stdout - @output = data.gsub("\r\n", "\n") - end - if type == :stdin - # $stdin.gets.chomp || "" - end - if type == :stderr - @error_messages = data.gsub("\r\n", "\n") - end - end - - def clear_output_buffer - @output = "" - @error_messages = "" - @json_output = nil - end - - def check_power_shell - unless Vagrant::Util::Which.which('powershell') - raise "Power Shell not found" - end - end - - def execute_powershell(path, options, &block) - lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) - path = lib_path.join(path).to_s.gsub("/", "\\") - options = options || {} - ps_options = [] - options.each do |key, value| - ps_options << "-#{key}" - ps_options << "'#{value}'" - end - clear_output_buffer - command = ["powershell", "-NoProfile", "-ExecutionPolicy", - "Bypass", path, ps_options, {notify: [:stdout, :stderr, :stdin]}].flatten - Vagrant::Util::Subprocess.execute(*command, &block) - end - end - end - end -end diff --git a/plugins/providers/hyperv/error.rb b/plugins/providers/hyperv/error.rb deleted file mode 100644 index 1b6cb1cc1..000000000 --- a/plugins/providers/hyperv/error.rb +++ /dev/null @@ -1,13 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -module VagrantPlugins - module HyperV - module Error - lib_path = Pathname.new(File.expand_path("../error", __FILE__)) - autoload :SubprocessError, lib_path.join("subprocess_error") - end - end -end diff --git a/plugins/providers/hyperv/error/subprocess_error.rb b/plugins/providers/hyperv/error/subprocess_error.rb deleted file mode 100644 index 06773cdc6..000000000 --- a/plugins/providers/hyperv/error/subprocess_error.rb +++ /dev/null @@ -1,24 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -require "json" -require "vagrant/util/which" -require "vagrant/util/subprocess" - -module VagrantPlugins - module HyperV - module Error - class SubprocessError < RuntimeError - def initialize(message) - @message = JSON.parse(message) if message - end - - def message - @message["error"] - end - end - end - end -end diff --git a/plugins/providers/hyperv/errors.rb b/plugins/providers/hyperv/errors.rb new file mode 100644 index 000000000..e4f647e5c --- /dev/null +++ b/plugins/providers/hyperv/errors.rb @@ -0,0 +1,14 @@ +module VagrantPlugins + module HyperV + module Errors + # A convenient superclass for all our errors. + class HyperVError < Vagrant::Errors::VagrantError + error_namespace("vagrant_hyperv.errors") + end + + class PowerShellRequired < HyperVError + error_key(:powershell_required) + end + end + end +end diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb index 666400c6c..5498a0051 100644 --- a/plugins/providers/hyperv/plugin.rb +++ b/plugins/providers/hyperv/plugin.rb @@ -1,5 +1,8 @@ module VagrantPlugins module HyperV + autoload :Action, File.expand_path("../action", __FILE__) + autoload :Errors, File.expand_path("../errors", __FILE__) + class Plugin < Vagrant.plugin("2") name "Hyper-V provider" description <<-DESC @@ -9,13 +12,25 @@ module VagrantPlugins provider(:hyperv, parallel: true) do require_relative "provider" + init! Provider end config(:hyperv, :provider) do require_relative "config" + init! Config end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path( + "templates/locales/providers_hyperv.yml", Vagrant.source_root) + I18n.reload! + @_init = true + end end end end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb index 4e60f5869..882b002a4 100644 --- a/plugins/providers/hyperv/provider.rb +++ b/plugins/providers/hyperv/provider.rb @@ -1,10 +1,22 @@ require "log4r" +require_relative "driver" +require_relative "plugin" + +require "vagrant/util/powershell" + module VagrantPlugins module HyperV class Provider < Vagrant.plugin("2", :provider) + attr_reader :driver + def initialize(machine) + @driver = Driver.new @machine = machine + + if !Vagrant::Util::PowerShell.available? + raise Errors::PowerShellRequired + end end def action(name) @@ -45,10 +57,6 @@ module VagrantPlugins env[:machine_ssh_info].merge!(:port => 22) end end - - def driver - @driver ||= Driver::Base.new() - end end end end diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml new file mode 100644 index 000000000..fb84baa3a --- /dev/null +++ b/templates/locales/providers_hyperv.yml @@ -0,0 +1,6 @@ +en: + vagrant_hyperv: + errors: + powershell_required: |- + The Vagrant Hyper-V provider requires PowerShell to be available. + Please make sure "powershell.exe" is available on your PATH. diff --git a/test/unit/plugins/providers/hyperv/provider_test.rb b/test/unit/plugins/providers/hyperv/provider_test.rb new file mode 100644 index 000000000..258087028 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/provider_test.rb @@ -0,0 +1,24 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/provider") + +describe VagrantPlugins::HyperV::Provider do + let(:machine) { double("machine") } + let(:powershell) { double("powershell") } + + subject { described_class.new(machine) } + + before do + stub_const("Vagrant::Util::PowerShell", powershell) + powershell.stub(available?: true) + end + + describe "#initialize" do + it "raises an exception if powershell is not available" do + powershell.stub(available?: false) + + expect { subject }. + to raise_error(VagrantPlugins::HyperV::Errors::PowerShellRequired) + end + end +end