diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 4bf7b152f..5836332e2 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -42,6 +42,10 @@ module Vagrant def self.source_root @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) end + + def self.hosts + @hosts ||= Registry.new + end end # # Default I18n to load the en locale @@ -53,3 +57,10 @@ require 'vagrant/provisioners' require 'vagrant/systems' require 'vagrant/version' Vagrant::Plugin.load! + +# Register the built-in hosts +Vagrant.hosts.register(:arch) { Vagrant::Hosts::Arch } +Vagrant.hosts.register(:freebsd) { Vagrant::Hosts::FreeBSD } +Vagrant.hosts.register(:fedora) { Vagrant::Hosts::Fedora } +Vagrant.hosts.register(:linux) { Vagrant::Hosts::Linux } +Vagrant.hosts.register(:bsd) { Vagrant::Hosts::BSD } diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 13e565e12..22c723cbc 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -179,7 +179,16 @@ module Vagrant # # @return [Hosts::Base] def host - @host ||= Hosts::Base.load(self, config.vagrant.host) + return @host if defined?(@host) + + # Attempt to figure out the host class. Note that the order + # matters here, so please don't touch. Specifically: The symbol + # check is done after the detect check because the symbol check + # will return nil, and we don't want to trigger a detect load. + host_klass = config.global.vagrant.host + host_klass = Hosts.detect(Vagrant.hosts) if host_klass.nil? || host_klass == :detect + host_klass = Vagrant.hosts.get(host_klass) if host_klass.is_a?(Symbol) + @host ||= host_klass.new(@ui) end # Action runner for executing actions in the context of this environment. diff --git a/lib/vagrant/hosts.rb b/lib/vagrant/hosts.rb index a5115a957..9ed48239f 100644 --- a/lib/vagrant/hosts.rb +++ b/lib/vagrant/hosts.rb @@ -6,5 +6,21 @@ module Vagrant autoload :FreeBSD,'vagrant/hosts/freebsd' autoload :Fedora, 'vagrant/hosts/fedora' autoload :Linux, 'vagrant/hosts/linux' + + # This method detects the correct host based on the `match?` methods + # implemented in the registered hosts. + def self.detect(registry) + # Sort the hosts by their precedence + host_klasses = registry.to_hash.values + host_klasses = host_klasses.sort_by { |a| a.precedence }.reverse + + # Test for matches and return the host class that matches + host_klasses.each do |klass| + return klass if klass.match? + end + + # No matches found... + return nil + end end end diff --git a/lib/vagrant/hosts/arch.rb b/lib/vagrant/hosts/arch.rb index c53e17936..f9e391b7d 100644 --- a/lib/vagrant/hosts/arch.rb +++ b/lib/vagrant/hosts/arch.rb @@ -1,13 +1,17 @@ module Vagrant module Hosts class Arch < Linux - def nfs_export(ip, folders) + def self.match? + File.exist?("/etc/rc.conf") && File.exist?("/etc/pacman.conf") + end + + def nfs_export(id, ip, folders) output = TemplateRenderer.render('nfs/exports_linux', - :uuid => env.vm.uuid, + :uuid => id, :ip => ip, :folders => folders) - env.ui.info I18n.t("vagrant.hosts.arch.nfs_export.prepare") + @ui.info I18n.t("vagrant.hosts.arch.nfs_export.prepare") sleep 0.5 output.split("\n").each do |line| diff --git a/lib/vagrant/hosts/base.rb b/lib/vagrant/hosts/base.rb index 5b7f5f39b..7c7868d5e 100644 --- a/lib/vagrant/hosts/base.rb +++ b/lib/vagrant/hosts/base.rb @@ -1,54 +1,39 @@ module Vagrant module Hosts - # Base class representing a host machine. These classes - # define methods which may have host-specific (Mac OS X, Windows, - # Linux, etc) behavior. The class is automatically determined by - # default but may be explicitly set via `config.vagrant.host`. + # Interface for classes which house behavior that is specific + # to the host OS that is running Vagrant. + # + # By default, Vagrant will attempt to choose the best option + # for your machine, but the host may also be explicitly set + # via the `config.vagrant.host` parameter. class Base - # The {Environment} which this host belongs to. - attr_reader :env - - # Loads the proper host for the given value. If the value is nil - # or is the symbol `:detect`, then the host class will be detected - # using the `RUBY_PLATFORM` constant. + # This returns true/false depending on if the current running system + # matches the host class. # - # @param [Environment] env - # @param [String] klass - # @return [Base] - def self.load(env, klass) - klass = detect if klass.nil? || klass == :detect - return nil if !klass - return klass.new(env) - end - - # Detects the proper host class for current platform and returns - # the class. - # - # @return [Class] - def self.detect - [BSD, Linux].each do |type| - result = type.distro_dispatch - return result if result - end - - nil - rescue Exception + # @return [Boolean] + def self.match? nil end - # This must be implemented by subclasses to dispatch to the proper - # distro-specific class for the host. If this returns nil then it is - # an invalid host class. - def self.distro_dispatch - nil + # The precedence of the host when checking for matches. This is to + # allow certain host such as generic OS's ("Linux", "BSD", etc.) + # to be specified last. + # + # The hosts with the higher numbers will be checked first. + # + # If you're implementing a basic host, you can probably ignore this. + def self.precedence + 5 end - # Initialzes a new host. This method shouldn't be called directly, - # typically, since it will be called by {Environment#load!}. + # Initializes a new host class. # - # @param [Environment] env - def initialize(env) - @env = env + # The only required parameter is a UI object so that the host + # objects have some way to communicate with the outside world. + # + # @param [UI] ui UI for the hosts to output to. + def initialize(ui) + @ui = ui end # Returns true of false denoting whether or not this host supports @@ -60,16 +45,19 @@ module Vagrant false end - # Exports the given hash of folders via NFS. This method will raise - # an {Vagrant::Action::ActionException} if anything goes wrong. + # Exports the given hash of folders via NFS. # + # @param [String] id A unique ID that is guaranteed to be unique to + # match these sets of folders. # @param [String] ip IP of the guest machine. # @param [Hash] folders Shared folders to sync. - def nfs_export(ip, folders) + def nfs_export(id, ip, folders) end - # Cleans up the exports for the current VM. - def nfs_cleanup + # Cleans up the exports for the given ID. + # + # @param [String] id A unique ID to identify the folder set to cleanup. + def nfs_cleanup(id) end end end diff --git a/lib/vagrant/hosts/bsd.rb b/lib/vagrant/hosts/bsd.rb index bee7187ae..0e8c85800 100644 --- a/lib/vagrant/hosts/bsd.rb +++ b/lib/vagrant/hosts/bsd.rb @@ -7,9 +7,20 @@ module Vagrant include Util include Util::Retryable - def self.distro_dispatch - return FreeBSD if Util::Platform.freebsd? - return self if Util::Platform.darwin? || Util::Platform.bsd? + def self.match? + Util::Platform.darwin? || Util::Platform.bsd? + end + + def self.precedence + # Set a lower precedence because this is a generic OS. We + # want specific distros to match first. + 2 + end + + def initialize(*args) + super + + @nfs_restart_command = "sudo nfsd restart" end def nfs? @@ -18,15 +29,15 @@ module Vagrant end end - def nfs_export(ip, folders) + def nfs_export(id, ip, folders) output = TemplateRenderer.render('nfs/exports', - :uuid => env.vm.uuid, + :uuid => id, :ip => ip, :folders => folders) # The sleep ensures that the output is truly flushed before any `sudo` # commands are issued. - env.ui.info I18n.t("vagrant.hosts.bsd.nfs_export.prepare") + @ui.info I18n.t("vagrant.hosts.bsd.nfs_export.prepare") sleep 0.5 output.split("\n").each do |line| @@ -38,19 +49,20 @@ module Vagrant # We run restart here instead of "update" just in case nfsd # is not starting - system("sudo nfsd restart") + system(@nfs_restart_command) end - def nfs_cleanup + def nfs_cleanup(id) return if !File.exist?("/etc/exports") retryable(:tries => 10, :on => TypeError) do - system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{env.vm.uuid}' > /dev/null 2>&1") + system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{id}' > /dev/null 2>&1") if $?.to_i == 0 # Use sed to just strip out the block of code which was inserted - # by Vagrant - system("sudo sed -e '/^# VAGRANT-BEGIN: #{env.vm.uuid}/,/^# VAGRANT-END: #{env.vm.uuid}/ d' -ibak /etc/exports") + # by Vagrant, and restart NFS. + system("sudo sed -e '/^# VAGRANT-BEGIN: #{id}/,/^# VAGRANT-END: #{id}/ d' -ibak /etc/exports") + system(@nfs_restart_command) end end end diff --git a/lib/vagrant/hosts/fedora.rb b/lib/vagrant/hosts/fedora.rb index b85d08f2f..b092a3072 100644 --- a/lib/vagrant/hosts/fedora.rb +++ b/lib/vagrant/hosts/fedora.rb @@ -1,6 +1,20 @@ +require 'pathname' + module Vagrant module Hosts class Fedora < Linux + def self.match? + release_file = Pathname.new("/etc/redhat-release") + + if release_file.exist? + release_file.open("r") do |f| + return true if f.gets =~ /^Fedora/ + end + end + + false + end + def initialize(*args) super diff --git a/lib/vagrant/hosts/freebsd.rb b/lib/vagrant/hosts/freebsd.rb index f61f1bf04..ea19dcc48 100644 --- a/lib/vagrant/hosts/freebsd.rb +++ b/lib/vagrant/hosts/freebsd.rb @@ -7,45 +7,15 @@ module Vagrant include Util include Util::Retryable - def nfs_export(ip, folders) - output = TemplateRenderer.render('nfs/exports', - :uuid => env.vm.uuid, - :ip => ip, - :folders => folders) - - # The sleep ensures that the output is truly flushed before any `sudo` - # commands are issued. - env.ui.info I18n.t("vagrant.hosts.bsd.nfs_export.prepare") - sleep 0.5 - - output.split("\n").each do |line| - # This should only ask for administrative permission once, even - # though its executed in multiple subshells. - line = line.gsub('"', '\"') - system(%Q[sudo su root -c "echo '#{line}' >> /etc/exports"]) - end - - # We run restart here instead of "update" just in case nfsd - # is not starting - system("sudo /etc/rc.d/mountd onereload") + def self.match? + Util::Platform.freebsd? end + def initialize(*args) + super + + @nfs_restart_command = "sudo /etc/rc.d/mountd onereload" + end end - - def nfs_cleanup - return if !File.exist?("/etc/exports") - - retryable(:tries => 10, :on => TypeError) do - system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{env.vm.uuid}' > /dev/null 2>&1") - - if $?.to_i == 0 - # Use sed to just strip out the block of code which was inserted - # by Vagrant - system("sudo sed -e '/^# VAGRANT-BEGIN: #{env.vm.uuid}/,/^# VAGRANT-END: #{env.vm.uuid}/ d' -ibak /etc/exports") - end - - system("sudo /etc/rc.d/mountd onereload") - end - end end end diff --git a/lib/vagrant/hosts/linux.rb b/lib/vagrant/hosts/linux.rb index 40375483e..f7107d2cf 100644 --- a/lib/vagrant/hosts/linux.rb +++ b/lib/vagrant/hosts/linux.rb @@ -7,18 +7,14 @@ module Vagrant include Util include Util::Retryable - def self.distro_dispatch - return nil if !Util::Platform.linux? - return Arch if File.exist?("/etc/rc.conf") && File.exist?("/etc/pacman.conf") + def self.match? + Util::Platform.linux? + end - if File.exist?("/etc/redhat-release") - # Check if we have a known redhat release - File.open("/etc/redhat-release") do |f| - return Fedora if f.gets =~ /^Fedora/ - end - end - - return self + def self.precedence + # Set a lower precedence because this is a generic OS. We + # want specific distros to match first. + 2 end def initialize(*args) @@ -34,13 +30,13 @@ module Vagrant end end - def nfs_export(ip, folders) + def nfs_export(id, ip, folders) output = TemplateRenderer.render('nfs/exports_linux', - :uuid => env.vm.uuid, + :uuid => id, :ip => ip, :folders => folders) - env.ui.info I18n.t("vagrant.hosts.linux.nfs_export.prepare") + @ui.info I18n.t("vagrant.hosts.linux.nfs_export.prepare") sleep 0.5 output.split("\n").each do |line| @@ -54,14 +50,14 @@ module Vagrant system("sudo #{@nfs_server_binary} restart") end - def nfs_cleanup + def nfs_cleanup(id) return if !File.exist?("/etc/exports") - system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{env.vm.uuid}' > /dev/null 2>&1") + system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{id}' > /dev/null 2>&1") if $?.to_i == 0 # Use sed to just strip out the block of code which was inserted # by Vagrant - system("sudo sed -e '/^# VAGRANT-BEGIN: #{env.vm.uuid}/,/^# VAGRANT-END: #{env.vm.uuid}/ d' -ibak /etc/exports") + system("sudo sed -e '/^# VAGRANT-BEGIN: #{id}/,/^# VAGRANT-END: #{id}/ d' -ibak /etc/exports") end end end diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index 0d608c72f..ef025ec05 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -35,5 +35,15 @@ module Vagrant yield key, get(key) end end + + # Converts this registry to a hash + def to_hash + result = {} + self.each do |key, value| + result[key] = value + end + + result + end end end diff --git a/test/unit/vagrant/hosts_test.rb b/test/unit/vagrant/hosts_test.rb new file mode 100644 index 000000000..60fe6ff68 --- /dev/null +++ b/test/unit/vagrant/hosts_test.rb @@ -0,0 +1,36 @@ +require File.expand_path("../../base", __FILE__) + +describe Vagrant::Hosts do + let(:registry) { Vagrant::Registry.new } + + it "detects the host that matches true" do + foo_klass = Class.new(Vagrant::Hosts::Base) do + def self.match?; false; end + end + + bar_klass = Class.new(Vagrant::Hosts::Base) do + def self.match?; true; end + end + + registry.register(:foo, foo_klass) + registry.register(:bar, bar_klass) + + described_class.detect(registry).should == bar_klass + end + + it "detects the host that matches true with the highest precedence first" do + foo_klass = Class.new(Vagrant::Hosts::Base) do + def self.match?; true; end + end + + bar_klass = Class.new(Vagrant::Hosts::Base) do + def self.match?; true; end + def self.precedence; 9; end + end + + registry.register(:foo, foo_klass) + registry.register(:bar, bar_klass) + + described_class.detect(registry).should == bar_klass + end +end diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index 5c74668e6..8d2518083 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -43,4 +43,14 @@ describe Vagrant::Registry do keys.sort.should == ["bar", "foo"] values.sort.should == ["barvalue", "foovalue"] end + + it "should be able to convert to a hash" do + instance.register("foo", "foovalue") + instance.register("bar", "barvalue") + + result = instance.to_hash + result.should be_a(Hash) + result["foo"].should == "foovalue" + result["bar"].should == "barvalue" + end end