commit
179a9244df
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$VmId
|
||||
)
|
||||
|
||||
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
|
||||
Remove-VM $VM -Force
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$VmId
|
||||
)
|
||||
|
||||
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
|
||||
Resume-VM $VM
|
|
@ -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 $_"
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
Param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$VmId
|
||||
)
|
||||
|
||||
$VM = Get-VM -Id $VmId -ErrorAction "Stop"
|
||||
Suspend-VM $VM
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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==="
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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">
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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>
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
Loading…
Reference in New Issue