Merge branch 'middleware'

This commit is contained in:
Mitchell Hashimoto 2010-07-08 22:05:09 -07:00
commit 8f0d6c8304
127 changed files with 3564 additions and 3666 deletions

View File

@ -12,5 +12,8 @@ require File.expand_path("util/glob_loader", libdir)
# Load them up
Vagrant::GlobLoader.glob_require(libdir, %w{util/stacked_proc_runner
actions/base downloaders/base actions/collection actions/runner config
provisioners/base provisioners/chef systems/base commands/base commands/box})
downloaders/base config provisioners/base provisioners/chef systems/base
commands/base commands/box action/exception_catcher})
# Initialize the built-in actions
Vagrant::Action.builtin!

62
lib/vagrant/action.rb Normal file
View File

@ -0,0 +1,62 @@
module Vagrant
# Manages action running and registration. Every Vagrant environment
# has an instance of {Action} to allow for running in the context of
# the environment.
class Action
include Util
class << self
# Returns the list of registered actions.
def actions
@actions ||= {}
end
# Registers an action and associates it with a symbol. This
# symbol can then be referenced in other action builds and
# callbacks can be registered on that symbol.
#
# @param [Symbol] key
def register(key, callable)
actions[key] = callable
end
# Retrieves a registered action by key.
def [](key)
actions[key]
end
end
# The environment to run the actions in.
attr_reader :env
# Initializes the action with the given environment which the actions
# will be run in.
#
# @param [Environment] env
def initialize(env)
@env = env
end
# Runs the given callable object in the context of the environment.
# If a symbol is given as the `callable` parameter, then it is looked
# up in the registered actions list which are registered with {register}.
#
# @param [Object] callable An object which responds to `call`.
def run(callable, options=nil)
callable = Builder.new.use(callable) if callable.kind_of?(Class)
callable = self.class.actions[callable] if callable.kind_of?(Symbol)
action_environment = Action::Environment.new(env)
action_environment.merge!(options || {})
callable.call(action_environment)
if action_environment.error?
# Erroneous environment resulted. Properly display error
# message.
key, options = action_environment.error
error_and_exit(key, options)
return false
end
end
end
end

View File

@ -0,0 +1,16 @@
module Vagrant
class Action
class ActionException < Exception
attr_reader :key
attr_reader :data
def initialize(key, data = {})
@key = key
@data = data
message = Vagrant::Util::Translator.t(key, data)
super(message)
end
end
end
end

View File

@ -0,0 +1,19 @@
module Vagrant
class Action
module Box
class Destroy
def initialize(app, env)
@app = app
@env = env
end
def call(env)
env.logger.info "Deleting box directory..."
FileUtils.rm_rf(env["box"].directory)
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,82 @@
module Vagrant
class Action
module Box
class Download
BASENAME = "box"
include Util
include ExceptionCatcher
attr_reader :temp_path
def initialize(app, env)
@app = app
@env = env
@env["download.classes"] ||= []
@env["download.classes"] << [Downloaders::HTTP, Downloaders::File]
@env["download.classes"].flatten!
end
def call(env)
@env = env
catch_action_exception(env) do
download if instantiate_downloader
end
return if env.error?
@app.call(@env)
cleanup
end
def instantiate_downloader
@env["download.classes"].each do |klass|
if klass.match?(@env["box"].uri)
@env.logger.info "Downloading with #{klass}..."
@downloader = klass.new(@env["box"].uri)
end
end
if !@downloader
@env.error!(:box_download_unknown_type)
return false
end
@downloader.prepare(@env["box"].uri)
true
end
def download
with_tempfile do |tempfile|
download_to(tempfile)
@temp_path = @env["download.temp_path"] = tempfile.path
end
end
def cleanup
if temp_path && File.exist?(temp_path)
@env.logger.info "Cleaning up downloaded box..."
File.unlink(temp_path)
end
end
def with_tempfile
@env.logger.info "Creating tempfile for storing box file..."
File.open(box_temp_path, Platform.tar_file_options) do |tempfile|
yield tempfile
end
end
def box_temp_path
File.join(@env.env.tmp_path, BASENAME + Time.now.to_i.to_s)
end
def download_to(f)
@env.logger.info "Copying box to temporary location..."
@downloader.download!(@env["box"].uri, f)
end
end
end
end
end

View File

@ -0,0 +1,58 @@
module Vagrant
class Action
module Box
# Unpackages a downloaded box to a given directory with a given
# name.
#
# # Required Variables
#
# * `download.temp_path` - A location for the downloaded box. This is
# set by the {Download} action.
# * `box` - A {Vagrant::Box} object.
#
class Unpackage
attr_reader :box_directory
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env = env
return if !setup_box_directory
decompress
@app.call(@env)
cleanup if @env.error?
end
def cleanup
if File.directory?(box_directory)
FileUtils.rm_rf(box_directory)
end
end
def setup_box_directory
if File.directory?(@env["box"].directory)
@env.error!(:box_already_exists, :box_name => @env["box"].name)
return false
end
FileUtils.mkdir_p(@env["box"].directory)
@box_directory = @env["box"].directory
true
end
def decompress
Dir.chdir(@env["box"].directory) do
@env.logger.info "Extracting box to #{@env["box"].directory}..."
Archive::Tar::Minitar.unpack(@env["download.temp_path"], @env["box"].directory)
end
end
end
end
end
end

View File

@ -0,0 +1,23 @@
module Vagrant
class Action
module Box
class Verify
def initialize(app, env)
@app = app
@env = env
end
def call(env)
begin
env.logger.info "Verifying box..."
VirtualBox::Appliance.new(env["box"].ovf_file)
rescue Exception
return env.error!(:box_verification_failed)
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,148 @@
module Vagrant
class Action
# Action builder which provides a nice DSL for building up
# a middleware sequence for Vagrant actions. This code is based
# heavily off of `Rack::Builder` and `ActionDispatch::MiddlewareStack`
# in Rack and Rails, respectively.
#
# Usage
#
# Building an action sequence is very easy:
#
# app = Vagrant::Action::Builder.new do
# use MiddlewareA
# use MiddlewareB
# end
#
# Vagrant::Action.run(app)
#
class Builder
# Initializes the builder. An optional block can be passed which
# will be evaluated in the context of the instance.
def initialize(&block)
instance_eval(&block) if block_given?
end
# Returns the current stack of middlewares. You probably won't
# need to use this directly, and its recommended that you don't.
#
# @return [Array]
def stack
@stack ||= []
end
# Returns a mergeable version of the builder. If `use` is called with
# the return value of this method, then the stack will merge, instead
# of being treated as a separate single middleware.
def flatten
lambda do |env|
self.call(env)
end
end
# Adds a middleware class to the middleware stack. Any additional
# args and a block, if given, are saved and passed to the initializer
# of the middleware.
#
# @param [Class] middleware The middleware class
def use(middleware, *args, &block)
if middleware.kind_of?(Builder)
# Merge in the other builder's stack into our own
self.stack.concat(middleware.stack)
else
self.stack << [middleware, args, block]
end
self
end
# Inserts a middleware at the given index or directly before the
# given middleware object.
def insert(index, middleware, *args, &block)
index = self.index(index) unless index.is_a?(Integer)
stack.insert(index, [middleware, args, block])
end
alias_method :insert_before, :insert
# Inserts a middleware after the given index or middleware object.
def insert_after(index, middleware, *args, &block)
index = self.index(index) unless index.is_a?(Integer)
raise "no such middleware to insert after: #{index.inspect}" unless index
insert(index + 1, middleware, *args, &block)
end
# Swaps out the given middlware object or index with the new
# middleware.
def swap(index, middleware, *args, &block)
if index.is_a?(Integer)
delete(index)
insert(index, middleware, *args, &block)
else
insert_before(index, middleware, *args, &block)
delete(index)
end
end
# Deletes the given middleware object or index
def delete(index)
index = self.index(index) unless index.is_a?(Integer)
stack.delete_at(index)
end
# Returns the numeric index for the given middleware object.
#
# @param [Object] object The item to find the index for
# @return [Integer]
def index(object)
stack.each_with_index do |item, i|
return i if item[0] == object
end
nil
end
# Converts the builder stack to a runnable action sequence.
#
# @param [Vagrant::Action::Environment] env The action environment
# @return [Object] A callable object
def to_app(env)
# Prepend the error halt task so errneous environments are halted
# before the chain even begins.
items = stack.dup.unshift([ErrorHalt, [], nil])
# Convert each middleware into a lambda which takes the next
# middleware.
items = items.collect do |item|
klass, args, block = item
lambda do |app|
if klass.is_a?(Class)
# A middleware klass which is to be instantiated with the
# app, env, and any arguments given
klass.new(app, env, *args, &block)
elsif klass.respond_to?(:call)
# Make it a lambda which calls the item then forwards
# up the chain
lambda do |e|
klass.call(e)
app.call(e)
end
else
raise "Invalid middleware: #{item.inspect}"
end
end
end
# Append the final step and convert into flattened call chain.
items << lambda { |env| }
items[0...-1].reverse.inject(items.last) { |a,e| e.call(a) }
end
# Runs the builder stack with the given environment.
def call(env)
to_app(env).call(env)
end
end
end
end

View File

@ -0,0 +1,104 @@
module Vagrant
class Action
# Registers the builtin actions. These are locked away in a
# method so that their definition can be deferred until after
# all the necessary Vagrant libraries are loaded. Hopefully
# in the future this will no longer be necessary with autoloading.
def self.builtin!
# provision - Provisions a running VM
provision = Builder.new do
use VM::Provision
end
register :provision, provision
# start - Starts a VM, assuming it already exists on the
# environment.
start = Builder.new do
use VM::Customize
use VM::ForwardPorts
use VM::Provision
use VM::ShareFolders
use VM::Network
use VM::Boot
end
register :start, start
# halt - Halts the VM, attempting gracefully but then forcing
# a restart if fails.
halt = Builder.new do
use VM::Halt
end
register :halt, halt
# suspend - Suspends the VM
suspend = Builder.new do
use VM::Suspend
end
register :suspend, suspend
# resume - Resume a VM
resume = Builder.new do
use VM::Resume
end
register :resume, resume
# reload - Halts then restarts the VM
reload = Builder.new do
use Action[:halt]
use Action[:start]
end
register :reload, reload
# up - Imports, prepares, then starts a fresh VM.
up = Builder.new do
use VM::Import
use VM::Persist
use VM::MatchMACAddress
use VM::CheckGuestAdditions
use Action[:start]
end
register :up, up
# destroy - Halts, cleans up, and destroys an existing VM
destroy = Builder.new do
use Action[:halt]
use VM::DestroyUnusedNetworkInterfaces
use VM::Destroy
end
register :destroy, destroy
# package - Export and package the VM
package = Builder.new do
use Action[:halt]
use VM::Export
use VM::Package
end
register :package, package
# box_add - Download and add a box.
box_add = Builder.new do
use Box::Download
use Box::Unpackage
use Box::Verify
end
register :box_add, box_add
# box_remove - Removes/deletes a box.
box_remove = Builder.new do
use Box::Destroy
end
register :box_remove, box_remove
end
end
end

View File

@ -0,0 +1,54 @@
module Vagrant
class Action
# Represents an action environment which is what is passed
# to the `call` method of each action. This environment contains
# some helper methods for accessing the environment as well
# as being a hash, to store any additional options.
class Environment < Hash
# The {Vagrant::Environment} object represented by this
# action environment.
attr_reader :env
# If nonnil, the error associated with this environment. Set
# using {#error!}
attr_reader :error
def initialize(env)
super() do |h,k|
# By default, try to find the key as a method on the
# environment. Gross eval use here.
begin
value = eval("h.env.#{k}")
h[k] = value
rescue Exception
nil
end
end
@env = env
@error = nil
end
# Returns a logger associated with the environment.
def logger
env.logger
end
# Flags the environment as erroneous. Stores the given key
# and options until the end of the action sequence.
#
# @param [Symbol] key Key to translation to display error message.
# @param [Hash] options Variables to pass to the translation
def error!(key, options=nil)
@error = [key, (options || {})]
end
# Returns boolean denoting if environment is in erroneous state.
#
# @return [Boolean]
def error?
!error.nil?
end
end
end
end

View File

@ -0,0 +1,14 @@
module Vagrant
class Action
# A middleware which simply halts if the environment is erroneous.
class ErrorHalt
def initialize(app,env)
@app = app
end
def call(env)
@app.call(env) if !env.error?
end
end
end
end

View File

@ -0,0 +1,14 @@
module Vagrant
class Action
# A helper to catch any ActionExceptions raised and to
# apply the error to the environment.
module ExceptionCatcher
def catch_action_exception(env)
yield env
rescue ActionException => e
env.error!(e.key, e.data)
false
end
end
end
end

View File

@ -0,0 +1,46 @@
module Vagrant
class Action
module VM
class Boot
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env = env
# Start up the VM and wait for it to boot.
boot
return env.error!(:vm_failed_to_boot) if !wait_for_boot
@app.call(env)
end
def boot
@env.logger.info "Booting VM..."
@env["vm"].vm.start(@env.env.config.vm.boot_mode)
end
def wait_for_boot(sleeptime=5)
@env.logger.info "Waiting for VM to boot..."
@env.env.config.ssh.max_tries.to_i.times do |i|
@env.logger.info "Trying to connect (attempt ##{i+1} of #{@env.env.config[:ssh][:max_tries]})..."
if @env["vm"].ssh.up?
@env.logger.info "VM booted and ready for use!"
return true
end
sleep sleeptime
end
@env.logger.info "Failed to connect to VM! Failed to boot?"
false
end
end
end
end
end

View File

@ -0,0 +1,32 @@
module Vagrant
class Action
module VM
# Middleware which verifies that the VM has the proper guest additions
# installed and prints a warning if they're not detected or if the
# version does not match the installed VirtualBox version.
class CheckGuestAdditions
include Util
def initialize(app, env)
@app = app
end
def call(env)
# Use the raw interface for now, while the virtualbox gem
# doesn't support guest properties (due to cross platform issues)
version = env["vm"].vm.interface.get_guest_property_value("/VirtualBox/GuestAdd/Version")
if version.empty?
env.logger.error Translator.t(:vm_additions_not_detected)
elsif version != VirtualBox.version
env.logger.error Translator.t(:vm_additions_version_mismatch,
:guest_additions_version => version,
:virtualbox_version => VirtualBox.version)
end
# Continue
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,21 @@
module Vagrant
class Action
module VM
class Customize
def initialize(app, env)
@app = app
end
def call(env)
if !env.env.config.vm.proc_stack.empty?
env.logger.info "Running any VM customizations..."
env.env.config.vm.run_procs!(env["vm"].vm)
env["vm"].vm.save
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,20 @@
module Vagrant
class Action
module VM
class Destroy
def initialize(app, env)
@app = app
end
def call(env)
env.logger.info "Destroying VM and associated drives..."
env["vm"].vm.destroy(:destroy_medium => :delete)
env["vm"].vm = nil
env.env.update_dotfile
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,35 @@
module Vagrant
class Action
module VM
# Destroys the unused host only interfaces. This middleware cleans
# up any created host only networks.
class DestroyUnusedNetworkInterfaces
def initialize(app, env)
@app = app
end
def call(env)
# We need to check if the host only network specified by any
# of the adapters would not have any more clients if it was
# destroyed. And if so, then destroy the host only network
# itself.
interfaces = env["vm"].vm.network_adapters.collect do |adapter|
adapter.host_interface_object
end
interfaces.compact.uniq.each do |interface|
# Destroy the network interface if there is only one
# attached VM (which must be this VM)
if interface.attached_vms.length == 1
env.logger.info "Destroying unused network interface..."
interface.destroy
end
end
# Continue along
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,42 @@
module Vagrant
class Action
module VM
class Export
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env = env
return env.error!(:vm_power_off_to_package) if !@env["vm"].vm.powered_off?
setup_temp_dir
export
@app.call(env)
end
def setup_temp_dir
@env.logger.info "Creating temporary directory for export..."
@env["export.temp_dir"] = File.join(@env.env.tmp_path, Time.now.to_i.to_s)
FileUtils.mkpath(@env["export.temp_dir"])
end
def export
@env.logger.info "Exporting VM to #{ovf_path}..."
@env["vm"].vm.export(ovf_path) do |progress|
@env.logger.report_progress(progress.percent, 100, false)
end
ensure
@env.logger.clear_progress
end
def ovf_path
File.join(@env["export.temp_dir"], @env.env.config.vm.box_ovf)
end
end
end
end
end

View File

@ -1,18 +1,26 @@
module Vagrant
module Actions
class Action
module VM
class ForwardPorts < Base
def prepare
class ForwardPorts
def initialize(app,env)
@app = app
@env = env
external_collision_check
end
#--------------------------------------------------------------
# Prepare Helpers - These functions are not involved in actually
# executing the action
#--------------------------------------------------------------
# This method checks for any port collisions with any VMs
# which are already created (by Vagrant or otherwise).
# report the collisions detected or will attempt to fix them
# automatically if the port is configured to do so.
def external_collision_check
existing = used_ports
runner.env.config.vm.forwarded_ports.each do |name, options|
@env.env.config.vm.forwarded_ports.each do |name, options|
if existing.include?(options[:hostport].to_s)
handle_collision(name, options, existing)
end
@ -26,18 +34,18 @@ module Vagrant
if !options[:auto]
# Auto fixing is disabled for this port forward, so we
# must throw an error so the user can fix it.
raise ActionException.new(:vm_port_collision, :name => name, :hostport => options[:hostport].to_s, :guestport => options[:guestport].to_s, :adapter => options[:adapter])
return @env.error!(:vm_port_collision, :name => name, :hostport => options[:hostport].to_s, :guestport => options[:guestport].to_s, :adapter => options[:adapter])
end
# Get the auto port range and get rid of the used ports and
# ports which are being used in other forwards so we're just
# left with available ports.
range = runner.env.config.vm.auto_port_range.to_a
range -= runner.env.config.vm.forwarded_ports.collect { |n, o| o[:hostport].to_i }
range = @env.env.config.vm.auto_port_range.to_a
range -= @env.env.config.vm.forwarded_ports.collect { |n, o| o[:hostport].to_i }
range -= existing_ports
if range.empty?
raise ActionException.new(:vm_port_auto_empty, :vm_name => @runner.name, :name => name, :options => options)
return @env.error!(:vm_port_auto_empty, :vm_name => @env["vm"].name, :name => name, :options => options)
end
# Set the port up to be the first one and add that port to
@ -46,51 +54,54 @@ module Vagrant
existing_ports << options[:hostport]
# Notify the user
logger.info "Fixed port collision: #{name} now on port #{options[:hostport]}"
@env.logger.info "Fixed port collision: #{name} now on port #{options[:hostport]}"
end
def execute!
#--------------------------------------------------------------
# Execution
#--------------------------------------------------------------
def call(env)
@env = env
clear
forward_ports
@app.call(env)
end
def clear
if used_ports.length > 0
logger.info "Deleting any previously set forwarded ports..."
@env.logger.info "Deleting any previously set forwarded ports..."
clear_ports
runner.reload!
@env["vm"].reload!
end
end
def forward_ports
logger.info "Forwarding ports..."
@env.logger.info "Forwarding ports..."
runner.env.config.vm.forwarded_ports.each do |name, options|
@env.env.config.vm.forwarded_ports.each do |name, options|
adapter = options[:adapter]
# Assuming the only reason to establish port forwarding is because the VM is using Virtualbox NAT networking.
# Host-only or Bridged networking don't require port-forwarding and establishing forwarded ports on these
# attachment types has uncertain behaviour.
if @runner.vm.network_adapters[adapter].attachment_type == :nat
logger.info "Forwarding \"#{name}\": #{options[:guestport]} on adapter \##{adapter+1} => #{options[:hostport]}"
if @env["vm"].vm.network_adapters[adapter].attachment_type == :nat
@env.logger.info "Forwarding \"#{name}\": #{options[:guestport]} on adapter \##{adapter+1} => #{options[:hostport]}"
forward_port(name, options)
else
logger.info "VirtualBox adapter \##{adapter+1} not configured as \"NAT\"."
logger.info "Skipped port forwarding \"#{name}\": #{options[:guestport]} on adapter\##{adapter+1} => #{options[:hostport]}"
@env.logger.info "VirtualBox adapter \##{adapter+1} not configured as \"NAT\"."
@env.logger.info "Skipped port forwarding \"#{name}\": #{options[:guestport]} on adapter\##{adapter+1} => #{options[:hostport]}"
end
end
runner.vm.save
runner.reload!
@env["vm"].vm.save
@env["vm"].reload!
end
#------------------------------------------------------------
# VirtualBox version-specific helpers below. VirtualBox 3.2
# introduced a breaking change into the way that forwarded
# ports are handled. For backwards compatability with 3.1, we
# need this trickery.
#------------------------------------------------------------
# TODO In a future version, fix this.
#--------------------------------------------------------------
# General Helpers
#--------------------------------------------------------------
# Returns an array of used ports. This method is implemented
# differently depending on the VirtualBox version, but the
@ -99,7 +110,7 @@ module Vagrant
# @return [Array<String>]
def used_ports
result = VirtualBox::VM.all.collect do |vm|
if vm.running? && vm.uuid != runner.uuid
if vm.running? && vm.uuid != @env["vm"].uuid
vm.network_adapters.collect do |na|
na.nat_driver.forwarded_ports.collect do |fp|
fp.hostport.to_s
@ -113,7 +124,7 @@ module Vagrant
# Deletes existing forwarded ports.
def clear_ports
runner.vm.network_adapters.each do |na|
@env["vm"].vm.network_adapters.each do |na|
na.nat_driver.forwarded_ports.dup.each do |fp|
fp.destroy
end
@ -126,7 +137,7 @@ module Vagrant
port.name = name
port.guestport = options[:guestport]
port.hostport = options[:hostport]
runner.vm.network_adapters[options[:adapter]].nat_driver.forwarded_ports << port
@env["vm"].vm.network_adapters[options[:adapter]].nat_driver.forwarded_ports << port
end
end
end

View File

@ -0,0 +1,29 @@
module Vagrant
class Action
module VM
class Halt
include ExceptionCatcher
def initialize(app, env)
@app = app
end
def call(env)
if env["vm"].vm.running?
if !env["force"]
catch_action_exception(env) { env["vm"].system.halt }
return if env.error?
end
if env["vm"].vm.state(true) != :powered_off
env.logger.info "Forcing shutdown of VM..."
env["vm"].vm.stop
end
end
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,30 @@
module Vagrant
class Action
module VM
class Import
def initialize(app, env)
@app = app
end
def call(env)
env.logger.info "Importing base VM (#{env.env.box.ovf_file})"
begin
# Import the virtual machine
env.env.vm.vm = VirtualBox::VM.import(env.env.box.ovf_file) do |progress|
env.logger.report_progress(progress.percent, 100, false)
end
# Flag as erroneous and return if import failed
return env.error!(:virtualbox_import_failure) if !env['vm'].vm
ensure
env.logger.clear_progress
end
# Import completed successfully. Continue the chain
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,19 @@
module Vagrant
class Action
module VM
class MatchMACAddress
def initialize(app, env)
@app = app
end
def call(env)
env.logger.info "Matching MAC addresses..."
env["vm"].vm.network_adapters.first.mac_address = env.env.config.vm.base_mac
env["vm"].vm.save
@app.call(env)
end
end
end
end
end

View File

@ -1,71 +1,43 @@
module Vagrant
module Actions
class Action
module VM
class Network < Base
def prepare
# Verify that the given network options are valid
runner.env.config.vm.network_options.compact.each do |network_options|
verify_no_bridge_collision(network_options)
end
end
# Networking middleware for Vagrant. This enables host only
# networking on VMs if configured as such.
class Network
include ExceptionCatcher
def before_destroy
# We need to check if the host only network specified by any
# of the adapters would not have any more clients if it was
# destroyed. And if so, then destroy the host only network
# itself.
interfaces = runner.vm.network_adapters.collect do |adapter|
adapter.host_interface_object
end
def initialize(app, env)
@app = app
@env = env
interfaces.compact.uniq.each do |interface|
# Destroy the network interface if there is only one
# attached VM (which must be this VM)
if interface.attached_vms.length == 1
logger.info "Destroying unused network interface..."
interface.destroy
env["config"].vm.network_options.compact.each do |network_options|
if !verify_no_bridge_collision(network_options)
env.error!(:network_collides)
return
end
end
end
def before_boot
def call(env)
@env = env
assign_network if enable_network?
end
def after_boot
if enable_network?
logger.info "Enabling host only network..."
@app.call(env)
runner.system.prepare_host_only_network
runner.env.config.vm.network_options.compact.each do |network_options|
runner.system.enable_host_only_network(network_options)
if !env.error? && enable_network?
catch_action_exception(env) do
@env.logger.info "Enabling host only network..."
@env["vm"].system.prepare_host_only_network
@env.env.config.vm.network_options.compact.each do |network_options|
@env["vm"].system.enable_host_only_network(network_options)
end
end
end
end
def enable_network?
!runner.env.config.vm.network_options.compact.empty?
end
# Enables and assigns the host only network to the proper
# adapter on the VM, and saves the adapter.
def assign_network
logger.info "Preparing host only network..."
runner.env.config.vm.network_options.compact.each do |network_options|
adapter = runner.vm.network_adapters[network_options[:adapter]]
adapter.enabled = true
adapter.attachment_type = :host_only
adapter.host_interface = network_name(network_options)
adapter.save
end
end
# Verifies that there is no collision with a bridged network interface
# for the given network options.
def verify_no_bridge_collision(net_options)
# First try to find a matching network
interfaces = VirtualBox::Global.global.host.network_interfaces
interfaces.each do |ni|
next if ni.interface_type == :host_only
@ -76,7 +48,27 @@ module Vagrant
true if matching_network?(ni, net_options)
end
raise ActionException.new(:network_collides) if result
return false if result
end
true
end
def enable_network?
!@env.env.config.vm.network_options.compact.empty?
end
# Enables and assigns the host only network to the proper
# adapter on the VM, and saves the adapter.
def assign_network
@env.logger.info "Preparing host only network..."
@env.env.config.vm.network_options.compact.each do |network_options|
adapter = @env["vm"].vm.network_adapters[network_options[:adapter]]
adapter.enabled = true
adapter.attachment_type = :host_only
adapter.host_interface = network_name(network_options)
adapter.save
end
end
@ -98,10 +90,10 @@ module Vagrant
end
end
raise ActionException.new(:network_not_found, :name => net_options[:name]) if net_options[:name]
return @env.error!(:network_not_found, :name => net_options[:name]) if net_options[:name]
# One doesn't exist, create it.
logger.info "Creating new host only network for environment..."
@env.logger.info "Creating new host only network for environment..."
ni = interfaces.create
ni.enable_static(network_ip(net_options[:ip], net_options[:netmask]),

View File

@ -0,0 +1,92 @@
module Vagrant
class Action
module VM
class Package
include Util
def initialize(app, env)
@app = app
@env = env
@env["package.output"] ||= "package"
@env["package.include"] ||= []
env.error!(:box_file_exists, :output_file => tar_path) if File.exist?(tar_path)
end
def call(env)
@env = env
return env.error!(:package_requires_export) if !@env["export.temp_dir"]
return if !verify_included_files
compress
@app.call(env)
end
def verify_included_files
@env["package.include"].each do |file|
if !File.exist?(file)
@env.error!(:package_include_file_doesnt_exist, :filename => file)
return false
end
end
true
end
# This method copies the include files (passed in via command line)
# to the temporary directory so they are included in a sub-folder within
# the actual box
def copy_include_files
if @env["package.include"].length > 0
include_dir = File.join(@env["export.temp_dir"], "include")
FileUtils.mkdir_p(include_dir)
@env["package.include"].each do |f|
@env.logger.info "Packaging additional file: #{f}"
FileUtils.cp(f, include_dir)
end
end
end
# This method creates the auto-generated Vagrantfile at the root of the
# box. This Vagrantfile contains the MAC address so that the user doesn't
# have to worry about it.
def create_vagrantfile
File.open(File.join(@env["export.temp_dir"], "Vagrantfile"), "w") do |f|
f.write(TemplateRenderer.render("package_Vagrantfile", {
:base_mac => @env["vm"].vm.network_adapters.first.mac_address
}))
end
end
# Compress the exported file into a package
def compress
@env.logger.info "Packaging VM into #{tar_path}..."
File.open(tar_path, Platform.tar_file_options) do |tar|
Archive::Tar::Minitar::Output.open(tar) do |output|
begin
current_dir = FileUtils.pwd
copy_include_files
create_vagrantfile
FileUtils.cd(@env["export.temp_dir"])
Dir.glob(File.join(".", "**", "*")).each do |entry|
Archive::Tar::Minitar.pack_file(entry, output)
end
ensure
FileUtils.cd(current_dir)
end
end
end
end
# Path to the final box output file
def tar_path
File.join(FileUtils.pwd, "#{@env["package.output"]}#{@env.env.config.package.extension}")
end
end
end
end
end

View File

@ -0,0 +1,22 @@
module Vagrant
class Action
module VM
class Persist
def initialize(app, env)
@app = app
# Error the environment if the dotfile is not valid
env.error!(:dotfile_error, :env => env.env) if File.exist?(env.env.dotfile_path) &&
!File.file?(env.env.dotfile_path)
end
def call(env)
env.logger.info "Persisting the VM UUID (#{env["vm"].uuid})"
env.env.update_dotfile
@app.call(env)
end
end
end
end
end

View File

@ -0,0 +1,50 @@
module Vagrant
class Action
module VM
class Provision
def initialize(app, env)
@app = app
@env = env
load_provisioner if provisioning_enabled?
end
def call(env)
@app.call(env)
if !env.error? && provisioning_enabled?
@env.logger.info "Beginning provisioning process..."
@provisioner.provision!
end
end
def provisioning_enabled?
!@env["config"].vm.provisioner.nil?
end
def load_provisioner
provisioner = @env["config"].vm.provisioner
if provisioner.is_a?(Class)
@provisioner = provisioner.new(@env)
return @env.error!(:provisioner_invalid_class) unless @provisioner.is_a?(Provisioners::Base)
elsif provisioner.is_a?(Symbol)
# We have a few hard coded provisioners for built-ins
mapping = {
:chef_solo => Provisioners::ChefSolo,
:chef_server => Provisioners::ChefServer
}
provisioner_klass = mapping[provisioner]
return @env.error!(:provisioner_unknown_type, :provisioner => provisioner.to_s) if provisioner_klass.nil?
@provisioner = provisioner_klass.new(@env)
end
@env.logger.info "Provisioning enabled with #{@provisioner.class}"
@provisioner.prepare
@provisioner
end
end
end
end
end

View File

@ -0,0 +1,20 @@
module Vagrant
class Action
module VM
class Resume
def initialize(app, env)
@app = app
end
def call(env)
if env["vm"].vm.saved?
env.logger.info "Resuming suspended VM..."
env["actions"].run(:start)
end
@app.call(env)
end
end
end
end
end

View File

@ -1,11 +1,36 @@
module Vagrant
module Actions
class Action
module VM
class SharedFolders < Base
class ShareFolders
include ExceptionCatcher
def initialize(app, env)
@app = app
@env = env
end
def call(env)
@env = env
clear_shared_folders
create_metadata
@app.call(env)
if !env.error?
catch_action_exception(env) do
# Only mount and setup shared folders in the absense of an
# error
mount_shared_folders
setup_unison
end
end
end
# This method returns an actual list of VirtualBox shared
# folders to create and their proper path.
def shared_folders
runner.env.config.vm.shared_folders.inject({}) do |acc, data|
@env.env.config.vm.shared_folders.inject({}) do |acc, data|
key, value = data
# This to prevent overwriting the actual shared folders data
@ -15,7 +40,7 @@ module Vagrant
# Syncing this folder. Change the guestpath to reflect
# what we're actually mounting.
value[:original] = value.dup
value[:guestpath] = "#{value[:guestpath]}#{runner.env.config.unison.folder_suffix}"
value[:guestpath] = "#{value[:guestpath]}#{@env.env.config.unison.folder_suffix}"
end
acc[key] = value
@ -33,23 +58,39 @@ module Vagrant
end
end
def before_boot
clear_shared_folders
create_metadata
def clear_shared_folders
if @env["vm"].vm.shared_folders.length > 0
@env.logger.info "Clearing previously set shared folders..."
folders = @env["vm"].vm.shared_folders.dup
folders.each do |shared_folder|
shared_folder.destroy
end
@env["vm"].reload!
end
end
def after_boot
mount_shared_folders
setup_unison
def create_metadata
@env.logger.info "Creating shared folders metadata..."
shared_folders.each do |name, data|
folder = VirtualBox::SharedFolder.new
folder.name = name
folder.host_path = File.expand_path(data[:hostpath], @env.env.root_path)
@env["vm"].vm.shared_folders << folder
end
@env["vm"].vm.save
end
def mount_shared_folders
logger.info "Mounting shared folders..."
@env.logger.info "Mounting shared folders..."
@runner.ssh.execute do |ssh|
@env["vm"].ssh.execute do |ssh|
shared_folders.each do |name, data|
logger.info "-- #{name}: #{data[:guestpath]}"
@runner.system.mount_shared_folder(ssh, name, data[:guestpath])
@env.logger.info "-- #{name}: #{data[:guestpath]}"
@env["vm"].system.mount_shared_folder(ssh, name, data[:guestpath])
end
end
end
@ -57,41 +98,15 @@ module Vagrant
def setup_unison
return if unison_folders.empty?
runner.ssh.execute do |ssh|
runner.system.prepare_unison(ssh)
@env["vm"].ssh.execute do |ssh|
@env["vm"].system.prepare_unison(ssh)
logger.info "Creating unison crontab entries..."
@env.logger.info "Creating unison crontab entries..."
unison_folders.each do |name, data|
runner.system.create_unison(ssh, data)
@env["vm"].system.create_unison(ssh, data)
end
end
end
def clear_shared_folders
if runner.vm.shared_folders.length > 0
logger.info "Clearing previously set shared folders..."
folders = @runner.vm.shared_folders.dup
folders.each do |shared_folder|
shared_folder.destroy
end
@runner.reload!
end
end
def create_metadata
logger.info "Creating shared folders metadata..."
shared_folders.each do |name, data|
folder = VirtualBox::SharedFolder.new
folder.name = name
folder.host_path = File.expand_path(data[:hostpath], runner.env.root_path)
@runner.vm.shared_folders << folder
end
@runner.vm.save
end
end
end
end

View File

@ -0,0 +1,20 @@
module Vagrant
class Action
module VM
class Suspend
def initialize(app, env)
@app = app
end
def call(env)
if env["vm"].vm.running?
env.logger.info "Saving VM state and suspending execution..."
env["vm"].vm.save_state
end
@app.call(env)
end
end
end
end
end

View File

@ -1,130 +0,0 @@
module Vagrant
module Actions
# Base class for any command actions.
#
# Actions are the smallest unit of functionality found within
# Vagrant. Vagrant composes many actions together to execute
# its complex tasks while keeping the individual pieces of a
# task as discrete reusable actions. Actions are ran exclusively
# by an {Runner action runner} which is simply a subclass of {Runner}.
#
# Actions work by implementing any or all of the following methods
# which a {Runner} executes:
#
# * `prepare` - Called once for each action before any action has `execute!`
# called. This is meant for basic setup.
# * `execute!` - This is where the meat of the action typically goes;
# the main code which executes the action.
# * `cleanup` - This is called exactly once for each action after every
# other action is completed. It is meant for cleaning up any resources.
# * `rescue` - This is called if an exception occurs in _any action_. This
# gives every other action a chance to clean itself up.
#
# For details of each step of an action, read the specific function call
# documentation below.
class Base
# The {Runner runner} which is executing the action
attr_reader :runner
# Any options which are passed into the initializer as a hash.
attr_reader :options
# Included so subclasses don't need to include it themselves.
include Vagrant::Util
# A helper method for logging which simply gets the logger from
# the runner. Since actions tend to log quite a bit, this
# removes the need to prefix `logger` with `@runner` over and
# over.
def logger
runner.env.logger
end
# Initialization of the action, passing any arguments which may have
# been given to the {Runner runner}. This method can be used by subclasses
# to save any of the configuration options which are passed in.
def initialize(runner, options=nil)
@runner = runner
@options = options || {}
end
# This method is called once per action, allowing the action
# to setup any callbacks, add more events, etc. Prepare is
# called in the order the actions are defined, and the action
# itself has no control over this.
#
# Examples of its usage:
#
# Perhaps we need an additional action only if a configuration is set:
#
# def prepare
# @vm.actions << FooAction if Vagrant.config[:foo] == :bar
# end
#
def prepare; end
# This method is called once, after preparing, to execute the
# actual task. This method is responsible for calling any
# callbacks. Adding new actions here will have unpredictable
# effects and should never be done.
#
# Examples of its usage:
#
# def execute!
# @vm.invoke_callback(:before_oven, "cookies")
# # Do lots of stuff here
# @vm.invoke_callback(:after_oven, "more", "than", "one", "option")
# end
#
def execute!; end
# This method is called after all actions have finished executing.
# It is meant as a place where final cleanup code can be done, knowing
# that all other actions are finished using your data.
def cleanup; end
# This method is only called if some exception occurs in the chain
# of actions. If an exception is raised in any action in the current
# chain, then every action part of that chain has {#rescue} called
# before raising the exception further. This method should be used to
# perform any cleanup necessary in the face of errors.
#
# **Warning:** Since this method is called when an exception is already
# raised, be _extra careful_ when implementing this method to handle
# all your own exceptions, otherwise it'll mask the initially raised
# exception.
def rescue(exception); end
# The following two methods are used for declaring action dependencies.
# For example, you require that the reload action be in place before
# a your new FooAction you might do the following
#
# def follows; [Reload] end
# This method is called when the runner is determining the actions that
# must precede a given action. You would say "This action follows [Action1, Action2]"
def follows; [] end
# This method is called when the runner is determining the actions that
# must follow a given action. You would say "This action precedes [Action3, Action4]
def precedes; [] end
end
# An exception which occured within an action. This should be used instead of
# {Vagrant::Util#error_and_exit error_and_exit}, since it allows the {Runner} to call
# {Base#rescue rescue} on all the actions and properly exit. Any message
# passed into the {ActionException} is then shown and and vagrant exits.
class ActionException < Exception
attr_reader :key
attr_reader :data
def initialize(key, data = {})
@key = key
@data = data
message = Vagrant::Util::Translator.t(key, data)
super(message)
end
end
end
end

View File

@ -1,23 +0,0 @@
module Vagrant
module Actions
module Box
# A meta-action which adds a box by downloading and unpackaging it.
# This action downloads and unpackages a box with a given URI. This
# is a _meta action_, meaning it simply adds more actions to the
# action chain, and those actions do the work.
#
# This is the action called by {Box#add}.
class Add < Base
def prepare
if File.exists?(@runner.directory)
raise ActionException.new(:box_add_already_exists, :box_name => @runner.name)
end
@runner.add_action(Download)
@runner.add_action(Unpackage)
@runner.add_action(Verify)
end
end
end
end
end

View File

@ -1,14 +0,0 @@
module Vagrant
module Actions
module Box
# Action to destroy a box. This action is not reversible and expects
# to be called by a {Box} object.
class Destroy < Base
def execute!
logger.info "Deleting box directory..."
FileUtils.rm_rf(@runner.directory)
end
end
end
end
end

View File

@ -1,67 +0,0 @@
module Vagrant
module Actions
module Box
# An action which acts on a box by downloading the box file from
# the given URI into a temporary location. This action parses a
# given URI and handles downloading it via one of the many Vagrant
# downloads (such as {Vagrant::Downloaders::File}).
#
# This action cleans itself up by removing the downloaded box file.
class Download < Base
BASENAME = "box"
BUFFERSIZE = 1048576 # 1 MB
attr_reader :downloader
def prepare
# Check the URI given and prepare a downloader
[Downloaders::HTTP, Downloaders::File].each do |dler|
if dler.match?(@runner.uri)
logger.info "Downloading via #{dler}..."
@downloader = dler.new(@runner.env)
end
end
raise ActionException.new(:box_download_unknown_type) unless @downloader
# Prepare the downloader
@downloader.prepare(@runner.uri)
end
def execute!
with_tempfile do |tempfile|
download_to(tempfile)
@runner.temp_path = tempfile.path
end
end
def cleanup
if @runner.temp_path && File.exist?(@runner.temp_path)
logger.info "Cleaning up downloaded box..."
File.unlink(@runner.temp_path)
end
end
def rescue(exception)
cleanup
end
def with_tempfile
logger.info "Creating tempfile for storing box file..."
File.open(box_temp_path, Platform.tar_file_options) do |tempfile|
yield tempfile
end
end
def box_temp_path
File.join(@runner.env.tmp_path, BASENAME + Time.now.to_i.to_s)
end
def download_to(f)
logger.info "Copying box to temporary location..."
downloader.download!(@runner.uri, f)
end
end
end
end
end

View File

@ -1,42 +0,0 @@
module Vagrant
module Actions
module Box
# This action unpackages a downloaded box file into its final
# box destination within the vagrant home folder.
class Unpackage < Base
def execute!
@runner.invoke_around_callback(:unpackage) do
setup_box_dir
decompress
end
end
def rescue(exception)
if File.directory?(box_dir)
logger.info "An error occurred, rolling back box unpackaging..."
FileUtils.rm_rf(box_dir)
end
end
def setup_box_dir
if File.directory?(box_dir)
error_and_exit(:box_already_exists, :box_name => @runner.name)
end
FileUtils.mkdir_p(box_dir)
end
def box_dir
@runner.directory
end
def decompress
Dir.chdir(box_dir) do
logger.info "Extracting box to #{box_dir}..."
Archive::Tar::Minitar.unpack(@runner.temp_path, box_dir)
end
end
end
end
end
end

View File

@ -1,32 +0,0 @@
module Vagrant
module Actions
module Box
# This action verifies that a given box is valid. This works by attempting
# to read/interpret the appliance file (OVF). If the reading succeeds, then
# the box is assumed to be valid.
class Verify < Base
def execute!
logger.info "Verifying box..."
reload_configuration
verify_appliance
end
def reload_configuration
# We have to reload the environment config since we _just_ added the
# box. We do this by setting the current box to the recently added box,
# then reloading
@runner.env.config.vm.box = @runner.name
@runner.env.load_box!
@runner.env.load_config!
end
def verify_appliance
# We now try to read the applince. If it succeeds, we return true.
VirtualBox::Appliance.new(@runner.ovf_file)
rescue Exception
raise ActionException.new(:box_verification_failed)
end
end
end
end
end

View File

@ -1,36 +0,0 @@
module Vagrant
module Actions
class Collection < Array
def dependencies!
each_with_index do |action, i|
action.follows.each do |klass|
unless self[0..i].klasses.include?(klass)
raise DependencyNotSatisfiedException.new("#{action.class} action must follow #{klass}")
end
end
action.precedes.each do |klass|
unless self[i..length].klasses.include?(klass)
raise DependencyNotSatisfiedException.new("#{action.class} action must precede #{klass}")
end
end
end
end
def duplicates?
klasses.uniq.size != size
end
def duplicates!
raise DuplicateActionException.new if duplicates?
end
def klasses
map { |o| o.class }
end
end
class DuplicateActionException < Exception; end
class DependencyNotSatisfiedException < Exception; end
end
end

View File

@ -1,131 +0,0 @@
module Vagrant
module Actions
# Base class for any class which will act as a runner
# for actions. A runner handles queueing up and executing actions,
# and executing the methods of an action in the proper order. The
# action runner also handles invoking callbacks that actions may
# request.
#
# # Executing Actions
#
# Actions can be executed by adding them and executing them all
# at once:
#
# runner = Vagrant::Actions::Runner.new
# runner.add_action(FooAction)
# runner.add_action(BarAction)
# runner.add_action(BazAction)
# runner.execute!
#
# Single actions have a shorthand to be executed:
#
# Vagrant::Actions::Runner.execute!(FooAction)
#
# Arguments may be passed into added actions by adding them after
# the action class:
#
# runner.add_action(FooAction, "many", "arguments", "may", "follow")
#
class Runner
include Vagrant::Util
class << self
# Executes a specific action, optionally passing in any arguments to that
# action. This method is shorthand to initializing a runner, adding a single
# action, and executing it.
def execute!(action_klass, *args)
runner = new
runner.add_action(action_klass, *args)
runner.execute!
end
end
# Returns an array of all the actions in queue. Because this
# will persist accross calls (calling {#actions} twice will yield
# exactly the same object), to clear or modify it, use the ruby
# array methods which act on `self`, such as `Array#clear`.
#
# @return [Array]
def actions
@actions ||= Actions::Collection.new
end
# Returns the first action instance which matches the given class.
#
# @param [Class] action_klass The action to search for in the queue
# @return [Object]
def find_action(action_klass)
actions.find { |a| a.is_a?(action_klass) }
end
# Add an action to the list of queued actions to execute. This method
# appends the given action class to the end of the queue. Any arguments
# given after the class are passed into the class constructor.
def add_action(action_klass, *args)
actions << action_klass.new(self, *args)
end
# Execute the actions in queue. This method can also optionally be used
# to execute a single action on an instance. The syntax for executing a
# single method on an instance is the same as the {execute!} class method.
def execute!(single_action=nil, *args)
if single_action
actions.clear
add_action(single_action, *args)
end
actions.duplicates!
actions.dependencies!
# Call the prepare method on each once its
# initialized, then call the execute! method
begin
[:prepare, :execute!, :cleanup].each do |method|
actions.each do |action|
action.send(method)
end
end
rescue Exception => e
# Run the rescue code to do any emergency cleanup
actions.each do |action|
action.rescue(e)
end
# If its an ActionException, error and exit the message
if e.is_a?(ActionException)
error_and_exit(e.key, e.data)
return
end
# Finally, reraise the exception
raise
end
# Clear the actions
actions.clear
end
# Invokes an "around callback" which invokes before_name and
# after_name for the given callback name, yielding a block between
# callback invokations.
def invoke_around_callback(name, *args)
invoke_callback("before_#{name}".to_sym, *args)
yield
invoke_callback("after_#{name}".to_sym, *args)
end
# Invokes a single callback. This method will go through each action
# and call the method given in the parameter `name` if the action
# responds to it.
def invoke_callback(name, *args)
# Attempt to call the method for the callback on each of the
# actions
results = []
actions.each do |action|
results << action.send(name, *args) if action.respond_to?(name)
end
results
end
end
end
end

View File

@ -1,43 +0,0 @@
module Vagrant
module Actions
module VM
class Boot < Base
def execute!
@runner.invoke_around_callback(:boot) do
# Startup the VM
boot
# Wait for it to complete booting, or error if we could
# never detect it booted up successfully
if !wait_for_boot
error_and_exit(:vm_failed_to_boot)
end
end
end
def boot
logger.info "Booting VM..."
@runner.vm.start(@runner.env.config.vm.boot_mode)
end
def wait_for_boot(sleeptime=5)
logger.info "Waiting for VM to boot..."
@runner.env.config.ssh.max_tries.to_i.times do |i|
logger.info "Trying to connect (attempt ##{i+1} of #{@runner.env.config[:ssh][:max_tries]})..."
if @runner.ssh.up?
logger.info "VM booted and ready for use!"
return true
end
sleep sleeptime
end
logger.info "Failed to connect to VM! Failed to boot?"
false
end
end
end
end
end

View File

@ -1,19 +0,0 @@
module Vagrant
module Actions
module VM
class Customize < Base
def execute!
if !runner.env.config.vm.proc_stack.empty?
logger.info "Running any VM customizations..."
# Run the customization procs over the VM
runner.env.config.vm.run_procs!(@runner.vm)
# Save the vm
runner.vm.save
end
end
end
end
end
end

View File

@ -1,24 +0,0 @@
module Vagrant
module Actions
module VM
class Destroy < Base
def execute!
@runner.invoke_around_callback(:destroy) do
destroy_vm
update_dotfile
end
end
def destroy_vm
logger.info "Destroying VM and associated drives..."
@runner.vm.destroy(:destroy_medium => :delete)
@runner.vm = nil
end
def update_dotfile
@runner.env.update_dotfile
end
end
end
end
end

View File

@ -1,22 +0,0 @@
module Vagrant
module Actions
module VM
class Down < Base
def prepare
# The true as the 2nd parameter always forces the shutdown so its
# fast (since we're destroying anyways)
@runner.add_action(Halt, :force => true) if @runner.vm.running?
@runner.add_action(Network)
@runner.add_action(Destroy)
end
def after_halt
# This sleep is necessary to wait for the VM to clean itself up.
# There appears to be nothing in the API that does this "wait"
# for us.
Kernel.sleep(1)
end
end
end
end
end

View File

@ -1,45 +0,0 @@
module Vagrant
module Actions
module VM
class Export < Base
attr_reader :temp_dir
def execute!
setup_temp_dir
export
end
def cleanup
if temp_dir
logger.info "Removing temporary export directory..."
FileUtils.rm_r(temp_dir)
end
end
def rescue(exception)
cleanup
end
def setup_temp_dir
@temp_dir = File.join(@runner.env.tmp_path, Time.now.to_i.to_s)
logger.info "Creating temporary directory for export..."
FileUtils.mkpath(temp_dir)
end
def ovf_path
File.join(temp_dir, @runner.env.config.vm.box_ovf)
end
def export
logger.info "Exporting VM to #{ovf_path}..."
@runner.vm.export(ovf_path) do |progress|
logger.report_progress(progress.percent, 100, false)
end
logger.clear_progress
end
end
end
end
end

View File

@ -1,24 +0,0 @@
module Vagrant
module Actions
module VM
class Halt < Base
def execute!
raise ActionException.new(:vm_not_running) unless @runner.vm.running?
@runner.invoke_around_callback(:halt) do
@runner.system.halt if !options[:force]
if @runner.vm.state(true) != :powered_off
logger.info "Forcing shutdown of VM..."
@runner.vm.stop
end
end
end
def force?
!!options[:force]
end
end
end
end
end

View File

@ -1,23 +0,0 @@
module Vagrant
module Actions
module VM
class Import < Base
def execute!
@runner.invoke_around_callback(:import) do
Busy.busy do
logger.info "Importing base VM (#{@runner.env.box.ovf_file})..."
# Use the first argument passed to the action
@runner.vm = VirtualBox::VM.import(@runner.env.box.ovf_file) do |progress|
logger.report_progress(progress.percent, 100, false)
end
logger.clear_progress
raise ActionException.new(:virtualbox_import_failure) unless @runner.vm
end
end
end
end
end
end
end

View File

@ -1,51 +0,0 @@
module Vagrant
module Actions
module VM
class MoveHardDrive < Base
def execute!
unless @runner.powered_off?
error_and_exit(:vm_power_off_to_move_hd)
return
end
destroy_drive_after { clone_and_attach }
end
def hard_drive
@hard_drive ||= find_hard_drive
end
# TODO won't work if the first disk is not the boot disk or even if there are multiple disks
def find_hard_drive
@runner.vm.storage_controllers.each do |sc|
sc.devices.each do |d|
return d if d.image.is_a?(VirtualBox::HardDrive)
end
end
end
def clone_and_attach
logger.info "Cloning current VM Disk to new location (#{new_image_path})..."
hard_drive.image = hard_drive.image.clone(new_image_path, @runner.env.config.vm.disk_image_format, true)
logger.info "Attaching new disk to VM ..."
@runner.vm.save
end
def destroy_drive_after
old_image = hard_drive.image
yield
logger.info "Destroying old VM Disk (#{old_image.filename})..."
old_image.destroy(true)
end
# Returns the path to the new location for the hard drive
def new_image_path
File.join(@runner.env.config.vm.hd_location, hard_drive.image.filename)
end
end
end
end
end

View File

@ -1,94 +0,0 @@
module Vagrant
module Actions
module VM
class Package < Base
attr_reader :export_action
def initialize(*args)
super
@temp_path = nil
end
def prepare
raise ActionException.new(:box_file_exists, :output_file => tar_path) if File.exist?(tar_path)
# Verify the existance of all the additional files, if any
include_files.each do |file|
raise ActionException.new(:package_include_file_doesnt_exist, :filename => file) unless File.exists?(file)
end
# Get the export action and store a reference to it
@export_action = @runner.find_action(Export)
raise ActionException.new(:packaged_requires_export) unless @export_action
end
def execute!
compress
end
def out_path
options[:output] || "package"
end
def include_files
options[:include] || []
end
def tar_path
File.join(FileUtils.pwd, "#{out_path}#{@runner.env.config.package.extension}")
end
def temp_path
export_action.temp_dir
end
# This method copies the include files (passed in via command line)
# to the temporary directory so they are included in a sub-folder within
# the actual box
def copy_include_files
if include_files.length > 0
include_dir = File.join(temp_path, "include")
FileUtils.mkdir_p(include_dir)
include_files.each do |f|
logger.info "Packaging additional file: #{f}"
FileUtils.cp(f, include_dir)
end
end
end
# This method creates the auto-generated Vagrantfile at the root of the
# box. This Vagrantfile contains the MAC address so that the user doesn't
# have to worry about it.
def create_vagrantfile
File.open(File.join(temp_path, "Vagrantfile"), "w") do |f|
f.write(TemplateRenderer.render("package_Vagrantfile", {
:base_mac => @runner.vm.network_adapters.first.mac_address
}))
end
end
def compress
logger.info "Packaging VM into #{tar_path}..."
File.open(tar_path, Platform.tar_file_options) do |tar|
Archive::Tar::Minitar::Output.open(tar) do |output|
begin
current_dir = FileUtils.pwd
copy_include_files
create_vagrantfile
FileUtils.cd(temp_path)
Dir.glob(File.join(".", "**", "*")).each do |entry|
Archive::Tar::Minitar.pack_file(entry, output)
end
ensure
FileUtils.cd(current_dir)
end
end
end
end
end
end
end
end

View File

@ -1,49 +0,0 @@
module Vagrant
module Actions
module VM
class Provision < Base
attr_reader :provisioner
def intialize(*args)
super
@provisioner = nil
end
def prepare
provisioner = @runner.env.config.vm.provisioner
if provisioner.nil?
logger.info("Provisioning not enabled, ignoring this step")
return
end
if provisioner.is_a?(Class)
@provisioner = provisioner.new(@runner)
raise ActionException.new(:provisioner_invalid_class) unless @provisioner.is_a?(Provisioners::Base)
elsif provisioner.is_a?(Symbol)
# We have a few hard coded provisioners for built-ins
mapping = {
:chef_solo => Provisioners::ChefSolo,
:chef_server => Provisioners::ChefServer
}
provisioner_klass = mapping[provisioner]
raise ActionException.new(:provisioner_unknown_type, :provisioner => provisioner.to_s) if provisioner_klass.nil?
@provisioner = provisioner_klass.new(@runner)
end
logger.info "Provisioning enabled with #{@provisioner.class}"
@provisioner.prepare
end
def execute!
if provisioner
logger.info "Beginning provisioning process..."
provisioner.provision!
end
end
end
end
end
end

View File

@ -1,17 +0,0 @@
module Vagrant
module Actions
module VM
class Reload < Base
def prepare
steps = [Customize, ForwardPorts, SharedFolders, Network, Boot]
steps.unshift(Halt) if @runner.vm.running?
steps << Provision if !@runner.env.config.vm.provisioner.nil?
steps.each do |action_klass|
@runner.add_action(action_klass)
end
end
end
end
end
end

View File

@ -1,16 +0,0 @@
module Vagrant
module Actions
module VM
class Resume < Base
def execute!
if !@runner.vm.saved?
raise ActionException.new(:vm_not_suspended)
end
logger.info "Resuming suspended VM..."
@runner.start
end
end
end
end
end

View File

@ -1,26 +0,0 @@
module Vagrant
module Actions
module VM
class Start < Base
def prepare
# Start is a "meta-action" so it really just queues up a bunch
# of other actions in its place:
steps = [Boot]
if !@runner.vm || !@runner.vm.saved?
steps.unshift([Customize, ForwardPorts, SharedFolders, Network])
steps << Provision if provision?
end
steps.flatten.each do |action_klass|
@runner.add_action(action_klass, options)
end
end
def provision?
enabled = options[:provision].nil? ? true : options[:provision]
!@runner.env.config.vm.provisioner.nil? && enabled
end
end
end
end
end

View File

@ -1,16 +0,0 @@
module Vagrant
module Actions
module VM
class Suspend < Base
def execute!
if !@runner.vm.running?
raise ActionException.new(:vm_not_running_for_suspend)
end
logger.info "Saving VM state and suspending execution..."
@runner.vm.save_state
end
end
end
end
end

View File

@ -1,53 +0,0 @@
module Vagrant
module Actions
module VM
class Up < Base
def prepare
# If the dotfile is not a file, raise error
if File.exist?(@runner.env.dotfile_path) && !File.file?(@runner.env.dotfile_path)
raise ActionException.new(:dotfile_error, :env => @runner.env)
end
# Up is a "meta-action" so it really just queues up a bunch
# of other actions in its place:
steps = [Import, Start]
steps.insert(0, MoveHardDrive) if @runner.env.config.vm.hd_location
steps.each do |action_klass|
@runner.add_action(action_klass, options)
end
end
def after_import
update_dotfile
setup_mac_address
check_guest_additions
end
def update_dotfile
logger.info "Persisting the VM UUID (#{@runner.uuid})..."
@runner.env.update_dotfile
end
def setup_mac_address
logger.info "Matching MAC addresses..."
@runner.vm.network_adapters.first.mac_address = @runner.env.config.vm.base_mac
@runner.vm.save
end
def check_guest_additions
# Use the raw interface for now, while the virtualbox gem
# doesn't support guest properties (due to cross platform issues)
version = @runner.vm.interface.get_guest_property_value("/VirtualBox/GuestAdd/Version")
if version.empty?
logger.error Translator.t(:vm_additions_not_detected)
elsif version != VirtualBox.version
logger.error Translator.t(:vm_additions_version_mismatch,
:guest_additions_version => version,
:virtualbox_version => VirtualBox.version)
end
end
end
end
end
end

View File

@ -40,7 +40,7 @@ module Vagrant
# box = Vagrant::Box.find("foo")
# box.destroy
#
class Box < Actions::Runner
class Box
# The name of the box.
attr_accessor :name
@ -133,12 +133,12 @@ module Vagrant
# method requires that `name` and `uri` be set. The logic of this method
# is kicked out to the {Actions::Box::Add add box} action.
def add
execute!(Actions::Box::Add)
env.actions.run(:box_add, { "box" => self })
end
# Beings the process of destroying this box.
def destroy
execute!(Actions::Box::Destroy)
env.actions.run(:box_remove, { "box" => self })
end
# Returns the directory to the location of this boxes content in the local

View File

@ -53,11 +53,6 @@ module Vagrant
end
def package_vm(vm)
if !vm.powered_off?
error_and_exit(:vm_power_off_to_package)
return # for tests
end
vm.package(options)
end

View File

@ -9,7 +9,7 @@ module Vagrant
def prepare(source_url)
if !::File.file?(source_url)
raise Actions::ActionException.new(:downloader_file_doesnt_exist, :source_url => source_url)
raise Action::ActionException.new(:downloader_file_doesnt_exist, :source_url => source_url)
end
end

View File

@ -21,6 +21,7 @@ module Vagrant
attr_reader :active_list
attr_reader :commands
attr_reader :logger
attr_reader :actions
#---------------------------------------------------------------
# Class Methods
@ -142,6 +143,7 @@ module Vagrant
load_vm!
load_active_list!
load_commands!
load_actions!
self
end
@ -301,6 +303,13 @@ module Vagrant
@commands = Command.new(self)
end
# Loads the instance of {Action} for this environment. This allows
# users of the instance to run action sequences in the context of
# this environment.
def load_actions!
@actions = Action.new(self)
end
#---------------------------------------------------------------
# Methods to manage VM
#---------------------------------------------------------------

View File

@ -7,19 +7,27 @@ module Vagrant
class Base
include Vagrant::Util
# The VM which this is being provisioned for
attr_reader :vm
# The environment which provisioner is running in. This is a
# {Vagrant::Action::Environment}
attr_reader :action_env
def initialize(vm)
@vm = vm
def initialize(env)
@action_env = env
end
# This method returns the environment which the provisioner is working
# on. This is also the environment of the VM. This method is provided
# as a simple helper since the environment is often used throughout the
# provisioner.
# Returns the actual {Vagrant::Environment} which this provisioner
# represents.
#
# @return [Vagrant::Environment]
def env
@vm.env
action_env.env
end
# Returns the VM which this provisioner is working on.
#
# @return [Vagrant::VM]
def vm
env.vm
end
# This method returns the environment's logger as a convenience

View File

@ -72,7 +72,7 @@ module Vagrant
Config.configures :chef, ChefConfig
def prepare
raise Actions::ActionException.new(:chef_base_invalid_provisioner)
action_env.error!(:chef_base_invalid_provisioner)
end
def verify_binary(binary)

View File

@ -5,13 +5,11 @@ module Vagrant
class ChefServer < Chef
def prepare
if env.config.chef.validation_key_path.nil?
raise Actions::ActionException.new(:chef_server_validation_key_required)
action_env.error!(:chef_server_validation_key_required)
elsif !File.file?(validation_key_path)
raise Actions::ActionException.new(:chef_server_validation_key_doesnt_exist)
end
if env.config.chef.chef_server_url.nil?
raise Actions::ActionException.new(:chef_server_url_required)
action_env.error!(:chef_server_validation_key_doesnt_exist)
elsif env.config.chef.chef_server_url.nil?
action_env.error!(:chef_server_url_required)
end
end

View File

@ -219,7 +219,7 @@ module Vagrant
}
}.merge(options || {})
raise Actions::ActionException.new(options[:error_key], options[:error_data])
raise Action::ActionException.new(options[:error_key], options[:error_data])
end
end
end

View File

@ -119,7 +119,7 @@ module Vagrant
break unless result
attempts += 1
raise Actions::ActionException.new(:vm_mount_fail) if attempts >= 10
raise Action::ActionException.new(:vm_mount_fail) if attempts >= 10
sleep sleeptime
end
end

View File

@ -1,5 +1,5 @@
module Vagrant
class VM < Actions::Runner
class VM
include Vagrant::Util
attr_reader :env
@ -94,43 +94,41 @@ module Vagrant
end
def package(options=nil)
add_action(Actions::VM::Export, options)
add_action(Actions::VM::Package, options)
execute!
env.actions.run(:package, options)
end
def up(options=nil)
execute!(Actions::VM::Up, options)
env.actions.run(:up, options)
end
def start
return if @vm.running?
execute!(Actions::VM::Start)
env.actions.run(:start)
end
def halt(options=nil)
execute!(Actions::VM::Halt, options)
env.actions.run(:halt, options)
end
def reload
execute!(Actions::VM::Reload)
env.actions.run(:reload)
end
def provision
execute!(Actions::VM::Provision)
env.actions.run(:provision)
end
def destroy
execute!(Actions::VM::Down)
env.actions.run(:destroy)
end
def suspend
execute!(Actions::VM::Suspend)
env.actions.run(:suspend)
end
def resume
execute!(Actions::VM::Resume)
env.actions.run(:resume)
end
def saved?

View File

@ -250,8 +250,6 @@
Failed to connect to VM! Failed to boot?
:vm_base_not_found: |-
The specified base VM "<%= name %>" was not found.
:vm_not_running: |-
VM is not running! Nothing to shut down!
:vm_not_running_for_suspend: |-
The vagrant virtual environment you are trying to suspend must be running to be suspended.
:vm_not_suspended: |-

View File

@ -94,30 +94,10 @@ class Test::Unit::TestCase
vm
end
# Sets up the mocks and instantiates an action for testing
def mock_action(action_klass, *args)
vm = mock("vboxvm")
mock_vm = mock("vm")
action = action_klass.new(mock_vm, *args)
stub_default_action_dependecies(action)
mock_vm.stubs(:name).returns("foo")
mock_vm.stubs(:vm).returns(vm)
mock_vm.stubs(:vm=)
mock_vm.stubs(:invoke_callback)
mock_vm.stubs(:invoke_around_callback).yields
mock_vm.stubs(:actions).returns([action])
mock_vm.stubs(:env).returns(mock_environment)
mock_vm.env.stubs(:logger).returns(quiet_logger("mock"))
mock_ssh = Vagrant::SSH.new(mock_vm.env)
mock_ssh.stubs(:execute)
mock_vm.stubs(:ssh).returns(mock_ssh)
vm.stubs(:env).returns(mock_vm.env)
[mock_vm, vm, action]
def mock_action_data
app = lambda { |env| }
env = Vagrant::Action::Environment.new(mock_environment)
[app, env]
end
# Returns a resource logger which is safe for tests

View File

@ -0,0 +1,30 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class DestroyBoxActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::Box::Destroy
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@env["box"] = Vagrant::Box.new(mock_environment, "foo")
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
setup do
@env.logger.stubs(:info)
end
should "delete the box directory" do
seq = sequence("seq")
FileUtils.expects(:rm_rf).with(@env["box"].directory).in_sequence(seq)
@app.expects(:call).with(@env).once.in_sequence(seq)
@instance.call(@env)
end
end
end

View File

@ -0,0 +1,141 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class DownloadBoxActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::Box::Download
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@env["box"] = Vagrant::Box.new(mock_environment, "foo")
@env["box"].uri = "http://google.com"
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
end
context "initializing" do
should "initialize download classes" do
@klass.new(@app, @env)
assert_equal [Vagrant::Downloaders::HTTP, Vagrant::Downloaders::File], @env["download.classes"]
end
end
context "with an instance" do
setup do
@instance = @klass.new(@app, @env)
end
context "calling" do
should "call the proper methods in sequence" do
seq = sequence("seq")
@instance.expects(:instantiate_downloader).in_sequence(seq).returns(true)
@instance.expects(:download).in_sequence(seq)
@app.expects(:call).with(@env).in_sequence(seq)
@instance.expects(:cleanup).in_sequence(seq)
@instance.call(@env)
end
should "halt the chain if downloader instantiation fails" do
seq = sequence("seq")
@env.error!(:foo)
@instance.expects(:instantiate_downloader).in_sequence(seq).returns(false)
@instance.expects(:download).never
@app.expects(:call).with(@env).never
@instance.expects(:cleanup).never
@instance.call(@env)
end
end
context "instantiating downloader" do
should "instantiate the proper class" do
instance = mock("instance")
Vagrant::Downloaders::HTTP.expects(:new).with(@env["box"].uri).returns(instance)
instance.expects(:prepare).with(@env["box"].uri).once
assert @instance.instantiate_downloader
end
should "error environment if URI is invalid for any downloaders" do
@env["box"].uri = "foobar"
assert !@instance.instantiate_downloader
assert @env.error?
assert_equal :box_download_unknown_type, @env.error.first
end
end
context "downloading" do
setup do
@path = "foo"
@tempfile = mock("tempfile")
@tempfile.stubs(:path).returns(@path)
@instance.stubs(:with_tempfile).yields(@tempfile)
@instance.stubs(:download_to)
end
should "make a tempfile and copy the URI contents to it" do
@instance.expects(:with_tempfile).yields(@tempfile)
@instance.expects(:download_to).with(@tempfile)
@instance.download
end
should "save the tempfile path" do
@instance.download
assert @env.has_key?("download.temp_path")
assert_equal @tempfile.path, @env["download.temp_path"]
assert_equal @tempfile.path, @instance.temp_path
end
end
context "tempfile" do
should "create a tempfile in the vagrant tmp directory" do
File.expects(:open).with { |name, bitmask|
name =~ /#{Vagrant::Action::Box::Download::BASENAME}/ && name =~ /#{@env.env.tmp_path}/
}.once
@instance.with_tempfile
end
should "yield the tempfile object" do
@tempfile = mock("tempfile")
File.expects(:open).yields(@tempfile)
@instance.with_tempfile do |otherfile|
assert @tempfile.equal?(otherfile)
end
end
end
context "cleaning up" do
setup do
@temp_path = "foo"
@instance.stubs(:temp_path).returns(@temp_path)
File.stubs(:exist?).returns(true)
end
should "delete the temporary file if it exists" do
File.expects(:unlink).with(@temp_path).once
@instance.cleanup
end
should "not delete anything if it doesn't exist" do
File.stubs(:exist?).returns(false)
File.expects(:unlink).never
@instance.cleanup
end
end
context "downloading to" do
setup do
@downloader = mock("downloader")
@instance.instance_variable_set(:@downloader, @downloader)
end
should "call download! on the download with the URI and tempfile" do
tempfile = "foo"
@downloader.expects(:download!).with(@env["box"].uri, tempfile)
@instance.download_to(tempfile)
end
end
end
end

View File

@ -0,0 +1,103 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class UnpackageBoxActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::Box::Unpackage
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@env["box"] = Vagrant::Box.new(mock_environment, "foo")
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
should "call the proper chain" do
seq = sequence("sequence")
@instance.expects(:setup_box_directory).in_sequence(seq).returns(true)
@instance.expects(:decompress).in_sequence(seq)
@app.expects(:call).with(@env)
@instance.expects(:cleanup).never
@instance.call(@env)
end
should "halt the chain if setting up the box directory fails" do
@instance.expects(:setup_box_directory).returns(false)
@instance.expects(:decompress).never
@app.expects(:call).never
@instance.expects(:cleanup).never
@instance.call(@env)
end
should "cleanup if there was an error" do
@env.error!(:foo)
seq = sequence("sequence")
@instance.expects(:setup_box_directory).in_sequence(seq).returns(true)
@instance.expects(:decompress).in_sequence(seq)
@app.expects(:call).with(@env)
@instance.expects(:cleanup).once
@instance.call(@env)
end
end
context "cleaning up" do
setup do
@instance.stubs(:box_directory).returns("foo")
File.stubs(:directory?).returns(false)
FileUtils.stubs(:rm_rf)
end
should "do nothing if not a directory" do
FileUtils.expects(:rm_rf).never
@instance.cleanup
end
should "remove the directory if exists" do
File.expects(:directory?).with(@instance.box_directory).once.returns(true)
FileUtils.expects(:rm_rf).with(@instance.box_directory).once
@instance.cleanup
end
end
context "setting up the box directory" do
setup do
File.stubs(:directory?).returns(false)
FileUtils.stubs(:mkdir_p)
end
should "error the environment if the box already exists" do
File.expects(:directory?).returns(true)
assert !@instance.setup_box_directory
assert @env.error?
assert_equal :box_already_exists, @env.error.first
end
should "create the directory" do
FileUtils.expects(:mkdir_p).with(@env["box"].directory).once
@instance.setup_box_directory
end
end
context "decompressing" do
setup do
@env["download.temp_path"] = "bar"
Dir.stubs(:chdir).yields
end
should "change to the box directory" do
Dir.expects(:chdir).with(@env["box"].directory)
@instance.decompress
end
should "open the tar file within the new directory, and extract it all" do
Archive::Tar::Minitar.expects(:unpack).with(@env["download.temp_path"], @env["box"].directory).once
@instance.decompress
end
end
end

View File

@ -0,0 +1,39 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class VerifyBoxActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::Box::Verify
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@env["box"] = Vagrant::Box.new(mock_environment, "foo")
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
setup do
@env.logger.stubs(:info)
end
should "continue fine if verification succeeds" do
seq = sequence("seq")
VirtualBox::Appliance.expects(:new).with(@env["box"].ovf_file).in_sequence(seq)
@app.expects(:call).with(@env).once.in_sequence(seq)
@instance.call(@env)
assert !@env.error?
end
should "halt chain if verification fails" do
VirtualBox::Appliance.expects(:new).with(@env["box"].ovf_file).raises(Exception)
@app.expects(:call).with(@env).never
@instance.call(@env)
assert @env.error?
assert_equal :box_verification_failed, @env.error.first
end
end
end

View File

@ -0,0 +1,207 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ActionBuilderTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::Builder
end
context "initializing" do
should "setup empty middleware stack" do
builder = @klass.new
assert builder.stack.empty?
end
should "take block to setup stack" do
builder = @klass.new do
use Hash
use lambda { |i| i }
end
assert !builder.stack.empty?
assert_equal 2, builder.stack.length
end
end
context "with an instance" do
setup do
@instance = @klass.new
end
context "adding to the stack" do
should "return self" do
assert @instance.equal?(@instance.use(1))
end
should "add to the end" do
@instance.use 1
@instance.use 2
assert_equal [2, [], nil], @instance.stack.last
end
should "merge in other builder's stack" do
other = @klass.new do
use 2
use 3
end
@instance.use 1
@instance.use other
assert_equal 3, @instance.stack.length
end
end
context "flatten" do
should "return the flattened format of the builder" do
env = Vagrant::Action::Environment.new(nil)
env.expects(:foo).once
func = lambda { |x| x.foo }
@instance.use func
proc = @instance.flatten
assert proc.respond_to?(:call)
proc.call(env)
end
end
context "inserting" do
setup do
@instance.use "1"
@instance.use "2"
end
should "insert at the proper numeric index" do
@instance.insert(1, "3")
assert_equal "3", @instance.stack[1].first
end
should "insert next to the proper object if given" do
@instance.insert("2", "3")
assert_equal "3", @instance.stack[1].first
end
should "be able to call insert_before as well" do
@instance.insert_before("1", "0")
assert_equal "0", @instance.stack.first.first
end
should "be able to insert_after" do
@instance.insert_after("1", "0")
assert_equal "0", @instance.stack[1].first
end
should "be able to insert_after using numeric index" do
@instance.insert_after(1, "0")
assert_equal "0", @instance.stack[2].first
end
should "raise an exception if invalid index" do
assert_raises(RuntimeError) {
@instance.insert_after("15", "0")
}
end
end
context "swapping" do
setup do
@instance.use "1"
@instance.use "2"
end
should "be able to swap using the object" do
@instance.swap "1", "3"
assert_equal "3", @instance.stack.first.first
end
should "be able to swap using the index" do
@instance.swap 0, "3"
assert_equal "3", @instance.stack.first.first
end
end
context "deleting" do
setup do
@instance.use "1"
end
should "delete the proper object" do
@instance.delete("1")
assert @instance.stack.empty?
end
should "delete by index if given" do
@instance.delete(0)
assert @instance.stack.empty?
end
end
context "getting an index of an object" do
should "return the proper index if it exists" do
@instance.use 1
@instance.use 2
@instance.use 3
assert_equal 1, @instance.index(2)
end
end
context "converting to an app" do
teardown do
Vagrant::Action.actions.clear
end
should "preprend error halt to the chain" do
result = mock("result")
env = {:a => :b}
middleware = mock("middleware")
middleware.stubs(:is_a?).with(Class).returns(true)
middleware.expects(:new).with(anything, env).returns(result)
@instance.use middleware
result = @instance.to_app(env)
assert result.kind_of?(Vagrant::Action::ErrorHalt)
end
should "make non-classes lambdas" do
env = Vagrant::Action::Environment.new(nil)
env.expects(:foo).once
func = lambda { |x| x.foo }
@instance.use func
@instance.to_app(env).call(env)
end
should "raise exception if given invalid middleware" do
@instance.use 7
assert_raises(RuntimeError) {
@instance.to_app(nil)
}
end
end
context "calling" do
def mock_middleware
middleware = Class.new do
def initialize(app, env)
@app = app
end
def call(env)
@app.call(env)
end
end
end
should "convert to an app then call with the env" do
mw = mock_middleware
mw.any_instance.expects(:call).with() do |env|
assert env.has_key?(:key)
true
end
env = Vagrant::Action::Environment.new(nil)
env[:key] = :value
@instance.use(mw)
@instance.call(env)
end
end
end
end

View File

@ -0,0 +1,31 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ActionEnvironmentTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::Environment
@instance = @klass.new(mock_environment)
end
should "default values to those on the env" do
@instance.env.stubs(:key).returns("value")
assert_equal "value", @instance["key"]
end
should "setup the logger" do
assert_equal @instance.env.logger, @instance.logger
end
should "not be erroneous initially" do
assert !@instance.error?
end
should "mark as erroneous" do
@instance.error!(:key)
assert_equal [:key, {}], @instance.error
end
should "properly report erroneous" do
@instance.error!(:key)
assert @instance.error?
end
end

View File

@ -0,0 +1,21 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ErrorHaltTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::ErrorHalt
@app, @env = mock_action_data
@instance = @klass.new(@app, @env)
end
should "continue the chain if no error" do
assert !@env.error?
@app.expects(:call).with(@env).once
@instance.call(@env)
end
should "halt the chain if an error occured" do
@env.error!(:foo)
@app.expects(:call).never
@instance.call(@env)
end
end

View File

@ -0,0 +1,30 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ExceptionCatcherTest < Test::Unit::TestCase
setup do
@klass = Class.new
@klass.send(:include, Vagrant::Action::ExceptionCatcher)
@env = Vagrant::Action::Environment.new(mock_environment)
@instance = @klass.new
end
should "run block and return result if no exception" do
result = @instance.catch_action_exception(@env) do
true
end
assert result
assert !@env.error?
end
should "run block and return false with error environment on exception" do
result = @instance.catch_action_exception(@env) do
raise Vagrant::Action::ActionException.new(:foo, :foo => :bar)
end
assert !result
assert @env.error?
assert_equal :foo, @env.error.first
end
end

View File

@ -0,0 +1,58 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class BootVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Boot
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:ssh).returns(mock("ssh"))
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
should "run the proper methods on success" do
boot_seq = sequence("boot_seq")
@instance.expects(:boot).in_sequence(boot_seq)
@instance.expects(:wait_for_boot).returns(true).in_sequence(boot_seq)
@app.expects(:call).with(@env).once.in_sequence(boot_seq)
@instance.call(@env)
end
should "error and halt chain if boot failed" do
boot_seq = sequence("boot_seq")
@instance.expects(:boot).in_sequence(boot_seq)
@instance.expects(:wait_for_boot).returns(false).in_sequence(boot_seq)
@app.expects(:call).never
@instance.call(@env)
end
end
context "booting" do
should "start the VM in specified mode" do
mode = mock("boot_mode")
@env.env.config.vm.boot_mode = mode
@internal_vm.expects(:start).with(mode).once
@instance.boot
end
end
context "waiting for boot" do
should "repeatedly ping the SSH port and return false with no response" do
seq = sequence('pings')
@vm.ssh.expects(:up?).times(@env.env.config.ssh.max_tries.to_i - 1).returns(false).in_sequence(seq)
@vm.ssh.expects(:up?).once.returns(true).in_sequence(seq)
assert @instance.wait_for_boot(0)
end
should "ping the max number of times then just return" do
@vm.ssh.expects(:up?).times(@env.env.config.ssh.max_tries.to_i).returns(false)
assert !@instance.wait_for_boot(0)
end
end
end

View File

@ -0,0 +1,9 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class CheckGuestAdditionsVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::CheckGuestAdditions
end
# TODO: This isn't tested.
end

View File

@ -0,0 +1,29 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class CustomizeVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Customize
@app, @env = mock_action_data
@instance = @klass.new(@app, @env)
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
end
should "not run anything if no customize blocks exist" do
@internal_vm.expects(:save).never
@app.expects(:call).with(@env).once
@instance.call(@env)
end
should "run the VM customization procs then save the VM" do
@env.env.config.vm.customize { |vm| }
@env.env.config.vm.expects(:run_procs!).with(@internal_vm)
@internal_vm.expects(:save).once
@app.expects(:call).with(@env).once
@instance.call(@env)
end
end

View File

@ -0,0 +1,26 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class DestroyVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Destroy
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "destroying the VM" do
should "destroy VM and attached images" do
@internal_vm.expects(:destroy).with(:destroy_medium => :delete).once
@env["vm"].expects(:vm=).with(nil).once
@env.env.expects(:update_dotfile).once
@app.expects(:call).with(@env).once
@instance.call(@env)
end
end
end

View File

@ -0,0 +1,46 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class DestroyUnusedNetworkInterfacesVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::DestroyUnusedNetworkInterfaces
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
setup do
@network_adapters = []
@internal_vm.stubs(:network_adapters).returns(@network_adapters)
end
def stub_interface(length=5)
interface = mock("interface")
adapter = mock("adapter")
adapter.stubs(:host_interface_object).returns(interface)
interface.stubs(:attached_vms).returns(Array.new(length))
@network_adapters << adapter
interface
end
should "destroy only the unused network interfaces" do
stub_interface(5)
stub_interface(7)
results = [stub_interface(1), stub_interface(1)]
results.each do |result|
result.expects(:destroy).once
end
@app.expects(:call).with(@env).once
@instance.call(@env)
end
end
end

View File

@ -0,0 +1,88 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class ExportVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Export
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
setup do
@internal_vm.stubs(:powered_off?).returns(true)
end
should "call the proper methods then continue chain" do
seq = sequence("seq")
@instance.expects(:setup_temp_dir).in_sequence(seq)
@instance.expects(:export).in_sequence(seq)
@app.expects(:call).with(@env).in_sequence(seq)
@instance.call(@env)
end
should "halt the chain if not powered off" do
@internal_vm.stubs(:powered_off?).returns(false)
@instance.expects(:setup_temp_dir).never
@instance.expects(:export).never
@app.expects(:call).with(@env).never
@instance.call(@env)
assert @env.error?
assert_equal :vm_power_off_to_package, @env.error.first
end
end
context "setting up the temporary directory" do
setup do
@time_now = Time.now.to_i.to_s
Time.stubs(:now).returns(@time_now)
@tmp_path = "foo"
@env.env.stubs(:tmp_path).returns(@tmp_path)
@temp_dir = File.join(@env.env.tmp_path, @time_now)
FileUtils.stubs(:mkpath)
end
should "create the temporary directory using the current time" do
FileUtils.expects(:mkpath).with(@temp_dir).once
@instance.setup_temp_dir
end
should "set to the environment" do
@instance.setup_temp_dir
assert_equal @temp_dir, @env["export.temp_dir"]
end
end
context "exporting" do
setup do
@ovf_path = mock("ovf_path")
@instance.stubs(:ovf_path).returns(@ovf_path)
end
should "call export on the runner with the ovf path" do
@internal_vm.expects(:export).with(@ovf_path).once
@instance.export
end
end
context "path to OVF file" do
setup do
@temp_dir = "foo"
@env["export.temp_dir"] = @temp_dir
end
should "be the temporary directory joined with the OVF filename" do
assert_equal File.join(@temp_dir, @env.env.config.vm.box_ovf), @instance.ovf_path
end
end
end

View File

@ -0,0 +1,270 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class ForwardPortsVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::ForwardPorts
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:name).returns("foo")
@env["vm"] = @vm
end
context "initializing" do
should "call proper methods" do
@klass.any_instance.expects(:external_collision_check)
@klass.new(@app, @env)
end
end
context "checking for colliding external ports" do
setup do
@env.env.config.vm.forwarded_ports.clear
@env.env.config.vm.forward_port("ssh", 22, 2222)
@used_ports = []
@klass.any_instance.stubs(:used_ports).returns(@used_ports)
@klass.any_instance.stubs(:handle_collision)
end
should "not raise any errors if no forwarded ports collide" do
@used_ports << "80"
@klass.new(@app, @env)
assert !@env.error?
end
should "handle collision if it happens" do
@used_ports << "2222"
@klass.any_instance.expects(:handle_collision).with("ssh", anything, anything).once
@klass.new(@app, @env)
assert !@env.error?
end
end
context "with instance" do
setup do
@klass.any_instance.stubs(:external_collision_check)
@instance = @klass.new(@app, @env)
end
context "handling collisions" do
setup do
@name = :foo
@options = {
:hostport => 0,
:auto => true
}
@used_ports = [1,2,3]
@env.env.config.vm.auto_port_range = (1..5)
end
should "error if auto forwarding is disabled" do
@options[:auto] = false
@instance.handle_collision(@name, @options, @used_ports)
assert @env.error?
assert_equal :vm_port_collision, @env.error.first
end
should "set the host port to the first available port" do
assert_equal 0, @options[:hostport]
@instance.handle_collision(@name, @options, @used_ports)
assert_equal 4, @options[:hostport]
end
should "add the newly used port to the list of used ports" do
assert !@used_ports.include?(4)
@instance.handle_collision(@name, @options, @used_ports)
assert @used_ports.include?(4)
end
should "not use a host port which is being forwarded later" do
@env.env.config.vm.forward_port("http", 80, 4)
assert_equal 0, @options[:hostport]
@instance.handle_collision(@name, @options, @used_ports)
assert_equal 5, @options[:hostport]
end
should "raise an exception if there are no auto ports available" do
@env.env.config.vm.auto_port_range = (1..3)
@instance.handle_collision(@name, @options, @used_ports)
assert @env.error?
assert_equal :vm_port_auto_empty, @env.error.first
end
end
context "calling" do
should "clear all previous ports and forward new ports" do
exec_seq = sequence("exec_seq")
@instance.expects(:clear).once.in_sequence(exec_seq)
@instance.expects(:forward_ports).once.in_sequence(exec_seq)
@app.expects(:call).once.with(@env).in_sequence(exec_seq)
@instance.call(@env)
end
end
context "forwarding ports" do
setup do
@internal_vm = mock("internal_vm")
@vm.stubs(:vm).returns(@internal_vm)
end
should "create a port forwarding for the VM" do
forwarded_ports = mock("forwarded_ports")
network_adapter = mock("network_adapter")
@internal_vm.stubs(:network_adapters).returns([network_adapter])
network_adapter.expects(:attachment_type).returns(:nat)
@instance.expects(:forward_port).once
@internal_vm.expects(:save).once
@vm.expects(:reload!).once
@instance.forward_ports
end
should "not port forward for non NAT interfaces" do
forwarded_ports = mock("forwarded_ports")
network_adapter = mock("network_adapter")
@internal_vm.expects(:network_adapters).returns([network_adapter])
network_adapter.expects(:attachment_type).returns(:host_only)
@internal_vm.expects(:save).once
@vm.expects(:reload!).once
@instance.forward_ports
end
end
context "clearing forwarded ports" do
setup do
@instance.stubs(:used_ports).returns([:a])
@instance.stubs(:clear_ports)
end
should "call destroy on all forwarded ports" do
@instance.expects(:clear_ports).once
@vm.expects(:reload!)
@instance.clear
end
should "do nothing if there are no forwarded ports" do
@instance.stubs(:used_ports).returns([])
@vm.expects(:reload!).never
@instance.clear
end
end
context "getting list of used ports" do
setup do
@vms = []
VirtualBox::VM.stubs(:all).returns(@vms)
VirtualBox.stubs(:version).returns("3.1.0")
@vm.stubs(:uuid).returns(:bar)
end
def mock_vm(options={})
options = {
:running? => true,
:uuid => :foo
}.merge(options)
vm = mock("vm")
options.each do |k,v|
vm.stubs(k).returns(v)
end
vm
end
def mock_fp(hostport)
fp = mock("fp")
fp.stubs(:hostport).returns(hostport.to_s)
fp
end
should "ignore VMs which aren't running" do
@vms << mock_vm(:running? => false)
@vms[0].expects(:forwarded_ports).never
@instance.used_ports
end
should "ignore VMs of the same uuid" do
@vms << mock_vm(:uuid => @vm.uuid)
@vms[0].expects(:forwarded_ports).never
@instance.used_ports
end
should "return the forwarded ports for VB 3.2.x" do
VirtualBox.stubs(:version).returns("3.2.4")
fps = [mock_fp(2222), mock_fp(80)]
na = mock("na")
ne = mock("ne")
na.stubs(:nat_driver).returns(ne)
ne.stubs(:forwarded_ports).returns(fps)
@vms << mock_vm(:network_adapters => [na])
assert_equal %W[2222 80], @instance.used_ports
end
end
context "clearing ports" do
def mock_fp
fp = mock("fp")
fp.expects(:destroy).once
fp
end
setup do
VirtualBox.stubs(:version).returns("3.2.8")
@adapters = []
@internal_vm = mock("internal_vm")
@internal_vm.stubs(:network_adapters).returns(@adapters)
@vm.stubs(:vm).returns(@internal_vm)
end
def mock_adapter
na = mock("adapter")
engine = mock("engine")
engine.stubs(:forwarded_ports).returns([mock_fp])
na.stubs(:nat_driver).returns(engine)
na
end
should "destroy each forwarded port" do
@adapters << mock_adapter
@adapters << mock_adapter
@instance.clear_ports
end
end
context "forwarding ports implementation" do
setup do
VirtualBox.stubs(:version).returns("3.2.8")
@internal_vm = mock("internal_vm")
@vm.stubs(:vm).returns(@internal_vm)
end
should "forward ports" do
name, opts = @env.env.config.vm.forwarded_ports.first
adapters = []
adapter = mock("adapter")
engine = mock("engine")
fps = mock("forwarded ports")
adapter.stubs(:nat_driver).returns(engine)
engine.stubs(:forwarded_ports).returns(fps)
fps.expects(:<<).with do |port|
assert_equal name, port.name
assert_equal opts[:hostport], port.hostport
assert_equal opts[:guestport], port.guestport
true
end
adapters[opts[:adapter]] = adapter
@internal_vm.stubs(:network_adapters).returns(adapters)
@instance.forward_port(name, opts)
end
end
end
end

View File

@ -0,0 +1,62 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class HaltVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Halt
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:name).returns("foo")
@vm.stubs(:ssh).returns(mock("ssh"))
@vm.stubs(:system).returns(mock("system"))
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
setup do
@internal_vm.stubs(:running?).returns(true)
@vm.system.stubs(:halt)
@internal_vm.stubs(:stop)
@internal_vm.stubs(:state).returns(:powered_off)
end
should "do nothing if VM not running" do
@internal_vm.stubs(:running?).returns(false)
@vm.system.expects(:halt).never
@internal_vm.expects(:stop).never
@app.expects(:call).once
@instance.call(@env)
end
should "halt with the system and NOT force VM to stop if powered off" do
@internal_vm.expects(:state).with(true).returns(:powered_off)
@vm.system.expects(:halt).once
@internal_vm.expects(:stop).never
@app.expects(:call).once
@instance.call(@env)
end
should "halt with the system and force VM to stop if NOT powered off" do
@internal_vm.expects(:state).with(true).returns(:running)
@vm.system.expects(:halt).once
@internal_vm.expects(:stop).once
@app.expects(:call).once
@instance.call(@env)
end
should "not call halt on the system if forcing" do
@env["force"] = true
@vm.system.expects(:halt).never
@instance.call(@env)
end
end
end

View File

@ -0,0 +1,39 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class ImportVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Import
@app, @env = mock_action_data
@instance = @klass.new(@app, @env)
ovf_file = "foo"
@box = mock("box")
@box.stubs(:ovf_file).returns(ovf_file)
@env.env.stubs(:box).returns(@box)
@env.env.vm = Vagrant::VM.new
VirtualBox::VM.stubs(:import)
end
should "call import on VirtualBox with proper base" do
VirtualBox::VM.expects(:import).once.with(@env.env.box.ovf_file).returns("foo")
@instance.call(@env)
end
should "call next in chain on success and set VM" do
vm = mock("vm")
VirtualBox::VM.stubs(:import).returns(vm)
@app.expects(:call).with(@env).once
@instance.call(@env)
assert_equal vm, @env["vm"].vm
end
should "mark environment erroneous and not continue chain on failure" do
@app.expects(:call).never
@instance.call(@env)
assert @env.error?
end
end

View File

@ -0,0 +1,28 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class MatchMACAddressVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::MatchMACAddress
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
should "match the mac addresses" do
nic = mock("nic")
nic.expects(:mac_address=).once
update_seq = sequence("update_seq")
@internal_vm.expects(:network_adapters).returns([nic]).once.in_sequence(update_seq)
@internal_vm.expects(:save).once.in_sequence(update_seq)
@app.expects(:call).with(@env).once.in_sequence(update_seq)
@instance.call(@env)
end
end

View File

@ -0,0 +1,246 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class NetworkVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Network
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:name).returns("foo")
@vm.stubs(:ssh).returns(mock("ssh"))
@vm.stubs(:system).returns(mock("system"))
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
end
context "initializing" do
should "verify no bridge collisions for each network enabled" do
@env.env.config.vm.network("foo")
@klass.any_instance.expects(:verify_no_bridge_collision).once.with() do |options|
assert_equal "foo", options[:ip]
true
end
@klass.new(@app, @env)
end
end
context "with an instance" do
setup do
@klass.any_instance.stubs(:verify_no_bridge_collision)
@instance = @klass.new(@app, @env)
@interfaces = []
VirtualBox::Global.global.host.stubs(:network_interfaces).returns(@interfaces)
end
def mock_interface(options=nil)
options = {
:interface_type => :host_only,
:name => "foo"
}.merge(options || {})
interface = mock("interface")
options.each do |k,v|
interface.stubs(k).returns(v)
end
@interfaces << interface
interface
end
context "calling" do
setup do
@env.env.config.vm.network("foo")
@instance.stubs(:enable_network?).returns(false)
end
should "do nothing if network should not be enabled" do
@instance.expects(:assign_network).never
@app.expects(:call).with(@env).once
@vm.system.expects(:prepare_host_only_network).never
@vm.system.expects(:enable_host_only_network).never
@instance.call(@env)
end
should "assign and enable the network if networking enabled" do
@instance.stubs(:enable_network?).returns(true)
run_seq = sequence("run")
@instance.expects(:assign_network).once.in_sequence(run_seq)
@app.expects(:call).with(@env).once.in_sequence(run_seq)
@vm.system.expects(:prepare_host_only_network).once.in_sequence(run_seq)
@vm.system.expects(:enable_host_only_network).once.in_sequence(run_seq)
@instance.call(@env)
end
end
context "checking if network is enabled" do
should "return true if the network options are set" do
@env.env.config.vm.network("foo")
assert @instance.enable_network?
end
should "return false if the network was not set" do
assert !@instance.enable_network?
end
end
context "assigning the network" do
setup do
@network_name = "foo"
@instance.stubs(:network_name).returns(@network_name)
@network_adapters = []
@internal_vm.stubs(:network_adapters).returns(@network_adapters)
@options = {
:ip => "foo",
:adapter => 7
}
@env.env.config.vm.network(@options[:ip], @options)
end
should "setup the specified network adapter" do
adapter = mock("adapter")
@network_adapters[@options[:adapter]] = adapter
adapter.expects(:enabled=).with(true).once
adapter.expects(:attachment_type=).with(:host_only).once
adapter.expects(:host_interface=).with(@network_name).once
adapter.expects(:save).once
@instance.assign_network
end
end
context "network name" do
setup do
@instance.stubs(:matching_network?).returns(false)
@options = { :ip => :foo, :netmask => :bar, :name => nil }
end
should "return the network which matches" do
result = mock("result")
interface = mock_interface(:name => result)
@instance.expects(:matching_network?).with(interface, @options).returns(true)
assert_equal result, @instance.network_name(@options)
end
should "ignore non-host only interfaces" do
@options[:name] = "foo"
mock_interface(:name => @options[:name],
:interface_type => :bridged)
@instance.network_name(@options)
assert @env.error?
end
should "return the network which matches the name if given" do
@options[:name] = "foo"
interface = mock_interface(:name => @options[:name])
assert_equal @options[:name], @instance.network_name(@options)
end
should "error and exit if the given network name is not found" do
@options[:name] = "foo"
@interfaces.expects(:create).never
@instance.network_name(@options)
assert @env.error?
assert_equal :network_not_found, @env.error.first
end
should "create a network for the IP and netmask" do
result = mock("result")
network_ip = :foo
interface = mock_interface(:name => result)
interface.expects(:enable_static).with(network_ip, @options[:netmask])
@interfaces.expects(:create).returns(interface)
@instance.expects(:network_ip).with(@options[:ip], @options[:netmask]).once.returns(network_ip)
assert_equal result, @instance.network_name(@options)
end
end
context "checking for a matching network" do
setup do
@interface = mock("interface")
@interface.stubs(:network_mask).returns("foo")
@interface.stubs(:ip_address).returns("192.168.0.1")
@options = {
:netmask => "foo",
:ip => "baz"
}
end
should "return false if the netmasks don't match" do
@options[:netmask] = "bar"
assert @interface.network_mask != @options[:netmask] # sanity
assert !@instance.matching_network?(@interface, @options)
end
should "return true if the netmasks yield the same IP" do
tests = [["255.255.255.0", "192.168.0.1", "192.168.0.45"],
["255.255.0.0", "192.168.45.1", "192.168.28.7"]]
tests.each do |netmask, interface_ip, guest_ip|
@options[:netmask] = netmask
@options[:ip] = guest_ip
@interface.stubs(:network_mask).returns(netmask)
@interface.stubs(:ip_address).returns(interface_ip)
assert @instance.matching_network?(@interface, @options)
end
end
end
context "applying the netmask" do
should "return the proper result" do
tests = {
["192.168.0.1","255.255.255.0"] => [192,168,0,0],
["192.168.45.10","255.255.255.0"] => [192,168,45,0]
}
tests.each do |k,v|
assert_equal v, @instance.apply_netmask(*k)
end
end
end
context "splitting an IP" do
should "return the proper result" do
tests = {
"192.168.0.1" => [192,168,0,1]
}
tests.each do |k,v|
assert_equal v, @instance.split_ip(k)
end
end
end
context "network IP" do
should "return the proper result" do
tests = {
["192.168.0.45", "255.255.255.0"] => "192.168.0.1"
}
tests.each do |args, result|
assert_equal result, @instance.network_ip(*args)
end
end
end
end
end

View File

@ -0,0 +1,227 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class PackageVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Package
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
end
context "initializing" do
setup do
@tar_path = "foo"
File.stubs(:exist?).returns(false)
@klass.any_instance.stubs(:tar_path).returns(@tar_path)
end
should "initialize fine" do
@klass.new(@app, @env)
assert !@env.error?
end
should "error the environment if the output file exists" do
File.stubs(:exist?).with(@tar_path).returns(true)
@klass.new(@app, @env)
assert @env.error?
assert_equal :box_file_exists, @env.error.first
end
should "set the output path to 'package' by default" do
@klass.new(@app, @env)
assert_equal "package", @env["package.output"]
end
should "not set the output path if it is already set" do
@env["package.output"] = "foo"
@klass.new(@app, @env)
assert_equal "foo", @env["package.output"]
end
should "set the included files to empty by default" do
@klass.new(@app, @env)
assert_equal [], @env["package.include"]
end
should "not set the output path if it is already set" do
@env["package.include"] = "foo"
@klass.new(@app, @env)
assert_equal "foo", @env["package.include"]
end
end
context "with an instance" do
setup do
File.stubs(:exist?).returns(false)
@instance = @klass.new(@app, @env)
@env["export.temp_dir"] = "foo"
end
context "calling" do
should "call the proper methods then continue chain" do
seq = sequence("seq")
@instance.expects(:verify_included_files).in_sequence(seq).returns(true)
@instance.expects(:compress).in_sequence(seq)
@app.expects(:call).with(@env).in_sequence(seq)
@instance.call(@env)
end
should "halt the chain if verify failed" do
@instance.expects(:verify_included_files).returns(false)
@instance.expects(:compress).never
@app.expects(:call).never
@instance.call(@env)
end
should "halt the chain if export didn't run" do
@env["export.temp_dir"] = nil
@app.expects(:call).never
@instance.call(@env)
assert @env.error?
assert_equal :package_requires_export, @env.error.first
end
end
context "verifying included files" do
setup do
@env["package.include"] = ["foo"]
File.stubs(:exist?).returns(true)
end
should "error if included file is not found" do
File.expects(:exist?).with("foo").returns(false)
assert !@instance.verify_included_files
assert @env.error?
assert_equal :package_include_file_doesnt_exist, @env.error.first
end
should "return true if all exist" do
assert @instance.verify_included_files
assert !@env.error?
end
end
context "copying include files" do
setup do
@env["package.include"] = []
end
should "do nothing if no include files are specified" do
assert @env["package.include"].empty?
FileUtils.expects(:mkdir_p).never
FileUtils.expects(:cp).never
@instance.copy_include_files
end
should "create the include directory and copy files to it" do
include_dir = File.join(@env["export.temp_dir"], "include")
copy_seq = sequence("copy_seq")
FileUtils.expects(:mkdir_p).with(include_dir).once.in_sequence(copy_seq)
5.times do |i|
file = mock("f#{i}")
@env["package.include"] << file
FileUtils.expects(:cp).with(file, include_dir).in_sequence(copy_seq)
end
@instance.copy_include_files
end
end
context "creating vagrantfile" do
setup do
@network_adapter = mock("nic")
@network_adapter.stubs(:mac_address).returns("mac_address")
@internal_vm.stubs(:network_adapters).returns([@network_adapter])
end
should "write the rendered vagrantfile to temp_path Vagrantfile" do
f = mock("file")
rendered = mock("rendered")
File.expects(:open).with(File.join(@env["export.temp_dir"], "Vagrantfile"), "w").yields(f)
Vagrant::Util::TemplateRenderer.expects(:render).returns(rendered).with("package_Vagrantfile", {
:base_mac => @internal_vm.network_adapters.first.mac_address
})
f.expects(:write).with(rendered)
@instance.create_vagrantfile
end
end
context "compression" do
setup do
@env["package.include"] = []
@tar_path = "foo"
@instance.stubs(:tar_path).returns(@tar_path)
@pwd = "bar"
FileUtils.stubs(:pwd).returns(@pwd)
FileUtils.stubs(:cd)
@file = mock("file")
File.stubs(:open).yields(@file)
@output = mock("output")
@tar = Archive::Tar::Minitar
Archive::Tar::Minitar::Output.stubs(:open).yields(@output)
@tar.stubs(:pack_file)
@instance.stubs(:copy_include_files)
@instance.stubs(:create_vagrantfile)
end
should "open the tar file with the tar path properly" do
File.expects(:open).with(@tar_path, Vagrant::Util::Platform.tar_file_options).once
@instance.compress
end
should "open tar file" do
Archive::Tar::Minitar::Output.expects(:open).with(@file).once
@instance.compress
end
#----------------------------------------------------------------
# Methods below this comment test the block yielded by Minitar open
#----------------------------------------------------------------
should "cd to the directory and append the directory" do
@files = []
compress_seq = sequence("compress_seq")
FileUtils.expects(:pwd).once.returns(@pwd).in_sequence(compress_seq)
@instance.expects(:copy_include_files).once.in_sequence(compress_seq)
@instance.expects(:create_vagrantfile).once.in_sequence(compress_seq)
FileUtils.expects(:cd).with(@env["export.temp_dir"]).in_sequence(compress_seq)
Dir.expects(:glob).returns(@files).in_sequence(compress_seq)
5.times do |i|
file = mock("file#{i}")
@tar.expects(:pack_file).with(file, @output).once.in_sequence(compress_seq)
@files << file
end
FileUtils.expects(:cd).with(@pwd).in_sequence(compress_seq)
@instance.compress
end
should "pop back to the current directory even if an exception is raised" do
cd_seq = sequence("cd_seq")
FileUtils.expects(:cd).with(@env["export.temp_dir"]).raises(Exception).in_sequence(cd_seq)
FileUtils.expects(:cd).with(@pwd).in_sequence(cd_seq)
assert_raises(Exception) {
@instance.compress
}
end
end
end
end

View File

@ -0,0 +1,50 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class PersistVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Persist
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:uuid).returns("123")
@env["vm"] = @vm
end
context "initializing" do
setup do
File.stubs(:file?).returns(true)
File.stubs(:exist?).returns(true)
@dotfile_path = "foo"
@env.env.stubs(:dotfile_path).returns(@dotfile_path)
end
should "error environment if dotfile exists but is not a file" do
File.expects(:file?).with(@env.env.dotfile_path).returns(false)
@klass.new(@app, @env)
assert @env.error?
assert_equal :dotfile_error, @env.error.first
end
should "initialize properly if dotfiles doesn't exist" do
File.expects(:exist?).with(@env.env.dotfile_path).returns(false)
@klass.new(@app, @env)
assert !@env.error?
end
end
context "with an instance" do
setup do
File.stubs(:file?).returns(true)
File.stubs(:exist?).returns(true)
@instance = @klass.new(@app, @env)
end
should "persist the dotfile then continue chain" do
update_seq = sequence("update_seq")
@env.env.expects(:update_dotfile).in_sequence(update_seq)
@app.expects(:call).with(@env).in_sequence(update_seq)
@instance.call(@env)
end
end
end

View File

@ -0,0 +1,134 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class ProvisionVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Provision
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:name).returns("foo")
@vm.stubs(:ssh).returns(mock("ssh"))
@vm.stubs(:system).returns(mock("system"))
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
end
context "initializing" do
setup do
@klass.any_instance.stubs(:load_provisioner)
end
should "load provisioner if provisioning enabled" do
@env["config"].vm.provisioner = :chef_solo
@klass.any_instance.expects(:load_provisioner).once
@klass.new(@app, @env)
end
should "not load provisioner if disabled" do
@env["config"].vm.provisioner = nil
@klass.any_instance.expects(:load_provisioner).never
@klass.new(@app, @env)
end
end
context "with an instance" do
setup do
# Set provisioner to nil so the provisioner isn't loaded on init
@env["config"].vm.provisioner = nil
@instance = @klass.new(@app, @env)
end
context "loading a provisioner" do
context "with a Class provisioner" do
setup do
@prov = mock("instance")
@prov.stubs(:is_a?).with(Vagrant::Provisioners::Base).returns(true)
@prov.stubs(:prepare)
@klass = mock("klass")
@klass.stubs(:is_a?).with(Class).returns(true)
@klass.stubs(:new).with(@env).returns(@prov)
@env["config"].vm.provisioner = @klass
end
should "set the provisioner to an instantiation of the class" do
@klass.expects(:new).with(@env).once.returns(@prov)
assert_equal @prov, @instance.load_provisioner
end
should "call prepare on the instance" do
@prov.expects(:prepare).once
@instance.load_provisioner
end
should "error environment if the class is not a subclass of the provisioner base" do
@prov.expects(:is_a?).with(Vagrant::Provisioners::Base).returns(false)
@instance.load_provisioner
assert @env.error?
assert_equal :provisioner_invalid_class, @env.error.first
end
end
context "with a Symbol provisioner" do
def provisioner_expectation(symbol, provisioner)
@env[:config].vm.provisioner = symbol
instance = mock("instance")
instance.expects(:prepare).once
provisioner.expects(:new).with(@env).returns(instance)
assert_equal instance, @instance.load_provisioner
end
should "raise an ActionException if its an unknown symbol" do
@env["config"].vm.provisioner = :this_will_never_exist
@instance.load_provisioner
assert @env.error?
assert_equal :provisioner_unknown_type, @env.error.first
end
should "set :chef_solo to the ChefSolo provisioner" do
provisioner_expectation(:chef_solo, Vagrant::Provisioners::ChefSolo)
end
should "set :chef_server to the ChefServer provisioner" do
provisioner_expectation(:chef_server, Vagrant::Provisioners::ChefServer)
end
end
end
context "calling" do
setup do
Vagrant::Provisioners::ChefSolo.any_instance.stubs(:prepare)
@env["config"].vm.provisioner = :chef_solo
@prov = @instance.load_provisioner
end
should "provision and continue chain" do
seq = sequence("seq")
@app.expects(:call).with(@env).in_sequence(seq)
@prov.expects(:provision!).in_sequence(seq)
@instance.call(@env)
end
should "continue chain and not provision if not enabled" do
@env["config"].vm.provisioner = nil
@prov.expects(:provision!).never
@app.expects(:call).with(@env).once
@instance.call(@env)
end
should "not provision if erroneous environment" do
@env.error!(:foo)
@prov.expects(:provision!).never
@app.expects(:call).with(@env).once
@instance.call(@env)
end
end
end
end

View File

@ -0,0 +1,35 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class ResumeVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Resume
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
should "run the proper methods when saved" do
@internal_vm.expects(:saved?).returns(true)
seq = sequence("seq")
@env.env.actions.expects(:run).with(:start).once.in_sequence(seq)
@app.expects(:call).with(@env).once.in_sequence(seq)
@instance.call(@env)
end
should "do nothing if VM is not saved" do
@internal_vm.expects(:saved?).returns(false)
@vm.expects(:start).never
@app.expects(:call).with(@env).once
@instance.call(@env)
end
end
end

View File

@ -1,9 +1,20 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class SharedFoldersActionTest < Test::Unit::TestCase
class ShareFoldersVMActionTest < Test::Unit::TestCase
setup do
@runner, @vm, @action = mock_action(Vagrant::Actions::VM::SharedFolders)
@runner.stubs(:system).returns(linux_system(@vm))
@klass = Vagrant::Action::VM::ShareFolders
@app, @env = mock_action_data
@vm = mock("vm")
@vm.stubs(:name).returns("foo")
@vm.stubs(:ssh).returns(mock("ssh"))
@vm.stubs(:system).returns(mock("system"))
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
def stub_shared_folders
@ -20,25 +31,33 @@ class SharedFoldersActionTest < Test::Unit::TestCase
end
end
@runner.stubs(:env).returns(env)
@env.stubs(:env).returns(env)
env.config.vm.shared_folders
end
context "before boot" do
should "clear folders and create metadata, in order" do
context "calling" do
should "run the methods in the proper order" do
before_seq = sequence("before")
@action.expects(:clear_shared_folders).once.in_sequence(before_seq)
@action.expects(:create_metadata).once.in_sequence(before_seq)
@action.before_boot
end
end
@instance.expects(:clear_shared_folders).once.in_sequence(before_seq)
@instance.expects(:create_metadata).once.in_sequence(before_seq)
@app.expects(:call).with(@env).in_sequence(before_seq)
@instance.expects(:mount_shared_folders).once.in_sequence(before_seq)
@instance.expects(:setup_unison).once.in_sequence(before_seq)
context "after boot" do
should "mount folders then setup unison" do
seq = sequence("after")
@action.expects(:mount_shared_folders).once.in_sequence(seq)
@action.expects(:setup_unison).once.in_sequence(seq)
@action.after_boot
@instance.call(@env)
end
should "run only the metadata actions if erroneous environment" do
@env.error!(:foo)
before_seq = sequence("before")
@instance.expects(:clear_shared_folders).once.in_sequence(before_seq)
@instance.expects(:create_metadata).once.in_sequence(before_seq)
@app.expects(:call).with(@env).in_sequence(before_seq)
@instance.expects(:mount_shared_folders).never
@instance.expects(:setup_unison).never
@instance.call(@env)
end
end
@ -59,7 +78,7 @@ class SharedFoldersActionTest < Test::Unit::TestCase
end
end
result = @action.shared_folders
result = @instance.shared_folders
assert_equal data.length, result.length
data.each do |name, value|
guest, host = value
@ -77,8 +96,8 @@ class SharedFoldersActionTest < Test::Unit::TestCase
config.vm.share_folder(name, guest, host, :sync => true)
end
result = @action.shared_folders
assert_equal "#{guest}#{@runner.env.config.unison.folder_suffix}", result[name][:guestpath]
result = @instance.shared_folders
assert_equal "#{guest}#{@env.env.config.unison.folder_suffix}", result[name][:guestpath]
assert_equal guest, result[name][:original][:guestpath]
end
@ -89,8 +108,8 @@ class SharedFoldersActionTest < Test::Unit::TestCase
folder = @folders["foo"].dup
@action.shared_folders
assert_equal folder, @runner.env.config.vm.shared_folders["foo"]
@instance.shared_folders
assert_equal folder, @env.env.config.vm.shared_folders["foo"]
end
end
@ -103,7 +122,7 @@ class SharedFoldersActionTest < Test::Unit::TestCase
end
should "only return the folders marked for syncing" do
result = @action.unison_folders
result = @instance.unison_folders
assert_equal 1, result.length
assert result.has_key?("foo")
assert !result.has_key?("bar")
@ -114,7 +133,7 @@ class SharedFoldersActionTest < Test::Unit::TestCase
setup do
@shared_folder = mock("shared_folder")
@shared_folders = [@shared_folder]
@vm.stubs(:shared_folders).returns(@shared_folders)
@internal_vm.stubs(:shared_folders).returns(@shared_folders)
end
should "call destroy on each shared folder then reload" do
@ -123,14 +142,14 @@ class SharedFoldersActionTest < Test::Unit::TestCase
sf.expects(:destroy).once.in_sequence(destroy_seq)
end
@runner.expects(:reload!).once.in_sequence(destroy_seq)
@action.clear_shared_folders
@vm.expects(:reload!).once.in_sequence(destroy_seq)
@instance.clear_shared_folders
end
should "do nothing if no shared folders existed" do
@shared_folders.clear
@runner.expects(:reload!).never
@action.clear_shared_folders
@vm.expects(:reload!).never
@instance.clear_shared_folders
end
end
@ -143,16 +162,16 @@ class SharedFoldersActionTest < Test::Unit::TestCase
shared_folders = []
data = %W[foo bar]
shared_folders.expects(:<<).times(data.length).with() do |sf|
hostpath = File.expand_path("#{sf.name}host", @runner.env.root_path)
hostpath = File.expand_path("#{sf.name}host", @env.env.root_path)
assert data.include?(sf.name)
assert_equal hostpath, sf.host_path
true
end
@vm.stubs(:shared_folders).returns(shared_folders)
@vm.expects(:save).once
@internal_vm.stubs(:shared_folders).returns(shared_folders)
@internal_vm.expects(:save).once
@action.create_metadata
@instance.create_metadata
end
end
@ -160,24 +179,24 @@ class SharedFoldersActionTest < Test::Unit::TestCase
setup do
@folders = stub_shared_folders
@ssh = mock("ssh")
@runner.ssh.stubs(:execute).yields(@ssh)
@runner.system.stubs(:mount_shared_folder)
@vm.ssh.stubs(:execute).yields(@ssh)
@vm.system.stubs(:mount_shared_folder)
end
should "mount all shared folders to the VM" do
mount_seq = sequence("mount_seq")
@folders.each do |name, data|
@runner.system.expects(:mount_shared_folder).with(@ssh, name, data[:guestpath]).in_sequence(mount_seq)
@vm.system.expects(:mount_shared_folder).with(@ssh, name, data[:guestpath]).in_sequence(mount_seq)
end
@action.mount_shared_folders
@instance.mount_shared_folders
end
end
context "setting up unison" do
setup do
@ssh = mock("ssh")
@runner.ssh.stubs(:execute).yields(@ssh)
@vm.ssh.stubs(:execute).yields(@ssh)
@folders = stub_shared_folders do |config|
config.vm.share_folder("foo", "bar", "baz", :sync => true)
@ -186,17 +205,17 @@ class SharedFoldersActionTest < Test::Unit::TestCase
end
should "do nothing if unison folders is empty" do
@action.stubs(:unison_folders).returns({})
@runner.ssh.expects(:execute).never
@action.setup_unison
@instance.stubs(:unison_folders).returns({})
@vm.ssh.expects(:execute).never
@instance.setup_unison
end
should "prepare unison then create for each folder" do
seq = sequence("unison seq")
@runner.system.expects(:prepare_unison).with(@ssh).once.in_sequence(seq)
@action.unison_folders.each do |name, data|
@vm.system.expects(:prepare_unison).with(@ssh).once.in_sequence(seq)
@instance.unison_folders.each do |name, data|
if data[:sync]
@runner.system.expects(:create_unison).with do |ssh, opts|
@vm.system.expects(:create_unison).with do |ssh, opts|
assert_equal @ssh, ssh
assert_equal data, opts
@ -205,7 +224,7 @@ class SharedFoldersActionTest < Test::Unit::TestCase
end
end
@action.setup_unison
@instance.setup_unison
end
end
end

View File

@ -0,0 +1,35 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class SuspendVMActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action::VM::Suspend
@app, @env = mock_action_data
@vm = mock("vm")
@env["vm"] = @vm
@internal_vm = mock("internal")
@vm.stubs(:vm).returns(@internal_vm)
@instance = @klass.new(@app, @env)
end
context "calling" do
should "run the proper methods when running" do
@internal_vm.expects(:running?).returns(true)
seq = sequence("seq")
@internal_vm.expects(:save_state).once.in_sequence(seq)
@app.expects(:call).with(@env).once.in_sequence(seq)
@instance.call(@env)
end
should "do nothing if VM is not running" do
@internal_vm.expects(:running?).returns(false)
@internal_vm.expects(:save_state).never
@app.expects(:call).with(@env).once
@instance.call(@env)
end
end
end

View File

@ -0,0 +1,96 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
class ActionTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Action
end
context "with a class" do
teardown do
@klass.actions.clear
end
should "be able to register an action" do
@klass.register(:foo, :bar)
assert @klass.actions.has_key?(:foo)
assert_equal :bar, @klass.actions[:foo]
end
should "be able to retrieve an action using []" do
@klass.register(:foo, :bar)
assert_equal :bar, @klass[:foo]
end
end
context "with an instance" do
setup do
@instance = @klass.new(mock_environment)
end
teardown do
@klass.actions.clear
end
should "run the callable item with the proper context" do
callable = mock("callable")
callable.expects(:call).with() do |env|
assert env.kind_of?(Vagrant::Action::Environment)
assert_equal @instance.env, env.env
true
end
@instance.run(callable)
end
should "run the callable with the passed in options if given" do
options = {
:key => :value,
:another => %W[1 2 3]
}
callable = mock("callable")
callable.expects(:call).with() do |env|
assert env.kind_of?(Vagrant::Action::Environment)
assert_equal @instance.env, env.env
options.each do |k,v|
assert_equal v, env[k]
end
true
end
@instance.run(callable, options)
end
should "run the registered callable if a symbol is given" do
callable = mock("callable")
callable.expects(:call).once
@klass.register(:call, callable)
@instance.run(:call)
end
should "run the given class if a class is given" do
callable = Class.new do
def initialize(app, env); end
end
callable.any_instance.expects(:call).with() do |env|
assert_equal :foo, env[:bar]
true
end
@instance.run(callable, :bar => :foo)
end
should "error and exit if erroneous environment results" do
callable = lambda do |env|
env.error!(:key, :foo => :bar)
end
@instance.expects(:error_and_exit).with(:key, :foo => :bar)
@instance.run(callable)
end
end
end

View File

@ -1,32 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class BaseActionTest < Test::Unit::TestCase
should "include the util class so subclasses have access to it" do
assert Vagrant::Actions::Base.include?(Vagrant::Util)
end
context "base instance" do
setup do
@mock_vm = mock("vm")
@base = Vagrant::Actions::Base.new(@mock_vm)
end
should "allow read-only access to the runner" do
assert_equal @mock_vm, @base.runner
end
should "implement prepare which does nothing" do
assert_nothing_raised do
assert @base.respond_to?(:prepare)
@base.prepare
end
end
should "implement the execute! method which does nothing" do
assert_nothing_raised do
assert @base.respond_to?(:execute!)
@base.execute!
end
end
end
end

View File

@ -1,36 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class AddBoxActionTest < Test::Unit::TestCase
setup do
@runner, @vm, @action = mock_action(Vagrant::Actions::Box::Add)
end
context "prepare" do
setup do
@default_order = [Vagrant::Actions::Box::Download, Vagrant::Actions::Box::Unpackage, Vagrant::Actions::Box::Verify]
@runner.stubs(:directory).returns("foo")
File.stubs(:exists?).returns(false)
end
def setup_action_expectations
default_seq = sequence("default_seq")
@default_order.each do |action|
@runner.expects(:add_action).with(action).once.in_sequence(default_seq)
end
end
should "setup the proper sequence of actions" do
setup_action_expectations
@action.prepare
end
should "result in an action exception if the box already exists" do
File.expects(:exists?).once.returns(true)
@runner.expects(:name).once.returns('foo')
@runner.expects(:add_action).never
assert_raise Vagrant::Actions::ActionException do
@action.prepare
end
end
end
end

View File

@ -1,17 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class DestroyBoxActionTest < Test::Unit::TestCase
setup do
@name = "foo"
@dir = "foo"
@runner, @vm, @action = mock_action(Vagrant::Actions::Box::Destroy)
@runner.stubs(:directory).returns(@dir)
end
context "executing" do
should "rm_rf the directory" do
FileUtils.expects(:rm_rf).with(@dir).once
@action.execute!
end
end
end

View File

@ -1,137 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class DownloadBoxActionTest < Test::Unit::TestCase
setup do
@uri = "foo.com"
@runner, @vm, @action = mock_action(Vagrant::Actions::Box::Download)
@runner.stubs(:uri).returns(@uri)
@runner.stubs(:temp_path=)
@runner.env.stubs(:tmp_path).returns("foo")
end
context "preparing" do
setup do
@downloader = mock("downloader")
Vagrant::Downloaders::File.any_instance.stubs(:prepare)
Vagrant::Downloaders::HTTP.any_instance.stubs(:prepare)
end
should "raise an exception if no URI type is matched" do\
Vagrant::Downloaders::File.expects(:match?).returns(false)
Vagrant::Downloaders::HTTP.expects(:match?).returns(false)
assert_raises(Vagrant::Actions::ActionException) {
@action.prepare
}
end
should "call #prepare on the downloader" do
@downloader.expects(:prepare).with(@runner.uri).once
Vagrant::Downloaders::File.expects(:new).returns(@downloader)
expect_file
@action.prepare
end
should "set the downloader to file if the uri provided is a file" do
expect_file
@action.prepare
assert @action.downloader.is_a?(Vagrant::Downloaders::File)
end
should "set the downloader to HTTP if the uri provided is a valid url" do
expect_http
@action.prepare
assert @action.downloader.is_a?(Vagrant::Downloaders::HTTP)
end
def expect_file
Vagrant::Downloaders::File.expects(:match?).returns(true)
Vagrant::Downloaders::HTTP.expects(:match?).returns(false)
end
def expect_http
Vagrant::Downloaders::File.expects(:match?).returns(false)
Vagrant::Downloaders::HTTP.expects(:match?).returns(true)
end
end
context "executing" do
setup do
@path = "foo"
@tempfile = mock("tempfile")
@tempfile.stubs(:path).returns(@path)
@action.stubs(:with_tempfile).yields(@tempfile)
@action.stubs(:download_to)
end
should "make a tempfile and copy the URI contents to it" do
@action.expects(:with_tempfile).yields(@tempfile)
@action.expects(:download_to).with(@tempfile)
@action.execute!
end
should "save the tempfile path" do
@runner.expects(:temp_path=).with(@path).once
@action.execute!
end
end
context "rescue" do
should "call cleanup method" do
@action.expects(:cleanup).once
@action.rescue(nil)
end
end
context "tempfile" do
should "create a tempfile in the vagrant tmp directory" do
File.expects(:open).with { |name, bitmask|
name =~ /#{Vagrant::Actions::Box::Download::BASENAME}/ && name =~ /#{@runner.env.tmp_path}/
}.once
@action.with_tempfile
end
should "yield the tempfile object" do
@tempfile = mock("tempfile")
File.expects(:open).yields(@tempfile)
@action.with_tempfile do |otherfile|
assert @tempfile.equal?(otherfile)
end
end
end
context "cleaning up" do
setup do
@temp_path = "foo"
@runner.stubs(:temp_path).returns(@temp_path)
File.stubs(:exist?).returns(true)
end
should "delete the temporary file if it exists" do
File.expects(:unlink).with(@temp_path).once
@action.cleanup
end
should "not delete anything if it doesn't exist" do
File.stubs(:exist?).returns(false)
File.expects(:unlink).never
@action.cleanup
end
end
context "downloading" do
setup do
@downloader = mock("downloader")
@action.stubs(:downloader).returns(@downloader)
end
should "call download! on the download with the URI and tempfile" do
tempfile = "foo"
@downloader.expects(:download!).with(@runner.uri, tempfile)
@action.download_to(tempfile)
end
end
end

View File

@ -1,99 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class UnpackageBoxActionTest < Test::Unit::TestCase
setup do
@runner, @vm, @action = mock_action(Vagrant::Actions::Box::Unpackage)
@runner.stubs(:name).returns("foo")
@runner.stubs(:temp_path).returns("bar")
@runner.env.stubs(:boxes_path).returns("bar")
end
context "executing" do
setup do
@runner.stubs(:invoke_around_callback).yields
end
should "execute the proper actions in the proper order" do
exec_seq = sequence("exec_seq")
@action.expects(:setup_box_dir).in_sequence(exec_seq)
@action.expects(:decompress).in_sequence(exec_seq)
@action.execute!
end
should "execute it in a around block" do
@runner.expects(:invoke_around_callback).with(:unpackage).once
@action.execute!
end
end
context "rescuing" do
setup do
File.stubs(:directory?).returns(false)
FileUtils.stubs(:rm_rf)
@box_dir = mock("foo")
@action.stubs(:box_dir).returns(@box_dir)
end
should "do nothing if a directory doesn't exist" do
FileUtils.expects(:rm_rf).never
@action.rescue(nil)
end
should "remove the box directory if it exists" do
File.expects(:directory?).returns(true)
FileUtils.expects(:rm_rf).with(@box_dir).once
@action.rescue(nil)
end
end
context "box directory" do
should "return the runner directory" do
result = mock("object")
@runner.expects(:directory).once.returns(result)
assert result.equal?(@action.box_dir)
end
end
context "setting up the box directory" do
setup do
File.stubs(:directory?).returns(false)
FileUtils.stubs(:mkdir_p)
@box_dir = "foo"
@action.stubs(:box_dir).returns(@box_dir)
end
should "error and exit if the directory exists" do
File.expects(:directory?).returns(true)
@action.expects(:error_and_exit).with(:box_already_exists, :box_name => @runner.name).once
@action.setup_box_dir
end
should "create the directory" do
FileUtils.expects(:mkdir_p).with(@box_dir).once
@action.setup_box_dir
end
end
context "decompressing" do
setup do
@box_dir = "foo"
@action.stubs(:box_dir).returns(@box_dir)
Dir.stubs(:chdir).yields
Archive::Tar::Minitar.stubs(:unpack)
end
should "change to the box directory" do
Dir.expects(:chdir).with(@box_dir)
@action.decompress
end
should "open the tar file within the new directory, and extract it all" do
Archive::Tar::Minitar.expects(:unpack).with(@runner.temp_path, @box_dir).once
@action.decompress
end
end
end

View File

@ -1,44 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class VerifyBoxActionTest < Test::Unit::TestCase
setup do
@runner, @vm, @action = mock_action(Vagrant::Actions::Box::Verify)
@runner.stubs(:name).returns("foo")
@runner.stubs(:temp_path).returns("bar")
end
context "executing" do
should "execute the proper actions in the proper order" do
exec_seq = sequence("exec_seq")
@action.expects(:reload_configuration).in_sequence(exec_seq)
@action.expects(:verify_appliance).in_sequence(exec_seq)
@action.execute!
end
end
context "reloading configuration" do
should "set the new box, load box, then load config" do
reload_seq = sequence("reload_seq")
@runner.env.config.vm.expects(:box=).with(@runner.name).in_sequence(reload_seq)
@runner.env.expects(:load_box!).in_sequence(reload_seq)
@runner.env.expects(:load_config!).in_sequence(reload_seq)
@action.reload_configuration
end
end
context "verifying appliance" do
setup do
@runner.stubs(:ovf_file).returns("foo")
end
should "create new appliance and return true if succeeds" do
VirtualBox::Appliance.expects(:new).with(@runner.ovf_file)
assert_nothing_raised { @action.verify_appliance }
end
should "return false if an exception is raised" do
VirtualBox::Appliance.expects(:new).with(@runner.ovf_file).raises(VirtualBox::Exceptions::FileErrorException)
assert_raises(Vagrant::Actions::ActionException) { @action.verify_appliance }
end
end
end

View File

@ -1,113 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class CollectionTest < Test::Unit::TestCase
class MockAction; end
class MockActionOther; end
context "checking uniqueness" do
setup do
@actions = Vagrant::Actions::Collection.new([1])
end
should "return true if there are duplicate classes in the collection" do
@actions << 1
assert @actions.duplicates?
end
should "return false it all the classes are unique" do
@actions << 1.0 << "foo"
assert !@actions.duplicates?
end
should "raise an exception when there are duplicates" do
@actions << 1
assert_raise Vagrant::Actions::DuplicateActionException do
@actions.duplicates!
end
end
should "not raise an exception when there are no duplicates" do
@actions << 1.0 << "foo"
assert_nothing_raised do
@actions.duplicates!
end
end
end
context "verifying dependencies" do
setup do
@mock_action = mock('action')
@mock_action.stubs(:class).returns(MockAction)
@mock_action2 = mock('action2')
@mock_action2.stubs(:class).returns(MockActionOther)
# see test_helper
stub_default_action_dependecies(@mock_action)
stub_default_action_dependecies(@mock_action2)
end
context "that come before an action" do
setup do
@mock_action.stubs(:follows).returns([MockActionOther])
end
should "raise an exception if they are not met" do
assert_raise Vagrant::Actions::DependencyNotSatisfiedException do
collection.new([@mock_action]).dependencies!
end
end
should "not raise an exception if they are met" do
assert_nothing_raised do
collection.new([@mock_action2, @mock_action]).dependencies!
end
end
end
context "that follow an an action" do
setup do
@mock_action.stubs(:precedes).returns([MockActionOther])
end
should "raise an exception if they are not met" do
assert_raise Vagrant::Actions::DependencyNotSatisfiedException do
collection.new([@mock_action]).dependencies!
end
end
should "not raise an exception if they are met" do
assert_nothing_raised do
collection.new([@mock_action, @mock_action2]).dependencies!
end
end
end
context "that are before and after an action" do
setup do
@mock_action.stubs(:precedes).returns([MockActionOther])
@mock_action.stubs(:follows).returns([MockActionOther])
end
should "raise an exception if they are met" do
assert_raise Vagrant::Actions::DependencyNotSatisfiedException do
collection.new([@mock_action2, @mock_action]).dependencies!
end
end
should "not raise and exception if they are met" do
assert_nothing_raised do
collection.new([@mock_action2, @mock_action, @mock_action2]).dependencies!
end
end
end
end
context "klasses" do
should "return a list of the collection element's classes" do
@action = mock('action')
assert_equal collection.new([@action]).klasses, [@action.class]
assert_equal collection.new([@action, 1.0, "foo"]).klasses, [@action.class, Float, String]
end
end
def collection; Vagrant::Actions::Collection end
end

View File

@ -1,268 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ActionRunnerTest < Test::Unit::TestCase
class MockAction; end
class MockActionOther; end
def mock_fake_action(action_klass = nil, runner = nil)
action = action_klass ? action_klass.new(runner) : mock("action")
action.stubs(:prepare)
action.stubs(:execute!)
action.stubs(:cleanup)
stub_default_action_dependecies(action)
action
end
context "callbacks" do
setup do
@runner = Vagrant::Actions::Runner.new
end
context "around callbacks" do
should "invoke before/after_name for around callbacks" do
block_obj = mock("block_obj")
around_seq = sequence("around_seq")
@runner.expects(:invoke_callback).with(:before_foo).once.in_sequence(around_seq)
block_obj.expects(:foo).once.in_sequence(around_seq)
@runner.expects(:invoke_callback).with(:after_foo).once.in_sequence(around_seq)
@runner.invoke_around_callback(:foo) do
block_obj.foo
end
end
should "forward arguments to invoke_callback" do
@runner.expects(:invoke_callback).with(:before_foo, "foo").once
@runner.expects(:invoke_callback).with(:after_foo, "foo").once
@runner.invoke_around_callback(:foo, "foo") do; end
end
end
should "not invoke callback on actions which don't respond to it" do
action = mock("action")
action.stubs(:respond_to?).with(:foo).returns(false)
action.expects(:foo).never
assert_nothing_raised do
@runner.actions << action
@runner.invoke_callback(:foo)
end
end
should "invoke callback on actions which do respond to the method" do
action = mock("action")
action.expects(:foo).once
@runner.actions << action
@runner.invoke_callback(:foo)
end
should "collect all the results and return them as an array" do
result = []
3.times do |i|
action = mock("action#{i}")
action.expects(:foo).returns("foo#{i}").once
@runner.actions << action
result << "foo#{i}"
end
assert_equal result, @runner.invoke_callback(:foo)
end
end
context "finding actions" do
setup do
@runner = Vagrant::Actions::Runner.new
end
should "return nil if the action could not be found" do
assert_nil @runner.find_action(Vagrant::Actions::VM::Export)
end
should "return the first instance of the action found" do
@runner.add_action(Vagrant::Actions::VM::Export)
@runner.add_action(Vagrant::Actions::VM::Export)
assert @runner.actions[0].equal?(@runner.find_action(Vagrant::Actions::VM::Export))
end
end
context "adding actions" do
setup do
@runner = Vagrant::Actions::Runner.new
end
should "initialize the action when added" do
action_klass = mock("action_class")
action_inst = mock("action_inst")
action_klass.expects(:new).once.returns(action_inst)
@runner.add_action(action_klass)
assert_equal 1, @runner.actions.length
end
should "initialize the action with given arguments when added" do
action_klass = mock("action_class")
action_klass.expects(:new).with(@runner, "foo", "bar").once
@runner.add_action(action_klass, "foo", "bar")
end
end
context "class method execute" do
should "run actions on class method execute!" do
vm = mock("vm")
execute_seq = sequence("execute_seq")
Vagrant::Actions::Runner.expects(:new).returns(vm).in_sequence(execute_seq)
vm.expects(:add_action).with("foo").in_sequence(execute_seq)
vm.expects(:execute!).once.in_sequence(execute_seq)
Vagrant::Actions::Runner.execute!("foo")
end
should "forward arguments to add_action on class method execute!" do
vm = mock("vm")
execute_seq = sequence("execute_seq")
Vagrant::Actions::Runner.expects(:new).returns(vm).in_sequence(execute_seq)
vm.expects(:add_action).with("foo", "bar", "baz").in_sequence(execute_seq)
vm.expects(:execute!).once.in_sequence(execute_seq)
Vagrant::Actions::Runner.execute!("foo", "bar", "baz")
end
end
context "instance method execute" do
setup do
@runner = Vagrant::Actions::Runner.new
@runner.stubs(:action_klasses).returns([Vagrant::Actions::Base])
end
should "clear the actions and run a single action if given to execute!" do
action = mock("action")
run_action = mock("action_run")
stub_default_action_dependecies(run_action)
run_class = mock("run_class")
run_class.expects(:new).once.returns(run_action)
@runner.actions << action
[:prepare, :execute!, :cleanup].each do |method|
action.expects(method).never
run_action.expects(method).once
end
@runner.execute!(run_class)
end
should "clear actions after running execute!" do
@runner.actions << mock_fake_action
assert !@runner.actions.empty? # sanity
@runner.execute!
assert @runner.actions.empty?
end
should "run #prepare on all actions, then #execute!" do
action_seq = sequence("action_seq")
actions = []
[MockAction, MockActionOther].each_with_index do |klass, i|
action = mock("action#{i}")
action.expects(:class).returns(klass)
stub_default_action_dependecies(action)
@runner.actions << action
actions << action
end
[:prepare, :execute!, :cleanup].each do |method|
actions.each do |action|
action.expects(method).once.in_sequence(action_seq)
end
end
@runner.execute!
end
context "exceptions" do
setup do
@actions = [MockAction, MockActionOther].map do |klass|
action = mock_fake_action
action.expects(:class).returns(klass)
action.stubs(:rescue)
@runner.actions << action
action
end
@exception = Exception.new
end
should "call #rescue on each action if an exception is raised during execute!" do
@actions.each do |a|
a.expects(:rescue).with(@exception).once
end
@actions[0].stubs(:execute!).raises(@exception)
@runner.expects(:error_and_exit).never
assert_raises(Exception) { @runner.execute! }
end
should "call #rescue on each action if an exception is raised during prepare" do
@actions.each do |a|
a.expects(:rescue).with(@exception).once
end
@actions[0].stubs(:prepare).raises(@exception)
@runner.expects(:error_and_exit).never
assert_raises(Exception) { @runner.execute! }
end
should "call error_and_exit if it is an ActionException" do
@exception = Vagrant::Actions::ActionException.new("foo")
@actions[0].stubs(:prepare).raises(@exception)
@runner.expects(:error_and_exit).with(@exception.key, @exception.data).once
@runner.execute!
end
end
end
context "actions" do
setup do
@runner = Vagrant::Actions::Runner.new
end
should "setup actions to be an array" do
assert_nil @runner.instance_variable_get(:@actions)
actions = @runner.actions
assert actions.is_a?(Array)
assert actions.equal?(@runner.actions)
end
should "be empty initially" do
assert @runner.actions.empty?
end
end
context "duplicate action exceptions" do
setup do
@runner = Vagrant::Actions::Runner.new
end
should "should be raised when a duplicate is added" do
action = mock_fake_action
2.times {@runner.actions << action }
assert_raise Vagrant::Actions::DuplicateActionException do
@runner.execute!
end
end
should "should not be raise when no duplicate actions are present" do
@runner.actions << mock_fake_action(Vagrant::Actions::Base, @runner)
@runner.actions << mock_fake_action(Vagrant::Actions::VM::Halt, @runner)
assert_nothing_raised { @runner.execute! }
end
should "should not raise when a single action is specified" do
assert_nothing_raised { @runner.execute!(Vagrant::Actions::Base) }
end
end
end

View File

@ -1,49 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class BootActionTest < Test::Unit::TestCase
setup do
@runner, @vm, @action = mock_action(Vagrant::Actions::VM::Boot)
@runner.stubs(:invoke_callback)
end
context "execution" do
should "invoke the 'boot' around callback" do
boot_seq = sequence("boot_seq")
@runner.expects(:invoke_around_callback).with(:boot).once.in_sequence(boot_seq).yields
@action.expects(:boot).in_sequence(boot_seq)
@action.expects(:wait_for_boot).returns(true).in_sequence(boot_seq)
@action.execute!
end
should "error and exit if the bootup failed" do
fail_boot_seq = sequence("fail_boot_seq")
@action.expects(:boot).once.in_sequence(fail_boot_seq)
@action.expects(:wait_for_boot).returns(false).in_sequence(fail_boot_seq)
@action.expects(:error_and_exit).with(:vm_failed_to_boot).once.in_sequence(fail_boot_seq)
@action.execute!
end
end
context "booting" do
should "start the VM in specified mode" do
mode = mock("boot_mode")
@runner.env.config.vm.boot_mode = mode
@vm.expects(:start).with(mode).once
@action.boot
end
end
context "waiting for boot" do
should "repeatedly ping the SSH port and return false with no response" do
seq = sequence('pings')
@runner.ssh.expects(:up?).times(@runner.env.config.ssh.max_tries.to_i - 1).returns(false).in_sequence(seq)
@runner.ssh.expects(:up?).once.returns(true).in_sequence(seq)
assert @action.wait_for_boot(0)
end
should "ping the max number of times then just return" do
@runner.ssh.expects(:up?).times(@runner.env.config.ssh.max_tries.to_i).returns(false)
assert !@action.wait_for_boot(0)
end
end
end

View File

@ -1,21 +0,0 @@
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
class CustomizeActionTest < Test::Unit::TestCase
setup do
@runner, @vm, @action = mock_action(Vagrant::Actions::VM::Customize)
end
context "executing" do
should "run the VM customization procs then save the VM" do
@runner.env.config.vm.customize { |vm| }
@runner.env.config.vm.expects(:run_procs!).with(@vm)
@vm.expects(:save).once
@action.execute!
end
should "not run anything if no customize blocks exist" do
@vm.expects(:save).never
@action.execute!
end
end
end

Some files were not shown because too many files have changed in this diff Show More