Merge pull request #2784 from mitchellh/f-host-capabilities

Host Capabilities
This commit is contained in:
Mitchell Hashimoto 2014-01-08 08:54:26 -08:00
commit 9a419c3f63
54 changed files with 1084 additions and 662 deletions

View File

@ -93,7 +93,7 @@ module Vagrant
autoload :Environment, 'vagrant/environment'
autoload :Errors, 'vagrant/errors'
autoload :Guest, 'vagrant/guest'
autoload :Hosts, 'vagrant/hosts'
autoload :Host, 'vagrant/host'
autoload :Machine, 'vagrant/machine'
autoload :MachineState, 'vagrant/machine_state'
autoload :Plugin, 'vagrant/plugin'

View File

@ -487,22 +487,34 @@ module Vagrant
def 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.
# Determine the host class to use. ":detect" is an old Vagrant config
# that shouldn't be valid anymore, but we respect it here by assuming
# its old behavior. No need to deprecate this because I thin it is
# fairly harmless.
host_klass = config_global.vagrant.host
if host_klass.nil? || host_klass == :detect
hosts = Vagrant.plugin("2").manager.hosts.to_hash
host_klass = nil if host_klass == :detect
# Get the flattened list of available hosts
host_klass = Hosts.detect(hosts)
begin
@host = Host.new(
host_klass,
Vagrant.plugin("2").manager.hosts,
Vagrant.plugin("2").manager.host_capabilities,
self)
rescue Errors::CapabilityHostNotDetected
# If the auto-detect failed, then we create a brand new host
# with no capabilities and use that. This should almost never happen
# since Vagrant works on most host OS's now, so this is a "slow path"
klass = Class.new(Vagrant.plugin("2", :host)) do
def detect?(env); true; end
end
# If no host class is detected, we use the base class.
host_klass ||= Vagrant.plugin("2", :host)
hosts = { generic: [klass, nil] }
host_caps = {}
@host ||= host_klass.new(@ui)
@host = Host.new(:generic, hosts, host_caps, self)
rescue Errors::CapabilityHostExplicitNotDetected => e
raise Errors::HostExplicitNotDetected, e.extra_data
end
end
# Action runner for executing actions in the context of this environment.

View File

@ -333,6 +333,10 @@ module Vagrant
error_key(:guest_not_detected)
end
class HostExplicitNotDetected < VagrantError
error_key(:host_explicit_not_detected)
end
class LinuxMountFailed < VagrantError
error_key(:linux_mount_failed)
end

16
lib/vagrant/host.rb Normal file
View File

@ -0,0 +1,16 @@
require "vagrant/capability_host"
module Vagrant
# This class handles host-OS specific interations. It is responsible for
# detecting the proper host OS implementation and delegating capabilities
# to plugins.
#
# See {Guest} for more information on capabilities.
class Host
include CapabilityHost
def initialize(host, hosts, capabilities, env)
initialize_capabilities!(host, hosts, capabilities, env)
end
end
end

View File

@ -1,28 +0,0 @@
require 'log4r'
module Vagrant
module Hosts
# This method detects the correct host based on the `match?` methods
# implemented in the registered hosts.
#
# @param [Hash] registry Hash mapping key to host class
def self.detect(registry)
logger = Log4r::Logger.new("vagrant::hosts")
# Sort the hosts by their precedence
host_klasses = registry.values.sort_by { |a| a.precedence }.reverse
logger.debug("Host path search classes: #{host_klasses.inspect}")
# Test for matches and return the host class that matches
host_klasses.each do |klass|
if klass.match?
logger.info("Host class: #{klass}")
return klass
end
end
# No matches found...
return nil
end
end
end

View File

@ -26,6 +26,16 @@ module Vagrant
# @return [Hash<Symbol, Registry>]
attr_reader :guest_capabilities
# This contains all the hosts and their parents.
#
# @return [Registry<Symbol, Array<Class, Symbol>>]
attr_reader :hosts
# This contains all the registered host capabilities.
#
# @return [Hash<Symbol, Registry>]
attr_reader :host_capabilities
# This contains all the provider plugins by name, and returns
# the provider class and options.
#
@ -44,6 +54,8 @@ module Vagrant
@configs = Hash.new { |h, k| h[k] = Registry.new }
@guests = Registry.new
@guest_capabilities = Hash.new { |h, k| h[k] = Registry.new }
@hosts = Registry.new
@host_capabilities = Hash.new { |h, k| h[k] = Registry.new }
@providers = Registry.new
@synced_folders = Registry.new
end

View File

@ -10,56 +10,9 @@ module Vagrant
# matches the host class.
#
# @return [Boolean]
def self.match?
nil
end
# 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
# Initializes a new host class.
#
# 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
# NFS shared folder setup. This method ideally should verify that
# NFS is installed.
#
# @return [Boolean]
def nfs?
def detect?(env)
false
end
# 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(id, ip, folders)
end
# Prunes any NFS exports made by Vagrant which aren't in the set
# of valid ids given.
#
# @param [Array<String>] valid_ids Valid IDs that should not be
# pruned.
def nfs_prune(valid_ids)
end
end
end
end

View File

@ -89,17 +89,33 @@ module Vagrant
results
end
# This returns all registered host classes.
# This returns all the registered guests.
#
# @return [Hash]
def hosts
Registry.new.tap do |result|
@registered.each do |plugin|
result.merge!(plugin.host)
result.merge!(plugin.components.hosts)
end
end
end
# This returns all the registered host capabilities.
#
# @return [Hash]
def host_capabilities
results = Hash.new { |h, k| h[k] = Registry.new }
@registered.each do |plugin|
plugin.components.host_capabilities.each do |host, caps|
results[host].merge!(caps)
end
end
results
end
# This returns all registered providers.
#
# @return [Hash]

View File

@ -135,7 +135,7 @@ module Vagrant
#
# @param [String] name Name of the guest.
# @param [String] parent Name of the parent guest (if any)
def self.guest(name=UNSET_VALUE, parent=nil, &block)
def self.guest(name, parent=nil, &block)
components.guests.register(name.to_sym) do
parent = parent.to_sym if parent
@ -160,14 +160,26 @@ module Vagrant
# the given key.
#
# @param [String] name Name of the host.
def self.host(name=UNSET_VALUE, &block)
data[:hosts] ||= Registry.new
# @param [String] parent Name of the parent host (if any)
def self.host(name, parent=nil, &block)
components.hosts.register(name.to_sym) do
parent = parent.to_sym if parent
# Register a new host class only if a name was given
data[:hosts].register(name.to_sym, &block) if name != UNSET_VALUE
[block.call, parent]
end
nil
end
# Return the registry
data[:hosts]
# Defines a capability for the given host. The block should return
# a class/module that has a method with the capability name, ready
# to be executed. This means that if it is an instance method,
# the block should return an instance of the class.
#
# @param [String] host The name of the host
# @param [String] cap The name of the capability
def self.host_capability(host, cap, &block)
components.host_capabilities[host.to_sym].register(cap.to_sym, &block)
nil
end
# Registers additional providers to be available.

View File

@ -8,5 +8,6 @@ module Vagrant
autoload :SafeExec, 'vagrant/util/safe_exec'
autoload :StackedProcRunner, 'vagrant/util/stacked_proc_runner'
autoload :TemplateRenderer, 'vagrant/util/template_renderer'
autoload :Subprocess, 'vagrant/util/subprocess'
end
end

View File

@ -0,0 +1,35 @@
module VagrantPlugins
module HostArch
module Cap
class NFS
def self.nfs_check_command(env)
if systemd?
return "/usr/sbin/systemctl status nfsd"
else
return "/etc/rc.d/nfs-server status"
end
end
def self.nfs_start_command(env)
if systemd?
return "/usr/sbin/systemctl start nfsd rpc-idmapd rpc-mountd rpcbind"
else
return "sh -c 'for s in {rpcbind,nfs-common,nfs-server}; do /etc/rc.d/$s start; done'"
end
end
def self.nfs_installed(environment)
Kernel.system("grep -Fq nfs /proc/filesystems")
end
protected
# This tests to see if systemd is used on the system. This is used
# in newer versions of Arch, and requires a change in behavior.
def self.systemd?
`ps -o comm= 1`.chomp == 'systemd'
end
end
end
end
end

View File

@ -1,46 +1,11 @@
require "vagrant"
require Vagrant.source_root.join("plugins/hosts/linux/host")
module VagrantPlugins
module HostArch
class Host < VagrantPlugins::HostLinux::Host
def self.match?
class Host < Vagrant.plugin("2", :host)
def detect?(env)
File.exist?("/etc/arch-release")
end
def self.nfs?
# HostLinux checks for nfsd which returns false unless the
# services are actively started. This leads to a misleading
# error message. Checking for nfs (no d) seems to work
# regardless. Also fixes useless use of cat, regex, and
# redirection.
Kernel.system("grep -Fq nfs /proc/filesystems")
end
# Normal, mid-range precedence.
def self.precedence
5
end
def initialize(*args)
super
if systemd?
@nfs_check_command = "/usr/sbin/systemctl status nfsd"
@nfs_start_command = "/usr/sbin/systemctl start nfsd rpc-idmapd rpc-mountd rpcbind"
else
@nfs_check_command = "/etc/rc.d/nfs-server status"
@nfs_start_command = "sh -c 'for s in {rpcbind,nfs-common,nfs-server}; do /etc/rc.d/$s start; done'"
end
end
protected
# This tests to see if systemd is used on the system. This is used
# in newer versions of Arch, and requires a change in behavior.
def systemd?
`ps -o comm= 1`.chomp == 'systemd'
end
end
end
end

View File

@ -6,10 +6,27 @@ module VagrantPlugins
name "Arch host"
description "Arch host support."
host("arch") do
require File.expand_path("../host", __FILE__)
host("arch", "linux") do
require_relative "host"
Host
end
host_capability("arch", "nfs_installed") do
require_relative "cap/nfs"
Cap::NFS
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("arch", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("arch", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,183 @@
require "log4r"
require "vagrant/util"
module VagrantPlugins
module HostBSD
module Cap
class NFS
extend Vagrant::Util::Retryable
def self.nfs_export(environment, ui, id, ips, folders)
nfs_exports_template = environment.host.capability(:nfs_exports_template)
nfs_restart_command = environment.host.capability(:nfs_restart_command)
logger = Log4r::Logger.new("vagrant::hosts::bsd")
nfs_checkexports! if File.file?("/etc/exports")
# We need to build up mapping of directories that are enclosed
# within each other because the exports file has to have subdirectories
# of an exported directory on the same line. e.g.:
#
# "/foo" "/foo/bar" ...
# "/bar"
#
# We build up this mapping within the following hash.
logger.debug("Compiling map of sub-directories for NFS exports...")
dirmap = {}
folders.each do |_, opts|
hostpath = opts[:hostpath].dup
hostpath.gsub!('"', '\"')
found = false
dirmap.each do |dirs, diropts|
dirs.each do |dir|
if dir.start_with?(hostpath) || hostpath.start_with?(dir)
# TODO: verify opts and diropts are _identical_, raise an error
# if not. NFS mandates subdirectories have identical options.
dirs << hostpath
found = true
break
end
end
break if found
end
if !found
dirmap[[hostpath]] = opts.dup
end
end
# Sort all the keys by length so that the directory closest to
# the root is exported first.
dirmap.each do |dirs, _|
dirs.sort_by! { |d| d.length }
end
# Setup the NFS options
dirmap.each do |dirs, opts|
if !opts[:bsd__nfs_options]
opts[:bsd__nfs_options] = ["alldirs"]
end
hasmapall = false
opts[:bsd__nfs_options].each do |opt|
# mapall/maproot are mutually exclusive, so we have to check
# for both here.
if opt =~ /^mapall=/ || opt =~ /^maproot=/
hasmapall = true
break
end
end
if !hasmapall
opts[:bsd__nfs_options] << "mapall=#{opts[:map_uid]}:#{opts[:map_gid]}"
end
opts[:bsd__compiled_nfs_options] = opts[:bsd__nfs_options].map do |opt|
"-#{opt}"
end.join(" ")
end
logger.info("Exporting the following for NFS...")
dirmap.each do |dirs, opts|
logger.info("NFS DIR: #{dirs.inspect}")
logger.info("NFS OPTS: #{opts.inspect}")
end
output = Vagrant::Util::TemplateRenderer.render(nfs_exports_template,
:uuid => id,
:ips => ips,
:folders => dirmap,
:user => Process.uid)
# The sleep ensures that the output is truly flushed before any `sudo`
# commands are issued.
ui.info I18n.t("vagrant.hosts.bsd.nfs_export")
sleep 0.5
# First, clean up the old entry
nfs_cleanup(id)
# Output the rendered template into the exports
output.split("\n").each do |line|
line.gsub!('"', '\"')
line.gsub!("'", "'\\\\''")
system(%Q[sudo -s -- "echo '#{line}' >> /etc/exports"])
end
# We run restart here instead of "update" just in case nfsd
# is not starting
system(nfs_restart_command)
end
def self.nfs_exports_template(environment)
"nfs/exports"
end
def self.nfs_installed(environment)
retryable(:tries => 10, :on => TypeError) do
system("which nfsd > /dev/null 2>&1")
end
end
def self.nfs_prune(environment, ui, valid_ids)
return if !File.exist?("/etc/exports")
logger = Log4r::Logger.new("vagrant::hosts::bsd")
logger.info("Pruning invalid NFS entries...")
output = false
user = Process.uid
File.read("/etc/exports").lines.each do |line|
if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2]
if valid_ids.include?(id)
logger.debug("Valid ID: #{id}")
else
if !output
# We want to warn the user but we only want to output once
ui.info I18n.t("vagrant.hosts.bsd.nfs_prune")
output = true
end
logger.info("Invalid ID, pruning: #{id}")
nfs_cleanup(id)
end
end
end
rescue Errno::EACCES
raise Vagrant::Errors::NFSCantReadExports
end
def self.nfs_restart_command(environment)
"sudo nfsd restart"
end
protected
def self.nfs_cleanup(id)
return if !File.exist?("/etc/exports")
# Escape sed-sensitive characters:
id = id.gsub("/", "\\/")
id = id.gsub(".", "\\.")
user = Process.uid
# Use sed to just strip out the block of code which was inserted
# by Vagrant, and restart NFS.
system("sudo sed -E -e '/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d' -ibak /etc/exports")
end
def self.nfs_checkexports!
r = Vagrant::Util::Subprocess.execute("nfsd", "checkexports")
if r.exit_code != 0
raise Vagrant::Errors::NFSBadExports, output: r.stderr
end
end
end
end
end
end

View File

@ -1,190 +1,12 @@
require 'log4r'
require "vagrant"
require 'vagrant/util/platform'
require "vagrant/util/subprocess"
module VagrantPlugins
module HostBSD
# Represents a BSD host, such as FreeBSD and Darwin (Mac OS X).
class Host < Vagrant.plugin("2", :host)
include Vagrant::Util
include Vagrant::Util::Retryable
def self.match?
def detect?(env)
Vagrant::Util::Platform.darwin? || Vagrant::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
@logger = Log4r::Logger.new("vagrant::hosts::bsd")
@nfs_restart_command = "sudo nfsd restart"
@nfs_exports_template = "nfs/exports"
end
def nfs?
retryable(:tries => 10, :on => TypeError) do
system("which nfsd > /dev/null 2>&1")
end
end
def nfs_export(id, ips, folders)
nfs_checkexports! if File.file?("/etc/exports")
# We need to build up mapping of directories that are enclosed
# within each other because the exports file has to have subdirectories
# of an exported directory on the same line. e.g.:
#
# "/foo" "/foo/bar" ...
# "/bar"
#
# We build up this mapping within the following hash.
@logger.debug("Compiling map of sub-directories for NFS exports...")
dirmap = {}
folders.each do |_, opts|
hostpath = opts[:hostpath].dup
hostpath.gsub!('"', '\"')
found = false
dirmap.each do |dirs, diropts|
dirs.each do |dir|
if dir.start_with?(hostpath) || hostpath.start_with?(dir)
# TODO: verify opts and diropts are _identical_, raise an error
# if not. NFS mandates subdirectories have identical options.
dirs << hostpath
found = true
break
end
end
break if found
end
if !found
dirmap[[hostpath]] = opts.dup
end
end
# Sort all the keys by length so that the directory closest to
# the root is exported first.
dirmap.each do |dirs, _|
dirs.sort_by! { |d| d.length }
end
# Setup the NFS options
dirmap.each do |dirs, opts|
if !opts[:bsd__nfs_options]
opts[:bsd__nfs_options] = ["alldirs"]
end
hasmapall = false
opts[:bsd__nfs_options].each do |opt|
# mapall/maproot are mutually exclusive, so we have to check
# for both here.
if opt =~ /^mapall=/ || opt =~ /^maproot=/
hasmapall = true
break
end
end
if !hasmapall
opts[:bsd__nfs_options] << "mapall=#{opts[:map_uid]}:#{opts[:map_gid]}"
end
opts[:bsd__compiled_nfs_options] = opts[:bsd__nfs_options].map do |opt|
"-#{opt}"
end.join(" ")
end
@logger.info("Exporting the following for NFS...")
dirmap.each do |dirs, opts|
@logger.info("NFS DIR: #{dirs.inspect}")
@logger.info("NFS OPTS: #{opts.inspect}")
end
output = TemplateRenderer.render(@nfs_exports_template,
:uuid => id,
:ips => ips,
:folders => dirmap,
:user => Process.uid)
# The sleep ensures that the output is truly flushed before any `sudo`
# commands are issued.
@ui.info I18n.t("vagrant.hosts.bsd.nfs_export")
sleep 0.5
# First, clean up the old entry
nfs_cleanup(id)
# Output the rendered template into the exports
output.split("\n").each do |line|
line.gsub!('"', '\"')
line.gsub!("'", "'\\\\''")
system(%Q[sudo -s -- "echo '#{line}' >> /etc/exports"])
end
# We run restart here instead of "update" just in case nfsd
# is not starting
system(@nfs_restart_command)
end
def nfs_prune(valid_ids)
return if !File.exist?("/etc/exports")
@logger.info("Pruning invalid NFS entries...")
output = false
user = Process.uid
File.read("/etc/exports").lines.each do |line|
if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2]
if valid_ids.include?(id)
@logger.debug("Valid ID: #{id}")
else
if !output
# We want to warn the user but we only want to output once
@ui.info I18n.t("vagrant.hosts.bsd.nfs_prune")
output = true
end
@logger.info("Invalid ID, pruning: #{id}")
nfs_cleanup(id)
end
end
end
rescue Errno::EACCES
raise Vagrant::Errors::NFSCantReadExports
end
protected
def nfs_checkexports!
r = Subprocess.execute("nfsd", "checkexports")
if r.exit_code != 0
raise Vagrant::Errors::NFSBadExports, output: r.stderr
end
end
def nfs_cleanup(id)
return if !File.exist?("/etc/exports")
# Escape sed-sensitive characters:
id = id.gsub("/", "\\/")
id = id.gsub(".", "\\.")
user = Process.uid
# Use sed to just strip out the block of code which was inserted
# by Vagrant, and restart NFS.
system("sudo sed -E -e '/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d' -ibak /etc/exports")
end
end
end
end

View File

@ -10,6 +10,31 @@ module VagrantPlugins
require File.expand_path("../host", __FILE__)
Host
end
host_capability("bsd", "nfs_export") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("bsd", "nfs_exports_template") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("bsd", "nfs_installed") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("bsd", "nfs_prune") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("bsd", "nfs_restart_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,31 @@
require "vagrant/util"
require "vagrant/util/retryable"
require Vagrant.source_root.join("plugins", "hosts", "bsd", "cap", "nfs")
module VagrantPlugins
module HostFreeBSD
module Cap
class NFS
def self.nfs_export(environment, ui, id, ips, folders)
folders.each do |folder_name, folder_values|
if folder_values[:hostpath] =~ /\s+/
raise Vagrant::Errors::VagrantError,
_key: :freebsd_nfs_whitespace
end
end
HostBSD::Cap::NFS.nfs_export(environment, ui, id, ips, folders)
end
def self.nfs_exports_template(environment)
"nfs/exports_freebsd"
end
def self.nfs_restart_command(environment)
"sudo /etc/rc.d/mountd onereload"
end
end
end
end
end

View File

@ -1,43 +1,12 @@
require "vagrant"
require 'vagrant/util/platform'
require Vagrant.source_root.join("plugins/hosts/bsd/host")
module VagrantPlugins
module HostFreeBSD
class Host < VagrantPlugins::HostBSD::Host
class FreeBSDHostError < Vagrant::Errors::VagrantError
error_namespace("vagrant.hosts.freebsd")
end
include Vagrant::Util
include Vagrant::Util::Retryable
def self.match?
class Host < Vagrant.plugin("2", :host)
def detect?(env)
Vagrant::Util::Platform.freebsd?
end
# Normal, mid-range precedence.
def self.precedence
5
end
def nfs_export(id, ip, folders)
folders.each do |folder_name, folder_values|
if folder_values[:hostpath] =~ /\s+/
raise FreeBSDHostError, :_key => :nfs_whitespace
end
end
super
end
def initialize(*args)
super
@nfs_restart_command = "sudo /etc/rc.d/mountd onereload"
@nfs_exports_template = "nfs/exports_freebsd"
end
end
end
end

View File

@ -6,10 +6,26 @@ module VagrantPlugins
name "FreeBSD host"
description "FreeBSD host support."
host("freebsd") do
require File.expand_path("../host", __FILE__)
host("freebsd", "bsd") do
require_relative "host"
Host
end
host_capability("freebsd", "nfs_export") do
require_relative "cap/nfs"
Cap::NFS
end
# BSD-specific helpers
host_capability("freebsd", "nfs_exports_template") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("freebsd", "nfs_restart_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,34 @@
require "vagrant/util/subprocess"
module VagrantPlugins
module HostGentoo
module Cap
class NFS
def self.nfs_check_command(env)
if systemd?
return "/usr/sbin/systemctl status nfsd"
else
return "/etc/init.d/nfs status"
end
end
def self.nfs_start_command(env)
if systemd?
return "/usr/sbin/systemctl start nfsd rpc-mountd rpcbind"
else
return "/etc/init.d/nfs restart"
end
end
protected
# This tests to see if systemd is used on the system. This is used
# in newer versions of Arch, and requires a change in behavior.
def self.systemd?
result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
return result.stdout.chomp == "systemd"
end
end
end
end
end

View File

@ -1,39 +1,10 @@
require "vagrant"
require "vagrant/util/subprocess"
require Vagrant.source_root.join("plugins/hosts/linux/host")
module VagrantPlugins
module HostGentoo
class Host < VagrantPlugins::HostLinux::Host
def self.match?
return File.exists?("/etc/gentoo-release")
end
# Normal, mid-range precedence.
def self.precedence
5
end
def initialize(*args)
super
@nfs_apply_command = "/usr/sbin/exportfs -r"
if systemd?
@nfs_check_command = "/usr/bin/systemctl status nfsd"
@nfs_start_command = "/usr/bin/systemctl start nfsd rpc-mountd rpcbind"
else
@nfs_check_command = "/etc/init.d/nfs status"
@nfs_start_command = "/etc/init.d/nfs restart"
end
end
protected
# Check for systemd presence from current processes.
def systemd?
result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1")
return result.stdout.chomp == "systemd"
class Host < Vagrant.plugin("2", :host)
def detect?(env)
File.exists?("/etc/gentoo-release")
end
end
end

View File

@ -6,10 +6,23 @@ module VagrantPlugins
name "Gentoo host"
description "Gentoo host support."
host("gentoo") do
require File.expand_path("../host", __FILE__)
host("gentoo", "linux") do
require_relative "host"
Host
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("gentoo", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("gentoo", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,126 @@
require "vagrant/util"
require "vagrant/util/retryable"
module VagrantPlugins
module HostLinux
module Cap
class NFS
extend Vagrant::Util::Retryabe
def self.nfs_apply_command(env)
"/usr/bin/exportfs -r"
end
def self.nfs_check_command(env)
"/etc/init.d/nfs-kernel-server status"
end
def self.nfs_start_command(env)
"/etc/init.d/nfs-kernel-server start"
end
def self.nfs_export(env, ui, id, ips, folders)
# Get some values we need before we do anything
nfs_apply_command = env.host.capability(:nfs_apply_command)
nfs_check_command = env.host.capability(:nfs_check_command)
nfs_start_command = env.host.capability(:nfs_start_command)
nfs_opts_setup(folders)
output = Vagrant::Util::TemplateRenderer.render('nfs/exports_linux',
:uuid => id,
:ips => ips,
:folders => folders,
:user => Process.uid)
ui.info I18n.t("vagrant.hosts.linux.nfs_export")
sleep 0.5
nfs_cleanup(id)
output.split("\n").each do |line|
# This should only ask for administrative permission once, even
# though its executed in multiple subshells.
system(%Q[sudo su root -c "echo '#{line}' >> /etc/exports"])
end
if nfs_running?(nfs_check_command)
system("sudo #{nfs_apply_command}")
else
system("sudo #{nfs_start_command}")
end
end
def self.nfs_installed(environment)
retryable(:tries => 10, :on => TypeError) do
# Check procfs to see if NFSd is a supported filesystem
system("cat /proc/filesystems | grep nfsd > /dev/null 2>&1")
end
end
def self.nfs_prune(environment, ui, valid_ids)
return if !File.exist?("/etc/exports")
logger = Log4r::Logger.new("vagrant::hosts::linux")
logger.info("Pruning invalid NFS entries...")
output = false
user = Process.uid
File.read("/etc/exports").lines.each do |line|
if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2]
if valid_ids.include?(id)
logger.debug("Valid ID: #{id}")
else
if !output
# We want to warn the user but we only want to output once
ui.info I18n.t("vagrant.hosts.linux.nfs_prune")
output = true
end
logger.info("Invalid ID, pruning: #{id}")
nfs_cleanup(id)
end
end
end
end
protected
def self.nfs_cleanup(id)
return if !File.exist?("/etc/exports")
user = Process.uid
# Use sed to just strip out the block of code which was inserted
# by Vagrant
system("sudo sed -r -e '/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d' -ibak /etc/exports")
end
def self.nfs_opts_setup(folders)
folders.each do |k, opts|
if !opts[:linux__nfs_options]
opts[:linux__nfs_options] ||= ["rw", "no_subtree_check", "all_squash"]
end
# Only automatically set anonuid/anongid if they weren't
# explicitly set by the user.
hasgid = false
hasuid = false
opts[:linux__nfs_options].each do |opt|
hasgid = !!(opt =~ /^anongid=/) if !hasgid
hasuid = !!(opt =~ /^anonuid=/) if !hasuid
end
opts[:linux__nfs_options] << "anonuid=#{opts[:map_uid]}" if !hasuid
opts[:linux__nfs_options] << "anongid=#{opts[:map_gid]}" if !hasgid
opts[:linux__nfs_options] << "fsid=#{opts[:uuid]}"
end
end
def self.nfs_running?(check_command)
system(check_command)
end
end
end
end
end

View File

@ -1,128 +1,12 @@
require 'log4r'
require "vagrant"
require 'vagrant/util/platform'
module VagrantPlugins
module HostLinux
# Represents a Linux based host, such as Ubuntu.
class Host < Vagrant.plugin("2", :host)
include Vagrant::Util
include Vagrant::Util::Retryable
def self.match?
def detect?(env)
Vagrant::Util::Platform.linux?
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
@logger = Log4r::Logger.new("vagrant::hosts::linux")
@nfs_apply_command = "/usr/sbin/exportfs -r"
@nfs_check_command = "/etc/init.d/nfs-kernel-server status"
@nfs_start_command = "/etc/init.d/nfs-kernel-server start"
end
def nfs?
retryable(:tries => 10, :on => TypeError) do
# Check procfs to see if NFSd is a supported filesystem
system("cat /proc/filesystems | grep nfsd > /dev/null 2>&1")
end
end
def nfs_opts_setup(folders)
folders.each do |k, opts|
if !opts[:linux__nfs_options]
opts[:linux__nfs_options] ||= ["rw", "no_subtree_check", "all_squash"]
end
# Only automatically set anonuid/anongid if they weren't
# explicitly set by the user.
hasgid = false
hasuid = false
opts[:linux__nfs_options].each do |opt|
hasgid = !!(opt =~ /^anongid=/) if !hasgid
hasuid = !!(opt =~ /^anonuid=/) if !hasuid
end
opts[:linux__nfs_options] << "anonuid=#{opts[:map_uid]}" if !hasuid
opts[:linux__nfs_options] << "anongid=#{opts[:map_gid]}" if !hasgid
opts[:linux__nfs_options] << "fsid=#{opts[:uuid]}"
end
end
def nfs_export(id, ips, folders)
nfs_opts_setup(folders)
output = TemplateRenderer.render('nfs/exports_linux',
:uuid => id,
:ips => ips,
:folders => folders,
:user => Process.uid)
@ui.info I18n.t("vagrant.hosts.linux.nfs_export")
sleep 0.5
nfs_cleanup(id)
output.split("\n").each do |line|
# This should only ask for administrative permission once, even
# though its executed in multiple subshells.
system(%Q[sudo su root -c "echo '#{line}' >> /etc/exports"])
end
if nfs_running?
system("sudo #{@nfs_apply_command}")
else
system("sudo #{@nfs_start_command}")
end
end
def nfs_prune(valid_ids)
return if !File.exist?("/etc/exports")
@logger.info("Pruning invalid NFS entries...")
output = false
user = Process.uid
File.read("/etc/exports").lines.each do |line|
if id = line[/^# VAGRANT-BEGIN:( #{user})? ([A-Za-z0-9-]+?)$/, 2]
if valid_ids.include?(id)
@logger.debug("Valid ID: #{id}")
else
if !output
# We want to warn the user but we only want to output once
@ui.info I18n.t("vagrant.hosts.linux.nfs_prune")
output = true
end
@logger.info("Invalid ID, pruning: #{id}")
nfs_cleanup(id)
end
end
end
end
protected
def nfs_running?
system("#{@nfs_check_command}")
end
def nfs_cleanup(id)
return if !File.exist?("/etc/exports")
user = Process.uid
# Use sed to just strip out the block of code which was inserted
# by Vagrant
system("sudo sed -r -e '/^# VAGRANT-BEGIN:( #{user})? #{id}/,/^# VAGRANT-END:( #{user})? #{id}/ d' -ibak /etc/exports")
end
end
end
end

View File

@ -7,9 +7,41 @@ module VagrantPlugins
description "Linux host support."
host("linux") do
require File.expand_path("../host", __FILE__)
require_relative "host"
Host
end
host_capability("linux", "nfs_export") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("linux", "nfs_installed") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("linux", "nfs_prune") do
require_relative "cap/nfs"
Cap::NFS
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("linux", "nfs_apply_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("linux", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("linux", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,12 @@
require "vagrant"
module VagrantPlugins
module HostNull
class Host < Vagrant.plugin("2", :host)
def detect?(env)
# This host can only be explicitly chosen.
false
end
end
end
end

View File

@ -0,0 +1,15 @@
require "vagrant"
module VagrantPlugins
module HostNull
class Plugin < Vagrant.plugin("2")
name "null host"
description "A host that implements no capabilities."
host("null") do
require_relative "host"
Host
end
end
end
end

View File

@ -0,0 +1,15 @@
module VagrantPlugins
module HostOpenSUSE
module Cap
class NFS
def self.nfs_check_command(env)
"service nfsserver status"
end
def self.nfs_start_command(env)
"service nfsserver start"
end
end
end
end
end

View File

@ -2,12 +2,10 @@ require "pathname"
require "vagrant"
require Vagrant.source_root.join("plugins/hosts/linux/host")
module VagrantPlugins
module HostOpenSUSE
class Host < VagrantPlugins::HostLinux::Host
def self.match?
class Host < Vagrant.plugin("2", :host)
def detect?(env)
release_file = Pathname.new("/etc/SuSE-release")
if release_file.exist?
@ -18,19 +16,6 @@ module VagrantPlugins
false
end
# Normal, mid-range precedence.
def self.precedence
5
end
def initialize(*args)
super
@nfs_apply_command = "/usr/sbin/exportfs -r"
@nfs_check_command = "service nfsserver status"
@nfs_start_command = "service nfsserver start"
end
end
end
end

View File

@ -6,10 +6,22 @@ module VagrantPlugins
name "OpenSUSE host"
description "OpenSUSE host support."
host("opensuse") do
require File.expand_path("../host", __FILE__)
host("opensuse", "linux") do
require_relative "host"
Host
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("opensuse", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("opensuse", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,45 @@
require "pathname"
module VagrantPlugins
module HostRedHat
module Cap
class NFS
def self.nfs_check_command(env)
"#{nfs_server_binary} status"
end
def self.nfs_start_command(env)
"#{nfs_server_binary} start"
end
protected
def self.nfs_server_binary
nfs_server_binary = "/etc/init.d/nfs"
# On Fedora 16+, systemd replaced init.d, so we have to use the
# proper NFS binary. This checks to see if we need to do that.
release_file = Pathname.new("/etc/redhat-release")
begin
release_file.open("r:ISO-8859-1:UTF-8") do |f|
fedora_match = /Fedora.* release ([0-9]+)/.match(f.gets)
if fedora_match
version_number = fedora_match[1].to_i
if version_number >= 16
# "service nfs-server" will redirect properly to systemctl
# when "service nfs-server restart" is called.
nfs_server_binary = "/usr/sbin/service nfs-server"
end
end
end
rescue Errno::ENOENT
# File doesn't exist, not a big deal, assume we're on a
# lower version.
end
nfs_server_binary
end
end
end
end
end

View File

@ -2,12 +2,10 @@ require "pathname"
require "vagrant"
require Vagrant.source_root.join("plugins/hosts/linux/host")
module VagrantPlugins
module HostRedHat
class Host < VagrantPlugins::HostLinux::Host
def self.match?
class Host < Vagrant.plugin("2", :host)
def detect?(env)
release_file = Pathname.new("/etc/redhat-release")
if release_file.exist?
@ -15,47 +13,17 @@ module VagrantPlugins
contents = f.gets
return true if contents =~ /^Fedora/ # Fedora
return true if contents =~ /^CentOS/ # CentOS
return true if contents =~ /^Enterprise Linux Enterprise Linux/ # Oracle Linux < 5.3
return true if contents =~ /^Red Hat Enterprise Linux/ # Red Hat Enterprise Linux and Oracle Linux >= 5.3
# Oracle Linux < 5.3
return true if contents =~ /^Enterprise Linux Enterprise Linux/
# Red Hat Enterprise Linux and Oracle Linux >= 5.3
return true if contents =~ /^Red Hat Enterprise Linux/
end
end
false
end
# Normal, mid-range precedence.
def self.precedence
5
end
def initialize(*args)
super
nfs_server_binary = "/etc/init.d/nfs"
# On Fedora 16+, systemd replaced init.d, so we have to use the
# proper NFS binary. This checks to see if we need to do that.
release_file = Pathname.new("/etc/redhat-release")
begin
release_file.open("r:ISO-8859-1:UTF-8") do |f|
fedora_match = /Fedora.* release ([0-9]+)/.match(f.gets)
if fedora_match
version_number = fedora_match[1].to_i
if version_number >= 16
# "service nfs-server" will redirect properly to systemctl
# when "service nfs-server restart" is called.
nfs_server_binary = "/usr/sbin/service nfs-server"
end
end
end
rescue Errno::ENOENT
# File doesn't exist, not a big deal, assume we're on a
# lower version.
end
@nfs_apply_command = "/usr/sbin/exportfs -r"
@nfs_check_command = "#{nfs_server_binary} status"
@nfs_start_command = "#{nfs_server_binary} start"
end
end
end
end

View File

@ -6,10 +6,22 @@ module VagrantPlugins
name "Red Hat host"
description "Red Hat host support."
host("redhat") do
host("redhat", "linux") do
require File.expand_path("../host", __FILE__)
Host
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("redhat", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("redhat", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,15 @@
module VagrantPlugins
module HostSlackware
module Cap
class NFS
def self.nfs_check_command(env)
"pidof nfsd >/dev/null"
end
def self.nfs_start_command(env)
"/etc/rc.d/rc.nfsd start"
end
end
end
end
end

View File

@ -1,25 +1,11 @@
require "vagrant"
require Vagrant.source_root.join("plugins/hosts/linux/host")
module VagrantPlugins
module HostSlackware
class Host < VagrantPlugins::HostLinux::Host
def self.match?
return File.exists?("/etc/slackware-release") || Dir.glob("/usr/lib/setup/Plamo-*").length > 0
end
# Normal, mid-range precedence.
def self.precedence
5
end
def initialize(*args)
super
@nfs_apply_command = "/usr/sbin/exportfs -r"
@nfs_check_command = "pidof nfsd > /dev/null"
@nfs_start_command = "/etc/rc.d/rc.nfsd start"
class Host < Vagrant.plugin("2", :host)
def detect?(env)
return File.exists?("/etc/slackware-release") ||
!Dir.glob("/usr/lib/setup/Plamo-*").empty?
end
end
end

View File

@ -6,10 +6,22 @@ module VagrantPlugins
name "Slackware host"
description "Slackware and derivertives host support."
host("slackware") do
host("slackware", "linux") do
require File.expand_path("../host", __FILE__)
Host
end
# Linux-specific helpers we need to determine paths that can
# be overriden.
host_capability("slackware", "nfs_check_command") do
require_relative "cap/nfs"
Cap::NFS
end
host_capability("slackware", "nfs_start_command") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module HostWindows
module Cap
class NFS
def self.nfs_installed(env)
false
end
end
end
end
end

View File

@ -1,17 +1,13 @@
require "vagrant"
require 'vagrant/util/platform'
module VagrantPlugins
module HostWindows
class Host < Vagrant.plugin("2", :host)
def self.match?
def detect?(env)
Vagrant::Util::Platform.windows?
end
# Windows does not support NFS
def nfs?
false
end
end
end
end

View File

@ -7,9 +7,14 @@ module VagrantPlugins
description "Windows host support."
host("windows") do
require File.expand_path("../host", __FILE__)
require_relative "host"
Host
end
host_capability("windows", "nfs_installed") do
require_relative "cap/nfs"
Cap::NFS
end
end
end
end

View File

@ -5,6 +5,15 @@ module VagrantPlugins
class VagrantConfig < Vagrant.plugin("2", :config)
attr_accessor :host
def initialize
@host = UNSET_VALUE
end
def finalize!
@host = nil if @host == UNSET_VALUE
@host = @host.to_sym if @host
end
def to_s
"Vagrant"
end

View File

@ -455,12 +455,8 @@ module VagrantPlugins
end
if has_nfs
if !machine.env.host
errors << I18n.t("vagrant.config.vm.nfs_requires_host")
else
errors << I18n.t("vagrant.config.vm.nfs_not_supported") if \
!machine.env.host.nfs?
end
!machine.env.host.capability(:nfs_installed)
end
# Validate networks

View File

@ -49,7 +49,8 @@ module VagrantPlugins
# overlapping input requests. [GH-2680]
@@lock.synchronize do
machine.ui.info I18n.t("vagrant.actions.vm.nfs.exporting")
machine.env.host.nfs_export(machine.id, machine_ip, folders)
machine.env.host.capability(:nfs_export,
machine.ui, machine.id, machine_ip, folders)
end
# Mount
@ -72,7 +73,7 @@ module VagrantPlugins
# Prune any of the unused machines
@logger.info("NFS pruning. Valid IDs: #{ids.inspect}")
machine.env.host.nfs_prune(ids)
machine.env.host.capability(:nfs_prune, machine.ui, ids)
end
protected

View File

@ -379,6 +379,9 @@ en:
Host port: %{host}
Guest port: %{guest}
Adapter: %{adapter}
freebsd_nfs_whitespace: |-
FreeBSD hosts do not support sharing directories with whitespace in
their path. Please adjust your path accordingly.
gem_command_in_bundler: |-
You cannot run the `vagrant plugin` command while in a bundler environment.
This should generally never happen unless Vagrant is installed outside
@ -412,6 +415,11 @@ en:
directory that Vagrant uses must be both readable and writable.
You specified: %{home_path}
host_explicit_not_detected: |-
The host implementation explicitly specified in your Vagrantfile
("%{value}") could not be found. Please verify that the plugin is
installed which implements this host and that the value you used
for `config.vagrant.host` is correct.
interrupted: |-
Vagrant exited after cleanup due to external interrupt.
local_data_dir_not_accessible: |-
@ -869,9 +877,6 @@ en:
that `nfsd` is installed on your machine, and try again. If you're
on Windows, NFS isn't supported. If the problem persists, please
contact Vagrant support.
nfs_requires_host: |-
Using NFS shared folders requires a host to be specified
using `config.vagrant.host`.
network_ip_ends_in_one: |-
Static IPs cannot end in ".1" since that address is always
reserved for the router. Please use another ending.
@ -1340,10 +1345,6 @@ en:
arch:
nfs_export:
prepare: "Preparing to edit /etc/exports. Administrator privileges will be required..."
freebsd:
nfs_whitespace: |-
FreeBSD hosts do not support sharing directories with whitespace in
their path. Please adjust your path accordingly.
provisioners:
chef:

View File

@ -29,3 +29,11 @@ end
# Configure VAGRANT_CWD so that the tests never find an actual
# Vagrantfile anywhere, or at least this minimizes those chances.
ENV["VAGRANT_CWD"] = Tempdir.new.path
# Unset all host plugins so that we aren't executing subprocess things
# to detect a host for every test.
Vagrant.plugin("2").manager.registered.dup.each do |plugin|
if plugin.components.hosts.to_hash.length > 0
Vagrant.plugin("2").manager.unregister(plugin)
end
end

View File

@ -1,6 +1,7 @@
require_relative "../base"
describe VagrantPlugins::ProviderVirtualBox::Action::PrepareNFSSettings do
include_context "unit"
include_context "virtualbox"
let(:machine) {

View File

@ -8,6 +8,7 @@ require "vagrant/util/file_mode"
describe Vagrant::Environment do
include_context "unit"
include_context "capability_helpers"
let(:env) do
isolated_environment.tap do |e|
@ -21,9 +22,83 @@ describe Vagrant::Environment do
end
let(:instance) { env.create_vagrant_env }
subject { instance }
describe "#host" do
let(:plugin_hosts) { {} }
let(:plugin_host_caps) { {} }
before do
m = Vagrant.plugin("2").manager
m.stub(hosts: plugin_hosts)
m.stub(host_capabilities: plugin_host_caps)
end
it "should default to some host even if there are none" do
env.vagrantfile <<-VF
Vagrant.configure("2") do |config|
config.vagrant.host = nil
end
VF
expect(subject.host).to be
end
it "should attempt to detect a host if no host is set" do
env.vagrantfile <<-VF
Vagrant.configure("2") do |config|
config.vagrant.host = nil
end
VF
plugin_hosts[:foo] = [detect_class(true), nil]
plugin_host_caps[:foo] = { bar: Class }
result = subject.host
expect(result.capability?(:bar)).to be_true
end
it "should attempt to detect a host if host is :detect" do
env.vagrantfile <<-VF
Vagrant.configure("2") do |config|
config.vagrant.host = :detect
end
VF
plugin_hosts[:foo] = [detect_class(true), nil]
plugin_host_caps[:foo] = { bar: Class }
result = subject.host
expect(result.capability?(:bar)).to be_true
end
it "should use an exact host if specified" do
env.vagrantfile <<-VF
Vagrant.configure("2") do |config|
config.vagrant.host = "foo"
end
VF
plugin_hosts[:foo] = [detect_class(false), nil]
plugin_hosts[:bar] = [detect_class(true), nil]
plugin_host_caps[:foo] = { bar: Class }
result = subject.host
expect(result.capability?(:bar)).to be_true
end
it "should raise an error if an exact match was specified but not found" do
env.vagrantfile <<-VF
Vagrant.configure("2") do |config|
config.vagrant.host = "bar"
end
VF
expect { subject.host }.
to raise_error(Vagrant::Errors::HostExplicitNotDetected)
end
end
describe "active machines" do
it "should be empty if the machines folder doesn't exist" do
folder = instance.local_data_path.join("machines")

View File

@ -0,0 +1,18 @@
require "pathname"
require File.expand_path("../../base", __FILE__)
describe Vagrant::Host do
include_context "capability_helpers"
let(:capabilities) { {} }
let(:hosts) { {} }
let(:env) { Object.new }
it "initializes the capabilities" do
described_class.any_instance.should_receive(:initialize_capabilities!).
with(:foo, hosts, capabilities, env)
described_class.new(:foo, hosts, capabilities, env)
end
end

View File

@ -1,37 +0,0 @@
require File.expand_path("../../base", __FILE__)
describe Vagrant::Hosts do
let(:registry) { Hash.new }
let(:base_class) { Vagrant::Plugin::V1::Host }
it "detects the host that matches true" do
foo_klass = Class.new(base_class) do
def self.match?; false; end
end
bar_klass = Class.new(base_class) do
def self.match?; true; end
end
registry[:foo] = foo_klass
registry[: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(base_class) do
def self.match?; true; end
end
bar_klass = Class.new(base_class) do
def self.match?; true; end
def self.precedence; 9; end
end
registry[:foo] = foo_klass
registry[:bar] = bar_klass
described_class.detect(registry).should == bar_klass
end
end

View File

@ -126,15 +126,32 @@ describe Vagrant::Plugin::V2::Manager do
end
pB = plugin do |p|
p.host("bar") { "baz" }
p.host("bar", "foo") { "baz" }
end
instance.register(pA)
instance.register(pB)
instance.hosts.to_hash.length.should == 2
instance.hosts[:foo].should == "bar"
instance.hosts[:bar].should == "baz"
instance.hosts[:foo].should == ["bar", nil]
instance.hosts[:bar].should == ["baz", :foo]
end
it "should enumerate registered host capabilities" do
pA = plugin do |p|
p.host_capability("foo", "foo") { "bar" }
end
pB = plugin do |p|
p.host_capability("bar", "foo") { "baz" }
end
instance.register(pA)
instance.register(pB)
instance.host_capabilities.length.should == 2
instance.host_capabilities[:foo][:foo].should == "bar"
instance.host_capabilities[:bar][:foo].should == "baz"
end
it "should enumerate registered provider classes" do

View File

@ -192,7 +192,7 @@ describe Vagrant::Plugin::V2::Plugin do
host("foo") { "bar" }
end
plugin.host[:foo].should == "bar"
plugin.components.hosts[:foo].should == ["bar", nil]
end
it "should lazily register host classes" do
@ -214,6 +214,16 @@ describe Vagrant::Plugin::V2::Plugin do
end
end
describe "host capabilities" do
it "should register host capabilities" do
plugin = Class.new(described_class) do
host_capability("foo", "bar") { "baz" }
end
plugin.components.host_capabilities[:foo][:bar].should == "baz"
end
end
describe "providers" do
it "should register provider classes" do
plugin = Class.new(described_class) do

View File

@ -219,6 +219,7 @@
<li<%= sidebar_current("plugins-guests") %>><a href="/v2/plugins/guests.html">Guests</a></li>
<li<%= sidebar_current("plugins-guestcapabilities") %>><a href="/v2/plugins/guest-capabilities.html">Guest Capabilities</a></li>
<li<%= sidebar_current("plugins-hosts") %>><a href="/v2/plugins/hosts.html">Hosts</a></li>
<li<%= sidebar_current("plugins-hostcapabilities") %>><a href="/v2/plugins/host-capabilities.html">Host Capabilities</a></li>
<li<%= sidebar_current("plugins-providers") %>><a href="/v2/plugins/providers.html">Providers</a></li>
<li<%= sidebar_current("plugins-provisioners") %>><a href="/v2/plugins/provisioners.html">Provisioners</a></li>
<li<%= sidebar_current("plugins-packaging") %>><a href="/v2/plugins/packaging.html">Packaging & Distribution</a></li>

View File

@ -64,7 +64,7 @@ end
```
After detecting an OS, that OS is used for various
[guest capabilities](/v2/plugins/guest_capabilities.html) that may be
[guest capabilities](/v2/plugins/guest-capabilities.html) that may be
required.
## Guest Inheritance
@ -76,7 +76,7 @@ Inheritance allows guests to share a lot of common behavior while allowing
distro-specific overrides.
Inheritance is not done via standard Ruby class inheritance because Vagrant
uses a custom [capability-based](/v2/plugins/guest_capabilities.html) system.
uses a custom [capability-based](/v2/plugins/guest-capabilities.html) system.
Vagrant handles inheritance dispatch for you.
To subclass another guest, specify that guest's name as a second parameter

View File

@ -0,0 +1,63 @@
---
page_title: "Host Capabilities - Plugin Development"
sidebar_current: "plugins-hostcapabilities"
---
# Plugin Development: Host Capabilities
This page documents how to add new capabilities for [hosts](/v2/plugins/hosts.html)
to Vagrant, allowing Vagrant to perform new actions on specific host
operating systems.
Prior to reading this, you should be familiar
with the [plugin development basics](/v2/plugins/development-basics.html).
<div class="alert alert-warn">
<p>
<strong>Warning: Advanced Topic!</strong> Developing plugins is an
advanced topic that only experienced Vagrant users who are reasonably
comfortable with Ruby should approach.
</p>
</div>
Host capabilities augment [hosts](/v2/plugins/hosts.html) by attaching
specific "capabilities" to the host, which are actions that can be performed
in the context of that host operating system.
The power of capabilities is that plugins can add new capabilities to
existing host operating systems without modifying the core of Vagrant.
In earlier versions of Vagrant, all the host logic was contained in the
core of Vagrant and wasn't easily augmented.
## Definition and Implementation
The definition and implementation of host capabilities is identical
to [guest capabilities](/v2/plugins/guest-capabilities.html).
The main difference from guest capabilities, however, is that instead of
taking a machine as the first argument, all host capabilities take an
instance of `Vagrant::Environment` as their first argument.
Access to the environment allows host capabilities to access global state,
specific machines, and also allows them to call other host capabilities.
## Calling Capabilities
Since you have access to the environment in every capability, capabilities can
also call _other_ host capabilities. This is useful for using the inheritance
mechanism of capabilities to potentially ask helpers for more information.
For example, the "linux" guest has a "nfs\_check\_command" capability that
returns the command to use to check if NFS is running.
Capabilities on child guests of Linux such as RedHat or Arch use this
capability to mostly inherit the Linux behavior, except for this minor
detail.
Capabilities can be called like so:
```ruby
environment.host.capability(:capability_name)
```
Any additional arguments given to the method will be passed on to the
capability, and the capability will return the value that the actual
capability returned.

View File

@ -5,9 +5,9 @@ sidebar_current: "plugins-hosts"
# Plugin Development: Hosts
This page documents how to add new host OS implementations to Vagrant,
allowing Vagrant to properly configure new host operating systems
for features such as NFS shared folders. Prior to reading this, you should be familiar
This page documents how to add new host OS detection to Vagrant, allowing
Vagrant to properly execute host-specific operations on new operating systems.
Prior to reading this, you should be familiar
with the [plugin development basics](/v2/plugins/development-basics.html).
<div class="alert alert-warn">
@ -18,29 +18,78 @@ with the [plugin development basics](/v2/plugins/development-basics.html).
</p>
</div>
Vagrant has some features that require host OS-specific actions, such as
exporting NFS folders. These tasks vary from operating system to operating
system. Vagrant uses host detection as well as
[host capabilities](/v2/plugins/host-capabilities.html) to perform these
host OS-specific operations.
## Definition Component
Within the context of a plugin definition, new hosts can be defined
like so:
```ruby
host "some_os" do
host "ubuntu" do
require_relative "host"
Host
end
```
Guests are defined with the `host` method. The first argument is th
name of the host. This name isn't actually used anywhere, but may in
the future, so choose something helpful. Then, the block argument returns a
Hosts are defined with the `host` method. The first argument is the
name of the host. This name isn't actually used anywhere, but may in the
future, so choose something helpful. Then, the block argument returns a
class that implements the `Vagrant.plugin(2, :host)` interface.
## Implementation
Implementations of hosts subclass `Vagrant.plugin(2, :host)`. Within
this implementation, various methods for different tasks must be implemented.
Instead of going over each task, the easiest example would be to take a
look at an existing host implementation.
Implementations of hosts subclass `Vagrant.plugin("2", "host")`. Within
this implementation, only the `detect?` method needs to be implemented.
There are [many host implementations](https://github.com/mitchellh/vagrant/tree/master/plugins/hosts),
but you can view the [BSD host implementation](https://github.com/mitchellh/vagrant/blob/master/plugins/hosts/bsd/host.rb) as a starting point.
The `detect?` method is called by Vagrant very early on in its initialization
process to determine if the OS that Vagrant is running on is this hsot.
If you detect that it is your operating system, return `true` from `detect?`.
Otherwise, return `false`.
```
class MyHost < Vagrant.plugin("2", "host")
def detect?(environment)
File.file?("/etc/arch-release")
end
end
```
After detecting an OS, that OS is used for various
[host capabilities](/v2/plugins/host-capabilities.html) that may be
required.
## Host Inheritance
Vagrant also supports a form of inheritance for hosts, since sometimes
operating systems stem from a common root. A good example of this is Linux
is the root of Debian, which further is the root of Ubuntu in many cases.
Inheritance allows hosts to share a lot of common behavior while allowing
distro-specific overrides.
Inheritance is not done via standard Ruby class inheritance because Vagrant
uses a custom [capability-based](/v2/plugins/host-capabilities.html) system.
Vagrant handles inheritance dispatch for you.
To subclass another host, specify that host's name as a second parameter
in the host definition:
```ruby
host "ubuntu", "debian" do
require_relative "host"
Host
end
```
With the above component, the "ubuntu" host inherits from "debian." When
a capability is looked up for "ubuntu", all capabilities from "debian" are
also available, and any capabilities in "ubuntu" override parent capabilities.
When detecting operating systems with `detect?`, Vagrant always does a
depth-first search by searching the children operating systems before
checking their parents. Therefore, it is guaranteed in the above example
that the `detect?` method on "ubuntu" will be called before "debian."