Merge pull request #2781 from mitchellh/f-abstract-capability
Abstract "Capability" system so it can be used with hosts
This commit is contained in:
commit
a83498e32a
|
@ -0,0 +1,175 @@
|
||||||
|
module Vagrant
|
||||||
|
# This module enables a class to host capabilities. Prior to being able
|
||||||
|
# to use any capabilities, the `initialize_capabilities!` method must be
|
||||||
|
# called.
|
||||||
|
#
|
||||||
|
# Capabilities allow small pieces of functionality to be plugged in using
|
||||||
|
# the Vagrant plugin model. Capabilities even allow for a certain amount
|
||||||
|
# of inheritence, where only a subset of capabilities may be implemented but
|
||||||
|
# a parent implements the rest.
|
||||||
|
#
|
||||||
|
# Capabilities are used heavily in Vagrant for host/guest interactions. For
|
||||||
|
# example, "mount_nfs_folder" is a guest-OS specific operation, so capabilities
|
||||||
|
# defer these operations to the guest.
|
||||||
|
module CapabilityHost
|
||||||
|
# Initializes the capability system by detecting the proper capability
|
||||||
|
# host to execute on and building the chain of capabilities to execute.
|
||||||
|
#
|
||||||
|
# @param [Symbol] host The host to use for the capabilities, or nil if
|
||||||
|
# we should auto-detect it.
|
||||||
|
# @param [Hash<Symbol, Array<Class, Symbol>>] hosts Potential capability
|
||||||
|
# hosts. The key is the name of the host, value[0] is a class that
|
||||||
|
# implements `#detect?` and value[1] is a parent host (if any).
|
||||||
|
# @param [Hash<Symbol, Hash<Symbol, Class>>] capabilities The capabilities
|
||||||
|
# that are supported. The key is the host of the capability. Within that
|
||||||
|
# is a hash where the key is the name of the capability and the value
|
||||||
|
# is the class/module implementing it.
|
||||||
|
def initialize_capabilities!(host, hosts, capabilities, *args)
|
||||||
|
@cap_logger = Log4r::Logger.new("vagrant::capability_host::#{self.class}")
|
||||||
|
|
||||||
|
if host && !hosts[host]
|
||||||
|
raise Errors::CapabilityHostExplicitNotDetected, value: host.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
if !host
|
||||||
|
host = autodetect_capability_host(hosts, *args) if !host
|
||||||
|
raise Errors::CapabilityHostNotDetected if !host
|
||||||
|
end
|
||||||
|
|
||||||
|
if !hosts[host]
|
||||||
|
# This should never happen because the autodetect above uses the
|
||||||
|
# hosts hash to look up hosts. And if an explicit host is specified,
|
||||||
|
# we do another check higher up.
|
||||||
|
raise "Internal error. Host not found: #{host}"
|
||||||
|
end
|
||||||
|
|
||||||
|
name = host
|
||||||
|
host_info = hosts[name]
|
||||||
|
host = host_info[0].new
|
||||||
|
chain = []
|
||||||
|
chain << [name, host]
|
||||||
|
|
||||||
|
# Build the proper chain of parents if there are any.
|
||||||
|
# This allows us to do "inheritance" of capabilities later
|
||||||
|
if host_info[1]
|
||||||
|
parent_name = host_info[1]
|
||||||
|
parent_info = hosts[parent_name]
|
||||||
|
while parent_info
|
||||||
|
chain << [parent_name, parent_info[0].new]
|
||||||
|
parent_name = parent_info[1]
|
||||||
|
parent_info = hosts[parent_name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@cap_host_chain = chain
|
||||||
|
@cap_args = args
|
||||||
|
@cap_caps = capabilities
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the chain of hosts that will be checked for capabilities.
|
||||||
|
#
|
||||||
|
# @return [Array<Array<Symbol, Class>>]
|
||||||
|
def capability_host_chain
|
||||||
|
@cap_host_chain
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tests whether the given capability is possible.
|
||||||
|
#
|
||||||
|
# @param [Symbol] cap_name Capability name
|
||||||
|
# @return [Boolean]
|
||||||
|
def capability?(cap_name)
|
||||||
|
!capability_module(cap_name.to_sym).nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Executes the capability with the given name, optionally passing more
|
||||||
|
# arguments onwards to the capability. If the capability returns a value,
|
||||||
|
# it will be returned.
|
||||||
|
#
|
||||||
|
# @param [Symbol] cap_name Name of the capability
|
||||||
|
def capability(cap_name, *args)
|
||||||
|
@cap_logger.info("Execute capability: #{cap_name} (#{@cap_host_chain[0][0]})")
|
||||||
|
cap_mod = capability_module(cap_name.to_sym)
|
||||||
|
if !cap_mod
|
||||||
|
raise Errors::CapabilityNotFound,
|
||||||
|
:cap => cap_name.to_s,
|
||||||
|
:host => @cap_host_chain[0][0].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
cap_method = nil
|
||||||
|
begin
|
||||||
|
cap_method = cap_mod.method(cap_name)
|
||||||
|
rescue NameError
|
||||||
|
raise Errors::CapabilityInvalid,
|
||||||
|
:cap => cap_name.to_s,
|
||||||
|
:host => @cap_host_chain[0][0].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
args = @cap_args + args
|
||||||
|
cap_method.call(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def autodetect_capability_host(hosts, *args)
|
||||||
|
@cap_logger.info("Autodetecting guest for machine: #{@machine}")
|
||||||
|
|
||||||
|
# Get the mapping of hosts with the most parents. We start searching
|
||||||
|
# with the hosts with the most parents first.
|
||||||
|
parent_count = {}
|
||||||
|
hosts.each do |name, parts|
|
||||||
|
parent_count[name] = 0
|
||||||
|
|
||||||
|
parent = parts[1]
|
||||||
|
while parent
|
||||||
|
parent_count[name] += 1
|
||||||
|
parent = hosts[parent]
|
||||||
|
parent = parent[1] if parent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now swap around the mapping so that it is a mapping of
|
||||||
|
# count to the actual list of host names
|
||||||
|
parent_count_to_hosts = {}
|
||||||
|
parent_count.each do |name, count|
|
||||||
|
parent_count_to_hosts[count] ||= []
|
||||||
|
parent_count_to_hosts[count] << name
|
||||||
|
end
|
||||||
|
|
||||||
|
sorted_counts = parent_count_to_hosts.keys.sort.reverse
|
||||||
|
sorted_counts.each do |count|
|
||||||
|
parent_count_to_hosts[count].each do |name|
|
||||||
|
@cap_logger.debug("Trying: #{name}")
|
||||||
|
host_info = hosts[name]
|
||||||
|
host = host_info[0].new
|
||||||
|
|
||||||
|
if host.detect?(*args)
|
||||||
|
@cap_logger.info("Detected: #{name}!")
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the registered module for a capability with the given name.
|
||||||
|
#
|
||||||
|
# @param [Symbol] cap_name
|
||||||
|
# @return [Module]
|
||||||
|
def capability_module(cap_name)
|
||||||
|
@cap_logger.debug("Searching for cap: #{cap_name}")
|
||||||
|
@cap_host_chain.each do |host_name, host|
|
||||||
|
@cap_logger.debug("Checking in: #{host_name}")
|
||||||
|
caps = @cap_caps[host_name]
|
||||||
|
|
||||||
|
if caps && caps.has_key?(cap_name)
|
||||||
|
@cap_logger.debug("Found cap: #{cap_name} in #{host_name}")
|
||||||
|
return caps[cap_name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,6 +43,9 @@ module Vagrant
|
||||||
# error code, and the error key is used as a default message from
|
# error code, and the error key is used as a default message from
|
||||||
# I18n.
|
# I18n.
|
||||||
class VagrantError < StandardError
|
class VagrantError < StandardError
|
||||||
|
# This is extra data passed into the message for translation.
|
||||||
|
attr_accessor :extra_data
|
||||||
|
|
||||||
def self.error_key(key=nil, namespace=nil)
|
def self.error_key(key=nil, namespace=nil)
|
||||||
define_method(:error_key) { key }
|
define_method(:error_key) { key }
|
||||||
error_namespace(namespace) if namespace
|
error_namespace(namespace) if namespace
|
||||||
|
@ -57,6 +60,8 @@ module Vagrant
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(message=nil, *args)
|
def initialize(message=nil, *args)
|
||||||
|
message ||= {}
|
||||||
|
@extra_data = message.dup
|
||||||
message = { :_key => message } if message && !message.is_a?(Hash)
|
message = { :_key => message } if message && !message.is_a?(Hash)
|
||||||
message = { :_key => error_key, :_namespace => error_namespace }.merge(message || {})
|
message = { :_key => error_key, :_namespace => error_namespace }.merge(message || {})
|
||||||
|
|
||||||
|
@ -172,6 +177,22 @@ module Vagrant
|
||||||
error_key(:bundler_error)
|
error_key(:bundler_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class CapabilityHostExplicitNotDetected < VagrantError
|
||||||
|
error_key(:capability_host_explicit_not_detected)
|
||||||
|
end
|
||||||
|
|
||||||
|
class CapabilityHostNotDetected < VagrantError
|
||||||
|
error_key(:capability_host_not_detected)
|
||||||
|
end
|
||||||
|
|
||||||
|
class CapabilityInvalid < VagrantError
|
||||||
|
error_key(:capability_invalid)
|
||||||
|
end
|
||||||
|
|
||||||
|
class CapabilityNotFound < VagrantError
|
||||||
|
error_key(:capability_not_found)
|
||||||
|
end
|
||||||
|
|
||||||
class CFEngineBootstrapFailed < VagrantError
|
class CFEngineBootstrapFailed < VagrantError
|
||||||
error_key(:cfengine_bootstrap_failed)
|
error_key(:cfengine_bootstrap_failed)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
require "log4r"
|
require "log4r"
|
||||||
|
|
||||||
|
require "vagrant/capability_host"
|
||||||
|
|
||||||
module Vagrant
|
module Vagrant
|
||||||
# This class handles guest-OS specific interactions with a machine.
|
# This class handles guest-OS specific interactions with a machine.
|
||||||
# It is primarily responsible for detecting the proper guest OS
|
# It is primarily responsible for detecting the proper guest OS
|
||||||
|
@ -17,21 +19,13 @@ module Vagrant
|
||||||
# This system allows for maximum flexibility and pluginability for doing
|
# This system allows for maximum flexibility and pluginability for doing
|
||||||
# guest OS specific operations.
|
# guest OS specific operations.
|
||||||
class Guest
|
class Guest
|
||||||
attr_reader :chain
|
include CapabilityHost
|
||||||
|
|
||||||
# The name of the guest OS. This is available after {#detect!} is
|
|
||||||
# called.
|
|
||||||
#
|
|
||||||
# @return [Symbol]
|
|
||||||
attr_reader :name
|
|
||||||
|
|
||||||
def initialize(machine, guests, capabilities)
|
def initialize(machine, guests, capabilities)
|
||||||
@logger = Log4r::Logger.new("vagrant::guest")
|
@logger = Log4r::Logger.new("vagrant::guest")
|
||||||
@capabilities = capabilities
|
@capabilities = capabilities
|
||||||
@chain = []
|
|
||||||
@guests = guests
|
@guests = guests
|
||||||
@machine = machine
|
@machine = machine
|
||||||
@name = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This will detect the proper guest OS for the machine and set up
|
# This will detect the proper guest OS for the machine and set up
|
||||||
|
@ -40,52 +34,7 @@ module Vagrant
|
||||||
@logger.info("Detect guest for machine: #{@machine}")
|
@logger.info("Detect guest for machine: #{@machine}")
|
||||||
|
|
||||||
guest_name = @machine.config.vm.guest
|
guest_name = @machine.config.vm.guest
|
||||||
if guest_name
|
initialize_capabilities!(guest_name, @guests, @capabilities, @machine)
|
||||||
@logger.info("Using explicit config.vm.guest value: #{guest_name}")
|
|
||||||
else
|
|
||||||
# No explicit guest was specified, so autodetect it.
|
|
||||||
guest_name = autodetect_guest
|
|
||||||
raise Errors::GuestNotDetected if !guest_name
|
|
||||||
end
|
|
||||||
|
|
||||||
if !@guests[guest_name]
|
|
||||||
# This can happen if config.vm.guest was specified with a value
|
|
||||||
# that doesn't exist.
|
|
||||||
raise Errors::GuestExplicitNotDetected, value: guest_name.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
@name = guest_name
|
|
||||||
@chain = guest_chain(@name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tests whether the guest has the named capability.
|
|
||||||
#
|
|
||||||
# @return [Boolean]
|
|
||||||
def capability?(cap_name)
|
|
||||||
!capability_module(cap_name.to_sym).nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Executes the capability with the given name, optionally passing
|
|
||||||
# more arguments onwards to the capability.
|
|
||||||
def capability(cap_name, *args)
|
|
||||||
@logger.info("Execute capability: #{cap_name} (#{@chain[0][0]})")
|
|
||||||
cap_mod = capability_module(cap_name.to_sym)
|
|
||||||
if !cap_mod
|
|
||||||
raise Errors::GuestCapabilityNotFound,
|
|
||||||
:cap => cap_name.to_s,
|
|
||||||
:guest => @chain[0][0].to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
cap_method = nil
|
|
||||||
begin
|
|
||||||
cap_method = cap_mod.method(cap_name)
|
|
||||||
rescue NameError
|
|
||||||
raise Errors::GuestCapabilityInvalid,
|
|
||||||
:cap => cap_name.to_s,
|
|
||||||
:guest => @chain[0][0].to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
cap_method.call(@machine, *args)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This returns whether the guest is ready to work. If this returns
|
# This returns whether the guest is ready to work. If this returns
|
||||||
|
@ -94,98 +43,7 @@ module Vagrant
|
||||||
#
|
#
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def ready?
|
def ready?
|
||||||
!@chain.empty?
|
!!capability_host_chain
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Returns the registered module for a capability with the given name.
|
|
||||||
#
|
|
||||||
# @param [Symbol] cap_name
|
|
||||||
# @return [Module]
|
|
||||||
def capability_module(cap_name)
|
|
||||||
@logger.debug("Searching for cap: #{cap_name}")
|
|
||||||
@chain.each do |guest_name, guest|
|
|
||||||
@logger.debug("Checking in: #{guest_name}")
|
|
||||||
caps = @capabilities[guest_name]
|
|
||||||
|
|
||||||
if caps && caps.has_key?(cap_name)
|
|
||||||
@logger.debug("Found cap: #{cap_name} in #{guest_name}")
|
|
||||||
return caps[cap_name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# This autodetects the guest to use and returns the name of the guest.
|
|
||||||
# This returns nil if the guest type could not be autodetected.
|
|
||||||
#
|
|
||||||
# @return [Symbol]
|
|
||||||
def autodetect_guest
|
|
||||||
@logger.info("Autodetecting guest for machine: #{@machine}")
|
|
||||||
|
|
||||||
# Get the mapping of guests with the most parents. We start searching
|
|
||||||
# with the guests with the most parents first.
|
|
||||||
parent_count = {}
|
|
||||||
@guests.each do |name, parts|
|
|
||||||
parent_count[name] = 0
|
|
||||||
|
|
||||||
parent = parts[1]
|
|
||||||
while parent
|
|
||||||
parent_count[name] += 1
|
|
||||||
parent = @guests[parent]
|
|
||||||
parent = parent[1] if parent
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Now swap around the mapping so that it is a mapping of
|
|
||||||
# count to the actual list of guest names
|
|
||||||
parent_count_to_guests = {}
|
|
||||||
parent_count.each do |name, count|
|
|
||||||
parent_count_to_guests[count] ||= []
|
|
||||||
parent_count_to_guests[count] << name
|
|
||||||
end
|
|
||||||
|
|
||||||
sorted_counts = parent_count_to_guests.keys.sort.reverse
|
|
||||||
sorted_counts.each do |count|
|
|
||||||
parent_count_to_guests[count].each do |name|
|
|
||||||
@logger.debug("Trying: #{name}")
|
|
||||||
guest_info = @guests[name]
|
|
||||||
guest = guest_info[0].new
|
|
||||||
|
|
||||||
if guest.detect?(@machine)
|
|
||||||
@logger.info("Detected: #{name}!")
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# This returns the complete chain for the given guest.
|
|
||||||
#
|
|
||||||
# @return [Array]
|
|
||||||
def guest_chain(name)
|
|
||||||
guest_info = @guests[name]
|
|
||||||
guest = guest_info[0].new
|
|
||||||
chain = []
|
|
||||||
chain << [name, guest]
|
|
||||||
|
|
||||||
# Build the proper chain of parents if there are any.
|
|
||||||
# This allows us to do "inheritance" of capabilities later
|
|
||||||
if guest_info[1]
|
|
||||||
parent_name = guest_info[1]
|
|
||||||
parent_info = @guests[parent_name]
|
|
||||||
while parent_info
|
|
||||||
chain << [parent_name, parent_info[0].new]
|
|
||||||
parent_name = parent_info[1]
|
|
||||||
parent_info = @guests[parent_name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return chain
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -261,6 +261,22 @@ en:
|
||||||
issues. The error from Bundler is:
|
issues. The error from Bundler is:
|
||||||
|
|
||||||
%{message}
|
%{message}
|
||||||
|
capability_host_explicit_not_detected: |-
|
||||||
|
The explicit capability host specified of '%{value}' could not be
|
||||||
|
found.
|
||||||
|
|
||||||
|
This is an internal error that users should never see. Please report
|
||||||
|
a bug.
|
||||||
|
capability_host_not_detected: |-
|
||||||
|
The capability host could not be detected. This is an internal error
|
||||||
|
that users should never see. Please report a bug.
|
||||||
|
capability_invalid: |-
|
||||||
|
The capability '%{cap}' is invalid. This is an internal error that
|
||||||
|
users should never see. Please report a bug.
|
||||||
|
capability_not_found: |-
|
||||||
|
The capability '%{cap}' could not be found. This is an internal error
|
||||||
|
that users should never see. Please report a bug.
|
||||||
|
|
||||||
cfengine_bootstrap_failed: |-
|
cfengine_bootstrap_failed: |-
|
||||||
Failed to bootstrap CFEngine. Please see the output above to
|
Failed to bootstrap CFEngine. Please see the output above to
|
||||||
see what went wrong and address the issue.
|
see what went wrong and address the issue.
|
||||||
|
|
|
@ -14,6 +14,7 @@ require "unit/support/dummy_communicator"
|
||||||
require "unit/support/dummy_provider"
|
require "unit/support/dummy_provider"
|
||||||
require "unit/support/shared/base_context"
|
require "unit/support/shared/base_context"
|
||||||
require "unit/support/shared/action_synced_folders_context"
|
require "unit/support/shared/action_synced_folders_context"
|
||||||
|
require "unit/support/shared/capability_helpers_context"
|
||||||
require "unit/support/shared/virtualbox_context"
|
require "unit/support/shared/virtualbox_context"
|
||||||
|
|
||||||
# Do not buffer output
|
# Do not buffer output
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
shared_context "capability_helpers" do
|
||||||
|
def detect_class(result)
|
||||||
|
Class.new do
|
||||||
|
define_method(:detect?) do |*args|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cap_instance(name, options=nil)
|
||||||
|
options ||= {}
|
||||||
|
|
||||||
|
Class.new do
|
||||||
|
if !options[:corrupt]
|
||||||
|
define_method(name) do |*args|
|
||||||
|
raise "cap: #{name} #{args.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end.new
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,163 @@
|
||||||
|
require File.expand_path("../../base", __FILE__)
|
||||||
|
|
||||||
|
require "vagrant/capability_host"
|
||||||
|
|
||||||
|
describe Vagrant::CapabilityHost do
|
||||||
|
include_context "capability_helpers"
|
||||||
|
|
||||||
|
subject do
|
||||||
|
Class.new do
|
||||||
|
extend Vagrant::CapabilityHost
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#initialize_capabilities! and #capability_host_chain" do
|
||||||
|
it "raises an error if an explicit host is not found" do
|
||||||
|
expect { subject.initialize_capabilities!(:foo, {}, {}) }.
|
||||||
|
to raise_error(Vagrant::Errors::CapabilityHostExplicitNotDetected)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error if a host can't be detected" do
|
||||||
|
hosts = {
|
||||||
|
foo: [detect_class(false), nil],
|
||||||
|
bar: [detect_class(false), :foo],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect { subject.initialize_capabilities!(nil, hosts, {}) }.
|
||||||
|
to raise_error(Vagrant::Errors::CapabilityHostNotDetected)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "passes on extra args to the detect method" do
|
||||||
|
klass = Class.new do
|
||||||
|
def detect?(*args)
|
||||||
|
raise "detect: #{args.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
hosts = {
|
||||||
|
foo: [klass, nil],
|
||||||
|
}
|
||||||
|
|
||||||
|
expect { subject.initialize_capabilities!(nil, hosts, {}, 1, 2) }.
|
||||||
|
to raise_error(RuntimeError, "detect: [1, 2]")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "detects a basic child" do
|
||||||
|
hosts = {
|
||||||
|
foo: [detect_class(false), nil],
|
||||||
|
bar: [detect_class(true), nil],
|
||||||
|
baz: [detect_class(false), nil],
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.initialize_capabilities!(nil, hosts, {})
|
||||||
|
|
||||||
|
chain = subject.capability_host_chain
|
||||||
|
expect(chain.length).to eql(1)
|
||||||
|
expect(chain[0][0]).to eql(:bar)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "detects the host with the most parents (deepest) first" do
|
||||||
|
hosts = {
|
||||||
|
foo: [detect_class(true), nil],
|
||||||
|
bar: [detect_class(true), :foo],
|
||||||
|
baz: [detect_class(true), :bar],
|
||||||
|
foo2: [detect_class(true), nil],
|
||||||
|
bar2: [detect_class(true), :foo2],
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.initialize_capabilities!(nil, hosts, {})
|
||||||
|
|
||||||
|
chain = subject.capability_host_chain
|
||||||
|
expect(chain.length).to eql(3)
|
||||||
|
expect(chain.map(&:first)).to eql([:baz, :bar, :foo])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "detects a forced host" do
|
||||||
|
hosts = {
|
||||||
|
foo: [detect_class(false), nil],
|
||||||
|
bar: [detect_class(false), nil],
|
||||||
|
baz: [detect_class(false), nil],
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.initialize_capabilities!(:bar, hosts, {})
|
||||||
|
|
||||||
|
chain = subject.capability_host_chain
|
||||||
|
expect(chain.length).to eql(1)
|
||||||
|
expect(chain[0][0]).to eql(:bar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#capability?" do
|
||||||
|
before do
|
||||||
|
host = nil
|
||||||
|
hosts = {
|
||||||
|
foo: [detect_class(true), nil],
|
||||||
|
bar: [detect_class(true), :foo],
|
||||||
|
}
|
||||||
|
|
||||||
|
caps = {
|
||||||
|
foo: { parent: Class.new },
|
||||||
|
bar: { self: Class.new },
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.initialize_capabilities!(host, hosts, caps)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not have a non-existent capability" do
|
||||||
|
expect(subject.capability?(:foo)).to be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has capabilities of itself" do
|
||||||
|
expect(subject.capability?(:self)).to be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has capabilities of parent" do
|
||||||
|
expect(subject.capability?(:parent)).to be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "capability" do
|
||||||
|
let(:caps) { {} }
|
||||||
|
|
||||||
|
def init
|
||||||
|
host = nil
|
||||||
|
hosts = {
|
||||||
|
foo: [detect_class(true), nil],
|
||||||
|
bar: [detect_class(true), :foo],
|
||||||
|
}
|
||||||
|
|
||||||
|
subject.initialize_capabilities!(host, hosts, caps)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "executes the capability" do
|
||||||
|
caps[:bar] = { test: cap_instance(:test) }
|
||||||
|
init
|
||||||
|
|
||||||
|
expect { subject.capability(:test) }.
|
||||||
|
to raise_error(RuntimeError, "cap: test []")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "executes the capability with arguments" do
|
||||||
|
caps[:bar] = { test: cap_instance(:test) }
|
||||||
|
init
|
||||||
|
|
||||||
|
expect { subject.capability(:test, 1) }.
|
||||||
|
to raise_error(RuntimeError, "cap: test [1]")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an exception if the capability doesn't exist" do
|
||||||
|
init
|
||||||
|
|
||||||
|
expect { subject.capability(:what_is_this_i_dont_even) }.
|
||||||
|
to raise_error(Vagrant::Errors::CapabilityNotFound)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an exception if the method doesn't exist on the module" do
|
||||||
|
caps[:bar] = { test_is_corrupt: cap_instance(:test_is_corrupt, corrupt: true) }
|
||||||
|
init
|
||||||
|
|
||||||
|
expect { subject.capability(:test_is_corrupt) }.
|
||||||
|
to raise_error(Vagrant::Errors::CapabilityInvalid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,10 +32,16 @@ describe Vagrant::Errors::VagrantError do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
subject { klass.new }
|
subject { klass.new(data: "yep") }
|
||||||
|
|
||||||
it "should use the translation for the message" do
|
it "should use the translation for the message" do
|
||||||
subject.to_s.should == "foo"
|
subject.to_s.should == "foo"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should expose translation keys to the user" do
|
||||||
|
expect(subject.extra_data.length).to eql(1)
|
||||||
|
expect(subject.extra_data).to have_key(:data)
|
||||||
|
expect(subject.extra_data[:data]).to eql("yep")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@ require "pathname"
|
||||||
require File.expand_path("../../base", __FILE__)
|
require File.expand_path("../../base", __FILE__)
|
||||||
|
|
||||||
describe Vagrant::Guest do
|
describe Vagrant::Guest do
|
||||||
include_context "unit"
|
include_context "capability_helpers"
|
||||||
|
|
||||||
let(:capabilities) { {} }
|
let(:capabilities) { {} }
|
||||||
let(:guests) { {} }
|
let(:guests) { {} }
|
||||||
|
@ -18,162 +18,27 @@ describe Vagrant::Guest do
|
||||||
|
|
||||||
subject { described_class.new(machine, guests, capabilities) }
|
subject { described_class.new(machine, guests, capabilities) }
|
||||||
|
|
||||||
# This registers a capability with a specific guest
|
|
||||||
def register_capability(guest, capability, options=nil)
|
|
||||||
options ||= {}
|
|
||||||
|
|
||||||
cap = Class.new do
|
|
||||||
if !options[:corrupt]
|
|
||||||
define_method(capability) do |*args|
|
|
||||||
raise "cap: #{capability} #{args.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
capabilities[guest] ||= {}
|
|
||||||
capabilities[guest][capability] = cap.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# This registers a guest with the class.
|
|
||||||
#
|
|
||||||
# @param [Symbol] name Name of the guest
|
|
||||||
# @param [Symbol] parent Name of the parent
|
|
||||||
# @param [Boolean] detect Whether or not to detect properly
|
|
||||||
def register_guest(name, parent, detect)
|
|
||||||
guest = Class.new(Vagrant.plugin("2", "guest")) do
|
|
||||||
define_method(:name) do
|
|
||||||
name
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method(:detect?) do |m|
|
|
||||||
detect
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
guests[name] = [guest, parent]
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#capability" do
|
|
||||||
before :each do
|
|
||||||
register_guest(:foo, nil, true)
|
|
||||||
register_guest(:bar, :foo, true)
|
|
||||||
|
|
||||||
subject.detect!
|
|
||||||
end
|
|
||||||
|
|
||||||
it "executes the capability" do
|
|
||||||
register_capability(:bar, :test)
|
|
||||||
|
|
||||||
expect { subject.capability(:test) }.
|
|
||||||
to raise_error(RuntimeError, "cap: test [machine]")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "executes the capability with arguments" do
|
|
||||||
register_capability(:bar, :test)
|
|
||||||
|
|
||||||
expect { subject.capability(:test, 1) }.
|
|
||||||
to raise_error(RuntimeError, "cap: test [machine, 1]")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an exception if the capability doesn't exist" do
|
|
||||||
expect { subject.capability(:what_is_this_i_dont_even) }.
|
|
||||||
to raise_error(Vagrant::Errors::GuestCapabilityNotFound)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an exception if the method doesn't exist on the module" do
|
|
||||||
register_capability(:bar, :test_is_corrupt, corrupt: true)
|
|
||||||
|
|
||||||
expect { subject.capability(:test_is_corrupt) }.
|
|
||||||
to raise_error(Vagrant::Errors::GuestCapabilityInvalid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#capability?" do
|
|
||||||
before :each do
|
|
||||||
register_guest(:foo, nil, true)
|
|
||||||
register_guest(:bar, :foo, true)
|
|
||||||
|
|
||||||
subject.detect!
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't have unknown capabilities" do
|
|
||||||
subject.capability?(:what_is_this_i_dont_even).should_not be
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't have capabilities registered to other guests" do
|
|
||||||
register_capability(:baz, :test)
|
|
||||||
|
|
||||||
subject.capability?(:test).should_not be
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has capability of detected guest" do
|
|
||||||
register_capability(:bar, :test)
|
|
||||||
|
|
||||||
subject.capability?(:test).should be
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has capability of parent guests" do
|
|
||||||
register_capability(:foo, :test)
|
|
||||||
|
|
||||||
subject.capability?(:test).should be
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#detect!" do
|
describe "#detect!" do
|
||||||
it "detects the first match" do
|
it "auto-detects if no explicit guest name given" do
|
||||||
register_guest(:foo, nil, false)
|
machine.config.vm.stub(guest: nil)
|
||||||
register_guest(:bar, nil, true)
|
subject.should_receive(:initialize_capabilities!).
|
||||||
register_guest(:baz, nil, false)
|
with(nil, guests, capabilities, machine)
|
||||||
|
|
||||||
subject.detect!
|
subject.detect!
|
||||||
subject.name.should == :bar
|
|
||||||
subject.chain.length.should == 1
|
|
||||||
subject.chain[0][0].should == :bar
|
|
||||||
subject.chain[0][1].name.should == :bar
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "detects those with the most parents first" do
|
it "uses the explicit guest name if specified" do
|
||||||
register_guest(:foo, nil, true)
|
machine.config.vm.stub(guest: :foo)
|
||||||
register_guest(:bar, :foo, true)
|
subject.should_receive(:initialize_capabilities!).
|
||||||
register_guest(:baz, :bar, true)
|
with(:foo, guests, capabilities, machine)
|
||||||
register_guest(:foo2, nil, true)
|
|
||||||
register_guest(:bar2, :foo2, true)
|
|
||||||
|
|
||||||
subject.detect!
|
subject.detect!
|
||||||
subject.name.should == :baz
|
|
||||||
subject.chain.length.should == 3
|
|
||||||
subject.chain.map(&:first).should == [:baz, :bar, :foo]
|
|
||||||
subject.chain.map { |x| x[1] }.map(&:name).should == [:baz, :bar, :foo]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "detects the forced guest setting" do
|
|
||||||
register_guest(:foo, nil, false)
|
|
||||||
register_guest(:bar, nil, false)
|
|
||||||
|
|
||||||
machine.config.vm.stub(:guest => :bar)
|
|
||||||
|
|
||||||
subject.detect!
|
|
||||||
subject.name.should == :bar
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an exception if the forced guest can't be found" do
|
|
||||||
register_guest(:foo, nil, true)
|
|
||||||
|
|
||||||
machine.config.vm.stub(:guest => :bar)
|
|
||||||
|
|
||||||
expect { subject.detect! }.
|
|
||||||
to raise_error(Vagrant::Errors::GuestExplicitNotDetected)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an exception if no guest can be detected" do
|
|
||||||
expect { subject.detect! }.
|
|
||||||
to raise_error(Vagrant::Errors::GuestNotDetected)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#ready?" do
|
describe "#ready?" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
register_guest(:foo, nil, true)
|
guests[:foo] = [detect_class(true), nil]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not be ready by default" do
|
it "should not be ready by default" do
|
||||||
|
|
|
@ -225,8 +225,8 @@ describe Vagrant::Machine do
|
||||||
it "should return the configured guest" do
|
it "should return the configured guest" do
|
||||||
result = instance.guest
|
result = instance.guest
|
||||||
result.should be_kind_of(Vagrant::Guest)
|
result.should be_kind_of(Vagrant::Guest)
|
||||||
result.ready?.should be
|
expect(result).to be_ready
|
||||||
result.chain[0][0].should == :test
|
expect(result.capability_host_chain[0][0]).to eql(:test)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue