diff --git a/CHANGELOG.md b/CHANGELOG.md index 994bc3e78..1fe83eecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: diff --git a/lib/vagrant/action/builtin/graceful_halt.rb b/lib/vagrant/action/builtin/graceful_halt.rb index 832695ff4..da1235551 100644 --- a/lib/vagrant/action/builtin/graceful_halt.rb +++ b/lib/vagrant/action/builtin/graceful_halt.rb @@ -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 diff --git a/lib/vagrant/action/builtin/nfs.rb b/lib/vagrant/action/builtin/nfs.rb index 364233668..17db963b2 100644 --- a/lib/vagrant/action/builtin/nfs.rb +++ b/lib/vagrant/action/builtin/nfs.rb @@ -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 diff --git a/lib/vagrant/action/builtin/set_hostname.rb b/lib/vagrant/action/builtin/set_hostname.rb index 39f06bc4c..45458db0b 100644 --- a/lib/vagrant/action/builtin/set_hostname.rb +++ b/lib/vagrant/action/builtin/set_hostname.rb @@ -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 diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 084d66c84..d37695f31 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -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 diff --git a/lib/vagrant/guest.rb b/lib/vagrant/guest.rb new file mode 100644 index 000000000..bc427df0b --- /dev/null +++ b/lib/vagrant/guest.rb @@ -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 diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index f8be78aef..47b23d202 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -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 diff --git a/lib/vagrant/plugin/v2/components.rb b/lib/vagrant/plugin/v2/components.rb index 9510b0a87..6b585d071 100644 --- a/lib/vagrant/plugin/v2/components.rb +++ b/lib/vagrant/plugin/v2/components.rb @@ -16,6 +16,16 @@ module Vagrant # @return [Hash] attr_reader :configs + # This contains all the guests and their parents. + # + # @return [Registry>] + attr_reader :guests + + # This contains all the registered guest capabilities. + # + # @return [Hash] + 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 diff --git a/lib/vagrant/plugin/v2/guest.rb b/lib/vagrant/plugin/v2/guest.rb index f757296dd..9af83887b 100644 --- a/lib/vagrant/plugin/v2/guest.rb +++ b/lib/vagrant/plugin/v2/guest.rb @@ -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. + # 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. + # + # The guest class itself is only responsible for detecting itself, + # and may provide helpers for the capabilties. 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. + # 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. # - # If this method returns nil, then this instance is assumed to be - # the most specific guest implementation. - def distro_dispatch + # @return [Boolean] + def guest?(machine) + false end # Halt the machine. This method should gracefully shut down the diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index f7cfa5c4e..b6e79e427 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -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] diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index be2d11802..e66da1b8b 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -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 diff --git a/plugins/guests/arch/cap/change_host_name.rb b/plugins/guests/arch/cap/change_host_name.rb new file mode 100644 index 000000000..a32da048e --- /dev/null +++ b/plugins/guests/arch/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/arch/cap/configure_networks.rb b/plugins/guests/arch/cap/configure_networks.rb new file mode 100644 index 000000000..11fda5ded --- /dev/null +++ b/plugins/guests/arch/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/arch/guest.rb b/plugins/guests/arch/guest.rb index 7b0b5f373..116484fd8 100644 --- a/plugins/guests/arch/guest.rb +++ b/plugins/guests/arch/guest.rb @@ -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 diff --git a/plugins/guests/arch/plugin.rb b/plugins/guests/arch/plugin.rb index bcbb945ae..ab3a0b016 100644 --- a/plugins/guests/arch/plugin.rb +++ b/plugins/guests/arch/plugin.rb @@ -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 diff --git a/plugins/guests/debian/cap/change_host_name.rb b/plugins/guests/debian/cap/change_host_name.rb new file mode 100644 index 000000000..6ce256d7e --- /dev/null +++ b/plugins/guests/debian/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/debian/cap/configure_networks.rb b/plugins/guests/debian/cap/configure_networks.rb new file mode 100644 index 000000000..0cb3d006f --- /dev/null +++ b/plugins/guests/debian/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/debian/guest.rb b/plugins/guests/debian/guest.rb index c5a32cb19..ec6b6a9a1 100644 --- a/plugins/guests/debian/guest.rb +++ b/plugins/guests/debian/guest.rb @@ -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 diff --git a/plugins/guests/debian/plugin.rb b/plugins/guests/debian/plugin.rb index 7cb276635..45fc1544a 100644 --- a/plugins/guests/debian/plugin.rb +++ b/plugins/guests/debian/plugin.rb @@ -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 diff --git a/plugins/guests/fedora/cap/configure_networks.rb b/plugins/guests/fedora/cap/configure_networks.rb new file mode 100644 index 000000000..5699d84b0 --- /dev/null +++ b/plugins/guests/fedora/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/fedora/cap/network_scripts_dir.rb b/plugins/guests/fedora/cap/network_scripts_dir.rb new file mode 100644 index 000000000..3ce0e43c3 --- /dev/null +++ b/plugins/guests/fedora/cap/network_scripts_dir.rb @@ -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 diff --git a/plugins/guests/fedora/guest.rb b/plugins/guests/fedora/guest.rb index 54856a3f5..1127dd28e 100644 --- a/plugins/guests/fedora/guest.rb +++ b/plugins/guests/fedora/guest.rb @@ -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 diff --git a/plugins/guests/fedora/plugin.rb b/plugins/guests/fedora/plugin.rb index 29323f188..fdfdab807 100644 --- a/plugins/guests/fedora/plugin.rb +++ b/plugins/guests/fedora/plugin.rb @@ -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 diff --git a/plugins/guests/freebsd/cap/change_host_name.rb b/plugins/guests/freebsd/cap/change_host_name.rb new file mode 100644 index 000000000..513a450bd --- /dev/null +++ b/plugins/guests/freebsd/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/freebsd/cap/configure_networks.rb b/plugins/guests/freebsd/cap/configure_networks.rb new file mode 100644 index 000000000..daca21cfa --- /dev/null +++ b/plugins/guests/freebsd/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/freebsd/cap/halt.rb b/plugins/guests/freebsd/cap/halt.rb new file mode 100644 index 000000000..938add189 --- /dev/null +++ b/plugins/guests/freebsd/cap/halt.rb @@ -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 diff --git a/plugins/guests/freebsd/cap/mount_nfs_folder.rb b/plugins/guests/freebsd/cap/mount_nfs_folder.rb new file mode 100644 index 000000000..bcd675887 --- /dev/null +++ b/plugins/guests/freebsd/cap/mount_nfs_folder.rb @@ -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 diff --git a/plugins/guests/freebsd/config.rb b/plugins/guests/freebsd/config.rb deleted file mode 100644 index 8dfa12edc..000000000 --- a/plugins/guests/freebsd/config.rb +++ /dev/null @@ -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 diff --git a/plugins/guests/freebsd/guest.rb b/plugins/guests/freebsd/guest.rb index 7a3b5ea66..faaff332e 100644 --- a/plugins/guests/freebsd/guest.rb +++ b/plugins/guests/freebsd/guest.rb @@ -6,18 +6,9 @@ module VagrantPlugins # # Contributed by Kenneth Vestergaard 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 diff --git a/plugins/guests/freebsd/plugin.rb b/plugins/guests/freebsd/plugin.rb index a9d50b7fa..8e147087b 100644 --- a/plugins/guests/freebsd/plugin.rb +++ b/plugins/guests/freebsd/plugin.rb @@ -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 diff --git a/plugins/guests/gentoo/cap/change_host_name.rb b/plugins/guests/gentoo/cap/change_host_name.rb new file mode 100644 index 000000000..4c0f50d8f --- /dev/null +++ b/plugins/guests/gentoo/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/gentoo/cap/configure_networks.rb b/plugins/guests/gentoo/cap/configure_networks.rb new file mode 100644 index 000000000..7234f36d0 --- /dev/null +++ b/plugins/guests/gentoo/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/gentoo/guest.rb b/plugins/guests/gentoo/guest.rb index 2f59dbd2c..33af4ae5d 100644 --- a/plugins/guests/gentoo/guest.rb +++ b/plugins/guests/gentoo/guest.rb @@ -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 diff --git a/plugins/guests/gentoo/plugin.rb b/plugins/guests/gentoo/plugin.rb index c888d811d..2054f81cd 100644 --- a/plugins/guests/gentoo/plugin.rb +++ b/plugins/guests/gentoo/plugin.rb @@ -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 diff --git a/plugins/guests/linux/cap/halt.rb b/plugins/guests/linux/cap/halt.rb new file mode 100644 index 000000000..6125a2082 --- /dev/null +++ b/plugins/guests/linux/cap/halt.rb @@ -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 diff --git a/plugins/guests/linux/cap/mount_nfs.rb b/plugins/guests/linux/cap/mount_nfs.rb new file mode 100644 index 000000000..69d4e92f2 --- /dev/null +++ b/plugins/guests/linux/cap/mount_nfs.rb @@ -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 diff --git a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb new file mode 100644 index 000000000..2b3fa026d --- /dev/null +++ b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb @@ -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 diff --git a/plugins/guests/linux/cap/shell_expand_guest_path.rb b/plugins/guests/linux/cap/shell_expand_guest_path.rb new file mode 100644 index 000000000..983e366f0 --- /dev/null +++ b/plugins/guests/linux/cap/shell_expand_guest_path.rb @@ -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 diff --git a/plugins/guests/linux/config.rb b/plugins/guests/linux/config.rb deleted file mode 100644 index f04b9d42d..000000000 --- a/plugins/guests/linux/config.rb +++ /dev/null @@ -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 diff --git a/plugins/guests/linux/guest.rb b/plugins/guests/linux/guest.rb index 7c649be92..59d09a608 100644 --- a/plugins/guests/linux/guest.rb +++ b/plugins/guests/linux/guest.rb @@ -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 diff --git a/plugins/guests/linux/plugin.rb b/plugins/guests/linux/plugin.rb index 7c766edb5..f9531bfa9 100644 --- a/plugins/guests/linux/plugin.rb +++ b/plugins/guests/linux/plugin.rb @@ -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 diff --git a/plugins/guests/openbsd/cap/halt.rb b/plugins/guests/openbsd/cap/halt.rb new file mode 100644 index 000000000..3b44d9b0c --- /dev/null +++ b/plugins/guests/openbsd/cap/halt.rb @@ -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 diff --git a/plugins/guests/openbsd/guest.rb b/plugins/guests/openbsd/guest.rb index dd4a4831a..180c85427 100644 --- a/plugins/guests/openbsd/guest.rb +++ b/plugins/guests/openbsd/guest.rb @@ -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 diff --git a/plugins/guests/openbsd/plugin.rb b/plugins/guests/openbsd/plugin.rb index ce74391c0..b3e3312a8 100644 --- a/plugins/guests/openbsd/plugin.rb +++ b/plugins/guests/openbsd/plugin.rb @@ -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 diff --git a/plugins/guests/pld/cap/network_scripts_dir.rb b/plugins/guests/pld/cap/network_scripts_dir.rb new file mode 100644 index 000000000..3a6fbfb43 --- /dev/null +++ b/plugins/guests/pld/cap/network_scripts_dir.rb @@ -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 diff --git a/plugins/guests/pld/guest.rb b/plugins/guests/pld/guest.rb index b183102a4..1d65ce044 100644 --- a/plugins/guests/pld/guest.rb +++ b/plugins/guests/pld/guest.rb @@ -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 diff --git a/plugins/guests/pld/plugin.rb b/plugins/guests/pld/plugin.rb index b072ed9f6..d02d76a02 100644 --- a/plugins/guests/pld/plugin.rb +++ b/plugins/guests/pld/plugin.rb @@ -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 diff --git a/plugins/guests/redhat/cap/change_host_name.rb b/plugins/guests/redhat/cap/change_host_name.rb new file mode 100644 index 000000000..42d1d7d9c --- /dev/null +++ b/plugins/guests/redhat/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/redhat/cap/configure_networks.rb b/plugins/guests/redhat/cap/configure_networks.rb new file mode 100644 index 000000000..00ab47766 --- /dev/null +++ b/plugins/guests/redhat/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/redhat/cap/network_scripts_dir.rb b/plugins/guests/redhat/cap/network_scripts_dir.rb new file mode 100644 index 000000000..5b2a26e04 --- /dev/null +++ b/plugins/guests/redhat/cap/network_scripts_dir.rb @@ -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 diff --git a/plugins/guests/redhat/guest.rb b/plugins/guests/redhat/guest.rb index 39c508856..197286200 100644 --- a/plugins/guests/redhat/guest.rb +++ b/plugins/guests/redhat/guest.rb @@ -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 diff --git a/plugins/guests/redhat/plugin.rb b/plugins/guests/redhat/plugin.rb index 4355152ff..60ca17af6 100644 --- a/plugins/guests/redhat/plugin.rb +++ b/plugins/guests/redhat/plugin.rb @@ -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 diff --git a/plugins/guests/solaris/cap/change_host_name.rb b/plugins/guests/solaris/cap/change_host_name.rb new file mode 100644 index 000000000..f9dd19606 --- /dev/null +++ b/plugins/guests/solaris/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/solaris/cap/configure_networks.rb b/plugins/guests/solaris/cap/configure_networks.rb new file mode 100644 index 000000000..cff7940d8 --- /dev/null +++ b/plugins/guests/solaris/cap/configure_networks.rb @@ -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 diff --git a/plugins/guests/solaris/cap/halt.rb b/plugins/guests/solaris/cap/halt.rb new file mode 100644 index 000000000..62959c4b7 --- /dev/null +++ b/plugins/guests/solaris/cap/halt.rb @@ -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 diff --git a/plugins/guests/solaris/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/solaris/cap/mount_virtualbox_shared_folder.rb new file mode 100644 index 000000000..dacbee451 --- /dev/null +++ b/plugins/guests/solaris/cap/mount_virtualbox_shared_folder.rb @@ -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 diff --git a/plugins/guests/solaris/guest.rb b/plugins/guests/solaris/guest.rb index a5f13c92e..06b2ae375 100644 --- a/plugins/guests/solaris/guest.rb +++ b/plugins/guests/solaris/guest.rb @@ -6,72 +6,8 @@ module VagrantPlugins # # Contributed by Blake Irvin 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 diff --git a/plugins/guests/solaris/plugin.rb b/plugins/guests/solaris/plugin.rb index 844f441b3..9f1fc80d9 100644 --- a/plugins/guests/solaris/plugin.rb +++ b/plugins/guests/solaris/plugin.rb @@ -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 diff --git a/plugins/guests/suse/cap/change_host_name.rb b/plugins/guests/suse/cap/change_host_name.rb new file mode 100644 index 000000000..3616171bf --- /dev/null +++ b/plugins/guests/suse/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/suse/cap/network_scripts_dir.rb b/plugins/guests/suse/cap/network_scripts_dir.rb new file mode 100644 index 000000000..d8c34b290 --- /dev/null +++ b/plugins/guests/suse/cap/network_scripts_dir.rb @@ -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 diff --git a/plugins/guests/suse/guest.rb b/plugins/guests/suse/guest.rb index 8694b669e..340f5818d 100644 --- a/plugins/guests/suse/guest.rb +++ b/plugins/guests/suse/guest.rb @@ -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 diff --git a/plugins/guests/suse/plugin.rb b/plugins/guests/suse/plugin.rb index 12e925f02..bf9fbfbb3 100644 --- a/plugins/guests/suse/plugin.rb +++ b/plugins/guests/suse/plugin.rb @@ -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 diff --git a/plugins/guests/ubuntu/cap/change_host_name.rb b/plugins/guests/ubuntu/cap/change_host_name.rb new file mode 100644 index 000000000..db052e40f --- /dev/null +++ b/plugins/guests/ubuntu/cap/change_host_name.rb @@ -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 diff --git a/plugins/guests/ubuntu/guest.rb b/plugins/guests/ubuntu/guest.rb index bc199b535..35e3d30c9 100644 --- a/plugins/guests/ubuntu/guest.rb +++ b/plugins/guests/ubuntu/guest.rb @@ -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 diff --git a/plugins/guests/ubuntu/plugin.rb b/plugins/guests/ubuntu/plugin.rb index 8e3cc72c1..38e054972 100644 --- a/plugins/guests/ubuntu/plugin.rb +++ b/plugins/guests/ubuntu/plugin.rb @@ -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 diff --git a/plugins/providers/virtualbox/action/network.rb b/plugins/providers/virtualbox/action/network.rb index dca3000c9..736524eaa 100644 --- a/plugins/providers/virtualbox/action/network.rb +++ b/plugins/providers/virtualbox/action/network.rb @@ -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 diff --git a/plugins/providers/virtualbox/action/share_folders.rb b/plugins/providers/virtualbox/action/share_folders.rb index 9dd394c0c..e795a56ed 100644 --- a/plugins/providers/virtualbox/action/share_folders.rb +++ b/plugins/providers/virtualbox/action/share_folders.rb @@ -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", diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 832fb2701..92768e584 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -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. diff --git a/test/unit/vagrant/action/builtin/graceful_halt_test.rb b/test/unit/vagrant/action/builtin/graceful_halt_test.rb index 244bf2d17..95bf76888 100644 --- a/test/unit/vagrant/action/builtin/graceful_halt_test.rb +++ b/test/unit/vagrant/action/builtin/graceful_halt_test.rb @@ -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) diff --git a/test/unit/vagrant/guest_test.rb b/test/unit/vagrant/guest_test.rb new file mode 100644 index 000000000..d778ccd05 --- /dev/null +++ b/test/unit/vagrant/guest_test.rb @@ -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 diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 7a24be9ab..287b49447 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -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,63 +223,11 @@ 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) + result.should be_kind_of(Vagrant::Guest) + result.ready?.should be + result.chain[0][0].should == :test 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) - end end describe "setting the ID" do diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb index 7ed9d751e..d0ab6fd58 100644 --- a/test/unit/vagrant/plugin/v2/manager_test.rb +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -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 diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb index 7e0b88d84..0295b3242 100644 --- a/test/unit/vagrant/plugin/v2/plugin_test.rb +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -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