Merge branch 'guest-capabilities-system'

This replaces the old "guest" sysetm with a new "guest + capabilities" system.
This introduces two new abstractions to replace the old system:

1. Guests - These are responsible for simply detecting the guest OS.
2. Guest Capabilities - These are specific tasks that a guest OS are
  capable of performing. For example, "mount_virtualbox_shared_folders" or
  "configure_networks"

This new system improves the old system because the old system hardcoded
all capabilities of guests into a single static class. This system allows
dynamically adding new capabilities as they're needed and testing for
their existence.

This means it is now possible for the Chef provisioner (as an example)
to run the "install_chef" capability if it exists, allowing guests to handle
installing Chef. COOL! If the capability doesn't exist, Chef can ignore it,
error, etc. Plugins can dynamically add new capabilities to existing operating
systems.

AWESOME!
This commit is contained in:
Mitchell Hashimoto 2013-04-04 12:58:16 -07:00
commit fd0661c20e
73 changed files with 1397 additions and 788 deletions

View File

@ -15,6 +15,10 @@ FEATURES:
Note that while this feature is available, the "Vagrant way" is instead
to use box manifests to ensure that the "box" for every provider matches,
so these sorts of overrides are unnecessary.
- A new "guest capabilities" system to replace the old "guest" system.
This new abstraction allows plugins to define "capabilities" that
certain guest operating systems can implement. This allows greater
flexibility in doing guest-specific behavior.
IMPROVEMENTS:

View File

@ -44,7 +44,7 @@ module Vagrant
# checked above.
if graceful
env[:ui].info I18n.t("vagrant.actions.vm.halt.graceful")
env[:machine].guest.halt
env[:machine].guest.capability(:halt)
@logger.debug("Waiting for target graceful halt state: #{@target_state}")
count = 0

View File

@ -78,7 +78,8 @@ module Vagrant
end
# Mount them!
env[:machine].guest.mount_nfs(env[:nfs_host_ip], mount_folders)
env[:machine].guest.capability(
:mount_nfs_folder, env[:nfs_host_ip], mount_folders)
end
end

View File

@ -18,7 +18,7 @@ module Vagrant
hostname = env[:machine].config.vm.hostname
if !hostname.nil?
env[:ui].info I18n.t("vagrant.actions.vm.hostname.setting")
env[:machine].guest.change_host_name(hostname)
env[:machine].guest.capability(:change_host_name, hostname)
end
end
end

View File

@ -227,6 +227,30 @@ module Vagrant
error_key(:port_collision_resume)
end
class GuestCapabilityInvalid < VagrantError
error_key(:guest_capability_invalid)
end
class GuestCapabilityNotFound < VagrantError
error_key(:guest_capability_not_found)
end
class GuestNotDetected < VagrantError
error_key(:guest_not_detected)
end
class LinuxMountFailed < VagrantError
error_key(:linux_mount_failed)
end
class LinuxNFSMountFailed < VagrantError
error_key(:linux_nfs_mount_failed)
end
class LinuxShellExpandFailed < VagrantError
error_key(:linux_shell_expand_failed)
end
class LocalDataDirectoryNotAccessible < VagrantError
error_key(:local_data_dir_not_accessible)
end

163
lib/vagrant/guest.rb Normal file
View File

@ -0,0 +1,163 @@
require "log4r"
module Vagrant
# This class handles guest-OS specific interactions with a machine.
# It is primarily responsible for detecting the proper guest OS
# implementation and then delegating capabilities.
#
# Vagrant has many tasks which require specific guest OS knowledge.
# These are implemented using a guest/capability system. Various plugins
# register as "guests" which determine the underlying OS of the system.
# Then, "guest capabilities" register themselves for a specific OS (one
# or more), and these capabilities are called.
#
# Example capabilities might be "mount_virtualbox_shared_folder" or
# "configure_networks".
#
# This system allows for maximum flexibility and pluginability for doing
# guest OS specific operations.
class Guest
attr_reader :chain
# The name of the guest OS. This is available after {#detect!} is
# called.
#
# @return [Symbol]
attr_reader :name
def initialize(machine, guests, capabilities)
@logger = Log4r::Logger.new("vagrant::guest")
@capabilities = capabilities
@chain = []
@guests = guests
@machine = machine
@name = nil
end
# This will detect the proper guest OS for the machine and set up
# the class to actually execute capabilities.
def detect!
@logger.info("Detect 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
catch(:guest_os) do
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}!")
@chain << [name, guest]
@name = name
# Build the proper chain of parents if there are any.
# This allows us to do "inheritence" 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
# Tests whether the guest has the named capability.
#
# @return [Boolean]
def capability?(cap_name)
!capability_module(cap_name).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)
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
# This returns whether the guest is ready to work. If this returns
# `false`, then {#detect!} should be called in order to detect the
# guest OS.
#
# @return [Boolean]
def ready?
!@chain.empty?
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
end
end

View File

@ -83,6 +83,10 @@ module Vagrant
@config = config
@data_dir = data_dir
@env = env
@guest = Guest.new(
self,
Vagrant.plugin("2").manager.guests,
Vagrant.plugin("2").manager.guest_capabilities)
@name = name
@provider_config = provider_config
@provider_name = provider_name
@ -166,35 +170,11 @@ module Vagrant
# knows how to do guest-OS specific tasks, such as configuring networks,
# mounting folders, etc.
#
# @return [Object]
# @return [Guest]
def guest
raise Errors::MachineGuestNotReady if !communicate.ready?
# Load the initial guest.
last_guest = config.vm.guest
guest = load_guest(last_guest)
# Loop and distro dispatch while there are distros.
while true
distro = guest.distro_dispatch
break if !distro
# This is just some really basic loop detection and avoiding for
# guest classes. This is just here to help implementers a bit
# avoid a situation that is fairly easy, since if you subclass
# a parent which does `distro_dispatch`, you'll end up dispatching
# forever.
if distro == last_guest
@logger.warn("Distro dispatch loop in '#{distro}'. Exiting loop.")
break
end
last_guest = distro
guest = load_guest(distro)
end
# Return the result
guest
@guest.detect! if !@guest.ready?
@guest
end
# This sets the unique ID associated with this machine. This will

View File

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

View File

@ -1,35 +1,21 @@
module Vagrant
module Plugin
module V2
# The base class for a guest. A guest represents an installed system
# within a machine that Vagrant manages. There are some portions of
# Vagrant which are OS-specific such as mountaing shared folders and
# halting the machine, and this abstraction allows the implementation
# for these to be seperate from the core of Vagrant.
class Guest
class BaseError < Errors::VagrantError
error_namespace("vagrant.guest.base")
end
include Vagrant::Util
# The VM which this system is tied to.
attr_reader :vm
# Initializes the system. Any subclasses MUST make sure this
# method is called on the parent. Therefore, if a subclass overrides
# `initialize`, then you must call `super`.
def initialize(vm)
@vm = vm
end
# This method is automatically called when the system is available (when
# Vagrant can successfully SSH into the machine) to give the system a chance
# to determine the distro and return a distro-specific system.
# A base class for a guest OS. A guest OS is responsible for detecting
# that the guest operating system running within the machine. The guest
# can then be extended with various "guest capabilities" which are their
# own form of plugin.
#
# If this method returns nil, then this instance is assumed to be
# the most specific guest implementation.
def distro_dispatch
# The guest class itself is only responsible for detecting itself,
# and may provide helpers for the capabilties.
class Guest
# This method is called when the machine is booted and has communication
# capabilities in order to detect whether this guest operating system
# is running within the machine.
#
# @return [Boolean]
def guest?(machine)
false
end
# Halt the machine. This method should gracefully shut down the

View File

@ -67,11 +67,26 @@ module Vagrant
def guests
Registry.new.tap do |result|
@registered.each do |plugin|
result.merge!(plugin.guest)
result.merge!(plugin.components.guests)
end
end
end
# This returns all the registered guest capabilities.
#
# @return [Hash]
def guest_capabilities
results = Hash.new { |h, k| h[k] = Registry.new }
@registered.each do |plugin|
plugin.components.guest_capabilities.each do |guest, caps|
results[guest].merge!(caps)
end
end
results
end
# This returns all registered host classes.
#
# @return [Hash]

View File

@ -124,7 +124,6 @@ module Vagrant
# without breaking anything in future versions of Vagrant.
#
# @param [String] name Configuration key.
# XXX: Document options hash
def self.config(name, scope=nil, &block)
scope ||= :top
components.configs[scope].register(name.to_sym, &block)
@ -135,14 +134,26 @@ module Vagrant
# the given key.
#
# @param [String] name Name of the guest.
def self.guest(name=UNSET_VALUE, &block)
data[:guests] ||= Registry.new
# @param [String] parent Name of the parent guest (if any)
def self.guest(name=UNSET_VALUE, parent=nil, &block)
components.guests.register(name.to_sym) do
parent = parent.to_sym if parent
# Register a new guest class only if a name was given
data[:guests].register(name.to_sym, &block) if name != UNSET_VALUE
[block.call, parent]
end
nil
end
# Return the registry
data[:guests]
# Defines a capability for the given guest. 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] guest The name of the guest
# @param [String] cap The name of the capability
def self.guest_capability(guest, cap, &block)
components.guest_capabilities[guest.to_sym].register(cap.to_sym, &block)
nil
end
# Defines an additionally available host implementation with

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module GuestArch
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
# Only do this if the hostname is not already set
if !comm.test("sudo hostname | grep '#{name}'")
comm.sudo("sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/rc.conf")
comm.sudo("hostname #{name}")
comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} @' /etc/hosts")
end
end
end
end
end
end
end

View File

@ -0,0 +1,23 @@
module VagrantPlugins
module GuestArch
module Cap
class ConfigureNetworks
def self.configure_networks(machine, networks)
networks.each do |network|
entry = TemplateRenderer.render("guests/arch/network_#{network[:type]}",
:options => network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant_network")
machine.communicate.sudo("mv /tmp/vagrant_network /etc/network.d/interfaces/eth#{network[:interface]}")
machine.communicate.sudo("netcfg interfaces/eth#{network[:interface]}")
end
end
end
end
end
end

View File

@ -1,40 +1,10 @@
require 'set'
require 'tempfile'
require "vagrant"
require 'vagrant/util/template_renderer'
require Vagrant.source_root.join("plugins/guests/linux/guest")
module VagrantPlugins
module GuestArch
class Guest < VagrantPlugins::GuestLinux::Guest
# Make the TemplateRenderer top-level
include Vagrant::Util
def change_host_name(name)
# Only do this if the hostname is not already set
if !vm.communicate.test("sudo hostname | grep '#{name}'")
vm.communicate.sudo("sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/rc.conf")
vm.communicate.sudo("hostname #{name}")
vm.communicate.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} @' /etc/hosts")
end
end
def configure_networks(networks)
networks.each do |network|
entry = TemplateRenderer.render("guests/arch/network_#{network[:type]}",
:options => network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
vm.communicate.upload(temp.path, "/tmp/vagrant_network")
vm.communicate.sudo("mv /tmp/vagrant_network /etc/network.d/interfaces/eth#{network[:interface]}")
vm.communicate.sudo("netcfg interfaces/eth#{network[:interface]}")
end
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/arch-release")
end
end
end

View File

@ -6,10 +6,20 @@ module VagrantPlugins
name "Arch guest"
description "Arch guest support."
guest("arch") do
guest("arch", "linux") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("arch", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("arch", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module GuestDebian
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
if !comm.test("hostname --fqdn | grep '^#{name}$' || hostname --short | grep '^#{name}$'")
comm.sudo("sed -r -i 's/^(127[.]0[.]1[.]1[[:space:]]+).*$/\\1#{name} #{name.split('.')[0]}/' /etc/hosts")
comm.sudo("sed -i 's/.*$/#{name.split('.')[0]}/' /etc/hostname")
comm.sudo("hostname -F /etc/hostname")
comm.sudo("hostname --fqdn > /etc/mailname")
end
end
end
end
end
end
end

View File

@ -0,0 +1,61 @@
require 'set'
require 'tempfile'
require "vagrant/util/template_renderer"
module VagrantPlugins
module GuestDebian
module Cap
class ConfigureNetworks
extend Vagrant::Util
def self.configure_networks(machine, networks)
machine.communicate.tap do |comm|
# First, remove any previous network modifications
# from the interface file.
comm.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces")
comm.sudo("su -c 'cat /tmp/vagrant-network-interfaces > /etc/network/interfaces'")
comm.sudo("rm /tmp/vagrant-network-interfaces")
# Accumulate the configurations to add to the interfaces file as
# well as what interfaces we're actually configuring since we use that
# later.
interfaces = Set.new
entries = []
networks.each do |network|
interfaces.add(network[:interface])
entry = TemplateRenderer.render("guests/debian/network_#{network[:type]}",
:options => network)
entries << entry
end
# Perform the careful dance necessary to reconfigure
# the network interfaces
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entries.join("\n"))
temp.close
comm.upload(temp.path, "/tmp/vagrant-network-entry")
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
comm.sudo("/sbin/ifdown eth#{interface} 2> /dev/null")
end
comm.sudo("cat /tmp/vagrant-network-entry >> /etc/network/interfaces")
comm.sudo("rm /tmp/vagrant-network-entry")
# Bring back up each network interface, reconfigured
interfaces.each do |interface|
comm.sudo("/sbin/ifup eth#{interface}")
end
end
end
end
end
end
end

View File

@ -1,71 +1,8 @@
require 'set'
require 'tempfile'
require "vagrant"
require 'vagrant/util/template_renderer'
require Vagrant.source_root.join("plugins/guests/linux/guest")
module VagrantPlugins
module GuestDebian
class Guest < VagrantPlugins::GuestLinux::Guest
# Make the TemplateRenderer top-level
include Vagrant::Util
def configure_networks(networks)
# First, remove any previous network modifications
# from the interface file.
vm.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces")
vm.communicate.sudo("su -c 'cat /tmp/vagrant-network-interfaces > /etc/network/interfaces'")
vm.communicate.sudo("rm /tmp/vagrant-network-interfaces")
# Accumulate the configurations to add to the interfaces file as
# well as what interfaces we're actually configuring since we use that
# later.
interfaces = Set.new
entries = []
networks.each do |network|
interfaces.add(network[:interface])
entry = TemplateRenderer.render("guests/debian/network_#{network[:type]}",
:options => network)
entries << entry
end
# Perform the careful dance necessary to reconfigure
# the network interfaces
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entries.join("\n"))
temp.close
vm.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
vm.communicate.sudo("/sbin/ifdown eth#{interface} 2> /dev/null")
end
vm.communicate.sudo("cat /tmp/vagrant-network-entry >> /etc/network/interfaces")
vm.communicate.sudo("rm /tmp/vagrant-network-entry")
# Bring back up each network interface, reconfigured
interfaces.each do |interface|
vm.communicate.sudo("/sbin/ifup eth#{interface}")
end
end
def change_host_name(name)
vm.communicate.tap do |comm|
if !comm.test("hostname --fqdn | grep '^#{name}$' || hostname --short | grep '^#{name}$'")
comm.sudo("sed -r -i 's/^(127[.]0[.]1[.]1[[:space:]]+).*$/\\1#{name} #{name.split('.')[0]}/' /etc/hosts")
comm.sudo("sed -i 's/.*$/#{name.split('.')[0]}/' /etc/hostname")
comm.sudo("hostname -F /etc/hostname")
comm.sudo("hostname --fqdn > /etc/mailname")
end
end
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /proc/version | grep 'Debian'")
end
end
end

View File

@ -6,10 +6,20 @@ module VagrantPlugins
name "Debian guest"
description "Debian guest support."
guest("debian") do
guest("debian", "linux") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("debian", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
guest_capability("debian", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
end
end
end

View File

@ -0,0 +1,54 @@
require "set"
require "tempfile"
require "vagrant/util/template_renderer"
module VagrantPlugins
module GuestFedora
module Cap
class ConfigureNetworks
extend Vagrant::Util
def self.configure_networks(machine, networks)
network_scripts_dir = machine.guest.capability("network_scripts_dir")
# Accumulate the configurations to add to the interfaces file as well
# as what interfaces we're actually configuring since we use that later.
interfaces = Set.new
networks.each do |network|
interfaces.add(network[:interface])
# Remove any previous vagrant configuration in this network
# interface's configuration files.
machine.communicate.sudo("touch #{network_scripts_dir}/ifcfg-p7p#{network[:interface]}")
machine.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' #{network_scripts_dir}/ifcfg-p7p#{network[:interface]} > /tmp/vagrant-ifcfg-p7p#{network[:interface]}")
machine.communicate.sudo("cat /tmp/vagrant-ifcfg-p7p#{network[:interface]} > #{network_scripts_dir}/ifcfg-p7p#{network[:interface]}")
machine.communicate.sudo("rm /tmp/vagrant-ifcfg-p7p#{network[:interface]}")
# Render and upload the network entry file to a deterministic
# temporary location.
entry = TemplateRenderer.render("guests/fedora/network_#{network[:type]}",
:options => network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
end
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring p7p (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
machine.communicate.sudo("/sbin/ifdown p7p#{interface} 2> /dev/null", :error_check => false)
machine.communicate.sudo("cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-p7p#{interface}")
machine.communicate.sudo("rm /tmp/vagrant-network-entry_#{interface}")
machine.communicate.sudo("/sbin/ifup p7p#{interface} 2> /dev/null")
end
end
end
end
end
end

View File

@ -0,0 +1,15 @@
module VagrantPlugins
module GuestFedora
module Cap
class NetworkScriptsDir
# The path to the directory with the network configuration scripts.
# This is pulled out into its own directory since there are other
# operating systems (SuSE) which behave similarly but with a different
# path to the network scripts.
def self.network_scripts_dir(machine)
"/etc/sysconfig/network-scripts"
end
end
end
end
end

View File

@ -1,70 +1,10 @@
require 'set'
require 'tempfile'
require "vagrant"
require 'vagrant/util/template_renderer'
require Vagrant.source_root.join("plugins/guests/linux/guest")
module VagrantPlugins
module GuestFedora
class Guest < VagrantPlugins::GuestLinux::Guest
# Make the TemplateRenderer top-level
include Vagrant::Util
def configure_networks(networks)
# Accumulate the configurations to add to the interfaces file as well
# as what interfaces we're actually configuring since we use that later.
interfaces = Set.new
networks.each do |network|
interfaces.add(network[:interface])
# Remove any previous vagrant configuration in this network
# interface's configuration files.
vm.communicate.sudo("touch #{network_scripts_dir}/ifcfg-p7p#{network[:interface]}")
vm.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' #{network_scripts_dir}/ifcfg-p7p#{network[:interface]} > /tmp/vagrant-ifcfg-p7p#{network[:interface]}")
vm.communicate.sudo("cat /tmp/vagrant-ifcfg-p7p#{network[:interface]} > #{network_scripts_dir}/ifcfg-p7p#{network[:interface]}")
vm.communicate.sudo("rm /tmp/vagrant-ifcfg-p7p#{network[:interface]}")
# Render and upload the network entry file to a deterministic
# temporary location.
entry = TemplateRenderer.render("guests/fedora/network_#{network[:type]}",
:options => network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
vm.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
end
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring p7p (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
vm.communicate.sudo("/sbin/ifdown p7p#{interface} 2> /dev/null", :error_check => false)
vm.communicate.sudo("cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-p7p#{interface}")
vm.communicate.sudo("rm /tmp/vagrant-network-entry_#{interface}")
vm.communicate.sudo("/sbin/ifup p7p#{interface} 2> /dev/null")
end
end
# The path to the directory with the network configuration scripts.
# This is pulled out into its own directory since there are other
# operating systems (SuSE) which behave similarly but with a different
# path to the network scripts.
def network_scripts_dir
'/etc/sysconfig/network-scripts'
end
def change_host_name(name)
# Only do this if the hostname is not already set
if !vm.communicate.test("sudo hostname | grep '#{name}'")
vm.communicate.sudo("sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/sysconfig/network")
vm.communicate.sudo("hostname #{name}")
vm.communicate.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
end
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("grep 'Fedora release 1[678]' /etc/redhat-release")
end
end
end

View File

@ -6,10 +6,20 @@ module VagrantPlugins
name "Fedora guest"
description "Fedora guest support."
guest("fedora") do
guest("fedora", "redhat") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("fedora", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
guest_capability("fedora", "network_scripts_dir") do
require_relative "cap/network_scripts_dir"
Cap::NetworkScriptsDir
end
end
end
end

View File

@ -0,0 +1,14 @@
module VagrantPlugins
module GuestFreeBSD
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
if !machine.communicate.test("hostname -f | grep '^#{name}$' || hostname -s | grep '^#{name}$'")
machine.communicate.sudo("sed -i '' 's/^hostname=.*$/hostname=#{name}/' /etc/rc.conf")
machine.communicate.sudo("hostname #{name}")
end
end
end
end
end
end

View File

@ -0,0 +1,39 @@
require "tempfile"
require "vagrant/util/template_renderer"
module VagrantPlugins
module GuestFreeBSD
module Cap
class ConfigureNetworks
extend Vagrant::Util
def self.configure_networks(machine, networks)
# Remove any previous network additions to the configuration file.
machine.communicate.sudo("sed -i '' -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/rc.conf")
networks.each do |network|
entry = TemplateRenderer.render("guests/freebsd/network_#{network[:type]}",
:options => network)
# Write the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
machine.communicate.sudo("su -m root -c 'cat /tmp/vagrant-network-entry >> /etc/rc.conf'")
machine.communicate.sudo("rm /tmp/vagrant-network-entry")
if network[:type].to_sym == :static
machine.communicate.sudo("ifconfig em#{network[:interface]} inet #{network[:ip]} netmask #{network[:netmask]}")
elsif network[:type].to_sym == :dhcp
machine.communicate.sudo("dhclient em#{network[:interface]}")
end
end
end
end
end
end
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module GuestFreeBSD
module Cap
class Halt
def self.halt(machine)
begin
machine.communicate.sudo("shutdown -p now")
rescue IOError
# Do nothing because SSH connection closed and it probably
# means the VM just shut down really fast.
end
end
end
end
end
end

View File

@ -0,0 +1,14 @@
module VagrantPlugins
module GuestFreeBSD
module Cap
class MountNFSFolder
def self.mount_nfs_folder(machine, ip, folders)
folders.each do |name, opts|
machine.communicate.sudo("mkdir -p #{opts[:guestpath]}")
machine.communicate.sudo("mount '#{ip}:#{opts[:hostpath]}' '#{opts[:guestpath]}'")
end
end
end
end
end
end

View File

@ -1,13 +0,0 @@
module VagrantPlugins
module GuestFreeBSD
class Config < Vagrant.plugin("2", :config)
attr_accessor :halt_timeout
attr_accessor :halt_check_interval
def initialize
@halt_timeout = 30
@halt_check_interval = 1
end
end
end
end

View File

@ -6,18 +6,9 @@ module VagrantPlugins
#
# Contributed by Kenneth Vestergaard <kvs@binarysolutions.dk>
class Guest < Vagrant.plugin("2", :guest)
# Here for whenever it may be used.
class FreeBSDError < Vagrant::Errors::VagrantError
error_namespace("vagrant.guest.freebsd")
end
def halt
begin
vm.communicate.sudo("shutdown -p now")
rescue IOError
# Do nothing because SSH connection closed and it probably
# means the VM just shut down really fast.
end
def detect?(machine)
# TODO: FreeBSD detection
false
end
# TODO: vboxsf is currently unsupported in FreeBSD, if you are able to
@ -30,46 +21,6 @@ module VagrantPlugins
# ssh.exec!("sudo mount -t vboxfs v-root #{guestpath}")
# ssh.exec!("sudo chown #{vm.config.ssh.username} #{guestpath}")
# end
def mount_nfs(ip, folders)
folders.each do |name, opts|
vm.communicate.sudo("mkdir -p #{opts[:guestpath]}")
vm.communicate.sudo("mount '#{ip}:#{opts[:hostpath]}' '#{opts[:guestpath]}'")
end
end
def configure_networks(networks)
# Remove any previous network additions to the configuration file.
vm.communicate.sudo("sed -i '' -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/rc.conf")
networks.each do |network|
entry = TemplateRenderer.render("guests/freebsd/network_#{network[:type]}",
:options => network)
# Write the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
vm.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
vm.communicate.sudo("su -m root -c 'cat /tmp/vagrant-network-entry >> /etc/rc.conf'")
vm.communicate.sudo("rm /tmp/vagrant-network-entry")
if network[:type].to_sym == :static
vm.communicate.sudo("ifconfig em#{network[:interface]} inet #{network[:ip]} netmask #{network[:netmask]}")
elsif network[:type].to_sym == :dhcp
vm.communicate.sudo("dhclient em#{network[:interface]}")
end
end
end
def change_host_name(name)
if !vm.communicate.test("hostname -f | grep '^#{name}$' || hostname -s | grep '^#{name}$'")
vm.communicate.sudo("sed -i '' 's/^hostname=.*$/hostname=#{name}/' /etc/rc.conf")
vm.communicate.sudo("hostname #{name}")
end
end
end
end
end

View File

@ -6,15 +6,30 @@ module VagrantPlugins
name "FreeBSD guest"
description "FreeBSD guest support."
config("freebsd") do
require File.expand_path("../config", __FILE__)
Config
end
guest("freebsd") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("freebsd", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("freebsd", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
guest_capability("freebsd", "halt") do
require_relative "cap/halt"
Cap::Halt
end
guest_capability("freebsd", "mount_nfs_folder") do
require_relative "cap/mount_nfs_folder"
Cap::MountNFSFolder
end
end
end
end

View File

@ -0,0 +1,17 @@
module VagrantPlugins
module GuestFreeBSD
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
if !comm.test("sudo hostname --fqdn | grep '#{name}'")
comm.sudo("echo 'hostname=#{name.split('.')[0]}' > /etc/conf.d/hostname")
comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
comm.sudo("hostname #{name.split('.')[0]}")
end
end
end
end
end
end
end

View File

@ -0,0 +1,43 @@
require "tempfile"
require "vagrant/util/template_renderer"
module VagrantPlugins
module GuestFreeBSD
module Cap
class ChangeHostName
extend Vagrant::Util
def self.configure_networks(machine, networks)
machine.communicate.tap do |comm|
# Remove any previous host only network additions to the interface file
comm.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/conf.d/net > /tmp/vagrant-network-interfaces")
comm.sudo("cat /tmp/vagrant-network-interfaces > /etc/conf.d/net")
comm.sudo("rm /tmp/vagrant-network-interfaces")
# Configure each network interface
networks.each do |network|
entry = TemplateRenderer.render("guests/gentoo/network_#{network[:type]}",
:options => network)
# Upload the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
comm.upload(temp.path, "/tmp/vagrant-network-entry")
# Configure the interface
comm.sudo("ln -fs /etc/init.d/net.lo /etc/init.d/net.eth#{network[:interface]}")
comm.sudo("/etc/init.d/net.eth#{network[:interface]} stop 2> /dev/null")
comm.sudo("cat /tmp/vagrant-network-entry >> /etc/conf.d/net")
comm.sudo("rm /tmp/vagrant-network-entry")
comm.sudo("/etc/init.d/net.eth#{network[:interface]} start")
end
end
end
end
end
end
end

View File

@ -1,50 +1,8 @@
require 'tempfile'
require "vagrant"
require 'vagrant/util/template_renderer'
require Vagrant.source_root.join("plugins/guests/linux/guest")
module VagrantPlugins
module GuestGentoo
class Guest < VagrantPlugins::GuestLinux::Guest
# Make the TemplateRenderer top-level
include Vagrant::Util
def configure_networks(networks)
# Remove any previous host only network additions to the interface file
vm.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/conf.d/net > /tmp/vagrant-network-interfaces")
vm.communicate.sudo("cat /tmp/vagrant-network-interfaces > /etc/conf.d/net")
vm.communicate.sudo("rm /tmp/vagrant-network-interfaces")
# Configure each network interface
networks.each do |network|
entry = TemplateRenderer.render("guests/gentoo/network_#{network[:type]}",
:options => network)
# Upload the entry to a temporary location
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
vm.communicate.upload(temp.path, "/tmp/vagrant-network-entry")
# Configure the interface
vm.communicate.sudo("ln -fs /etc/init.d/net.lo /etc/init.d/net.eth#{network[:interface]}")
vm.communicate.sudo("/etc/init.d/net.eth#{network[:interface]} stop 2> /dev/null")
vm.communicate.sudo("cat /tmp/vagrant-network-entry >> /etc/conf.d/net")
vm.communicate.sudo("rm /tmp/vagrant-network-entry")
vm.communicate.sudo("/etc/init.d/net.eth#{network[:interface]} start")
end
end
def change_host_name(name)
if !vm.communicate.test("sudo hostname --fqdn | grep '#{name}'")
vm.communicate.sudo("echo 'hostname=#{name.split('.')[0]}' > /etc/conf.d/hostname")
vm.communicate.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
vm.communicate.sudo("hostname #{name.split('.')[0]}")
end
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/gentoo-release")
end
end
end

View File

@ -6,10 +6,20 @@ module VagrantPlugins
name "Gentoo guest"
description "Gentoo guest support."
guest("gentoo") do
guest("gentoo", "linux") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("gentoo", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("gentoo", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
end
end
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module GuestLinux
module Cap
class Halt
def self.halt(machine)
begin
machine.communicate.sudo("shutdown -h now")
rescue IOError
# Do nothing, because it probably means the machine shut down
# and SSH connection was lost.
end
end
end
end
end
end

View File

@ -0,0 +1,30 @@
require "vagrant/util/retryable"
module VagrantPlugins
module GuestLinux
module Cap
class MountNFS
extend Vagrant::Util::Retryable
def self.mount_nfs_folder(machine, ip, folders)
folders.each do |name, opts|
# Expand the guest path so we can handle things like "~/vagrant"
expanded_guest_path = machine.guest.capability(
:shell_expand_guest_path, opts[:guestpath])
# Do the actual creating and mounting
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
# Mount
mount_command = "mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{expanded_guest_path}"
retryable(:on => Vagrant::Errors::LinuxNFSMountFailed, :tries => 5, :sleep => 2) do
machine.communicate.sudo(mount_command,
:error_class => Vagrant::Errors::LinuxNFSMountFailed)
end
end
end
end
end
end
end

View File

@ -0,0 +1,40 @@
module VagrantPlugins
module GuestLinux
module Cap
class MountVirtualBoxSharedFolder
def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)
expanded_guest_path = machine.guest.capability(
:shell_expand_guest_path, guestpath)
# Determine the permission string to attach to the mount command
mount_options = "-o uid=`id -u #{options[:owner]}`,gid=`id -g #{options[:group]}`"
mount_options += ",#{options[:extra]}" if options[:extra]
mount_command = "mount -t vboxsf #{mount_options} #{name} #{expanded_guest_path}"
# Create the guest path if it doesn't exist
machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
# Attempt to mount the folder. We retry here a few times because
# it can fail early on.
attempts = 0
while true
success = true
machine.communicate.sudo(mount_command) do |type, data|
success = false if type == :stderr && data =~ /No such device/i
end
break if success
attempts += 1
raise Vagrant::Errors::LinuxMountFailed, :command => mount_command
sleep 2
end
# Chown the directory to the proper user
machine.communicate.sudo(
"chown `id -u #{options[:owner]}`:`id -g #{options[:group]}` #{expanded_guest_path}")
end
end
end
end
end

View File

@ -0,0 +1,26 @@
module VagrantPlugins
module GuestLinux
module Cap
class ShellExpandGuestPath
def self.shell_expand_guest_path(machine, path)
real_path = nil
machine.communicate.execute("printf #{path}") do |type, data|
if type == :stdout
real_path ||= ""
real_path += data
end
end
if !real_path
# If no real guest path was detected, this is really strange
# and we raise an exception because this is a bug.
raise LinuxShellExpandFailed
end
# Chomp the string so that any trailing newlines are killed
return real_path.chomp
end
end
end
end
end

View File

@ -1,13 +0,0 @@
module VagrantPlugins
module GuestLinux
class Config < Vagrant.plugin("2", :config)
attr_accessor :halt_timeout
attr_accessor :halt_check_interval
def initialize
@halt_timeout = 30
@halt_check_interval = 1
end
end
end
end

View File

@ -1,124 +1,11 @@
require 'log4r'
require "vagrant"
require "vagrant/util/retryable"
module VagrantPlugins
module GuestLinux
class Guest < Vagrant.plugin("2", :guest)
include Vagrant::Util::Retryable
class LinuxError < Vagrant::Errors::VagrantError
error_namespace("vagrant.guest.linux")
end
def initialize(*args)
super
@logger = Log4r::Logger.new("vagrant::guest::linux")
end
def distro_dispatch
@vm.communicate.tap do |comm|
if comm.test("cat /etc/debian_version")
return :debian if comm.test("cat /proc/version | grep 'Debian'")
return :ubuntu if comm.test("cat /proc/version | grep 'Ubuntu'")
end
return :gentoo if comm.test("cat /etc/gentoo-release")
return :fedora if comm.test("grep 'Fedora release 1[678]' /etc/redhat-release")
return :redhat if comm.test("cat /etc/redhat-release")
return :suse if comm.test("cat /etc/SuSE-release")
return :pld if comm.test("cat /etc/pld-release")
return :arch if comm.test("cat /etc/arch-release")
return :solaris if comm.test("grep 'Solaris' /etc/release")
end
# Can't detect the distro, assume vanilla linux
nil
end
def halt
begin
@vm.communicate.sudo("shutdown -h now")
rescue IOError
# Do nothing, because it probably means the machine shut down
# and SSH connection was lost.
end
end
def mount_shared_folder(name, guestpath, options)
real_guestpath = expanded_guest_path(guestpath)
@logger.debug("Shell expanded guest path: #{real_guestpath}")
@vm.communicate.sudo("mkdir -p #{real_guestpath}")
mount_folder(name, real_guestpath, options)
@vm.communicate.sudo("chown `id -u #{options[:owner]}`:`id -g #{options[:group]}` #{real_guestpath}")
end
def mount_nfs(ip, folders)
# TODO: Maybe check for nfs support on the guest, since its often
# not installed by default
folders.each do |name, opts|
# Expand the guestpath, so we can handle things like "~/vagrant"
real_guestpath = expanded_guest_path(opts[:guestpath])
# Do the actual creating and mounting
@vm.communicate.sudo("mkdir -p #{real_guestpath}")
retryable(:on => LinuxError, :tries => 5, :sleep => 2) do
@vm.communicate.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
:error_class => LinuxError,
:error_key => :mount_nfs_fail)
end
end
end
protected
# Determine the real guest path. Since we use a `sudo` shell everywhere
# else, things like '~' don't expand properly in shared folders. We have
# to `echo` here to get that path.
#
# @param [String] guestpath The unexpanded guest path.
# @return [String] The expanded guestpath
def expanded_guest_path(guestpath)
real_guestpath = nil
@vm.communicate.execute("printf #{guestpath}") do |type, data|
if type == :stdout
real_guestpath ||= ""
real_guestpath += data
end
end
if !real_guestpath
# Really strange error case if this happens. Let's throw an error,
# tell the user to check the echo output.
raise LinuxError, :_key => :guestpath_expand_fail
end
# Chomp the string so that any trailing newlines are killed
return real_guestpath.chomp
end
def mount_folder(name, guestpath, options)
# Determine the permission string to attach to the mount command
mount_options = "-o uid=`id -u #{options[:owner]}`,gid=`id -g #{options[:group]}`"
mount_options += ",#{options[:extra]}" if options[:extra]
attempts = 0
while true
success = true
@vm.communicate.sudo("mount -t vboxsf #{mount_options} #{name} #{guestpath}") do |type, data|
success = false if type == :stderr && data =~ /No such device/i
end
break if success
attempts += 1
raise LinuxError, :mount_fail if attempts >= 10
sleep 5
end
def detect?(machine)
# TODO: Linux detection
false
end
end
end

View File

@ -6,15 +6,30 @@ module VagrantPlugins
name "Linux guest."
description "Linux guest support."
config("linux") do
require File.expand_path("../config", __FILE__)
Config
end
guest("linux") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("linux", "halt") do
require_relative "cap/halt"
Cap::Halt
end
guest_capability("linux", "shell_expand_guest_path") do
require_relative "cap/shell_expand_guest_path"
Cap::ShellExpandGuestPath
end
guest_capability("linux", "mount_nfs_folder") do
require_relative "cap/mount_nfs"
Cap::MountNFS
end
guest_capability("linux", "mount_virtualbox_shared_folder") do
require_relative "cap/mount_virtualbox_shared_folder"
Cap::MountVirtualBoxSharedFolder
end
end
end
end

View File

@ -0,0 +1,16 @@
module VagrantPlugins
module GuestOpenBSD
module Cap
class Halt
def self.halt(machine)
begin
machine.communicate.sudo("shutdown -p -h now")
rescue IOError
# Do nothing, because it probably means the machine shut down
# and SSH connection was lost.
end
end
end
end
end
end

View File

@ -1,12 +1,11 @@
require "vagrant"
require Vagrant.source_root.join("plugins/guests/linux/guest")
module VagrantPlugins
module GuestOpenBSD
class Guest < VagrantPlugins::GuestLinux::Guest
def halt
vm.communicate.sudo("shutdown -p -h now")
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
# TODO: OpenBSD detection
false
end
end
end

View File

@ -6,10 +6,15 @@ module VagrantPlugins
name "OpenBSD guest"
description "OpenBSD guest support."
guest("openbsd") do
guest("openbsd", "linux") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("openbsd", "halt") do
require_relative "cap/halt"
Cap::Halt
end
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module GuestPld
module Cap
class NetworkScriptsDir
def self.network_scripts_dir(machine)
"/etc/sysconfig/interfaces"
end
end
end
end
end

View File

@ -1,12 +1,10 @@
require "vagrant"
require Vagrant.source_root.join("plugins/guests/redhat/guest")
module VagrantPlugins
module GuestPld
class Guest < VagrantPlugins::GuestRedHat::Guest
def network_scripts_dir
'/etc/sysconfig/interfaces/'
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/pld-release")
end
end
end

View File

@ -6,10 +6,15 @@ module VagrantPlugins
name "PLD Linux guest"
description "PLD Linux guest support."
guest("pld") do
guest("pld", "redhat") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("pld", "network_scripts_dir") do
require_relative "cap/network_scripts_dir"
Cap::NetworkScriptsDir
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module GuestRedHat
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
# Only do this if the hostname is not already set
if !comm.test("sudo hostname | grep '#{name}'")
comm.sudo("sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/sysconfig/network")
comm.sudo("hostname #{name}")
comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
end
end
end
end
end
end
end

View File

@ -0,0 +1,55 @@
require "set"
require "tempfile"
require "vagrant/util/template_renderer"
module VagrantPlugins
module GuestRedHat
module Cap
class ConfigureNetworks
extend Vagrant::Util
def self.configure_networks(machine, networks)
network_scripts_dir = machine.guest.capability("network_scripts_dir")
# Accumulate the configurations to add to the interfaces file as
# well as what interfaces we're actually configuring since we use that
# later.
interfaces = Set.new
networks.each do |network|
interfaces.add(network[:interface])
# Remove any previous vagrant configuration in this network interface's
# configuration files.
machine.communicate.sudo("touch #{network_scripts_dir}/ifcfg-eth#{network[:interface]}")
machine.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' #{network_scripts_dir}/ifcfg-eth#{network[:interface]} > /tmp/vagrant-ifcfg-eth#{network[:interface]}")
machine.communicate.sudo("cat /tmp/vagrant-ifcfg-eth#{network[:interface]} > #{network_scripts_dir}/ifcfg-eth#{network[:interface]}")
machine.communicate.sudo("rm /tmp/vagrant-ifcfg-eth#{network[:interface]}")
# Render and upload the network entry file to a deterministic
# temporary location.
entry = TemplateRenderer.render("guests/redhat/network_#{network[:type]}",
:options => network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
machine.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
end
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
machine.communicate.sudo("/sbin/ifdown eth#{interface} 2> /dev/null", :error_check => false)
machine.communicate.sudo("cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-eth#{interface}")
machine.communicate.sudo("rm /tmp/vagrant-network-entry_#{interface}")
machine.communicate.sudo("/sbin/ifup eth#{interface} 2> /dev/null")
end
end
end
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module GuestRedHat
module Cap
class NetworkScriptsDir
def self.network_scripts_dir(machine)
"/etc/sysconfig/network-scripts"
end
end
end
end
end

View File

@ -1,73 +1,10 @@
require 'set'
require 'tempfile'
require "vagrant"
require 'vagrant/util/template_renderer'
require Vagrant.source_root.join("plugins/guests/linux/guest")
module VagrantPlugins
module GuestRedHat
class Guest < VagrantPlugins::GuestLinux::Guest
# Make the TemplateRenderer top-level
include Vagrant::Util
def configure_networks(networks)
# Accumulate the configurations to add to the interfaces file as
# well as what interfaces we're actually configuring since we use that
# later.
interfaces = Set.new
networks.each do |network|
interfaces.add(network[:interface])
# Remove any previous vagrant configuration in this network interface's
# configuration files.
vm.communicate.sudo("touch #{network_scripts_dir}/ifcfg-eth#{network[:interface]}")
vm.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' #{network_scripts_dir}/ifcfg-eth#{network[:interface]} > /tmp/vagrant-ifcfg-eth#{network[:interface]}")
vm.communicate.sudo("cat /tmp/vagrant-ifcfg-eth#{network[:interface]} > #{network_scripts_dir}/ifcfg-eth#{network[:interface]}")
vm.communicate.sudo("rm /tmp/vagrant-ifcfg-eth#{network[:interface]}")
# Render and upload the network entry file to a deterministic
# temporary location.
entry = TemplateRenderer.render("guests/redhat/network_#{network[:type]}",
:options => network)
temp = Tempfile.new("vagrant")
temp.binmode
temp.write(entry)
temp.close
vm.communicate.upload(temp.path, "/tmp/vagrant-network-entry_#{network[:interface]}")
end
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
vm.communicate.sudo("/sbin/ifdown eth#{interface} 2> /dev/null", :error_check => false)
vm.communicate.sudo("cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-eth#{interface}")
vm.communicate.sudo("rm /tmp/vagrant-network-entry_#{interface}")
vm.communicate.sudo("/sbin/ifup eth#{interface} 2> /dev/null")
end
end
# The path to the directory with the network configuration scripts.
# This is pulled out into its own directory since there are other
# operating systems (SuSE) which behave similarly but with a different
# path to the network scripts.
def network_scripts_dir
'/etc/sysconfig/network-scripts'
end
def change_host_name(name)
vm.communicate.tap do |comm|
# Only do this if the hostname is not already set
if !comm.test("sudo hostname | grep '#{name}'")
comm.sudo("sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/sysconfig/network")
comm.sudo("hostname #{name}")
comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
end
end
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/redhat-release")
end
end
end

View File

@ -6,10 +6,25 @@ module VagrantPlugins
name "RedHat guest"
description "RedHat guest support."
guest("redhat") do
guest("redhat", "linux") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("redhat", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("redhat", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
guest_capability("redhat", "network_scripts_dir") do
require_relative "cap/network_scripts_dir"
Cap::NetworkScriptsDir
end
end
end
end

View File

@ -0,0 +1,17 @@
module VagrantPlugins
module GuestSolaris
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
su_cmd = machine.config.solaris.suexec_cmd
# Only do this if the hostname is not already set
if !machine.communicate.test("#{su_cmd} hostname | grep '#{name}'")
machine.communicate.execute("#{su_cmd} sh -c \"echo '#{name}' > /etc/nodename\"")
machine.communicate.execute("#{su_cmd} uname -S #{name}")
end
end
end
end
end
end

View File

@ -0,0 +1,25 @@
module VagrantPlugins
module GuestSolaris
module Cap
class ConfigureNetworks
def self.configure_networks(machine, networks)
networks.each do |network|
device = "#{machine.config.solaris.device}#{network[:interface]}"
su_cmd = machine.config.solaris.suexec_cmd
ifconfig_cmd = "#{su_cmd} /sbin/ifconfig #{device}"
machine.communicate.execute("#{ifconfig_cmd} plumb")
if network[:type].to_sym == :static
machine.communicate.execute("#{ifconfig_cmd} inet #{network[:ip]} netmask #{network[:netmask]}")
machine.communicate.execute("#{ifconfig_cmd} up")
machine.communicate.execute("#{su_cmd} sh -c \"echo '#{network[:ip]}' > /etc/hostname.#{device}\"")
elsif network[:type].to_sym == :dhcp
machine.communicate.execute("#{ifconfig_cmd} dhcp start")
end
end
end
end
end
end
end

View File

@ -0,0 +1,21 @@
module VagrantPlugins
module GuestSolaris
module Cap
class Halt
def self.halt(machine)
# There should be an exception raised if the line
#
# vagrant::::profiles=Primary Administrator
#
# does not exist in /etc/user_attr. TODO
begin
machine.communicate.execute("#{machine.config.solaris.suexec_cmd} /usr/sbin/poweroff")
rescue IOError
# Ignore, this probably means connection closed because it
# shut down.
end
end
end
end
end
end

View File

@ -0,0 +1,28 @@
module VagrantPlugins
module GuestSolaris
module Cap
class MountVirtualBoxSharedFolder
def self.mount_virtualbox_shared_folder(machine, name, guestpath, options)
# These are just far easier to use than the full options syntax
owner = options[:owner]
group = options[:group]
# Create the shared folder
machine.communicate.execute("#{machine.config.solaris.suexec_cmd} mkdir -p #{guestpath}")
# We have to use this `id` command instead of `/usr/bin/id` since this
# one accepts the "-u" and "-g" flags.
id_cmd = "/usr/xpg4/bin/id"
# Mount the folder with the proper owner/group
mount_options = "-o uid=`#{id_cmd} -u #{owner}`,gid=`#{id_cmd} -g #{group}`"
mount_options += ",#{options[:extra]}" if options[:extra]
machine.communicate.execute("#{machine.config.solaris.suexec_cmd} /sbin/mount -F vboxfs #{mount_options} #{name} #{guestpath}")
# chown the folder to the proper owner/group
machine.communicate.execute("#{machine.config.solaris.suexec_cmd} chown `#{id_cmd} -u #{owner}`:`#{id_cmd} -g #{group}` #{guestpath}")
end
end
end
end
end

View File

@ -6,72 +6,8 @@ module VagrantPlugins
#
# Contributed by Blake Irvin <b.irvin@modcloth.com>
class Guest < Vagrant.plugin("2", :guest)
# Here for whenever it may be used.
class SolarisError < Vagrant::Errors::VagrantError
error_namespace("vagrant.guest.solaris")
end
def configure_networks(networks)
networks.each do |network|
device = "#{vm.config.solaris.device}#{network[:interface]}"
su_cmd = vm.config.solaris.suexec_cmd
ifconfig_cmd = "#{su_cmd} /sbin/ifconfig #{device}"
vm.communicate.execute("#{ifconfig_cmd} plumb")
if network[:type].to_sym == :static
vm.communicate.execute("#{ifconfig_cmd} inet #{network[:ip]} netmask #{network[:netmask]}")
vm.communicate.execute("#{ifconfig_cmd} up")
vm.communicate.execute("#{su_cmd} sh -c \"echo '#{network[:ip]}' > /etc/hostname.#{device}\"")
elsif network[:type].to_sym == :dhcp
vm.communicate.execute("#{ifconfig_cmd} dhcp start")
end
end
end
def change_host_name(name)
su_cmd = vm.config.solaris.suexec_cmd
# Only do this if the hostname is not already set
if !vm.communicate.test("#{su_cmd} hostname | grep '#{name}'")
vm.communicate.execute("#{su_cmd} sh -c \"echo '#{name}' > /etc/nodename\"")
vm.communicate.execute("#{su_cmd} uname -S #{name}")
end
end
# There should be an exception raised if the line
#
# vagrant::::profiles=Primary Administrator
#
# does not exist in /etc/user_attr. TODO
def halt
begin
vm.communicate.execute("#{vm.config.solaris.suexec_cmd} /usr/sbin/poweroff")
rescue IOError
# Ignore, this probably means connection closed because it
# shut down.
end
end
def mount_shared_folder(name, guestpath, options)
# These are just far easier to use than the full options syntax
owner = options[:owner]
group = options[:group]
# Create the shared folder
vm.communicate.execute("#{vm.config.solaris.suexec_cmd} mkdir -p #{guestpath}")
# We have to use this `id` command instead of `/usr/bin/id` since this
# one accepts the "-u" and "-g" flags.
id_cmd = "/usr/xpg4/bin/id"
# Mount the folder with the proper owner/group
mount_options = "-o uid=`#{id_cmd} -u #{owner}`,gid=`#{id_cmd} -g #{group}`"
mount_options += ",#{options[:extra]}" if options[:extra]
vm.communicate.execute("#{vm.config.solaris.suexec_cmd} /sbin/mount -F vboxfs #{mount_options} #{name} #{guestpath}")
# chown the folder to the proper owner/group
vm.communicate.execute("#{vm.config.solaris.suexec_cmd} chown `#{id_cmd} -u #{owner}`:`#{id_cmd} -g #{group}` #{guestpath}")
def detect?(machine)
machine.communicate.test("grep 'Solaris' /etc/release")
end
end
end

View File

@ -15,6 +15,26 @@ module VagrantPlugins
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("solaris", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("solaris", "configure_networks") do
require_relative "cap/configure_networks"
Cap::ConfigureNetworks
end
guest_capability("solaris", "halt") do
require_relative "cap/halt"
Cap::Halt
end
guest_capability("solaris", "mount_virtualbox_shared_folder") do
require_relative "cap/mount_virtualbox_shared_folder"
Cap::MountVirtualBoxSharedFolder
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module GuestSuse
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
# Only do this if the hostname is not already set
if !comm.test("sudo hostname | grep '#{name}'")
comm.sudo("sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/sysconfig/network")
comm.sudo("hostname #{name}")
comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
end
end
end
end
end
end
end

View File

@ -0,0 +1,11 @@
module VagrantPlugins
module GuestSuse
module Cap
class NetworkScriptsDir
def self.network_scripts_dir(machine)
"/etc/sysconfig/network/"
end
end
end
end
end

View File

@ -1,23 +1,10 @@
require "vagrant"
require Vagrant.source_root.join("plugins/guests/redhat/guest")
module VagrantPlugins
module GuestSuse
class Guest < VagrantPlugins::GuestRedHat::Guest
def network_scripts_dir
'/etc/sysconfig/network/'
end
def change_host_name(name)
vm.communicate.tap do |comm|
# Only do this if the hostname is not already set
if !comm.test("sudo hostname | grep '#{name}'")
comm.sudo("echo '#{name}' > /etc/HOSTNAME")
comm.sudo("hostname #{name}")
comm.sudo("sed -i 's@^\\(127[.]0[.]0[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
end
end
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("cat /etc/SuSE-release")
end
end
end

View File

@ -6,10 +6,20 @@ module VagrantPlugins
name "SUSE guest"
description "SUSE guest support."
guest("suse") do
guest("suse", "redhat") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("suse", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
guest_capability("suse", "network_scripts_dir") do
require_relative "cap/network_scripts_dir"
Cap::NetworkScriptsDir
end
end
end
end

View File

@ -0,0 +1,23 @@
module VagrantPlugins
module GuestUbuntu
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
if !comm.test("sudo hostname | grep '^#{name}$'")
comm.sudo("sed -i 's/.*$/#{name}/' /etc/hostname")
comm.sudo("sed -i 's@^\\(127[.]0[.]1[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
if comm.test("[ `lsb_release -c -s` = hardy ]")
# hostname.sh returns 1, so I grep for the right name in /etc/hostname just to have a 0 exitcode
comm.sudo("/etc/init.d/hostname.sh start; grep '#{name}' /etc/hostname")
else
comm.sudo("service hostname start")
end
comm.sudo("hostname --fqdn > /etc/mailname")
end
end
end
end
end
end
end

View File

@ -5,6 +5,10 @@ require Vagrant.source_root.join("plugins/guests/debian/guest")
module VagrantPlugins
module GuestUbuntu
class Guest < VagrantPlugins::GuestDebian::Guest
def detect?(machine)
machine.communicate.test("cat /proc/version | grep 'Ubuntu'")
end
def mount_shared_folder(name, guestpath, options)
# Mount it like normal
super
@ -23,22 +27,6 @@ module VagrantPlugins
vm.communicate.sudo("[ -x /sbin/initctl ] && /sbin/initctl emit vagrant-mounted MOUNTPOINT=#{real_guestpath}")
end
end
def change_host_name(name)
vm.communicate.tap do |comm|
if !comm.test("sudo hostname | grep '^#{name}$'")
comm.sudo("sed -i 's/.*$/#{name}/' /etc/hostname")
comm.sudo("sed -i 's@^\\(127[.]0[.]1[.]1[[:space:]]\\+\\)@\\1#{name} #{name.split('.')[0]} @' /etc/hosts")
if comm.test("[ `lsb_release -c -s` = hardy ]")
# hostname.sh returns 1, so I grep for the right name in /etc/hostname just to have a 0 exitcode
comm.sudo("/etc/init.d/hostname.sh start; grep '#{name}' /etc/hostname")
else
comm.sudo("service hostname start")
end
comm.sudo("hostname --fqdn > /etc/mailname")
end
end
end
end
end
end

View File

@ -6,10 +6,15 @@ module VagrantPlugins
name "Ubuntu guest"
description "Ubuntu guest support."
guest("ubuntu") do
guest("ubuntu", "debian") do
require File.expand_path("../guest", __FILE__)
Guest
end
guest_capability("ubuntu", "change_host_name") do
require_relative "cap/change_host_name"
Cap::ChangeHostName
end
end
end
end

View File

@ -112,7 +112,7 @@ module VagrantPlugins
# Only configure the networks the user requested us to configure
networks_to_configure = networks.select { |n| n[:auto_config] }
env[:ui].info I18n.t("vagrant.actions.vm.network.configuring")
env[:machine].guest.configure_networks(networks_to_configure)
env[:machine].guest.capability(:configure_networks, networks_to_configure)
end
end

View File

@ -108,7 +108,8 @@ module VagrantPlugins
data[:group] ||= @env[:machine].config.ssh.username
# Mount the actual folder
@env[:machine].guest.mount_shared_folder(id, data[:guestpath], data)
@env[:machine].guest.capability(
:mount_virtualbox_shared_folder, id, data[:guestpath], data)
else
# If no guest path is specified, then automounting is disabled
@env[:ui].info(I18n.t("vagrant.actions.vm.share_folders.nomount_entry",

View File

@ -173,18 +173,23 @@ en:
of the official installers or another gem is wrongly attempting to
use Vagrant internals directly. Please properly install Vagrant to
fix this. If this error persists, please contact support.
guest:
invalid_class: |-
The specified guest class does not inherit from a proper guest
component class. The guest class must inherit from this.
The specified guest class was: %{guest}
unknown_type: |-
The specified guest type is unknown: %{guest}. Please change this
to a proper value.
unspecified: |-
A VM guest type must be specified! This is done via the `config.vm.guest`
configuration value. Please read the documentation online for more information.
guest_capability_invalid: |-
The registered guest capability '%{cap}' for the
detected guest OS '%{guest}' is invalid. The capability does
not implement the proper method. This is a bug with Vagrant or the
plugin that implements this capability. Please report a bug.
guest_capability_not_found: |-
Vagrant attempted to execute the capability '%{cap}'
on the detect guest OS '%{guest}', but the guest doesn't
support that capability. This capability is required for your
configuration of Vagrant. Please either reconfigure Vagrant to
avoid this capability or fix the issue by creating the capability.
guest_not_detected: |-
The guest operating system of the machine could not be detected!
Vagrant requires this knowledge to perform specific tasks such
as mounting shared folders and configuring networks. Please add
the ability to detect this guest operating system to Vagrant
by creating a plugin or reporting a bug.
home_dir_not_accessible: |-
The home directory you specified is not accessible. The home
directory that Vagrant uses must be both readable and writable.
@ -199,6 +204,24 @@ en:
running Vagrant.
Local data directory: %{local_data_path}
linux_mount_failed: |-
Failed to mount folders in Linux guest. This is usually beacuse
the "vboxsf" file system is not available. Please verify that
the guest additions are properly installed in the guest and
can work properly. The command attempted was:
%{command}
linux_nfs_mount_failed: |-
Mounting NFS shared folders failed. This is most often caused by the NFS
client software not being installed on the guest machine. Please verify
that the NFS client software is properly installed, and consult any resources
specific to the linux distro you're using for more information on how to
do this.
linux_shell_expand_failed: |-
Vagrant failed to determine the shell expansion of the guest path
for one of your shared folders. This is an extremely rare error case
and most likely indicates an unusual configuration of the guest system.
Please report a bug with your Vagrantfile.
machine_guest_not_ready: |-
Guest-specific operations were attempted on a machine that is not
ready for guest communication. This should not happen and a bug
@ -1015,56 +1038,3 @@ en:
no_path_or_inline: "One of `path` or `inline` must be set."
path_invalid: "`path` for shell provisioner does not exist on the host system: %{path}"
upload_path_not_set: "`upload_path` must be set for the shell provisioner."
guest:
base:
unsupported_configure_networks: |-
Networking features require support that is dependent on the operating
system running within the guest virtual machine. Vagrant has built-in support
for many operating systems: Debian, Ubuntu, Gentoo, and RedHat. The distro
of your VM couldn't be detected or doesn't support networking features.
Most of the time this is simply due to the fact that no one has contributed
back the logic necessary to set this up. Please report a bug as well as the
box you're using.
unsupported_host_name: |-
Setting host name is currently only supported on Debian, Ubuntu and RedHat.
If you'd like your guest OS to be supported, please open a ticket on the
project.
unsupported_nfs: |-
Vagrant doesn't support mounting NFS shared folders for your specific
guest operating system yet, or possibly couldn't properly detect the
operating system on the VM.
Most of the time this is simply due to the fact that no one has contributed
back the logic necessary to set this up. Please report a bug as well as the
box you're using.
unsupported_halt: |-
Vagrant doesn't support graceful shutdowns for your specific
guest operating system yet, or possibly couldn't properly detect the
operating system on the VM.
Most of the time this is simply due to the fact that no one has contributed
back the logic necessary to set this up. Please report a bug as well as the
box you're using.
unsupported_shared_folder: |-
Vagrant doesn't support mounting shared folders for your specific
guest operating system yet, or possibly couldn't properly detect the
operating system on the VM.
Most of the time this is simply due to the fact that no one has contributed
back the logic necessary to set this up. Please report a bug as well as the
box you're using.
linux:
guestpath_expand_fail: |-
Vagrant failed to determine the shell expansion of the guest path
for one of your shared folders. This is an extremely rare error case
and most likely indicates an unusual configuration of the guest system.
Please report a bug with your Vagrantfile.
mount_fail: "Failed to mount shared folders. `vboxsf` was not available."
mount_nfs_fail: |-
Mounting NFS shared folders failed. This is most often caused by the NFS
client software not being installed on the guest machine. Please verify
that the NFS client software is properly installed, and consult any resources
specific to the linux distro you're using for more information on how to
do this.

View File

@ -34,7 +34,7 @@ describe Vagrant::Action::Builtin::GracefulHalt do
it "should do nothing if force is specified" do
env[:force_halt] = true
machine_guest.should_not_receive(:halt)
machine_guest.should_not_receive(:capability)
described_class.new(app, env, target_state).call(env)
@ -43,7 +43,7 @@ describe Vagrant::Action::Builtin::GracefulHalt do
it "should do nothing if there is an invalid source state" do
machine_state.stub(:id).and_return(:invalid_source)
machine_guest.should_not_receive(:halt)
machine_guest.should_not_receive(:capability)
described_class.new(app, env, target_state, :target_source).call(env)
@ -51,7 +51,7 @@ describe Vagrant::Action::Builtin::GracefulHalt do
end
it "should gracefully halt and wait for the target state" do
machine_guest.should_receive(:halt).once
machine_guest.should_receive(:capability).with(:halt).once
machine_state.stub(:id).and_return(target_state)
described_class.new(app, env, target_state).call(env)

View File

@ -0,0 +1,166 @@
require "pathname"
require File.expand_path("../../base", __FILE__)
describe Vagrant::Guest do
include_context "unit"
let(:capabilities) { {} }
let(:guests) { {} }
let(:machine) do
double("machine").tap do |m|
m.stub(:inspect => "machine")
end
end
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
it "detects the first match" do
register_guest(:foo, nil, false)
register_guest(:bar, nil, true)
register_guest(:baz, nil, false)
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
it "detects those with the most parents first" do
register_guest(:foo, nil, true)
register_guest(:bar, :foo, true)
register_guest(:baz, :bar, true)
register_guest(:foo2, nil, true)
register_guest(:bar2, :foo2, true)
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 "raises an exception if no guest can be detected" do
expect { subject.detect! }.
to raise_error(Vagrant::Errors::GuestNotDetected)
end
end
describe "#ready?" do
before(:each) do
register_guest(:foo, nil, true)
end
it "should not be ready by default" do
subject.ready?.should_not be
end
it "should be ready after detecting" do
subject.detect!
subject.ready?.should be
end
end
end

View File

@ -197,10 +197,21 @@ describe Vagrant::Machine do
let(:communicator) do
result = double("communicator")
result.stub(:ready?).and_return(true)
result.stub(:test).and_return(false)
result
end
before(:each) do
test_guest = Class.new(Vagrant.plugin("2", :guest)) do
def detect?(machine)
true
end
end
register_plugin do |p|
p.guest(:test) { test_guest }
end
instance.stub(:communicate).and_return(communicator)
end
@ -212,62 +223,10 @@ describe Vagrant::Machine do
end
it "should return the configured guest" do
test_guest = Class.new(Vagrant.plugin("2", :guest))
register_plugin do |p|
p.guest(:test) { test_guest }
end
config.vm.guest = :test
result = instance.guest
result.should be_kind_of(test_guest)
end
it "should raise an exception if it can't find the configured guest" do
config.vm.guest = :bad
expect { instance.guest }.
to raise_error(Vagrant::Errors::VMGuestError)
end
it "should distro dispatch to the most specific guest" do
# Create the classes and dispatch the parent into the child
guest_parent = Class.new(Vagrant.plugin("2", :guest)) do
def distro_dispatch
:child
end
end
guest_child = Class.new(Vagrant.plugin("2", :guest))
# Register the classes
register_plugin do |p|
p.guest(:parent) { guest_parent }
p.guest(:child) { guest_child }
end
# Test that the result is the child
config.vm.guest = :parent
instance.guest.should be_kind_of(guest_child)
end
it "should protect against loops in the distro dispatch" do
# Create the classes and dispatch the parent into the child
guest_parent = Class.new(Vagrant.plugin("2", :guest)) do
def distro_dispatch
:parent
end
end
# Register the classes
register_plugin do |p|
p.guest(:parent) { guest_parent }
end
# Test that the result is the child
config.vm.guest = :parent
instance.guest.should be_kind_of(guest_parent)
result.should be_kind_of(Vagrant::Guest)
result.ready?.should be
result.chain[0][0].should == :test
end
end

View File

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

View File

@ -154,7 +154,7 @@ describe Vagrant::Plugin::V2::Plugin do
guest("foo") { "bar" }
end
plugin.guest[:foo].should == "bar"
plugin.components.guests[:foo].should == ["bar", nil]
end
it "should lazily register guest classes" do
@ -176,6 +176,16 @@ describe Vagrant::Plugin::V2::Plugin do
end
end
describe "guest capabilities" do
it "should register guest capabilities" do
plugin = Class.new(described_class) do
guest_capability("foo", "bar") { "baz" }
end
plugin.components.guest_capabilities[:foo][:bar].should == "baz"
end
end
describe "hosts" do
it "should register host classes" do
plugin = Class.new(described_class) do