providers/hyperv: shuffle things around

This commit is contained in:
Mitchell Hashimoto 2014-02-15 16:28:11 -08:00
parent 3d8971b15f
commit a1958ee12f
13 changed files with 202 additions and 162 deletions

View File

@ -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

View File

@ -44,8 +44,8 @@ module VagrantPlugins
def self.action_start def self.action_start
Vagrant::Action::Builder.new.tap do |b| Vagrant::Action::Builder.new.tap do |b|
b.use StartInstance b.use StartInstance
b.use ShareFolders #b.use ShareFolders
b.use SyncFolders #b.use SyncFolders
end end
end end

View File

@ -1,9 +1,5 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft Open Technologies, Inc.
# All Rights Reserved. Licensed under the MIT License.
#--------------------------------------------------------------------------
require "debugger"
require "log4r" require "log4r"
module VagrantPlugins module VagrantPlugins
module HyperV module HyperV
module Action module Action
@ -19,7 +15,7 @@ module VagrantPlugins
options = { vm_id: env[:machine].id } options = { vm_id: env[:machine].id }
response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) response = env[:machine].provider.driver.execute('get_vm_status.ps1', options)
env[:machine_state_id] = response["state"].downcase.to_sym env[:machine_state_id] = response["state"].downcase.to_sym
rescue Error::SubprocessError => e rescue Error::SubprocessError
env[:machine].id = nil env[:machine].id = nil
env[:ui].info "Could not find a machine, assuming it to be deleted or terminated." env[:ui].info "Could not find a machine, assuming it to be deleted or terminated."
env[:machine_state_id] = :not_created env[:machine_state_id] = :not_created
@ -29,7 +25,6 @@ module VagrantPlugins
end end
@app.call(env) @app.call(env)
end end
end end
end end
end end

View File

@ -32,6 +32,7 @@ module VagrantPlugins
def validate(machine) def validate(machine)
errors = _detected_errors errors = _detected_errors
=begin
unless host_share.valid_config? unless host_share.valid_config?
errors << host_share.errors.flatten.join(" ") errors << host_share.errors.flatten.join(" ")
end end
@ -39,6 +40,7 @@ module VagrantPlugins
unless guest.valid_config? unless guest.valid_config?
errors << guest.errors.flatten.join(" ") errors << guest.errors.flatten.join(" ")
end end
=end
{ "HyperV" => errors } { "HyperV" => errors }
end end
end end

View File

@ -1,13 +1,95 @@
#------------------------------------------------------------------------- require "json"
# Copyright (c) Microsoft Open Technologies, Inc.
# All Rights Reserved. Licensed under the MIT License. require "vagrant/util/powershell"
#--------------------------------------------------------------------------
module VagrantPlugins module VagrantPlugins
module HyperV module HyperV
module Driver class Driver
lib_path = Pathname.new(File.expand_path("../driver", __FILE__)) attr_reader :vmid
autoload :Base, lib_path.join("base")
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 end
end end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,8 @@
module VagrantPlugins module VagrantPlugins
module HyperV module HyperV
autoload :Action, File.expand_path("../action", __FILE__)
autoload :Errors, File.expand_path("../errors", __FILE__)
class Plugin < Vagrant.plugin("2") class Plugin < Vagrant.plugin("2")
name "Hyper-V provider" name "Hyper-V provider"
description <<-DESC description <<-DESC
@ -9,13 +12,25 @@ module VagrantPlugins
provider(:hyperv, parallel: true) do provider(:hyperv, parallel: true) do
require_relative "provider" require_relative "provider"
init!
Provider Provider
end end
config(:hyperv, :provider) do config(:hyperv, :provider) do
require_relative "config" require_relative "config"
init!
Config Config
end 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 end
end end

View File

@ -1,10 +1,22 @@
require "log4r" require "log4r"
require_relative "driver"
require_relative "plugin"
require "vagrant/util/powershell"
module VagrantPlugins module VagrantPlugins
module HyperV module HyperV
class Provider < Vagrant.plugin("2", :provider) class Provider < Vagrant.plugin("2", :provider)
attr_reader :driver
def initialize(machine) def initialize(machine)
@driver = Driver.new
@machine = machine @machine = machine
if !Vagrant::Util::PowerShell.available?
raise Errors::PowerShellRequired
end
end end
def action(name) def action(name)
@ -45,10 +57,6 @@ module VagrantPlugins
env[:machine_ssh_info].merge!(:port => 22) env[:machine_ssh_info].merge!(:port => 22)
end end
end end
def driver
@driver ||= Driver::Base.new()
end
end end
end end
end end

View File

@ -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.

View File

@ -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