core: improve guest type detection

/cc @fgrehm - I figured you might be interested in this. :)
This commit is contained in:
Mitchell Hashimoto 2013-12-08 11:14:18 -08:00
parent 581841a195
commit ba18d98d45
4 changed files with 101 additions and 64 deletions

View File

@ -300,6 +300,10 @@ module Vagrant
error_key(:guest_capability_not_found) error_key(:guest_capability_not_found)
end end
class GuestExplicitNotDetected < VagrantError
error_key(:guest_explicit_not_detected)
end
class GuestNotDetected < VagrantError class GuestNotDetected < VagrantError
error_key(:guest_not_detected) error_key(:guest_not_detected)
end end

View File

@ -39,74 +39,23 @@ module Vagrant
def detect! def detect!
@logger.info("Detect guest for machine: #{@machine}") @logger.info("Detect guest for machine: #{@machine}")
# Get the mapping of guests with the most parents. We start searching guest_name = @machine.config.vm.guest
# with the guests with the most parents first. if guest_name
parent_count = {} @logger.info("Using explicit config.vm.guest value: #{guest_name}")
@guests.each do |name, parts| else
parent_count[name] = 0 # No explicit guest was specified, so autodetect it.
guest_name = autodetect_guest
parent = parts[1] raise Errors::GuestNotDetected if !guest_name
while parent
parent_count[name] += 1
parent = @guests[parent]
parent = parent[1] if parent
end
end end
# Now swap around the mapping so that it is a mapping of if !@guests[guest_name]
# count to the actual list of guest names # This can happen if config.vm.guest was specified with a value
parent_count_to_guests = {} # that doesn't exist.
parent_count.each do |name, count| raise Errors::GuestExplicitNotDetected, value: guest_name.to_s
parent_count_to_guests[count] ||= []
parent_count_to_guests[count] << name
end end
catch(:guest_os) do @name = guest_name
sorted_counts = parent_count_to_guests.keys.sort.reverse @chain = guest_chain(@name)
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 a specific guest was specified, then attempt to use that
# guest no matter what. Otherwise, only use it if it was detected.
use_this_guest = false
if @machine.config.vm.guest.nil?
use_this_guest = guest.detect?(@machine)
else
use_this_guest = @machine.config.vm.guest.to_sym == name.to_sym
end
if use_this_guest
@logger.info("Detected: #{name}!")
@chain << [name, guest]
@name = name
# 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
@logger.info("Full guest chain: #{@chain.inspect}")
# Exit the search
throw :guest_os
end
end
end
end
# We shouldn't reach this point. Ideally we would detect
# all operating systems.
raise Errors::GuestNotDetected if @chain.empty?
end end
# Tests whether the guest has the named capability. # Tests whether the guest has the named capability.
@ -168,5 +117,75 @@ module Vagrant
nil nil
end 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

View File

@ -366,6 +366,11 @@ en:
support that capability. This capability is required for your support that capability. This capability is required for your
configuration of Vagrant. Please either reconfigure Vagrant to configuration of Vagrant. Please either reconfigure Vagrant to
avoid this capability or fix the issue by creating the capability. avoid this capability or fix the issue by creating the capability.
guest_explicit_not_detected: |-
The guest implementation explicitly specified in your Vagrantfile
("%{value}") could not be found. Please verify that the plugin is
installed which implements this guest and that the value you
used for `config.vm.guest` is correct.
guest_not_detected: |- guest_not_detected: |-
The guest operating system of the machine could not be detected! The guest operating system of the machine could not be detected!
Vagrant requires this knowledge to perform specific tasks such Vagrant requires this knowledge to perform specific tasks such

View File

@ -156,6 +156,15 @@ describe Vagrant::Guest do
subject.name.should == :bar subject.name.should == :bar
end 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 it "raises an exception if no guest can be detected" do
expect { subject.detect! }. expect { subject.detect! }.
to raise_error(Vagrant::Errors::GuestNotDetected) to raise_error(Vagrant::Errors::GuestNotDetected)