Merge pull request #3043 from mitchellh/f-hyperv

Hyper-V and SMB
This commit is contained in:
Mitchell Hashimoto 2014-02-28 21:37:05 +01:00
commit 179a9244df
54 changed files with 2243 additions and 0 deletions

View File

@ -20,7 +20,9 @@ module Vagrant
autoload :HandleBox, "vagrant/action/builtin/handle_box"
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
autoload :HandleForwardedPortCollisions, "vagrant/action/builtin/handle_forwarded_port_collisions"
autoload :IsState, "vagrant/action/builtin/is_state"
autoload :Lock, "vagrant/action/builtin/lock"
autoload :Message, "vagrant/action/builtin/message"
autoload :Provision, "vagrant/action/builtin/provision"
autoload :ProvisionerCleanup, "vagrant/action/builtin/provisioner_cleanup"
autoload :SetHostname, "vagrant/action/builtin/set_hostname"

View File

@ -0,0 +1,32 @@
module Vagrant
module Action
module Builtin
# This middleware is meant to be used with Call and can check if
# a machine is in the given state ID.
class IsState
# Note: Any of the arguments can be arrays as well.
#
# @param [Symbol] target_state The target state ID that means that
# the machine was properly shut down.
# @param [Symbol] source_state The source state ID that the machine
# must be in to be shut down.
def initialize(app, env, check, **opts)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::is_state")
@check = check
@invert = !!opts[:invert]
end
def call(env)
@logger.debug("Checking if machine state is '#{@check}'")
state = env[:machine].state.id
@logger.debug("-- Machine state: #{state}")
env[:result] = @check == state
env[:result] = !env[:result] if @invert
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module Vagrant
module Action
module Builtin
# This middleware simply outputs a message to the UI.
class Message
def initialize(app, env, message, **opts)
@app = app
@message = message
end
def call(env)
env[:ui].output(@message)
@app.call(env)
end
end
end
end
end

View File

@ -29,6 +29,27 @@ module Vagrant
false
end
# Checks if the user running Vagrant on Windows has administrative
# privileges.
#
# @return [Boolean]
def windows_admin?
# We lazily-load this because it is only available on Windows
require 'win32/registry'
# Verify that we have administrative privileges. The odd method of
# detecting this is based on this StackOverflow question:
#
# http://stackoverflow.com/questions/560366/
# detect-if-running-with-administrator-privileges-under-windows-xp
begin
Win32::Registry::HKEY_USERS.open("S-1-5-19") {}
return true
rescue Win32::Registry::Error
return false
end
end
# This takes any path and converts it to a full-length Windows
# path on Windows machines in Cygwin.
#

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

@ -0,0 +1,20 @@
module VagrantPlugins
module GuestLinux
module Cap
module ChooseAddressableIPAddr
def self.choose_addressable_ip_addr(machine, possible)
machine.communicate.tap do |comm|
possible.each do |ip|
command = "ping -c1 -w1 -W1 #{ip}"
if comm.test(command)
return ip
end
end
end
nil
end
end
end
end
end

View File

@ -0,0 +1,80 @@
module VagrantPlugins
module GuestLinux
module Cap
class MountSMBSharedFolder
def self.mount_smb_shared_folder(machine, name, guestpath, options)
expanded_guest_path = machine.guest.capability(
:shell_expand_guest_path, guestpath)
mount_commands = []
mount_device = "//#{options[:smb_host]}/#{name}"
if options[:owner].is_a? Integer
mount_uid = options[:owner]
else
mount_uid = "`id -u #{options[:owner]}`"
end
if options[:group].is_a? Integer
mount_gid = options[:group]
mount_gid_old = options[:group]
else
mount_gid = "`getent group #{options[:group]} | cut -d: -f3`"
mount_gid_old = "`id -g #{options[:group]}`"
end
options[:mount_options] ||= []
options[:mount_options] << "sec=ntlm"
options[:mount_options] << "username=#{options[:smb_username]}"
options[:mount_options] << "pass=#{options[:smb_password]}"
# First mount command uses getent to get the group
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}"
mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options]
mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
# Second mount command uses the old style `id -g`
mount_options = "-o uid=#{mount_uid},gid=#{mount_gid_old}"
mount_options += ",#{options[:mount_options].join(",")}" if options[:mount_options]
mount_commands << "mount -t cifs #{mount_options} #{mount_device} #{expanded_guest_path}"
# Create the guest path if it doesn't exist
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
# Attempt to mount the folder. We retry here a few times because
# it can fail early on.
attempts = 0
while true
success = true
mount_commands.each do |command|
no_such_device = false
status = machine.communicate.sudo(command, error_check: false) do |type, data|
no_such_device = true if type == :stderr && data =~ /No such device/i
end
success = status == 0 && !no_such_device
break if success
end
break if success
attempts += 1
if attempts > 10
raise Vagrant::Errors::LinuxMountFailed,
command: mount_commands.join("\n")
end
sleep 2
end
# Emit an upstart event if we can
if machine.communicate.test("test -x /sbin/initctl")
machine.communicate.sudo(
"/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}")
end
end
end
end
end
end

View File

@ -11,6 +11,11 @@ module VagrantPlugins
Guest
end
guest_capability("linux", "choose_addressable_ip_addr") do
require_relative "cap/choose_addressable_ip_addr"
Cap::ChooseAddressableIPAddr
end
guest_capability("linux", "halt") do
require_relative "cap/halt"
Cap::Halt
@ -31,6 +36,11 @@ module VagrantPlugins
Cap::MountNFS
end
guest_capability("linux", "mount_smb_shared_folder") do
require_relative "cap/mount_smb_shared_folder"
Cap::MountSMBSharedFolder
end
guest_capability("linux", "mount_virtualbox_shared_folder") do
require_relative "cap/mount_virtualbox_shared_folder"
Cap::MountVirtualBoxSharedFolder

View File

@ -0,0 +1,185 @@
require "pathname"
require "vagrant/action/builder"
module VagrantPlugins
module HyperV
module Action
# Include the built-in modules so we can use them as top-level things.
include Vagrant::Action::Builtin
def self.action_reload
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use Call, IsState, :not_created do |env, b2|
if env[:result]
b2.use Message, I18n.t("vagrant_hyperv.message_not_created")
next
end
b2.use action_halt
b2.use action_start
end
end
end
def self.action_destroy
Vagrant::Action::Builder.new.tap do |b|
b.use Call, IsState, :not_created do |env1, b1|
if env1[:result]
b1.use Message, I18n.t("vagrant_hyperv.message_not_created")
next
end
b1.use Call, DestroyConfirm do |env2, b2|
if !env2[:result]
b2.use MessageWillNotDestroy
next
end
b2.use ConfigValidate
b2.use StopInstance
b2.use DeleteVM
end
end
end
end
def self.action_halt
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use Call, IsState, :not_created do |env, b2|
if env[:result]
b2.use Message, I18n.t("vagrant_hyperv.message_not_created")
next
end
b2.use Call, GracefulHalt, :off, :running do |env2, b3|
if !env2[:result]
b3.use StopInstance
end
end
end
end
end
def self.action_resume
Vagrant::Action::Builder.new.tap do |b|
b.use HandleBox
b.use ConfigValidate
b.use Call, IsState, :not_created do |env, b2|
if env1[:result]
b1.use Message, I18n.t("vagrant_hyperv.message_not_created")
next
end
b1.use ResumeVM
b1.use WaitForIPAddress
b1.use WaitForCommunicator, [:running]
end
end
end
def self.action_start
Vagrant::Action::Builder.new.tap do |b|
b.use Call, IsState, :running do |env1, b1|
if env1[:result]
b1.use Message, I18n.t("vagrant_hyperv.message_already_running")
next
end
b1.use Call, IsState, :paused do |env2, b2|
if env2[:result]
b2.use action_resume
next
end
b2.use Provision
b2.use StartInstance
b2.use WaitForIPAddress
b2.use WaitForCommunicator, [:running]
b2.use SyncedFolders
b2.use SetHostname
end
end
end
end
def self.action_up
Vagrant::Action::Builder.new.tap do |b|
b.use HandleBox
b.use ConfigValidate
b.use Call, IsState, :not_created do |env1, b1|
if env1[:result]
b1.use Import
end
b1.use action_start
end
end
end
def self.action_read_state
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ReadState
end
end
def self.action_ssh
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use Call, IsState, :not_created do |env, b2|
if env[:result]
b2.use Message, I18n.t("vagrant_hyperv.message_not_created")
next
end
b2.use Call, IsState, :running do |env1, b3|
if !env1[:result]
b3.use Message, I18n.t("vagrant_hyperv.message_not_running")
next
end
b3.use SSHExec
end
end
end
end
def self.action_suspend
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use Call, IsState, :not_created do |env, b2|
if env[:result]
b2.use Message, I18n.t("vagrant_hyperv.message_not_created")
next
end
b2.use SuspendVM
end
end
end
def self.action_read_guest_ip
Vagrant::Action::Builder.new.tap do |b|
b.use ConfigValidate
b.use ReadGuestIP
end
end
# The autoload farm
action_root = Pathname.new(File.expand_path("../action", __FILE__))
autoload :DeleteVM, action_root.join("delete_vm")
autoload :Import, action_root.join("import")
autoload :ReadState, action_root.join("read_state")
autoload :ResumeVM, action_root.join("resume_vm")
autoload :StartInstance, action_root.join('start_instance')
autoload :StopInstance, action_root.join('stop_instance')
autoload :SuspendVM, action_root.join("suspend_vm")
autoload :ReadGuestIP, action_root.join('read_guest_ip')
autoload :WaitForIPAddress, action_root.join("wait_for_ip_address")
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module HyperV
module Action
class DeleteVM
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info("Deleting the machine...")
options = { VmId: env[:machine].id }
env[:machine].provider.driver.execute('delete_vm.ps1', options)
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,90 @@
require "fileutils"
require "log4r"
module VagrantPlugins
module HyperV
module Action
class Import
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::hyperv::import")
end
def call(env)
vm_dir = env[:machine].box.directory.join("Virtual Machines")
hd_dir = env[:machine].box.directory.join("Virtual Hard Disks")
if !vm_dir.directory? || !hd_dir.directory?
raise Errors::BoxInvalid
end
config_path = nil
vm_dir.each_child do |f|
if f.extname.downcase == ".xml"
config_path = f
break
end
end
vhdx_path = nil
hd_dir.each_child do |f|
if f.extname.downcase == ".vhdx"
vhdx_path = f
break
end
end
if !config_path || !vhdx_path
raise Errors::BoxInvalid
end
env[:ui].output("Importing a Hyper-V instance")
switches = env[:machine].provider.driver.execute("get_switches.ps1", {})
raise Errors::NoSwitches if switches.empty?
switch = switches[0]["Name"]
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]["Name"]
end
env[:ui].detail("Cloning virtual hard drive...")
source_path = vhdx_path.to_s
dest_path = env[:machine].data_dir.join("disk.vhdx").to_s
FileUtils.cp(source_path, dest_path)
vhdx_path = dest_path
# We have to normalize the paths to be Windows paths since
# we're executing PowerShell.
options = {
vm_xml_config: config_path.to_s.gsub("/", "\\"),
vhdx_path: vhdx_path.to_s.gsub("/", "\\")
}
options[:switchname] = switch if switch
env[:ui].detail("Creating and registering the VM...")
server = env[:machine].provider.driver.execute(
'import_vm.ps1', options)
env[:ui].detail("Successfully imported a VM with name: #{server['name']}")
env[:machine].id = server["id"]
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,43 @@
require "log4r"
require "timeout"
module VagrantPlugins
module HyperV
module Action
# This action reads the SSH info for the machine and puts it into the
# `:machine_ssh_info` key in the environment.
class ReadGuestIP
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::hyperv::connection")
end
def call(env)
env[:machine_ssh_info] = read_host_ip(env)
@app.call(env)
end
def read_host_ip(env)
return nil if env[:machine].id.nil?
# Get Network details from WMI Provider
# Wait for 120 sec By then the machine should be ready
host_ip = nil
begin
Timeout.timeout(120) do
begin
options = { VmId: env[:machine].id }
network_info = env[:machine].provider.driver.execute('get_network_config.ps1', options)
host_ip = network_info["ip"]
sleep 10 if host_ip.empty?
end while host_ip.empty?
end
rescue Timeout::Error
@logger.info("Cannot find the IP address of the virtual machine")
end
return { host: host_ip } unless host_ip.nil?
end
end
end
end
end

View File

@ -0,0 +1,32 @@
require "log4r"
module VagrantPlugins
module HyperV
module Action
class ReadState
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::hyperv::connection")
end
def call(env)
if env[:machine].id
options = { VmId: env[:machine].id }
response = env[:machine].provider.driver.execute(
"get_vm_status.ps1", options)
env[:machine_state_id] = response["state"].downcase.to_sym
# If the machine isn't created, then our ID is stale, so just
# mark it as not created.
if env[:machine_state_id] == :not_created
env[:machine].id = nil
end
else
env[:machine_state_id] = :not_created
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module HyperV
module Action
class ResumeVM
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info("Resuming the machine...")
options = { VmId: env[:machine].id }
env[:machine].provider.driver.execute("resume_vm.ps1", options)
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,19 @@
module VagrantPlugins
module HyperV
module Action
class StartInstance
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].output('Starting the machine...')
options = { vm_id: env[:machine].id }
env[:machine].provider.driver.execute('start_vm.ps1', options)
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module HyperV
module Action
class StopInstance
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info("Stopping the machine...")
options = { VmId: env[:machine].id }
env[:machine].provider.driver.execute('stop_vm.ps1', options)
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module HyperV
module Action
class SuspendVM
def initialize(app, env)
@app = app
end
def call(env)
env[:ui].info("Suspending the machine...")
options = { VmId: env[:machine].id }
env[:machine].provider.driver.execute("suspend_vm.ps1", options)
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,45 @@
require "timeout"
module VagrantPlugins
module HyperV
module Action
class WaitForIPAddress
def initialize(app, env)
@app = app
end
def call(env)
timeout = env[:machine].provider_config.ip_address_timeout
env[:ui].output("Waiting for the machine to report its IP address...")
env[:ui].detail("Timeout: #{timeout} seconds")
guest_ip = nil
Timeout.timeout(timeout) do
while true
# If a ctrl-c came through, break out
return if env[:interrupted]
# Try to get the IP
network_info = env[:machine].provider.driver.execute(
"get_network_config.ps1", VmId: env[:machine].id)
guest_ip = network_info["ip"]
break if guest_ip && guest_ip != ""
sleep 1
end
end
# If we were interrupted then return now
return if env[:interrupted]
env[:ui].detail("IP: #{guest_ip}")
@app.call(env)
rescue Timeout::Error
raise Errors::IPAddrTimeout
end
end
end
end
end

View File

@ -0,0 +1,29 @@
require "vagrant"
module VagrantPlugins
module HyperV
class Config < Vagrant.plugin("2", :config)
# The timeout to wait for an IP address when booting the machine,
# in seconds.
#
# @return [Integer]
attr_accessor :ip_address_timeout
def initialize
@ip_address_timeout = UNSET_VALUE
end
def finalize!
if @ip_address_timeout == UNSET_VALUE
@ip_address_timeout = 120
end
end
def validate(machine)
errors = _detected_errors
{ "Hyper-V" => errors }
end
end
end
end

View File

@ -0,0 +1,62 @@
require "json"
require "vagrant/util/powershell"
require_relative "plugin"
module VagrantPlugins
module HyperV
class Driver
ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m
OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m
def execute(path, options)
r = execute_powershell(path, options)
if r.exit_code != 0
raise Errors::PowerShellError,
script: path,
stderr: r.stderr
end
# We only want unix-style line endings within Vagrant
r.stdout.gsub!("\r\n", "\n")
r.stderr.gsub!("\r\n", "\n")
error_match = ERROR_REGEXP.match(r.stdout)
output_match = OUTPUT_REGEXP.match(r.stdout)
if error_match
data = JSON.parse(error_match[1])
# We have some error data.
raise Errors::PowerShellError,
script: path,
stderr: data["error"]
end
# Nothing
return nil if !output_match
return JSON.parse(output_match[1])
end
protected
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
# Always have a stop error action for failures
ps_options << "-ErrorAction" << "Stop"
opts = { notify: [:stdout, :stderr, :stdin] }
Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block)
end
end
end
end

View File

@ -0,0 +1,38 @@
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 AdminRequired < HyperVError
error_key(:admin_required)
end
class BoxInvalid < HyperVError
error_key(:box_invalid)
end
class IPAddrTimeout < HyperVError
error_key(:ip_addr_timeout)
end
class NoSwitches < HyperVError
error_key(:no_switches)
end
class PowerShellError < HyperVError
error_key(:powershell_error)
end
class PowerShellRequired < HyperVError
error_key(:powershell_required)
end
class WindowsRequired < HyperVError
error_key(:windows_required)
end
end
end
end

View File

@ -0,0 +1,36 @@
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
This plugin installs a provider that allows Vagrant to manage
machines in Hyper-V.
DESC
provider(:hyperv) 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

View File

@ -0,0 +1,76 @@
require "log4r"
require_relative "driver"
require_relative "plugin"
require "vagrant/util/platform"
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::Platform.windows?
raise Errors::WindowsRequired
end
if !Vagrant::Util::Platform.windows_admin?
raise Errors::AdminRequired
end
if !Vagrant::Util::PowerShell.available?
raise Errors::PowerShellRequired
end
end
def action(name)
# Attempt to get the action method from the Action class if it
# exists, otherwise return nil to show that we don't support the
# given action.
action_method = "action_#{name}"
return Action.send(action_method) if Action.respond_to?(action_method)
nil
end
def state
state_id = nil
state_id = :not_created if !@machine.id
if !state_id
# Run a custom action we define called "read_state" which does
# what it says. It puts the state in the `:machine_state_id`
# key in the environment.
env = @machine.action(:read_state)
state_id = env[:machine_state_id]
end
# Get the short and long description
short = state_id.to_s
long = ""
# Return the MachineState object
Vagrant::MachineState.new(state_id, short, long)
end
def to_s
id = @machine.id.nil? ? "new" : @machine.id
"Hyper-V (#{id})"
end
def ssh_info
# Run a custom action called "read_guest_ip" which does what it
# says and puts the resulting SSH info into the `:machine_ssh_info`
# key in the environment.
env = @machine.action("read_guest_ip")
if env[:machine_ssh_info]
env[:machine_ssh_info].merge!(:port => 22)
end
end
end
end
end

View File

@ -0,0 +1,7 @@
Param(
[Parameter(Mandatory=$true)]
[string]$VmId
)
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
Remove-VM $VM -Force

View File

@ -0,0 +1,123 @@
#-------------------------------------------------------------------------
# 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 }
function Get-file-hash($source_path, $delimiter) {
$source_files = @()
(Get-ChildItem $source_path -rec | ForEach-Object -Process {
Get-FileHash -Path $_.FullName -Algorithm MD5 } ) |
ForEach-Object -Process {
$source_files += $_.Path.Replace($source_path, "") + $delimiter + $_.Hash
}
$source_files
}
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
}
function Get-remote-file-hash($source_path, $delimiter, $session) {
Invoke-Command -Session $session -ScriptBlock ${function:Get-file-hash} -ArgumentList $source_path, $delimiter
# TODO:
# Check if remote PS Scripting errors
}
function Sync-Remote-Machine($machine, $remove_files, $copy_files, $host_path, $guest_path) {
ForEach ($item in $copy_files) {
$from = $host_path + $item
$to = $guest_path + $item
# Copy VM can also take a VM object
Copy-VMFile -VM $machine -SourcePath $from -DestinationPath $to -CreateFullPath -FileSource Host -Force
}
}
function Create-Remote-Folders($empty_source_folders, $guest_path) {
ForEach ($item in $empty_source_folders) {
$new_name = $guest_path + $item
New-Item "$new_name" -type directory -Force
}
}
function Get-Empty-folders-From-Source($host_path) {
Get-ChildItem $host_path -recurse |
Where-Object {$_.PSIsContainer -eq $True} |
Where-Object {$_.GetFiles().Count -eq 0} |
Select-Object FullName | ForEach-Object -Process {
$empty_source_folders += ($_.FullName.Replace($host_path, ""))
}
}
$delimiter = " || "
$machine = Get-VM -Id $vm_id
# FIXME: PowerShell guys please fix this.
# The below script checks for all VMIntegrationService which are not enabled
# and will enable this.
# When when all the services are enabled this throws an error.
# Enable VMIntegrationService to true
try {
Get-VM -Id $vm_id | Get-VMIntegrationService -Name "Guest Service Interface" | Enable-VMIntegrationService -Passthru
}
catch { }
$session = Get-Remote-Session $guest_ip $username $password
$source_files = Get-file-hash $host_path $delimiter
$destination_files = Get-remote-file-hash $guest_path $delimiter $session
if (!$destination_files) {
$destination_files = @()
}
if (!$source_files) {
$source_files = @()
}
# Compare source and destination files
$remove_files = @()
$copy_files = @()
Compare-Object -ReferenceObject $source_files -DifferenceObject $destination_files | ForEach-Object {
if ($_.SideIndicator -eq '=>') {
$remove_files += $_.InputObject.Split($delimiter)[0]
} else {
$copy_files += $_.InputObject.Split($delimiter)[0]
}
}
# Update the files to remote machine
Sync-Remote-Machine $machine $remove_files $copy_files $host_path $guest_path
# Create any empty folders which missed to sync to remote machine
$empty_source_folders = @()
$directories = Get-Empty-folders-From-Source $host_path
$result = Invoke-Command -Session $session -ScriptBlock ${function:Create-Remote-Folders} -ArgumentList $empty_source_folders, $guest_path
# Always remove the connection after Use
Remove-PSSession -Id $session.Id
$resultHash = @{
message = "OK"
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result

View File

@ -0,0 +1,17 @@
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"))
$vm = Get-VM -Id $VmId -ErrorAction "Stop"
$network = Get-VMNetworkAdapter -VM $vm
$ip_address = $network.IpAddresses[0]
$resultHash = @{
ip = "$ip_address"
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result

View File

@ -0,0 +1,12 @@
# 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 = @(Get-VMSwitch `
| Select-Object Name,SwitchType,NetAdapterInterfaceDescription)
Write-Output-Message $(ConvertTo-JSON $Switches)

View File

@ -0,0 +1,25 @@
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"))
# Get the VM with the given name
try {
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
$State = $VM.state
$Status = $VM.status
} catch [Microsoft.HyperV.PowerShell.VirtualizationOperationFailedException] {
$State = "not_created"
$Status = $State
}
$resultHash = @{
state = "$State"
status = "$Status"
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result

View File

@ -0,0 +1,144 @@
Param(
[Parameter(Mandatory=$true)]
[string]$vm_xml_config,
[Parameter(Mandatory=$true)]
[string]$vhdx_path,
[string]$switchname=$null
)
# 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_xml_config
$vm_name = $vmconfig.configuration.properties.name.'#text'
$processors = $vmconfig.configuration.settings.processors.count.'#text'
function GetUniqueName($name) {
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)
$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
if (!$switchname) {
# Get the name of the virtual switch
$switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text"
}
# 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
# 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"
}
# Create the VM using the values in the hash map
$vm = 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)
}
# Set the values on the VM
$vm | Set-VM @more_vm_params -Passthru
# Add drives to the virtual machine
$controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]"
# 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 = $vhdx_path
}
if ($drive.pool_id."#text") {
$ResourcePoolName = $drive.pool_id."#text"
$addDriveParam.Add("ResourcePoolname",$ResourcePoolName)
}
if ($drivetype -eq 'VHD') {
$addDriveParam.add("ControllerType",$ControllerType)
$vm | Add-VMHardDiskDrive @AddDriveparam
}
}
}
$vm_id = (Get-VM $vm_name).id.guid
$resultHash = @{
name = $vm_name
id = $vm_id
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result

View File

@ -0,0 +1,7 @@
Param(
[Parameter(Mandatory=$true)]
[string]$VmId
)
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
Resume-VM $VM

View File

@ -0,0 +1,27 @@
param (
[string]$vm_id = $(throw "-vm_id is required.")
)
# Include the following modules
$presentDir = Split-Path -parent $PSCommandPath
$modules = @()
$modules += $presentDir + "\utils\write_messages.ps1"
forEach ($module in $modules) { . $module }
try {
$vm = Get-VM -Id $vm_id -ErrorAction "stop"
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 a VM $_"
}

View File

@ -0,0 +1,8 @@
Param(
[Parameter(Mandatory=$true)]
[string]$VmId
)
# Shuts down virtual machine regardless of any unsaved application data
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
Stop-VM $VM -Force

View File

@ -0,0 +1,7 @@
Param(
[Parameter(Mandatory=$true)]
[string]$VmId
)
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
Suspend-VM $VM

View File

@ -0,0 +1,34 @@
#-------------------------------------------------------------------------
# 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
}
}

View File

@ -0,0 +1,20 @@
#-------------------------------------------------------------------------
# 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==="
}

View File

@ -0,0 +1,30 @@
module VagrantPlugins
module SyncedFolderSMB
module Errors
# A convenient superclass for all our errors.
class SMBError < Vagrant::Errors::VagrantError
error_namespace("vagrant_sf_smb.errors")
end
class DefineShareFailed < SMBError
error_key(:define_share_failed)
end
class NoHostIPAddr < SMBError
error_key(:no_routable_host_addr)
end
class PowershellError < SMBError
error_key(:powershell_error)
end
class WindowsHostRequired < SMBError
error_key(:windows_host_required)
end
class WindowsAdminRequired < SMBError
error_key(:windows_admin_required)
end
end
end
end

View File

@ -0,0 +1,32 @@
require "vagrant"
module VagrantPlugins
module SyncedFolderSMB
autoload :Errors, File.expand_path("../errors", __FILE__)
# This plugin implements SMB synced folders.
class Plugin < Vagrant.plugin("2")
name "SMB synced folders"
description <<-EOF
The SMB synced folders plugin enables you to use SMB folders on
Windows and share them to guest machines.
EOF
synced_folder("smb", 7) do
require_relative "synced_folder"
init!
SyncedFolder
end
protected
def self.init!
return if defined?(@_init)
I18n.load_path << File.expand_path(
"templates/locales/synced_folder_smb.yml", Vagrant.source_root)
I18n.reload!
@_init = true
end
end
end
end

View File

@ -0,0 +1,8 @@
$ErrorAction = "Stop"
$net = Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"'
$result = @{
ip_addresses = $net.ipaddress
}
Write-Output $(ConvertTo-Json $result)

View File

@ -0,0 +1,56 @@
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
}

View File

@ -0,0 +1,34 @@
Param(
[Parameter(Mandatory=$true)]
[string]$path,
[Parameter(Mandatory=$true)]
[string]$share_name,
[string]$host_share_username = $null
)
$ErrorAction = "Stop"
# See all available shares and check alert user for existing/conflicting
# share names.
$path_regexp = [System.Text.RegularExpressions.Regex]::Escape($path)
$name_regexp = [System.Text.RegularExpressions.Regex]::Escape($share_name)
$reg = "(?m)$name_regexp\s+$path_regexp\s"
$existing_share = $($(net share) -join "`n") -Match $reg
if ($existing_share) {
# Always clear the existing share name and create a new one
net share $share_name /delete /y
}
$grant = "Everyone,Full"
if (![string]::IsNullOrEmpty($host_share_username)) {
$computer_name = $(Get-WmiObject Win32_Computersystem).name
$grant = "$computer_name\$host_share_username,Full"
}
$result = net share $share_name=$path /unlimited /GRANT:$grant
if ($result -Match "$share_name was shared successfully.") {
exit 0
}
$host.ui.WriteErrorLine("Error: $result")
exit 1

View File

@ -0,0 +1,166 @@
require "json"
require "log4r"
require "vagrant/util/platform"
require "vagrant/util/powershell"
module VagrantPlugins
module SyncedFolderSMB
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
def initialize(*args)
super
@logger = Log4r::Logger.new("vagrant::synced_folders::smb")
end
def usable?(machine, raise_error=false)
if !Vagrant::Util::Platform.windows?
raise Errors::WindowsHostRequired if raise_error
return false
end
if !Vagrant::Util::Platform.windows_admin?
raise Errors::WindowsAdminRequired if raise_error
return false
end
true
end
def prepare(machine, folders, opts)
script_path = File.expand_path("../scripts/set_share.ps1", __FILE__)
folders.each do |id, data|
hostpath = data[:hostpath]
data[:smb_id] ||= "#{machine.id}-#{id.gsub("/", "-")}"
args = []
args << "-path" << hostpath.gsub("/", "\\")
args << "-share_name" << data[:smb_id]
#args << "-host_share_username" << "mitchellh"
r = Vagrant::Util::PowerShell.execute(script_path, *args)
if r.exit_code != 0
raise Errors::DefineShareFailed,
host: hostpath.to_s,
stderr: r.stderr,
stdout: r.stdout
end
end
end
def enable(machine, folders, nfsopts)
machine.ui.output(I18n.t("vagrant_sf_smb.mounting"))
# Make sure that this machine knows this dance
if !machine.guest.capability?(:mount_smb_shared_folder)
raise Vagrant::Errors::GuestCapabilityNotFound,
cap: "mount_smb_shared_folder",
guest: machine.guest.name.to_s
end
# Detect the host IP for this guest if one wasn't specified
# for every folder.
host_ip = nil
need_host_ip = false
folders.each do |id, data|
if !data[:smb_host]
need_host_ip = true
break
end
end
if need_host_ip
candidate_ips = load_host_ips
@logger.debug("Potential host IPs: #{candidate_ips.inspect}")
host_ip = machine.guest.capability(
:choose_addressable_ip_addr, candidate_ips)
if !host_ip
raise Errors::NoHostIPAddr
end
end
# If we need auth information, then ask the user
username = nil
password = nil
need_auth = false
folders.each do |id, data|
if !data[:smb_username] || !data[:smb_password]
need_auth = true
break
end
end
if need_auth
machine.ui.detail(I18n.t("vagrant_sf_smb.warning_password") + "\n ")
username = machine.ui.ask("Username: ")
password = machine.ui.ask("Password (will be hidden): ", echo: false)
end
# This is used for defaulting the owner/group
ssh_info = machine.ssh_info
folders.each do |id, data|
data = data.dup
data[:smb_host] ||= host_ip
data[:smb_username] ||= username
data[:smb_password] ||= password
# Default the owner/group of the folder to the SSH user
data[:owner] ||= ssh_info[:username]
data[:group] ||= ssh_info[:username]
machine.ui.detail(I18n.t(
"vagrant_sf_smb.mounting_single",
host: data[:hostpath].to_s,
guest: data[:guestpath].to_s))
machine.guest.capability(
:mount_smb_shared_folder, data[:smb_id], data[:guestpath], data)
end
end
def cleanup(machine, opts)
end
protected
def load_host_ips
script_path = File.expand_path("../scripts/host_info.ps1", __FILE__)
r = Vagrant::Util::PowerShell.execute(script_path)
if r.exit_code != 0
raise Errors::PowershellError,
script: script_path,
stderr: r.stderr
end
JSON.parse(r.stdout)["ip_addresses"]
end
=begin
def mount_shared_folders_to_windows
result = @env[:machine].provider.driver.execute('host_info.ps1', {})
@smb_shared_folders.each do |id, data|
begin
options = { :share_name => data[:share_name],
:guest_path => data[:guestpath].gsub("/", "\\"),
:guest_ip => ssh_info[:host],
:username => ssh_info[:username],
:host_ip => result["host_ip"],
:password => @env[:machine].provider_config.guest.password,
:host_share_username => @env[:machine].provider_config.host_share.username,
:host_share_password => @env[:machine].provider_config.host_share.password}
@env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...")
@env[:machine].provider.driver.execute('mount_share.ps1', options)
rescue Error::SubprocessError => e
@env[:ui].info "Failed to link #{data[:share_name]} to Guest"
@env[:ui].info e.message
end
end
end
=end
end
end
end

View File

@ -0,0 +1,69 @@
en:
vagrant_hyperv:
choose_switch: |-
Please choose a switch to attach to your Hyper-V instance.
If none of these are appropriate, please open the Hyper-V manager
to create a new virtual switch.
message_already_running: |-
Hyper-V instance already running.
message_not_created: |-
VM not created. Moving on...
message_not_running: |-
Hyper-V machine isn't running. Can't SSH in!
errors:
admin_required: |-
The Hyper-V provider requires that Vagrant be run with
administrative privileges. This is a limitation of Hyper-V itself.
Hyper-V requires administrative privileges for management
commands. Please restart your console with administrative
privileges and try again.
box_invalid: |-
The box you're using with the Hyper-V provider ('%{name}')
is invalid. A Hyper-V box should contain both a
"Virtual Machines" and a "Virtual Hard Disks" folder that are
created as part of exporting a Hyper-V machine.
Within these directories, Vagrant expects to find the
virtual machine configuration as well as the root hard disk.
The box you're attempting to use is missing one or both of
these directories or does not contain the files expected. Verify
that you added the correct box. If this problem persists,
please contact the creator of the box for assistance.
ip_addr_timeout: |-
Hyper-V failed to determine your machine's IP address within the
configured timeout. Please verify the machine properly booted and
the network works. To do this, open the Hyper-V manager, find your
virtual machine, and connect to it.
The most common cause for this error is that the running virtual
machine doesn't have the latest Hyper-V integration drivers. Please
research for your operating system how to install these in order
for the VM to properly communicate its IP address to Hyper-V.
no_switches: |-
There are no virtual switches created for Hyper-V! Please open
the Hyper-V Manager, go to the "Virtual Switch Manager", and create
at least one virtual switch.
A virtual switch is required for Vagrant to create a Hyper-V
machine that is connected to a network so it can access it.
For more help, please see the documentation on the Vagrant website
for Hyper-V.
powershell_error: |-
An error occurred while executing a PowerShell script. This error
is shown below. Please read the error message and see if this is
a configuration error with your system. If it is not, then please
report a bug.
Script: %{script}
Error:
%{stderr}
powershell_required: |-
The Vagrant Hyper-V provider requires PowerShell to be available.
Please make sure "powershell.exe" is available on your PATH.
windows_required: |-
The Hyper-V provider only works on Windows. Please try to
use another provider.

View File

@ -0,0 +1,48 @@
en:
vagrant_sf_smb:
mounting: |-
Mounting SMB shared folders...
mounting_single: |-
%{host} => %{guest}
warning_password: |-
You will be asked for the username and password to use to mount the
folders shortly. Please use the proper username/password of your
Windows account.
errors:
define_share_failed: |-
Exporting an SMB share failed! Details about the failure are shown
below. Please inspect the error message and correct any problems.
Host path: %{host}
Stderr: %{stderr}
Stdout: %{stdout}
no_routable_host_addr: |-
We couldn't detect an IP address that was routable to this
machine from the guest machine! Please verify networking is properly
setup in the guest machine and that it is able to access this
host.
As another option, you can manually specify an IP for the machine
to mount from using the `smb_host` option to the synced folder.
powershell_error: |-
An error occurred while executing a PowerShell script. This error
is shown below. Please read the error message and see if this is
a configuration error with your system. If it is not, then please
report a bug.
Script: %{script}
Error:
%{stderr}
windows_admin_required: |-
SMB shared folders require running Vagrant with administrative
privileges. This is a limitation of Windows, since creating new
network shares requires admin privileges. Please try again in a
console with proper permissions or use another synced folder type.
windows_host_required: |-
SMB shared folders are only available when Vagrant is running
on Windows. The guest machine can be running non-Windows. Please use
another synced folder type.

View File

@ -0,0 +1,18 @@
require_relative "../../../base"
require Vagrant.source_root.join("plugins/providers/hyperv/config")
describe VagrantPlugins::HyperV::Config do
describe "#ip_address_timeout" do
it "can be set" do
subject.ip_address_timeout = 180
subject.finalize!
expect(subject.ip_address_timeout).to eq(180)
end
it "defaults to a number" do
subject.finalize!
expect(subject.ip_address_timeout).to eq(120)
end
end
end

View File

@ -0,0 +1,64 @@
require_relative "../../../base"
require Vagrant.source_root.join("plugins/providers/hyperv/provider")
describe VagrantPlugins::HyperV::Provider do
let(:machine) { double("machine") }
let(:platform) { double("platform") }
let(:powershell) { double("powershell") }
subject { described_class.new(machine) }
before do
stub_const("Vagrant::Util::Platform", platform)
stub_const("Vagrant::Util::PowerShell", powershell)
platform.stub(windows?: true)
platform.stub(windows_admin?: true)
powershell.stub(available?: true)
end
describe "#initialize" do
it "raises an exception if not windows" do
platform.stub(windows?: false)
expect { subject }.
to raise_error(VagrantPlugins::HyperV::Errors::WindowsRequired)
end
it "raises an exception if not an admin" do
platform.stub(windows_admin?: false)
expect { subject }.
to raise_error(VagrantPlugins::HyperV::Errors::AdminRequired)
end
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
describe "#driver" do
it "is initialized" do
expect(subject.driver).to be_kind_of(VagrantPlugins::HyperV::Driver)
end
end
describe "#state" do
it "returns not_created if no ID" do
machine.stub(id: nil)
expect(subject.state.id).to eq(:not_created)
end
it "calls an action to determine the ID" do
machine.stub(id: "foo")
machine.should_receive(:action).with(:read_state).
and_return({ machine_state_id: :bar })
expect(subject.state.id).to eq(:bar)
end
end
end

View File

@ -0,0 +1,51 @@
require "pathname"
require "tmpdir"
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::IsState do
let(:app) { lambda { |env| } }
let(:env) { { :machine => machine } }
let(:machine) do
double("machine").tap do |machine|
machine.stub(:state).and_return(state)
end
end
let(:state) { double("state") }
describe "#call" do
it "sets result to false if is proper state" do
state.stub(id: :foo)
subject = described_class.new(app, env, :bar)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:result]).to be_false
end
it "sets result to true if is proper state" do
state.stub(id: :foo)
subject = described_class.new(app, env, :foo)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:result]).to be_true
end
it "inverts the result if specified" do
state.stub(id: :foo)
subject = described_class.new(app, env, :foo, invert: true)
app.should_receive(:call).with(env)
subject.call(env)
expect(env[:result]).to be_false
end
end
end

View File

@ -0,0 +1,22 @@
require "pathname"
require "tmpdir"
require File.expand_path("../../../../base", __FILE__)
describe Vagrant::Action::Builtin::Message do
let(:app) { lambda { |env| } }
let(:env) { { :ui => ui } }
let(:ui) { double("ui") }
describe "#call" do
it "outputs the given message" do
subject = described_class.new(app, env, "foo")
ui.should_receive(:output).with("foo")
app.should_receive(:call).with(env)
subject.call(env)
end
end
end

View File

@ -190,6 +190,7 @@
<li<%= sidebar_current("syncedfolder-basic") %>><a href="/v2/synced-folders/basic_usage.html">Basic Usage</a></li>
<li<%= sidebar_current("syncedfolder-nfs") %>><a href="/v2/synced-folders/nfs.html">NFS</a></li>
<li<%= sidebar_current("syncedfolder-rsync") %>><a href="/v2/synced-folders/rsync.html">RSync</a></li>
<li<%= sidebar_current("syncedfolder-smb") %>><a href="/v2/synced-folders/smb.html">SMB</a></li>
<li<%= sidebar_current("syncedfolder-virtualbox") %>><a href="/v2/synced-folders/virtualbox.html">VirtualBox</a></li>
</ul>
<% end %>
@ -227,6 +228,16 @@
</ul>
<% end %>
<li<%= sidebar_current("hyperv") %>><a href="/v2/hyperv/index.html">Hyper-V</a></li>
<% if sidebar_section == "hyperv" %>
<ul class="sub unstyled">
<li<%= sidebar_current("hyperv-usage") %>><a href="/v2/hyperv/usage.html">Usage</a></li>
<li<%= sidebar_current("hyperv-boxes") %>><a href="/v2/hyperv/boxes.html">Creating a Base Box</a></li>
<li<%= sidebar_current("hyperv-configuration") %>><a href="/v2/hyperv/configuration.html">Configuration</a></li>
<li<%= sidebar_current("hyperv-limitations") %>><a href="/v2/hyperv/limitations.html">Limitations</a></li>
</ul>
<% end %>
<li<%= sidebar_current("plugins") %>><a href="/v2/plugins/index.html">Plugins</a></li>
<% if sidebar_section == "plugins" %>
<ul class="sub unstyled">

View File

@ -0,0 +1,93 @@
---
page_title: "Creating a Base Box - Hyper-V Provider"
sidebar_current: "hyperv-boxes"
---
# Creating a Base Box
As with [every provider](/v2/providers/basic_usage.html), the Hyper-V
provider has a custom box format that affects how base boxes are made.
Prior to reading this, you should read the
[general guide to creating base boxes](/v2/boxes/base.html). Actually,
it would probably be most useful to keep this open in a separate tab
as you may be referencing it frequently while creating a base box. That
page contains important information about common software to install
on the box.
Additionally, it is helpful to understand the
[basics of the box file format](/v2/boxes/format.html).
<div class="alert alert-block alert-warn">
<p>
<strong>Advanced topic!</strong> This is a reasonably advanced topic that
a beginning user of Vagrant doesn't need to understand. If you're
just getting started with Vagrant, skip this and use an available
box. If you're an experienced user of Vagrant and want to create
your own custom boxes, this is for you.
</p>
</div>
## Additional Software
In addition to the software that should be installed based on the
[general guide to creating base boxes](/v2/boxes/base.html),
Hyper-V base boxes require some additional software.
### Hyper-V Kernel Modules
You'll need to install Hyper-V kernel modules. While this improves performance,
it also enables necessary features such as reporting its IP address so that
Vagrant can access it.
You can verify Hyper-V kernel modules are properly installed by
running `lsmod` on Linux machines and looking for modules prefixed with
`hv_`. Additionally, you'll need to verify that the "Network" tab for your
virtual machine in the Hyper-V manager is reporting an IP address. If it
is not reporting an IP address, Vagrant will not be able to access it.
For most newer Linux distributions, the Hyper-V modules will be available
out of the box.
Ubuntu 12.04 requires some special steps to make networking work. These
are reproduced here in case similar steps are needed with other distributions.
Without these commands, Ubuntu 12.04 will not report an IP address to
Hyper-V:
```
$ sudo apt-get install linux-tools-3.11.0-15-generic
$ sudo apt-get install hv-kvp-daemon-init
$ cp/usr/lib/linux-tools-3.11.0-15/hv_* /usr/sbin/
```
## Packaging the Box
To package a Hyper-V box, export the virtual machine from the
Hyper-V Manager using the "Export" feature. This will create a directory
with a structure similar to the following:
```
.
|-- Snapshots
|-- Virtual Hard drives
|-- Virtual Machines
```
Delete the "Snapshots" folder. It is of no use to the Vagrant Hyper-V
provider and can only add to the size of the box if there are snapshots
in that folder.
Then, create the "metadata.json" file necessary for the box, as documented
in [basics of the box file format](/v2/boxes/format.html). The proper
provider value to use for the metadata is "hyperv".
Finally, create an archive of those contents (but _not_ the parent folder)
using a tool such as `tar`:
```
$ tar cvzf ~/custom.box ./*
```
A common mistake is to also package the parent folder by accident. Vagrant
will not work in this case. To verify you've packaged it properly, add the
box to Vagrant and try to bring up the machine.

View File

@ -0,0 +1,13 @@
---
page_title: "Configuration- Hyper-V Provider"
sidebar_current: "hyperv-configuration"
---
# Configuration
The Hyper-V provider has some provider-specific configuration options
you may set. A complete reference is shown below:
* `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.

View File

@ -0,0 +1,25 @@
---
page_title: "Hyper-V Provider"
sidebar_current: "hyperv"
---
# Hyper-V
Vagrant comes with support out of the box for [Hyper-V](http://en.wikipedia.org/wiki/Hyper-V),
a native hypervisor written by Microsoft. Hyper-V is available by default for
almost all Windows 8.1 installs.
The Hyper-V provider is compatible with Windows 8.1 only. Prior versions
of Hyper-V do not include the necessary APIs for Vagrant to work.
Hyper-V must be enabled prior to using the provider. Most Windows installations
will not have Hyper-V enabled by default. To enable Hyper-V, go to
"Programs and Features" and check the box next to "Hyper-V."
<div class="alert alert-block alert-warn">
<strong>Warning:</strong> Enabling Hyper-V will cause VirtualBox, VMware,
and any other virtualization technology to no longer work. See
<a href="http://www.hanselman.com/blog/SwitchEasilyBetweenVirtualBoxAndHyperVWithABCDEditBootEntryInWindows81.aspx">this blog post</a>
for an easy way to create a boot entry to boot Windows without Hyper-V
enabled, if there will be times you'll need other hypervisors.
</div>

View File

@ -0,0 +1,30 @@
---
page_title: "Limitations - Hyper-V Provider"
sidebar_current: "hyperv-limitations"
---
# Limitations
The Hyper-V provider works in almost every way like the VirtualBox
or VMware provider would, but has some limitations that are inherent to
Hyper-V itself.
## Limited Networking
Vagrant doesn't yet know how to create and configure new networks for
Hyper-V. When launching a machine with Hyper-V, Vagrant will prompt you
asking what virtual switch you want to connect the virtual machine to.
A result of this is that networking configurations in the Vagrantfile
are completely ignored with Hyper-V. Vagrant can't enforce a static IP
or automatically configure a NAT.
However, the IP address of the machine will be reported as part of
the `vagrant up`, and you can use that IP address as if it were
a host only network.
## Packaging
Vagrant doesn't implement the `vagrant package` command for Hyper-V
yet, though this should be fairly straightforward to add in a Vagrant
release in the near future.

View File

@ -0,0 +1,21 @@
---
page_title: "Usage - Hyper-V Provider"
sidebar_current: "hyperv-usage"
---
# Usage
The Hyper-V provider is used just like any other provider. Please
read the general [basic usage](/v2/providers/basic_usage.html) page for
providers.
The value to use for the `--provider` flag is `hyperv`.
Hyper-V also requires that you execute Vagrant with administrative
privileges. Creating and managing virtual machines with Hyper-V requires
admin rights. Vagrant will show you an error if it doesn't have the proper
permissions.
Boxes for Hyper-V can be easily found on
[Vagrant Cloud](http://www.vagrantcloud.com). To get started, you might
want to try the `hashicorp/precise64` box.

View File

@ -0,0 +1,75 @@
---
page_title: "SMB - Synced Folders"
sidebar_current: "syncedfolder-smb"
---
# SMB
**Synced folder type:** `smb`
Vagrant can use [SMB](http://en.wikipedia.org/wiki/Server_Message_Block)
as a mechanism to create a bi-directional synced folder between the host
machine and the Vagrant machine.
SMB is built-in to Windows machines and provides a higher performance
alternative to some other mechanisms such as VirtualBox shared folders.
<div class="alert alert-info">
<p>
<strong>Windows only!</strong> SMB is currently only supported
when the host machine is Windows. The guest machine can be Windows
or Linux.
</p>
</div>
## Prerequisites
To use the SMB synced folder type, the machine running Vagrant must be
a Windows machine. In addition to this, the command prompt executing Vagrant
must have administrative privileges. Vagrant requires these privileges in
order to create new network folder shares.
The destination machine must be able to mount SMB filesystems. On Linux
the package to do this is usually called `smbfs` or `cifs`. Vagrant knows
how to automatically install this for some operating systems.
## Options
The SMB synced folder type has a variety of options it accepts:
* `smb_host` (string) - The host IP where the SMB mount is located. If this
isn't specified, Vagrant will attempt to determine this automatically.
* `smb_password` (string) - The password used for authentication to mount
the SMB mount. This is the password for the username specified by
`smb_username`. If this is not specified, Vagrant will prompt you for it.
It is highly recommended that you do not set this, since it would expose
your password directly in your Vagrantfile.
* `smb_username` (string) - The username used for authentication to mount
the SMB mount. This is the username to access the mount, _not_ the username
of the account where the folder is being mounted to. This is usually your
Windows username. If this isn't specified, Vagrant will prompt you for
it.
## Example
The following is an example of using SMB to sync a folder:
<pre class="prettyprint">
Vagrant.configure("2") do |config|
config.vm.synced_folder ".", "/vagrant", type: "smb"
end
</pre>
## Limitations
Because SMB is a relatively new synced folder type in Vagrant, it still
has some rough edges. Hopefully, future versions of Vagrant will address
these.
The primary limitation of SMB synced folders at the moment is that they are
never pruned or cleaned up. Once the folder share is defined, Vagrant never
removes it. To clean up SMB synced folder shares, periodically run
`net share` in a command prompt, find the shares you don't want, then
run `net share NAME /delete` for each, where NAME is the name of the share.