From f2c6175d167678952f39e2cd72e1473bc09a7ad3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 16:15:12 -0800 Subject: [PATCH 01/54] Use bundler to load plugins --- lib/vagrant.rb | 321 ++-------------------------------- lib/vagrant/bundler.rb | 44 +++++ lib/vagrant/environment.rb | 71 +------- lib/vagrant/init.rb | 308 ++++++++++++++++++++++++++++++++ lib/vagrant/paths.rb | 26 +++ lib/vagrant/plugin_manager.rb | 16 ++ vagrant.gemspec | 3 +- 7 files changed, 408 insertions(+), 381 deletions(-) create mode 100644 lib/vagrant/bundler.rb create mode 100644 lib/vagrant/init.rb create mode 100644 lib/vagrant/paths.rb create mode 100644 lib/vagrant/plugin_manager.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index e8d46327f..e0458add8 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -1,314 +1,17 @@ -require 'log4r' -require 'rubygems' +# This file is load before RubyGems are loaded, and allow us to actually +# resolve plugin dependencies and load the proper versions of everything. -# Enable logging if it is requested. We do this before -# anything else so that we can setup the output before -# any logging occurs. -if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != "" - # Require Log4r and define the levels we'll be using - require 'log4r/config' - Log4r.define_levels(*Log4r::Log4rConfig::LogLevels) - - level = nil - begin - level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) - rescue NameError - # This means that the logging constant wasn't found, - # which is fine. We just keep `level` as `nil`. But - # we tell the user. - level = nil - end - - # Some constants, such as "true" resolve to booleans, so the - # above error checking doesn't catch it. This will check to make - # sure that the log level is an integer, as Log4r requires. - level = nil if !level.is_a?(Integer) - - if !level - # We directly write to stderr here because the VagrantError system - # is not setup yet. - $stderr.puts "Invalid VAGRANT_LOG level is set: #{ENV["VAGRANT_LOG"]}" - $stderr.puts "" - $stderr.puts "Please use one of the standard log levels: debug, info, warn, or error" - exit 1 - end - - # Set the logging level on all "vagrant" namespaced - # logs as long as we have a valid level. - if level - logger = Log4r::Logger.new("vagrant") - logger.outputters = Log4r::Outputter.stderr - logger.level = level - logger = nil - end +if defined?(Vagrant) + raise "vagrant is somehow already loaded. bug." end -require 'json' -require 'pathname' -require 'stringio' +ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = "/Applications/Vagrant/embedded" -require 'childprocess' -require 'i18n' +# Initialize Bundler before we load _any_ RubyGems. +require_relative "vagrant/bundler" +require_relative "vagrant/plugin_manager" +Vagrant::Bundler.instance.init!(Vagrant::PluginManager.plugins) -# OpenSSL must be loaded here since when it is loaded via `autoload` -# there are issues with ciphers not being properly loaded. -require 'openssl' - -# Always make the version available -require 'vagrant/version' -global_logger = Log4r::Logger.new("vagrant::global") -global_logger.info("Vagrant version: #{Vagrant::VERSION}") -global_logger.info("Ruby version: #{RUBY_VERSION}") -global_logger.info("RubyGems version: #{Gem::VERSION}") -ENV.each do |k, v| - global_logger.info("#{k}=#{v.inspect}") if k =~ /^VAGRANT_/ -end - -# We need these components always so instead of an autoload we -# just require them explicitly here. -require "vagrant/registry" - -module Vagrant - autoload :Action, 'vagrant/action' - autoload :BatchAction, 'vagrant/batch_action' - autoload :Box, 'vagrant/box' - autoload :BoxCollection, 'vagrant/box_collection' - autoload :CLI, 'vagrant/cli' - autoload :Command, 'vagrant/command' - autoload :Config, 'vagrant/config' - autoload :Driver, 'vagrant/driver' - autoload :Environment, 'vagrant/environment' - autoload :Errors, 'vagrant/errors' - autoload :Guest, 'vagrant/guest' - autoload :Hosts, 'vagrant/hosts' - autoload :Machine, 'vagrant/machine' - autoload :MachineState, 'vagrant/machine_state' - autoload :Plugin, 'vagrant/plugin' - autoload :UI, 'vagrant/ui' - autoload :Util, 'vagrant/util' - - # These are the various plugin versions and their components in - # a lazy loaded Hash-like structure. - PLUGIN_COMPONENTS = Registry.new.tap do |c| - c.register(:"1") { Plugin::V1::Plugin } - c.register([:"1", :command]) { Plugin::V1::Command } - c.register([:"1", :communicator]) { Plugin::V1::Communicator } - c.register([:"1", :config]) { Plugin::V1::Config } - c.register([:"1", :guest]) { Plugin::V1::Guest } - c.register([:"1", :host]) { Plugin::V1::Host } - c.register([:"1", :provider]) { Plugin::V1::Provider } - c.register([:"1", :provisioner]) { Plugin::V1::Provisioner } - - c.register(:"2") { Plugin::V2::Plugin } - c.register([:"2", :command]) { Plugin::V2::Command } - c.register([:"2", :communicator]) { Plugin::V2::Communicator } - c.register([:"2", :config]) { Plugin::V2::Config } - c.register([:"2", :guest]) { Plugin::V2::Guest } - c.register([:"2", :host]) { Plugin::V2::Host } - c.register([:"2", :provider]) { Plugin::V2::Provider } - c.register([:"2", :provisioner]) { Plugin::V2::Provisioner } - c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder } - end - - # This returns a true/false showing whether we're running from the - # environment setup by the Vagrant installers. - # - # @return [Boolean] - def self.in_installer? - !!ENV["VAGRANT_INSTALLER_ENV"] - end - - # The source root is the path to the root directory of - # the Vagrant gem. - def self.source_root - @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) - end - - # Configure a Vagrant environment. The version specifies the version - # of the configuration that is expected by the block. The block, based - # on that version, configures the environment. - # - # Note that the block isn't run immediately. Instead, the configuration - # block is stored until later, and is run when an environment is loaded. - # - # @param [String] version Version of the configuration - def self.configure(version, &block) - Config.run(version, &block) - end - - # This checks if a plugin with the given name is installed. This can - # be used from the Vagrantfile to easily branch based on plugin - # availability. - def self.has_plugin?(name) - manager = plugin("2").manager - - manager.required.any? { |gem_name| gem_name == name } || - manager.registered.any? { |plugin| plugin.name == name } - end - - # Returns a superclass to use when creating a plugin for Vagrant. - # Given a specific version, this returns a proper superclass to use - # to register plugins for that version. - # - # Optionally, if you give a specific component, then it will return - # the proper superclass for that component as well. - # - # Plugins and plugin components should subclass the classes returned by - # this method. This method lets Vagrant core control these superclasses - # and change them over time without affecting plugins. For example, if - # the V1 superclass happens to be "Vagrant::V1," future versions of - # Vagrant may move it to "Vagrant::Plugins::V1" and plugins will not be - # affected. - # - # @param [String] version - # @param [String] component - # @return [Class] - def self.plugin(version, component=nil) - # Build up the key and return a result - key = version.to_s.to_sym - key = [key, component.to_s.to_sym] if component - result = PLUGIN_COMPONENTS.get(key) - - # If we found our component then we return that - return result if result - - # If we didn't find a result, then raise an exception, depending - # on if we got a component or not. - raise ArgumentError, "Plugin superclass not found for version/component: " + - "#{version} #{component}" - end - - # This should be used instead of Ruby's built-in `require` in order to - # load a Vagrant plugin. This will load the given plugin by first doing - # a normal `require`, giving a nice error message if things go wrong, - # and second by verifying that a Vagrant plugin was actually defined in - # the process. - # - # @param [String] name Name of the plugin to load. - def self.require_plugin(name) - logger = Log4r::Logger.new("vagrant::root") - - if ENV["VAGRANT_NO_PLUGINS"] - logger.warn("VAGRANT_NO_PLUGINS is set, not loading 3rd party plugin: #{name}") - return - end - - # Redirect stdout/stderr so that we can output it in our own way. - previous_stderr = $stderr - previous_stdout = $stdout - $stderr = StringIO.new - $stdout = StringIO.new - - # Attempt the normal require - begin - require name - plugin("2").manager.plugin_required(name) - rescue Exception => e - # Since this is a rare case, we create a one-time logger here - # in order to output the error - logger.error("Failed to load plugin: #{name}") - logger.error(" -- Error: #{e.inspect}") - logger.error(" -- Backtrace:") - logger.error(e.backtrace.join("\n")) - - # If it is a LoadError we first try to see if it failed loading - # the top-level entrypoint. If so, then we report a different error. - if e.is_a?(LoadError) - # Parse the message in order to get what failed to load, and - # add some extra protection around if the message is different. - parts = e.to_s.split(" -- ", 2) - if parts.length == 2 && parts[1] == name - raise Errors::PluginLoadError, :plugin => name - end - end - - # Get the string data out from the stdout/stderr captures - stderr = $stderr.string - stdout = $stdout.string - if !stderr.empty? || !stdout.empty? - raise Errors::PluginLoadFailedWithOutput, - :plugin => name, - :stderr => stderr, - :stdout => stdout - end - - # And raise an error itself - raise Errors::PluginLoadFailed, - :plugin => name - end - - # Log plugin version - gem = Gem::Specification.find { |spec| spec.name == name } - version = gem ? gem.version : "" - logger.info("Loaded plugin #{name}, version #{version}") - ensure - $stderr = previous_stderr if previous_stderr - $stdout = previous_stdout if previous_stdout - end - - # This allows a Vagrantfile to specify the version of Vagrant that is - # required. You can specify a list of requirements which will all be checked - # against the running Vagrant version. - # - # This should be specified at the _top_ of any Vagrantfile. - # - # Examples are shown below: - # - # Vagrant.require_version(">= 1.3.5") - # Vagrant.require_version(">= 1.3.5", "< 1.4.0") - # Vagrant.require_version("~> 1.3.5") - # - def self.require_version(*requirements) - logger = Log4r::Logger.new("vagrant::root") - logger.info("Version requirements from Vagrantfile: #{requirements.inspect}") - - req = Gem::Requirement.new(*requirements) - if req.satisfied_by?(Gem::Version.new(VERSION)) - logger.info(" - Version requirements satisfied!") - return - end - - raise Errors::VagrantVersionBad, - requirements: requirements.join(", "), - version: VERSION - end -end - -# Default I18n to load the en locale -I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_root) - -if I18n.config.respond_to?(:enforce_available_locales=) - # Make sure only available locales are used. This will be the default in the - # future but we need this to silence a deprecation warning from 0.6.9 - I18n.config.enforce_available_locales = true -end - -# A lambda that knows how to load plugins from a single directory. -plugin_load_proc = lambda do |directory| - # We only care about directories - next false if !directory.directory? - - # If there is a plugin file in the top-level directory, then load - # that up. - plugin_file = directory.join("plugin.rb") - if plugin_file.file? - global_logger.debug("Loading core plugin: #{plugin_file}") - load(plugin_file) - next true - end -end - -# Go through the `plugins` directory and attempt to load any plugins. The -# plugins are allowed to be in a directory in `plugins` or at most one -# directory deep within the plugins directory. So a plugin can be at -# `plugins/foo` or also at `plugins/foo/bar`, but no deeper. -Vagrant.source_root.join("plugins").children(true).each do |directory| - # Ignore non-directories - next if !directory.directory? - - # Load from this directory, and exit if we successfully loaded a plugin - next if plugin_load_proc.call(directory) - - # Otherwise, attempt to load from sub-directories - directory.children(true).each(&plugin_load_proc) -end +# Initialize Vagrant first, then load the remaining dependencies +require "vagrant/init" +Bundler.require(:default) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb new file mode 100644 index 000000000..769b30f4e --- /dev/null +++ b/lib/vagrant/bundler.rb @@ -0,0 +1,44 @@ +require "tempfile" + +require_relative "paths" +require_relative "version" + +module Vagrant + # This class manages Vagrant's interaction with Bundler. Vagrant uses + # Bundler as a way to properly resolve all dependencies of Vagrant and + # all Vagrant-installed plugins. + class Bundler + def self.instance + @bundler ||= self.new + end + + # Initializes Bundler and the various gem paths so that we can begin + # loading gems. This must only be called once. + def init!(plugins) + raise "Bundler already initialized" if defined?(::Bundler) + + # Setup the Bundler configuration + @configfile = Tempfile.new("vagrant-bundler-config") + @configfile.close + + # Build up the Gemfile for our Bundler context + @gemfile = Tempfile.new("vagrant-gemfile") + @gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) + plugins.each do |plugin| + @gemfile.puts(%Q[gem "#{plugin}"]) + end + @gemfile.close + + # Set the environmental variables for Bundler + ENV["BUNDLE_CONFIG"] = @configfile.path + ENV["BUNDLE_GEMFILE"] = @gemfile.path + ENV["GEM_PATH"] = + "#{Vagrant.user_data_path.join("gems")}#{::File::PATH_SEPARATOR}#{ENV["GEM_PATH"]}" + Gem.clear_paths + + # Load Bundler and setup our paths + require "bundler" + ::Bundler.setup + end + end +end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 62d059dd1..1365ebc83 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -128,9 +128,6 @@ module Vagrant @default_private_key_path = @home_path.join("insecure_private_key") copy_insecure_private_key - # Load the plugins - load_plugins - # Call the hooks that does not require configurations to be loaded # by using a "clean" action runner hook(:environment_plugins_loaded, runner: Action::Runner.new(env: self)) @@ -591,7 +588,7 @@ module Vagrant def setup_home_path @home_path = Pathname.new(File.expand_path(@home_path || ENV["VAGRANT_HOME"] || - default_home_path)) + Vagrant.user_data_path)) @logger.info("Home path: #{@home_path}") # Setup the list of child directories that need to be created if they @@ -691,23 +688,6 @@ module Vagrant end end - # This returns the default home directory path for Vagrant, which - # can differ depending on the system. - # - # @return [Pathname] - def default_home_path - path = "~/.vagrant.d" - - # On Windows, we default ot the USERPROFILE directory if it - # is available. This is more compatible with Cygwin and sharing - # the home directory across shells. - if Util::Platform.windows? && ENV["USERPROFILE"] - path = "#{ENV["USERPROFILE"]}/.vagrant.d" - end - - Pathname.new(path) - end - # Finds the Vagrantfile in the given directory. # # @param [Pathname] path Path to search in. @@ -722,55 +702,6 @@ module Vagrant nil end - # Loads the Vagrant plugins by properly setting up RubyGems so that - # our private gem repository is on the path. - def load_plugins - # Add our private gem path to the gem path and reset the paths - # that Rubygems knows about. - ENV["GEM_PATH"] = "#{@gems_path}#{::File::PATH_SEPARATOR}#{ENV["GEM_PATH"]}" - ::Gem.clear_paths - - # If we're in a Bundler environment, don't load plugins. This only - # happens in plugin development environments. - if defined?(Bundler) - require 'bundler/shared_helpers' - if Bundler::SharedHelpers.in_bundle? - @logger.warn("In a bundler environment, not loading environment plugins!") - return - end - end - - # This keeps track of the old plugins that need to be reinstalled - # because they were installed with an old version of Ruby. - reinstall = [] - - # Load the plugins - plugins_json_file = @home_path.join("plugins.json") - @logger.debug("Loading plugins from: #{plugins_json_file}") - state = VagrantPlugins::CommandPlugin::StateFile.new(plugins_json_file) - state.installed_plugins.each do |name, extra| - # If the Ruby version changed, then they need to reinstall the plugin - if extra["ruby_version"] != RUBY_VERSION - reinstall << name - next - end - - @logger.info("Loading plugin from JSON: #{name}") - begin - Vagrant.require_plugin(name) - rescue Errors::PluginLoadError => e - @ui.error(e.message + "\n") - rescue Errors::PluginLoadFailed => e - @ui.error(e.message + "\n") - end - end - - if !reinstall.empty? - @ui.warn(I18n.t("vagrant.plugin_needs_reinstall", - names: reinstall.join(", "))) - end - end - # This upgrades a Vagrant 1.0.x "dotfile" to the new V2 format. # # This is a destructive process. Once the upgrade is complete, the diff --git a/lib/vagrant/init.rb b/lib/vagrant/init.rb new file mode 100644 index 000000000..c4a17be6b --- /dev/null +++ b/lib/vagrant/init.rb @@ -0,0 +1,308 @@ +require 'rubygems' +require 'log4r' + +# Enable logging if it is requested. We do this before +# anything else so that we can setup the output before +# any logging occurs. +if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != "" + # Require Log4r and define the levels we'll be using + require 'log4r/config' + Log4r.define_levels(*Log4r::Log4rConfig::LogLevels) + + level = nil + begin + level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) + rescue NameError + # This means that the logging constant wasn't found, + # which is fine. We just keep `level` as `nil`. But + # we tell the user. + level = nil + end + + # Some constants, such as "true" resolve to booleans, so the + # above error checking doesn't catch it. This will check to make + # sure that the log level is an integer, as Log4r requires. + level = nil if !level.is_a?(Integer) + + if !level + # We directly write to stderr here because the VagrantError system + # is not setup yet. + $stderr.puts "Invalid VAGRANT_LOG level is set: #{ENV["VAGRANT_LOG"]}" + $stderr.puts "" + $stderr.puts "Please use one of the standard log levels: debug, info, warn, or error" + exit 1 + end + + # Set the logging level on all "vagrant" namespaced + # logs as long as we have a valid level. + if level + logger = Log4r::Logger.new("vagrant") + logger.outputters = Log4r::Outputter.stderr + logger.level = level + logger = nil + end +end + +require 'json' +require 'pathname' +require 'stringio' + +require 'childprocess' +require 'i18n' + +# OpenSSL must be loaded here since when it is loaded via `autoload` +# there are issues with ciphers not being properly loaded. +require 'openssl' + +# Always make the version available +require 'vagrant/version' +global_logger = Log4r::Logger.new("vagrant::global") +global_logger.info("Vagrant version: #{Vagrant::VERSION}") +global_logger.info("Ruby version: #{RUBY_VERSION}") +global_logger.info("RubyGems version: #{Gem::VERSION}") +ENV.each do |k, v| + global_logger.info("#{k}=#{v.inspect}") if k =~ /^VAGRANT_/ +end + +# We need these components always so instead of an autoload we +# just require them explicitly here. +require "vagrant/registry" + +module Vagrant + autoload :Action, 'vagrant/action' + autoload :BatchAction, 'vagrant/batch_action' + autoload :Box, 'vagrant/box' + autoload :BoxCollection, 'vagrant/box_collection' + autoload :CLI, 'vagrant/cli' + autoload :Command, 'vagrant/command' + autoload :Config, 'vagrant/config' + autoload :Driver, 'vagrant/driver' + autoload :Environment, 'vagrant/environment' + autoload :Errors, 'vagrant/errors' + autoload :Guest, 'vagrant/guest' + autoload :Hosts, 'vagrant/hosts' + autoload :Machine, 'vagrant/machine' + autoload :MachineState, 'vagrant/machine_state' + autoload :Plugin, 'vagrant/plugin' + autoload :UI, 'vagrant/ui' + autoload :Util, 'vagrant/util' + + # These are the various plugin versions and their components in + # a lazy loaded Hash-like structure. + PLUGIN_COMPONENTS = Registry.new.tap do |c| + c.register(:"1") { Plugin::V1::Plugin } + c.register([:"1", :command]) { Plugin::V1::Command } + c.register([:"1", :communicator]) { Plugin::V1::Communicator } + c.register([:"1", :config]) { Plugin::V1::Config } + c.register([:"1", :guest]) { Plugin::V1::Guest } + c.register([:"1", :host]) { Plugin::V1::Host } + c.register([:"1", :provider]) { Plugin::V1::Provider } + c.register([:"1", :provisioner]) { Plugin::V1::Provisioner } + + c.register(:"2") { Plugin::V2::Plugin } + c.register([:"2", :command]) { Plugin::V2::Command } + c.register([:"2", :communicator]) { Plugin::V2::Communicator } + c.register([:"2", :config]) { Plugin::V2::Config } + c.register([:"2", :guest]) { Plugin::V2::Guest } + c.register([:"2", :host]) { Plugin::V2::Host } + c.register([:"2", :provider]) { Plugin::V2::Provider } + c.register([:"2", :provisioner]) { Plugin::V2::Provisioner } + c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder } + end + + # This returns a true/false showing whether we're running from the + # environment setup by the Vagrant installers. + # + # @return [Boolean] + def self.in_installer? + !!ENV["VAGRANT_INSTALLER_ENV"] + end + + # Configure a Vagrant environment. The version specifies the version + # of the configuration that is expected by the block. The block, based + # on that version, configures the environment. + # + # Note that the block isn't run immediately. Instead, the configuration + # block is stored until later, and is run when an environment is loaded. + # + # @param [String] version Version of the configuration + def self.configure(version, &block) + Config.run(version, &block) + end + + # This checks if a plugin with the given name is installed. This can + # be used from the Vagrantfile to easily branch based on plugin + # availability. + def self.has_plugin?(name) + manager = plugin("2").manager + + manager.required.any? { |gem_name| gem_name == name } || + manager.registered.any? { |plugin| plugin.name == name } + end + + # Returns a superclass to use when creating a plugin for Vagrant. + # Given a specific version, this returns a proper superclass to use + # to register plugins for that version. + # + # Optionally, if you give a specific component, then it will return + # the proper superclass for that component as well. + # + # Plugins and plugin components should subclass the classes returned by + # this method. This method lets Vagrant core control these superclasses + # and change them over time without affecting plugins. For example, if + # the V1 superclass happens to be "Vagrant::V1," future versions of + # Vagrant may move it to "Vagrant::Plugins::V1" and plugins will not be + # affected. + # + # @param [String] version + # @param [String] component + # @return [Class] + def self.plugin(version, component=nil) + # Build up the key and return a result + key = version.to_s.to_sym + key = [key, component.to_s.to_sym] if component + result = PLUGIN_COMPONENTS.get(key) + + # If we found our component then we return that + return result if result + + # If we didn't find a result, then raise an exception, depending + # on if we got a component or not. + raise ArgumentError, "Plugin superclass not found for version/component: " + + "#{version} #{component}" + end + + # This should be used instead of Ruby's built-in `require` in order to + # load a Vagrant plugin. This will load the given plugin by first doing + # a normal `require`, giving a nice error message if things go wrong, + # and second by verifying that a Vagrant plugin was actually defined in + # the process. + # + # @param [String] name Name of the plugin to load. + def self.require_plugin(name) + logger = Log4r::Logger.new("vagrant::root") + + if ENV["VAGRANT_NO_PLUGINS"] + logger.warn("VAGRANT_NO_PLUGINS is set, not loading 3rd party plugin: #{name}") + return + end + + # Redirect stdout/stderr so that we can output it in our own way. + previous_stderr = $stderr + previous_stdout = $stdout + $stderr = StringIO.new + $stdout = StringIO.new + + # Attempt the normal require + begin + require name + plugin("2").manager.plugin_required(name) + rescue Exception => e + # Since this is a rare case, we create a one-time logger here + # in order to output the error + logger.error("Failed to load plugin: #{name}") + logger.error(" -- Error: #{e.inspect}") + logger.error(" -- Backtrace:") + logger.error(e.backtrace.join("\n")) + + # If it is a LoadError we first try to see if it failed loading + # the top-level entrypoint. If so, then we report a different error. + if e.is_a?(LoadError) + # Parse the message in order to get what failed to load, and + # add some extra protection around if the message is different. + parts = e.to_s.split(" -- ", 2) + if parts.length == 2 && parts[1] == name + raise Errors::PluginLoadError, :plugin => name + end + end + + # Get the string data out from the stdout/stderr captures + stderr = $stderr.string + stdout = $stdout.string + if !stderr.empty? || !stdout.empty? + raise Errors::PluginLoadFailedWithOutput, + :plugin => name, + :stderr => stderr, + :stdout => stdout + end + + # And raise an error itself + raise Errors::PluginLoadFailed, + :plugin => name + end + + # Log plugin version + gem = Gem::Specification.find { |spec| spec.name == name } + version = gem ? gem.version : "" + logger.info("Loaded plugin #{name}, version #{version}") + ensure + $stderr = previous_stderr if previous_stderr + $stdout = previous_stdout if previous_stdout + end + + # This allows a Vagrantfile to specify the version of Vagrant that is + # required. You can specify a list of requirements which will all be checked + # against the running Vagrant version. + # + # This should be specified at the _top_ of any Vagrantfile. + # + # Examples are shown below: + # + # Vagrant.require_version(">= 1.3.5") + # Vagrant.require_version(">= 1.3.5", "< 1.4.0") + # Vagrant.require_version("~> 1.3.5") + # + def self.require_version(*requirements) + logger = Log4r::Logger.new("vagrant::root") + logger.info("Version requirements from Vagrantfile: #{requirements.inspect}") + + req = Gem::Requirement.new(*requirements) + if req.satisfied_by?(Gem::Version.new(VERSION)) + logger.info(" - Version requirements satisfied!") + return + end + + raise Errors::VagrantVersionBad, + requirements: requirements.join(", "), + version: VERSION + end +end + +# Default I18n to load the en locale +I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_root) + +if I18n.config.respond_to?(:enforce_available_locales=) + # Make sure only available locales are used. This will be the default in the + # future but we need this to silence a deprecation warning from 0.6.9 + I18n.config.enforce_available_locales = true +end + +# A lambda that knows how to load plugins from a single directory. +plugin_load_proc = lambda do |directory| + # We only care about directories + next false if !directory.directory? + + # If there is a plugin file in the top-level directory, then load + # that up. + plugin_file = directory.join("plugin.rb") + if plugin_file.file? + global_logger.debug("Loading core plugin: #{plugin_file}") + load(plugin_file) + next true + end +end + +# Go through the `plugins` directory and attempt to load any plugins. The +# plugins are allowed to be in a directory in `plugins` or at most one +# directory deep within the plugins directory. So a plugin can be at +# `plugins/foo` or also at `plugins/foo/bar`, but no deeper. +Vagrant.source_root.join("plugins").children(true).each do |directory| + # Ignore non-directories + next if !directory.directory? + + # Load from this directory, and exit if we successfully loaded a plugin + next if plugin_load_proc.call(directory) + + # Otherwise, attempt to load from sub-directories + directory.children(true).each(&plugin_load_proc) +end diff --git a/lib/vagrant/paths.rb b/lib/vagrant/paths.rb new file mode 100644 index 000000000..6871e16f0 --- /dev/null +++ b/lib/vagrant/paths.rb @@ -0,0 +1,26 @@ +require "pathname" + +module Vagrant + # The source root is the path to the root directory of + # the Vagrant gem. + def self.source_root + @source_root ||= Pathname.new(File.expand_path('../../../', __FILE__)) + end + + # This returns the path to the ~/.vagrant.d folder where Vagrant's + # per-user state is stored. + # + # @return [Pathname] + def self.user_data_path + path = "~/.vagrant.d" + + # On Windows, we default ot the USERPROFILE directory if it + # is available. This is more compatible with Cygwin and sharing + # the home directory across shells. + if ENV["USERPROFILE"] + path = "#{ENV["USERPROFILE"]}/.vagrant.d" + end + + return Pathname.new(path).expand_path + end +end diff --git a/lib/vagrant/plugin_manager.rb b/lib/vagrant/plugin_manager.rb new file mode 100644 index 000000000..4379af569 --- /dev/null +++ b/lib/vagrant/plugin_manager.rb @@ -0,0 +1,16 @@ +require "json" + +require_relative "paths" + +module Vagrant + class PluginManager + def self.global_plugins_file + Vagrant.user_data_path.join("plugins.json") + end + + def self.plugins + plugins = JSON.parse(global_plugins_file.read) + plugins["installed"].keys + end + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index 6c2a1ae28..6238af891 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -14,6 +14,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "vagrant" + s.add_dependency "bundler", "~> 1.5.1" s.add_dependency "childprocess", "~> 0.3.7" s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "i18n", "~> 0.6.0" @@ -25,8 +26,6 @@ Gem::Specification.new do |s| s.add_development_dependency "contest", ">= 0.1.2" s.add_development_dependency "minitest", "~> 2.5.1" s.add_development_dependency "mocha" - # This has problems on Windows, we need to find a better way: - # s.add_development_dependency "sys-proctable", "~> 0.9.0" s.add_development_dependency "rspec", "~> 2.14.0" # The following block of code determines the files that should be included From 8adef9c15f85b996012d4a09e76a5ca1139f8e15 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 16:29:22 -0800 Subject: [PATCH 02/54] warn if running in a bundler env, don't load plugins --- bin/vagrant | 15 +--------- lib/vagrant.rb | 32 ++++++++++++++------- lib/vagrant/bundler.rb | 6 ++-- lib/vagrant/plugin_manager.rb | 2 +- lib/vagrant/{paths.rb => shared_helpers.rb} | 12 ++++++-- templates/locales/en.yml | 5 ---- 6 files changed, 38 insertions(+), 34 deletions(-) rename lib/vagrant/{paths.rb => shared_helpers.rb} (70%) diff --git a/bin/vagrant b/bin/vagrant index cd7fbc9d3..aaa6e3afa 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -105,21 +105,8 @@ begin env = Vagrant::Environment.new(opts) if !Vagrant.in_installer? - warned = false - - # If we're in a bundler environment, we assume it is for plugin - # development and will let the user know that. - if defined?(Bundler) - require 'bundler/shared_helpers' - if Bundler::SharedHelpers.in_bundle? - env.ui.warn(I18n.t("vagrant.general.in_bundler")) - env.ui.warn("") - warned = true - end - end - # If we're not in the installer, warn. - env.ui.warn(I18n.t("vagrant.general.not_in_installer")) if !warned + env.ui.warn(I18n.t("vagrant.general.not_in_installer")) end begin diff --git a/lib/vagrant.rb b/lib/vagrant.rb index e0458add8..2458137e0 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -1,17 +1,29 @@ # This file is load before RubyGems are loaded, and allow us to actually # resolve plugin dependencies and load the proper versions of everything. -if defined?(Vagrant) - raise "vagrant is somehow already loaded. bug." -end - ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = "/Applications/Vagrant/embedded" -# Initialize Bundler before we load _any_ RubyGems. -require_relative "vagrant/bundler" -require_relative "vagrant/plugin_manager" -Vagrant::Bundler.instance.init!(Vagrant::PluginManager.plugins) +if defined?(Bundler) + require "bundler/shared_helpers" + if Bundler::SharedHelpers.in_bundle? + puts "Vagrant appears to be running in a Bundler environment. Plugins" + puts "will not be loaded and plugin commands are disabled." + puts + ENV["VAGRANT_NO_PLUGINS"] = "1" + end +end -# Initialize Vagrant first, then load the remaining dependencies +require_relative "vagrant/shared_helpers" + +if Vagrant.plugins_enabled? + # Initialize Bundler before we load _any_ RubyGems. + require_relative "vagrant/bundler" + require_relative "vagrant/plugin_manager" + Vagrant::Bundler.instance.init!(Vagrant::PluginManager.plugins) +end + +# Initialize Vagrant now that our Gem paths are setup require "vagrant/init" -Bundler.require(:default) + +# If we have plugins enabled, then load those +Bundler.require(:default) if Vagrant.plugins_enabled? diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 769b30f4e..81c445860 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -1,6 +1,6 @@ require "tempfile" -require_relative "paths" +require_relative "shared_helpers" require_relative "version" module Vagrant @@ -21,7 +21,9 @@ module Vagrant @configfile = Tempfile.new("vagrant-bundler-config") @configfile.close - # Build up the Gemfile for our Bundler context + # Build up the Gemfile for our Bundler context. We make sure to + # lock Vagrant to our current Vagrant version. In addition to that, + # we add all our plugin dependencies. @gemfile = Tempfile.new("vagrant-gemfile") @gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) plugins.each do |plugin| diff --git a/lib/vagrant/plugin_manager.rb b/lib/vagrant/plugin_manager.rb index 4379af569..2887dac19 100644 --- a/lib/vagrant/plugin_manager.rb +++ b/lib/vagrant/plugin_manager.rb @@ -1,6 +1,6 @@ require "json" -require_relative "paths" +require_relative "shared_helpers" module Vagrant class PluginManager diff --git a/lib/vagrant/paths.rb b/lib/vagrant/shared_helpers.rb similarity index 70% rename from lib/vagrant/paths.rb rename to lib/vagrant/shared_helpers.rb index 6871e16f0..78cdb4a92 100644 --- a/lib/vagrant/paths.rb +++ b/lib/vagrant/shared_helpers.rb @@ -1,8 +1,16 @@ require "pathname" module Vagrant - # The source root is the path to the root directory of - # the Vagrant gem. + # This returns whether or not 3rd party plugins should be loaded. + # + # @return [Boolean] + def self.plugins_enabled? + !ENV["VAGRANT_NO_PLUGINS"] + end + + # The source root is the path to the root directory of the Vagrant source. + # + # @return [Pathname] def self.source_root @source_root ||= Pathname.new(File.expand_path('../../../', __FILE__)) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 154693760..e02c1c30c 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -139,11 +139,6 @@ en: Old: %{old} New: %{new} - in_bundler: |- - You appear to be running Vagrant in a Bundler environment. Because - Vagrant should be run within installers (outside of Bundler), Vagrant - will assume that you're developing plugins and will change its behavior - in certain ways to better assist plugin development. not_in_installer: |- You appear to be running Vagrant outside of the official installers. Note that the installers are what ensure that Vagrant has all required From 5387984e0f5ff151002c0ded524a2f8b80c15ad0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 16:32:46 -0800 Subject: [PATCH 03/54] commands/plugin: allow within Bundler env (cause we always are) --- plugins/commands/plugin/action.rb | 6 ----- .../commands/plugin/action/bundler_check.rb | 25 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 plugins/commands/plugin/action/bundler_check.rb diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index a132aec13..0e21edd64 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -8,7 +8,6 @@ module VagrantPlugins # This middleware sequence will install a plugin. def self.action_install Vagrant::Action::Builder.new.tap do |b| - b.use BundlerCheck b.use InstallGem b.use PruneGems end @@ -17,7 +16,6 @@ module VagrantPlugins # This middleware sequence licenses paid addons. def self.action_license Vagrant::Action::Builder.new.tap do |b| - b.use BundlerCheck b.use LicensePlugin end end @@ -25,7 +23,6 @@ module VagrantPlugins # This middleware sequence will list all installed plugins. def self.action_list Vagrant::Action::Builder.new.tap do |b| - b.use BundlerCheck b.use ListPlugins end end @@ -33,7 +30,6 @@ module VagrantPlugins # This middleware sequence will uninstall a plugin. def self.action_uninstall Vagrant::Action::Builder.new.tap do |b| - b.use BundlerCheck b.use UninstallPlugin b.use PruneGems end @@ -42,7 +38,6 @@ module VagrantPlugins # This middleware sequence will update a plugin. def self.action_update Vagrant::Action::Builder.new.tap do |b| - b.use BundlerCheck b.use PluginExistsCheck b.use InstallGem b.use PruneGems @@ -51,7 +46,6 @@ module VagrantPlugins # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) - autoload :BundlerCheck, action_root.join("bundler_check") autoload :InstallGem, action_root.join("install_gem") autoload :LicensePlugin, action_root.join("license_plugin") autoload :ListPlugins, action_root.join("list_plugins") diff --git a/plugins/commands/plugin/action/bundler_check.rb b/plugins/commands/plugin/action/bundler_check.rb deleted file mode 100644 index b53a83c5d..000000000 --- a/plugins/commands/plugin/action/bundler_check.rb +++ /dev/null @@ -1,25 +0,0 @@ -module VagrantPlugins - module CommandPlugin - module Action - class BundlerCheck - def initialize(app, env) - @app = app - end - - def call(env) - # Bundler sets up its own custom gem load paths such that our - # own gems are never loaded. Therefore, give an error if a user - # tries to install gems while within a Bundler-managed environment. - if defined?(Bundler) - require 'bundler/shared_helpers' - if Bundler::SharedHelpers.in_bundle? - raise Vagrant::Errors::GemCommandInBundler - end - end - - @app.call(env) - end - end - end - end -end From d98868d1502bde0bd3a2f2b65a4e89793e90e5f8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 16:35:28 -0800 Subject: [PATCH 04/54] core: Vagrant.require_plugin is gone --- lib/vagrant/init.rb | 69 +++------------------------------------ test/unit/vagrant_test.rb | 30 ----------------- 2 files changed, 4 insertions(+), 95 deletions(-) diff --git a/lib/vagrant/init.rb b/lib/vagrant/init.rb index c4a17be6b..32ef6e4e5 100644 --- a/lib/vagrant/init.rb +++ b/lib/vagrant/init.rb @@ -172,72 +172,11 @@ module Vagrant "#{version} #{component}" end - # This should be used instead of Ruby's built-in `require` in order to - # load a Vagrant plugin. This will load the given plugin by first doing - # a normal `require`, giving a nice error message if things go wrong, - # and second by verifying that a Vagrant plugin was actually defined in - # the process. - # - # @param [String] name Name of the plugin to load. + # @deprecated def self.require_plugin(name) - logger = Log4r::Logger.new("vagrant::root") - - if ENV["VAGRANT_NO_PLUGINS"] - logger.warn("VAGRANT_NO_PLUGINS is set, not loading 3rd party plugin: #{name}") - return - end - - # Redirect stdout/stderr so that we can output it in our own way. - previous_stderr = $stderr - previous_stdout = $stdout - $stderr = StringIO.new - $stdout = StringIO.new - - # Attempt the normal require - begin - require name - plugin("2").manager.plugin_required(name) - rescue Exception => e - # Since this is a rare case, we create a one-time logger here - # in order to output the error - logger.error("Failed to load plugin: #{name}") - logger.error(" -- Error: #{e.inspect}") - logger.error(" -- Backtrace:") - logger.error(e.backtrace.join("\n")) - - # If it is a LoadError we first try to see if it failed loading - # the top-level entrypoint. If so, then we report a different error. - if e.is_a?(LoadError) - # Parse the message in order to get what failed to load, and - # add some extra protection around if the message is different. - parts = e.to_s.split(" -- ", 2) - if parts.length == 2 && parts[1] == name - raise Errors::PluginLoadError, :plugin => name - end - end - - # Get the string data out from the stdout/stderr captures - stderr = $stderr.string - stdout = $stdout.string - if !stderr.empty? || !stdout.empty? - raise Errors::PluginLoadFailedWithOutput, - :plugin => name, - :stderr => stderr, - :stdout => stdout - end - - # And raise an error itself - raise Errors::PluginLoadFailed, - :plugin => name - end - - # Log plugin version - gem = Gem::Specification.find { |spec| spec.name == name } - version = gem ? gem.version : "" - logger.info("Loaded plugin #{name}, version #{version}") - ensure - $stderr = previous_stderr if previous_stderr - $stdout = previous_stdout if previous_stdout + puts "Vagrant.require_plugin is deprecated and has no effect any longer." + puts "Use `vagrant plugin` commands to manage plugins. This warning will" + puts "be removed in the next version of Vagrant." end # This allows a Vagrantfile to specify the version of Vagrant that is diff --git a/test/unit/vagrant_test.rb b/test/unit/vagrant_test.rb index 0d143d42b..cb8c0f105 100644 --- a/test/unit/vagrant_test.rb +++ b/test/unit/vagrant_test.rb @@ -47,36 +47,6 @@ describe Vagrant do end end - describe "requiring plugins" do - it "should require the plugin given" do - # For now, just require a stdlib - expect { described_class.require_plugin "set" }. - to_not raise_error - end - - it "should add the gem name to plugin manager" do - expect(described_class.plugin("2").manager). - to receive(:plugin_required).with("set") - described_class.require_plugin "set" - end - - it "should raise an error if the file doesn't exist" do - expect { described_class.require_plugin("i_dont_exist") }. - to raise_error(Vagrant::Errors::PluginLoadError) - end - - it "should raise an error if the loading failed in some other way" do - plugin_dir = temporary_dir - plugin_path = plugin_dir.join("test.rb") - plugin_path.open("w") do |f| - f.write(%Q[require 'I_dont_exist']) - end - - expect { described_class.require_plugin(plugin_path.to_s) }. - to raise_error(Vagrant::Errors::PluginLoadFailed) - end - end - describe "has_plugin?" do before(:each) do Class.new(described_class.plugin("2")) do From 8450f20e873c9e9e206749c92b9a5ba1cc898a2e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 16:45:41 -0800 Subject: [PATCH 05/54] Move StateFile into Vagrant::Plugin core --- lib/vagrant/plugin.rb | 5 +++-- {plugins/commands => lib/vagrant}/plugin/state_file.rb | 4 ++-- plugins/commands/plugin/command/base.rb | 4 +++- plugins/commands/plugin/plugin.rb | 1 - .../{plugins/commands => vagrant}/plugin/state_file_test.rb | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) rename {plugins/commands => lib/vagrant}/plugin/state_file.rb (97%) rename test/unit/{plugins/commands => vagrant}/plugin/state_file_test.rb (94%) diff --git a/lib/vagrant/plugin.rb b/lib/vagrant/plugin.rb index 5538746a5..71d0073bd 100644 --- a/lib/vagrant/plugin.rb +++ b/lib/vagrant/plugin.rb @@ -1,6 +1,7 @@ module Vagrant module Plugin - autoload :V1, "vagrant/plugin/v1" - autoload :V2, "vagrant/plugin/v2" + autoload :V1, "vagrant/plugin/v1" + autoload :V2, "vagrant/plugin/v2" + autoload :StateFile, "vagrant/plugin/state_file" end end diff --git a/plugins/commands/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb similarity index 97% rename from plugins/commands/plugin/state_file.rb rename to lib/vagrant/plugin/state_file.rb index 55603ffb8..a73bedb5a 100644 --- a/plugins/commands/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -1,7 +1,7 @@ require "json" -module VagrantPlugins - module CommandPlugin +module Vagrant + module Plugin # This is a helper to deal with the plugin state file that Vagrant # uses to track what plugins are installed and activated and such. class StateFile diff --git a/plugins/commands/plugin/command/base.rb b/plugins/commands/plugin/command/base.rb index 5180dfd5d..770d71697 100644 --- a/plugins/commands/plugin/command/base.rb +++ b/plugins/commands/plugin/command/base.rb @@ -1,3 +1,5 @@ +require "vagrant/plugin/state_file" + module VagrantPlugins module CommandPlugin module Command @@ -11,7 +13,7 @@ module VagrantPlugins def action(callable, env=nil) env = { :gem_helper => GemHelper.new(@env.gems_path), - :plugin_state_file => StateFile.new(@env.home_path.join("plugins.json")) + :plugin_state_file => Vagrant::Plugin::StateFile.new(@env.home_path.join("plugins.json")) }.merge(env || {}) @env.action_runner.run(callable, env) diff --git a/plugins/commands/plugin/plugin.rb b/plugins/commands/plugin/plugin.rb index db9763319..a738d01f1 100644 --- a/plugins/commands/plugin/plugin.rb +++ b/plugins/commands/plugin/plugin.rb @@ -17,6 +17,5 @@ DESC autoload :Action, File.expand_path("../action", __FILE__) autoload :GemHelper, File.expand_path("../gem_helper", __FILE__) - autoload :StateFile, File.expand_path("../state_file", __FILE__) end end diff --git a/test/unit/plugins/commands/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb similarity index 94% rename from test/unit/plugins/commands/plugin/state_file_test.rb rename to test/unit/vagrant/plugin/state_file_test.rb index 888c650b5..8e94a9f52 100644 --- a/test/unit/plugins/commands/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -1,9 +1,9 @@ require "json" require "pathname" -require File.expand_path("../../../../base", __FILE__) +require File.expand_path("../../../base", __FILE__) -describe VagrantPlugins::CommandPlugin::StateFile do +describe Vagrant::Plugin::StateFile do let(:path) do f = Tempfile.new("vagrant") p = f.path From ae17dc09ebc2ed050bacccb10094e547ff8fe8dc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 17:21:53 -0800 Subject: [PATCH 06/54] commands/plugin: list plugins uses Bundler --- .../commands/plugin/action/list_plugins.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index f5dd0574b..4abbcab99 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -1,4 +1,3 @@ -require "rubygems" require "set" module VagrantPlugins @@ -23,18 +22,16 @@ module VagrantPlugins # Go through the plugins installed in this environment and # get the latest version of each. installed_map = {} - env[:gem_helper].with_environment do - Gem::Specification.find_all.each do |spec| - # Ignore specs that aren't in our installed list - next if !installed.include?(spec.name) + Bundler.load.specs.each do |spec| + # Ignore specs that aren't in our installed list + next if !installed.include?(spec.name) - # If we already have a newer version in our list of installed, - # then ignore it - next if installed_map.has_key?(spec.name) && - installed_map[spec.name].version >= spec.version + # If we already have a newer version in our list of installed, + # then ignore it + next if installed_map.has_key?(spec.name) && + installed_map[spec.name].version >= spec.version - installed_map[spec.name] = spec - end + installed_map[spec.name] = spec end # Output! From 279b171339da6d4b5b4044c1061e44cb657e2be8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Jan 2014 17:22:07 -0800 Subject: [PATCH 07/54] commands/plugin: remove unnecessary line --- plugins/commands/plugin/action/list_plugins.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 4abbcab99..b446b323a 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -1,5 +1,3 @@ -require "set" - module VagrantPlugins module CommandPlugin module Action From 1eef75a71592e7b4e08801c1d870e7f0f5e23f0d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 08:42:34 -0800 Subject: [PATCH 08/54] commands/plugin: list uses PluginManager --- lib/vagrant.rb | 2 +- lib/vagrant/plugin.rb | 1 + lib/vagrant/plugin/manager.rb | 39 +++++++++++++ lib/vagrant/plugin_manager.rb | 16 ------ .../commands/plugin/action/list_plugins.rb | 4 +- test/unit/vagrant/plugin/manager_test.rb | 56 +++++++++++++++++++ 6 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 lib/vagrant/plugin/manager.rb delete mode 100644 lib/vagrant/plugin_manager.rb create mode 100644 test/unit/vagrant/plugin/manager_test.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 2458137e0..c6c0fe904 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -19,7 +19,7 @@ if Vagrant.plugins_enabled? # Initialize Bundler before we load _any_ RubyGems. require_relative "vagrant/bundler" require_relative "vagrant/plugin_manager" - Vagrant::Bundler.instance.init!(Vagrant::PluginManager.plugins) + Vagrant::Bundler.instance.init!(Vagrant::Plugin::Manager.instance.installed_plugins) end # Initialize Vagrant now that our Gem paths are setup diff --git a/lib/vagrant/plugin.rb b/lib/vagrant/plugin.rb index 71d0073bd..66dcbdbec 100644 --- a/lib/vagrant/plugin.rb +++ b/lib/vagrant/plugin.rb @@ -2,6 +2,7 @@ module Vagrant module Plugin autoload :V1, "vagrant/plugin/v1" autoload :V2, "vagrant/plugin/v2" + autoload :Manager, "vagrant/plugin/manager" autoload :StateFile, "vagrant/plugin/state_file" end end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb new file mode 100644 index 000000000..da5704c7f --- /dev/null +++ b/lib/vagrant/plugin/manager.rb @@ -0,0 +1,39 @@ +require_relative "../shared_helpers" + +module Vagrant + module Plugin + # The Manager helps with installing, listing, and initializing plugins. + class Manager + # Returns the path to the [StateFile] for global plugins. + # + # @return [Pathname] + def self.global_plugins_file + Vagrant.user_data_path.join("plugins.json") + end + + def self.instance + @instance ||= self.new(global_plugins_file) + end + + # @param [Pathname] global_file + def initialize(global_file) + @global_file = StateFile.new(global_file) + end + + # This returns the list of plugins that should be enabled. + # + # @return [Array] + def installed_plugins + @global_file.installed_plugins.keys + end + + # This returns the list of plugins that are installed as + # Gem::Specifications. + # + # @return [Array] + def installed_specs + ::Bundler.load.specs + end + end + end +end diff --git a/lib/vagrant/plugin_manager.rb b/lib/vagrant/plugin_manager.rb deleted file mode 100644 index 2887dac19..000000000 --- a/lib/vagrant/plugin_manager.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "json" - -require_relative "shared_helpers" - -module Vagrant - class PluginManager - def self.global_plugins_file - Vagrant.user_data_path.join("plugins.json") - end - - def self.plugins - plugins = JSON.parse(global_plugins_file.read) - plugins["installed"].keys - end - end -end diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index b446b323a..8c173e6b3 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -1,3 +1,5 @@ +require "vagrant/plugin/manager" + module VagrantPlugins module CommandPlugin module Action @@ -20,7 +22,7 @@ module VagrantPlugins # Go through the plugins installed in this environment and # get the latest version of each. installed_map = {} - Bundler.load.specs.each do |spec| + Vagrant::Plugin::Manager.instance.installed_specs.each do |spec| # Ignore specs that aren't in our installed list next if !installed.include?(spec.name) diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb new file mode 100644 index 000000000..d886656f1 --- /dev/null +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -0,0 +1,56 @@ +require "json" +require "pathname" + +require "vagrant/plugin" +require "vagrant/plugin/manager" +require "vagrant/plugin/state_file" + +require File.expand_path("../../../base", __FILE__) + +describe Vagrant::Plugin::Manager do + let(:path) do + f = Tempfile.new("vagrant") + p = f.path + f.close + f.unlink + Pathname.new(p) + end + + after do + path.unlink if path.file? + end + + subject { described_class.new(path) } + + context "without state" do + describe "#installed_plugins" do + it "is empty initially" do + expect(subject.installed_plugins).to be_empty + end + end + end + + + context "with state" do + before do + sf = Vagrant::Plugin::StateFile.new(path) + sf.add_plugin("foo") + end + + describe "#installed_plugins" do + it "has the plugins" do + expect(subject.installed_plugins).to eql(["foo"]) + end + end + + describe "#installed_specs" do + it "has the plugins" do + runtime = double("runtime") + runtime.stub(specs: ["foo"]) + ::Bundler.stub(:load => runtime) + + expect(subject.installed_specs).to eql(["foo"]) + end + end + end +end From 86610bf735610d2bf0f0cd787b76050a5eefe0c6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 14:54:50 -0800 Subject: [PATCH 09/54] installing gems works --- lib/vagrant.rb | 2 +- lib/vagrant/bundler.rb | 114 ++++++++++++++++-- lib/vagrant/init.rb | 1 + lib/vagrant/plugin/manager.rb | 16 +++ plugins/commands/plugin/action.rb | 3 - plugins/commands/plugin/action/install_gem.rb | 37 +----- 6 files changed, 129 insertions(+), 44 deletions(-) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index c6c0fe904..2e9739fdf 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -18,7 +18,7 @@ require_relative "vagrant/shared_helpers" if Vagrant.plugins_enabled? # Initialize Bundler before we load _any_ RubyGems. require_relative "vagrant/bundler" - require_relative "vagrant/plugin_manager" + require_relative "vagrant/plugin/manager" Vagrant::Bundler.instance.init!(Vagrant::Plugin::Manager.instance.installed_plugins) end diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 81c445860..e4afd9fbe 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -1,3 +1,4 @@ +require "pathname" require "tempfile" require_relative "shared_helpers" @@ -12,6 +13,11 @@ module Vagrant @bundler ||= self.new end + def initialize + @gem_home = ENV["GEM_HOME"] + @gem_path = ENV["GEM_PATH"] + end + # Initializes Bundler and the various gem paths so that we can begin # loading gems. This must only be called once. def init!(plugins) @@ -24,23 +30,117 @@ module Vagrant # Build up the Gemfile for our Bundler context. We make sure to # lock Vagrant to our current Vagrant version. In addition to that, # we add all our plugin dependencies. - @gemfile = Tempfile.new("vagrant-gemfile") - @gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) - plugins.each do |plugin| - @gemfile.puts(%Q[gem "#{plugin}"]) - end - @gemfile.close + @gemfile = build_gemfile(plugins) # Set the environmental variables for Bundler ENV["BUNDLE_CONFIG"] = @configfile.path ENV["BUNDLE_GEMFILE"] = @gemfile.path ENV["GEM_PATH"] = - "#{Vagrant.user_data_path.join("gems")}#{::File::PATH_SEPARATOR}#{ENV["GEM_PATH"]}" + "#{Vagrant.user_data_path.join("gems")}#{::File::PATH_SEPARATOR}#{@gem_path}" Gem.clear_paths # Load Bundler and setup our paths require "bundler" ::Bundler.setup + + # Do some additional Bundler initialization + ::Bundler.ui = ::Bundler::UI.new + if !::Bundler.ui.respond_to?(:silence) + ui = ::Bundler.ui + def ui.silence(*args) + yield + end + end + end + + # Installs the list of plugins. + # + # @return [Array] + def install(plugins) + gemfile = build_gemfile(plugins) + lockfile = "#{gemfile.path}.lock" + definition = ::Bundler::Definition.build(gemfile, lockfile, nil) + root = File.dirname(gemfile.path) + opts = {} + opts["update"] = true + + with_isolated_gem do + ::Bundler::Installer.install(root, definition, opts) + end + + # Clean up any unused/old gems + runtime = ::Bundler::Runtime.new(root, definition) + runtime.clean + + definition.specs + end + + # Builds a valid Gemfile for use with Bundler given the list of + # plugins. + # + # @return [Tempfile] + def build_gemfile(plugins) + Tempfile.new("vagrant-gemfile").tap do |gemfile| + gemfile.puts(%Q[source "https://rubygems.org"]) + gemfile.puts(%Q[source "http://gems.hashicorp.com"]) + gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) + plugins.each do |plugin| + gemfile.puts(%Q[gem "#{plugin}"]) + end + gemfile.close + end + end + + protected + + def with_isolated_gem + # Remove bundler settings so that Bundler isn't loaded when building + # native extensions because it causes all sorts of problems. + old_rubyopt = ENV["RUBYOPT"] + old_gemfile = ENV["BUNDLE_GEMFILE"] + ENV["BUNDLE_GEMFILE"] = nil + ENV["RUBYOPT"] = (ENV["RUBYOPT"] || "").gsub(/-rbundler\/setup\s*/, "") + + # Set the GEM_HOME so gems are installed only to our local gem dir + ENV["GEM_HOME"] = Vagrant.user_data_path.join("gems").to_s + + # Clear paths so that it reads the new GEM_HOME setting + Gem.paths = ENV + + # Set a custom configuration to avoid loading ~/.gemrc loads and + # /etc/gemrc and so on. + old_config = Gem.configuration + Gem.configuration = NilGemConfig.new + + # Use a silent UI so that we have no output + Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do + return yield + end + ensure + ENV["BUNDLE_GEMFILE"] = old_gemfile + ENV["GEM_HOME"] = @gem_home + ENV["RUBYOPT"] = old_rubyopt + + Gem.configuration = old_config + Gem.paths = ENV + end + + # This is pretty hacky but it is a custom implementation of + # Gem::ConfigFile so that we don't load any gemrc files. + class NilGemConfig < Gem::ConfigFile + def initialize + # We _can not_ `super` here because that can really mess up + # some other configuration state. We need to just set everything + # directly. + + @api_keys = {} + @args = [] + @backtrace = false + @bulk_threshold = 1000 + @hash = {} + @update_sources = true + @verbose = true + end end end end diff --git a/lib/vagrant/init.rb b/lib/vagrant/init.rb index 32ef6e4e5..49c2668cb 100644 --- a/lib/vagrant/init.rb +++ b/lib/vagrant/init.rb @@ -66,6 +66,7 @@ end # We need these components always so instead of an autoload we # just require them explicitly here. +require "vagrant/plugin" require "vagrant/registry" module Vagrant diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index da5704c7f..20ff87d25 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -1,4 +1,6 @@ +require_relative "../bundler" require_relative "../shared_helpers" +require_relative "state_file" module Vagrant module Plugin @@ -20,6 +22,20 @@ module Vagrant @global_file = StateFile.new(global_file) end + # Installs another plugin into our gem directory. + # + # @param [String] name Name of the plugin (gem) + def install_plugin(name) + result = nil + Vagrant::Bundler.instance.install(installed_plugins.push(name)).each do |spec| + next if spec.name != name + next if result && result.version >= spec.version + result = spec + end + + result + end + # This returns the list of plugins that should be enabled. # # @return [Array] diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 0e21edd64..4c171256a 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -9,7 +9,6 @@ module VagrantPlugins def self.action_install Vagrant::Action::Builder.new.tap do |b| b.use InstallGem - b.use PruneGems end end @@ -31,7 +30,6 @@ module VagrantPlugins def self.action_uninstall Vagrant::Action::Builder.new.tap do |b| b.use UninstallPlugin - b.use PruneGems end end @@ -40,7 +38,6 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use PluginExistsCheck b.use InstallGem - b.use PruneGems end end diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index 07a5000fb..ac91754e3 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -8,6 +8,7 @@ rescue LoadError end require "log4r" +require "vagrant/plugin/manager" module VagrantPlugins module CommandPlugin @@ -49,40 +50,10 @@ module VagrantPlugins plugin_name_label += " --version '#{version}'" if version env[:ui].info(I18n.t("vagrant.commands.plugin.installing", :name => plugin_name_label)) - installed_gems = env[:gem_helper].with_environment do - # Override the list of sources by the ones set as a parameter if given - if env[:plugin_sources] - @logger.info("Custom plugin sources: #{env[:plugin_sources]}") - Gem.sources = env[:plugin_sources] - end - installer = Gem::DependencyInstaller.new(:document => [], :prerelease => prerelease) - - # If we don't have a version, use the default version - version ||= Gem::Requirement.default - - begin - installer.install(plugin_name, version) - rescue Gem::GemNotFoundException - raise Vagrant::Errors::PluginInstallNotFound, - :name => plugin_name - end - end - - # The plugin spec is the last installed gem since RubyGems - # currently always installed the requested gem last. - @logger.debug("Installed #{installed_gems.length} gems.") - plugin_spec = installed_gems.find do |gem| - gem.name.downcase == find_plugin_name.downcase - end - - # Store the installed name so we can uninstall it if things go - # wrong. - @installed_plugin_name = plugin_spec.name - - # Mark that we installed the gem - @logger.info("Adding the plugin to the state file...") - env[:plugin_state_file].add_plugin(plugin_spec.name) + # TODO: support version, pre-release, custom sources + manager = Vagrant::Plugin::Manager.instance + plugin_spec = manager.install_plugin(plugin_name) # Tell the user env[:ui].success(I18n.t("vagrant.commands.plugin.installed", From 95aeb9443de02eb36cce81d58e1253ec6a8ab4a3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 14:55:44 -0800 Subject: [PATCH 10/54] commands/plugin: Remove PruneGems --- plugins/commands/plugin/action.rb | 1 - plugins/commands/plugin/action/prune_gems.rb | 158 ------------------- 2 files changed, 159 deletions(-) delete mode 100644 plugins/commands/plugin/action/prune_gems.rb diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 4c171256a..2d0024915 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -47,7 +47,6 @@ module VagrantPlugins autoload :LicensePlugin, action_root.join("license_plugin") autoload :ListPlugins, action_root.join("list_plugins") autoload :PluginExistsCheck, action_root.join("plugin_exists_check") - autoload :PruneGems, action_root.join("prune_gems") autoload :UninstallPlugin, action_root.join("uninstall_plugin") end end diff --git a/plugins/commands/plugin/action/prune_gems.rb b/plugins/commands/plugin/action/prune_gems.rb deleted file mode 100644 index d5e754994..000000000 --- a/plugins/commands/plugin/action/prune_gems.rb +++ /dev/null @@ -1,158 +0,0 @@ -require "rubygems" -require "rubygems/user_interaction" -require "rubygems/uninstaller" -require "set" - -require "log4r" - -module VagrantPlugins - module CommandPlugin - module Action - # This class prunes any unnecessary gems from the Vagrant-managed - # gem folder. This keeps the gem folder to the absolute minimum set - # of required gems and doesn't let it blow up out of control. - # - # A high-level description of how this works: - # - # 1. Get the list of installed plugins. Vagrant maintains this - # list on its own. - # 2. Get the list of installed RubyGems. - # 3. Find the latest version of each RubyGem that matches an installed - # plugin. These are our root RubyGems that must be installed. - # 4. Go through each root and mark all dependencies recursively as - # necessary. - # 5. Set subtraction between all gems and necessary gems yields a - # list of gems that aren't needed. Uninstall them. - # - class PruneGems - def initialize(app, env) - @app = app - @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::prune") - end - - def call(env) - @logger.info("Pruning gems...") - - # Get the list of installed plugins according to the state file - installed = env[:plugin_state_file].installed_plugins.keys - - # Get the actual specifications of installed gems - all_specs = env[:gem_helper].with_environment do - [].tap do |result| - Gem::Specification.find_all do |s| - # Ignore default gems since they can't be uninstalled - next if s.respond_to?(:default_gem?) && s.default_gem? - - result << s - end - end - end - - # The list of specs to prune initially starts out as all of them - all_specs = Set.new(all_specs) - - # Go through each spec and find the latest version of the installed - # gems, since we want to keep those. - installed_specs = {} - - @logger.debug("Collecting installed plugin gems...") - all_specs.each do |spec| - # If this isn't a spec that we claim is installed, skip it - next if !installed.include?(spec.name) - - # If it is already in the specs, then we need to make sure we - # have the latest version. - if installed_specs.has_key?(spec.name) - if installed_specs[spec.name].version > spec.version - next - end - end - - @logger.debug(" -- #{spec.name} (#{spec.version})") - installed_specs[spec.name] = spec - end - - # Recursive dependency checker to keep all dependencies and remove - # all non-crucial gems from the prune list. - good_specs = Set.new - to_check = installed_specs.values - - while true - # If we're out of gems to check then we break out - break if to_check.empty? - - # Get a random (first) element to check - spec = to_check.shift - - # If we already checked this, then do the next one - next if good_specs.include?(spec) - - # Find all the dependencies and add the latest compliant gem - # to the `to_check` list. - if spec.dependencies.length > 0 - @logger.debug("Finding dependencies for '#{spec.name}' to mark as good...") - spec.dependencies.each do |dep| - # Ignore non-runtime dependencies - next if dep.type != :runtime - @logger.debug("Searching for: '#{dep.name}'") - - latest_matching = nil - - all_specs.each do |prune_spec| - if dep =~ prune_spec - # If we have a matching one already and this one isn't newer - # then we ditch it. - next if latest_matching && - prune_spec.version <= latest_matching.version - - latest_matching = prune_spec - end - end - - if latest_matching.nil? - @logger.error("Missing dependency for '#{spec.name}': #{dep.name}") - next - end - - @logger.debug("Latest matching dep: '#{latest_matching.name}' (#{latest_matching.version})") - to_check << latest_matching - end - end - - # Add ito the list of checked things so we don't accidentally - # re-check it - good_specs.add(spec) - end - - # Figure out the gems we need to prune - prune_specs = all_specs - good_specs - @logger.debug("Gems to prune: #{prune_specs.inspect}") - @logger.info("Pruning #{prune_specs.length} gems.") - - if prune_specs.length > 0 - env[:gem_helper].with_environment do - # Due to a bug in rubygems 2.0, we need to load the - # specifications before removing any. This achieves that. - Gem::Specification.to_a - - prune_specs.each do |prune_spec| - uninstaller = Gem::Uninstaller.new(prune_spec.name, { - :all => true, - :executables => true, - :force => true, - :ignore => true, - :version => prune_spec.version.version - }) - - @logger.info("Uninstalling: #{prune_spec.name} (#{prune_spec.version})") - uninstaller.uninstall - end - end - end - - @app.call(env) - end - end - end - end -end From 91751f6e41b0fb452acc64c8c6caea3c9dc82571 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 15:26:44 -0800 Subject: [PATCH 11/54] load with bundler context --- bin/vagrant | 12 ++ lib/vagrant.rb | 265 ++++++++++++++++++++++++++++++++++-- lib/vagrant/bundler.rb | 5 +- lib/vagrant/init.rb | 248 --------------------------------- lib/vagrant/pre-rubygems.rb | 24 ++++ 5 files changed, 289 insertions(+), 265 deletions(-) delete mode 100644 lib/vagrant/init.rb create mode 100644 lib/vagrant/pre-rubygems.rb diff --git a/bin/vagrant b/bin/vagrant index aaa6e3afa..3636225c2 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -5,6 +5,18 @@ # initializing which have historically resulted in stack traces. Signal.trap("INT") { abort } +# First, make sure that we're executing using the proper Bundler context +# with our plugins. If we're not, then load that and reload Vagrant. +if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] + require "rbconfig" + ruby_path = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"]) + Kernel.exec( + ruby_path, + File.expand_path("../../lib/vagrant/pre-rubygems.rb", __FILE__), + *ARGV) + raise "Fatal error: this line should never be reached" +end + # Split arguments by "--" if its there, we'll recombine them later argv = ARGV.dup argv_extra = [] diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 2e9739fdf..b7c870526 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -3,27 +3,262 @@ ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = "/Applications/Vagrant/embedded" -if defined?(Bundler) - require "bundler/shared_helpers" - if Bundler::SharedHelpers.in_bundle? - puts "Vagrant appears to be running in a Bundler environment. Plugins" - puts "will not be loaded and plugin commands are disabled." - puts - ENV["VAGRANT_NO_PLUGINS"] = "1" +if !defined?(Bundler) + puts "It appears that Vagrant was not properly loaded. Specifically," + puts "the bundler context Vagrant requires was not setup. Please execute" + puts "vagrant using only the `vagrant` executable." + abort +end + +require 'rubygems' +require 'log4r' + +# Enable logging if it is requested. We do this before +# anything else so that we can setup the output before +# any logging occurs. +if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != "" + # Require Log4r and define the levels we'll be using + require 'log4r/config' + Log4r.define_levels(*Log4r::Log4rConfig::LogLevels) + + level = nil + begin + level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) + rescue NameError + # This means that the logging constant wasn't found, + # which is fine. We just keep `level` as `nil`. But + # we tell the user. + level = nil + end + + # Some constants, such as "true" resolve to booleans, so the + # above error checking doesn't catch it. This will check to make + # sure that the log level is an integer, as Log4r requires. + level = nil if !level.is_a?(Integer) + + if !level + # We directly write to stderr here because the VagrantError system + # is not setup yet. + $stderr.puts "Invalid VAGRANT_LOG level is set: #{ENV["VAGRANT_LOG"]}" + $stderr.puts "" + $stderr.puts "Please use one of the standard log levels: debug, info, warn, or error" + exit 1 + end + + # Set the logging level on all "vagrant" namespaced + # logs as long as we have a valid level. + if level + logger = Log4r::Logger.new("vagrant") + logger.outputters = Log4r::Outputter.stderr + logger.level = level + logger = nil end end -require_relative "vagrant/shared_helpers" +require 'json' +require 'pathname' +require 'stringio' -if Vagrant.plugins_enabled? - # Initialize Bundler before we load _any_ RubyGems. - require_relative "vagrant/bundler" - require_relative "vagrant/plugin/manager" - Vagrant::Bundler.instance.init!(Vagrant::Plugin::Manager.instance.installed_plugins) +require 'childprocess' +require 'i18n' + +# OpenSSL must be loaded here since when it is loaded via `autoload` +# there are issues with ciphers not being properly loaded. +require 'openssl' + +# Always make the version available +require 'vagrant/version' +global_logger = Log4r::Logger.new("vagrant::global") +global_logger.info("Vagrant version: #{Vagrant::VERSION}") +global_logger.info("Ruby version: #{RUBY_VERSION}") +global_logger.info("RubyGems version: #{Gem::VERSION}") +ENV.each do |k, v| + global_logger.info("#{k}=#{v.inspect}") if k =~ /^VAGRANT_/ end -# Initialize Vagrant now that our Gem paths are setup -require "vagrant/init" +# We need these components always so instead of an autoload we +# just require them explicitly here. +require "vagrant/plugin" +require "vagrant/registry" +require "vagrant/shared_helpers" + +module Vagrant + autoload :Action, 'vagrant/action' + autoload :BatchAction, 'vagrant/batch_action' + autoload :Box, 'vagrant/box' + autoload :BoxCollection, 'vagrant/box_collection' + autoload :CLI, 'vagrant/cli' + autoload :Command, 'vagrant/command' + autoload :Config, 'vagrant/config' + autoload :Driver, 'vagrant/driver' + autoload :Environment, 'vagrant/environment' + autoload :Errors, 'vagrant/errors' + autoload :Guest, 'vagrant/guest' + autoload :Hosts, 'vagrant/hosts' + autoload :Machine, 'vagrant/machine' + autoload :MachineState, 'vagrant/machine_state' + autoload :Plugin, 'vagrant/plugin' + autoload :UI, 'vagrant/ui' + autoload :Util, 'vagrant/util' + + # These are the various plugin versions and their components in + # a lazy loaded Hash-like structure. + PLUGIN_COMPONENTS = Registry.new.tap do |c| + c.register(:"1") { Plugin::V1::Plugin } + c.register([:"1", :command]) { Plugin::V1::Command } + c.register([:"1", :communicator]) { Plugin::V1::Communicator } + c.register([:"1", :config]) { Plugin::V1::Config } + c.register([:"1", :guest]) { Plugin::V1::Guest } + c.register([:"1", :host]) { Plugin::V1::Host } + c.register([:"1", :provider]) { Plugin::V1::Provider } + c.register([:"1", :provisioner]) { Plugin::V1::Provisioner } + + c.register(:"2") { Plugin::V2::Plugin } + c.register([:"2", :command]) { Plugin::V2::Command } + c.register([:"2", :communicator]) { Plugin::V2::Communicator } + c.register([:"2", :config]) { Plugin::V2::Config } + c.register([:"2", :guest]) { Plugin::V2::Guest } + c.register([:"2", :host]) { Plugin::V2::Host } + c.register([:"2", :provider]) { Plugin::V2::Provider } + c.register([:"2", :provisioner]) { Plugin::V2::Provisioner } + c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder } + end + + # This returns a true/false showing whether we're running from the + # environment setup by the Vagrant installers. + # + # @return [Boolean] + def self.in_installer? + !!ENV["VAGRANT_INSTALLER_ENV"] + end + + # Configure a Vagrant environment. The version specifies the version + # of the configuration that is expected by the block. The block, based + # on that version, configures the environment. + # + # Note that the block isn't run immediately. Instead, the configuration + # block is stored until later, and is run when an environment is loaded. + # + # @param [String] version Version of the configuration + def self.configure(version, &block) + Config.run(version, &block) + end + + # This checks if a plugin with the given name is installed. This can + # be used from the Vagrantfile to easily branch based on plugin + # availability. + def self.has_plugin?(name) + manager = plugin("2").manager + + manager.required.any? { |gem_name| gem_name == name } || + manager.registered.any? { |plugin| plugin.name == name } + end + + # Returns a superclass to use when creating a plugin for Vagrant. + # Given a specific version, this returns a proper superclass to use + # to register plugins for that version. + # + # Optionally, if you give a specific component, then it will return + # the proper superclass for that component as well. + # + # Plugins and plugin components should subclass the classes returned by + # this method. This method lets Vagrant core control these superclasses + # and change them over time without affecting plugins. For example, if + # the V1 superclass happens to be "Vagrant::V1," future versions of + # Vagrant may move it to "Vagrant::Plugins::V1" and plugins will not be + # affected. + # + # @param [String] version + # @param [String] component + # @return [Class] + def self.plugin(version, component=nil) + # Build up the key and return a result + key = version.to_s.to_sym + key = [key, component.to_s.to_sym] if component + result = PLUGIN_COMPONENTS.get(key) + + # If we found our component then we return that + return result if result + + # If we didn't find a result, then raise an exception, depending + # on if we got a component or not. + raise ArgumentError, "Plugin superclass not found for version/component: " + + "#{version} #{component}" + end + + # @deprecated + def self.require_plugin(name) + puts "Vagrant.require_plugin is deprecated and has no effect any longer." + puts "Use `vagrant plugin` commands to manage plugins. This warning will" + puts "be removed in the next version of Vagrant." + end + + # This allows a Vagrantfile to specify the version of Vagrant that is + # required. You can specify a list of requirements which will all be checked + # against the running Vagrant version. + # + # This should be specified at the _top_ of any Vagrantfile. + # + # Examples are shown below: + # + # Vagrant.require_version(">= 1.3.5") + # Vagrant.require_version(">= 1.3.5", "< 1.4.0") + # Vagrant.require_version("~> 1.3.5") + # + def self.require_version(*requirements) + logger = Log4r::Logger.new("vagrant::root") + logger.info("Version requirements from Vagrantfile: #{requirements.inspect}") + + req = Gem::Requirement.new(*requirements) + if req.satisfied_by?(Gem::Version.new(VERSION)) + logger.info(" - Version requirements satisfied!") + return + end + + raise Errors::VagrantVersionBad, + requirements: requirements.join(", "), + version: VERSION + end +end + +# Default I18n to load the en locale +I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_root) + +if I18n.config.respond_to?(:enforce_available_locales=) + # Make sure only available locales are used. This will be the default in the + # future but we need this to silence a deprecation warning from 0.6.9 + I18n.config.enforce_available_locales = true +end + +# A lambda that knows how to load plugins from a single directory. +plugin_load_proc = lambda do |directory| + # We only care about directories + next false if !directory.directory? + + # If there is a plugin file in the top-level directory, then load + # that up. + plugin_file = directory.join("plugin.rb") + if plugin_file.file? + global_logger.debug("Loading core plugin: #{plugin_file}") + load(plugin_file) + next true + end +end + +# Go through the `plugins` directory and attempt to load any plugins. The +# plugins are allowed to be in a directory in `plugins` or at most one +# directory deep within the plugins directory. So a plugin can be at +# `plugins/foo` or also at `plugins/foo/bar`, but no deeper. +Vagrant.source_root.join("plugins").children(true).each do |directory| + # Ignore non-directories + next if !directory.directory? + + # Load from this directory, and exit if we successfully loaded a plugin + next if plugin_load_proc.call(directory) + + # Otherwise, attempt to load from sub-directories + directory.children(true).each(&plugin_load_proc) +end # If we have plugins enabled, then load those Bundler.require(:default) if Vagrant.plugins_enabled? diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index e4afd9fbe..4659a8085 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -24,7 +24,7 @@ module Vagrant raise "Bundler already initialized" if defined?(::Bundler) # Setup the Bundler configuration - @configfile = Tempfile.new("vagrant-bundler-config") + @configfile = File.open(Tempfile.new("vagrant").path + "1", "w+") @configfile.close # Build up the Gemfile for our Bundler context. We make sure to @@ -80,7 +80,8 @@ module Vagrant # # @return [Tempfile] def build_gemfile(plugins) - Tempfile.new("vagrant-gemfile").tap do |gemfile| + f = File.open(Tempfile.new("vagrant").path + "2", "w+") + f.tap do |gemfile| gemfile.puts(%Q[source "https://rubygems.org"]) gemfile.puts(%Q[source "http://gems.hashicorp.com"]) gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) diff --git a/lib/vagrant/init.rb b/lib/vagrant/init.rb deleted file mode 100644 index 49c2668cb..000000000 --- a/lib/vagrant/init.rb +++ /dev/null @@ -1,248 +0,0 @@ -require 'rubygems' -require 'log4r' - -# Enable logging if it is requested. We do this before -# anything else so that we can setup the output before -# any logging occurs. -if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != "" - # Require Log4r and define the levels we'll be using - require 'log4r/config' - Log4r.define_levels(*Log4r::Log4rConfig::LogLevels) - - level = nil - begin - level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) - rescue NameError - # This means that the logging constant wasn't found, - # which is fine. We just keep `level` as `nil`. But - # we tell the user. - level = nil - end - - # Some constants, such as "true" resolve to booleans, so the - # above error checking doesn't catch it. This will check to make - # sure that the log level is an integer, as Log4r requires. - level = nil if !level.is_a?(Integer) - - if !level - # We directly write to stderr here because the VagrantError system - # is not setup yet. - $stderr.puts "Invalid VAGRANT_LOG level is set: #{ENV["VAGRANT_LOG"]}" - $stderr.puts "" - $stderr.puts "Please use one of the standard log levels: debug, info, warn, or error" - exit 1 - end - - # Set the logging level on all "vagrant" namespaced - # logs as long as we have a valid level. - if level - logger = Log4r::Logger.new("vagrant") - logger.outputters = Log4r::Outputter.stderr - logger.level = level - logger = nil - end -end - -require 'json' -require 'pathname' -require 'stringio' - -require 'childprocess' -require 'i18n' - -# OpenSSL must be loaded here since when it is loaded via `autoload` -# there are issues with ciphers not being properly loaded. -require 'openssl' - -# Always make the version available -require 'vagrant/version' -global_logger = Log4r::Logger.new("vagrant::global") -global_logger.info("Vagrant version: #{Vagrant::VERSION}") -global_logger.info("Ruby version: #{RUBY_VERSION}") -global_logger.info("RubyGems version: #{Gem::VERSION}") -ENV.each do |k, v| - global_logger.info("#{k}=#{v.inspect}") if k =~ /^VAGRANT_/ -end - -# We need these components always so instead of an autoload we -# just require them explicitly here. -require "vagrant/plugin" -require "vagrant/registry" - -module Vagrant - autoload :Action, 'vagrant/action' - autoload :BatchAction, 'vagrant/batch_action' - autoload :Box, 'vagrant/box' - autoload :BoxCollection, 'vagrant/box_collection' - autoload :CLI, 'vagrant/cli' - autoload :Command, 'vagrant/command' - autoload :Config, 'vagrant/config' - autoload :Driver, 'vagrant/driver' - autoload :Environment, 'vagrant/environment' - autoload :Errors, 'vagrant/errors' - autoload :Guest, 'vagrant/guest' - autoload :Hosts, 'vagrant/hosts' - autoload :Machine, 'vagrant/machine' - autoload :MachineState, 'vagrant/machine_state' - autoload :Plugin, 'vagrant/plugin' - autoload :UI, 'vagrant/ui' - autoload :Util, 'vagrant/util' - - # These are the various plugin versions and their components in - # a lazy loaded Hash-like structure. - PLUGIN_COMPONENTS = Registry.new.tap do |c| - c.register(:"1") { Plugin::V1::Plugin } - c.register([:"1", :command]) { Plugin::V1::Command } - c.register([:"1", :communicator]) { Plugin::V1::Communicator } - c.register([:"1", :config]) { Plugin::V1::Config } - c.register([:"1", :guest]) { Plugin::V1::Guest } - c.register([:"1", :host]) { Plugin::V1::Host } - c.register([:"1", :provider]) { Plugin::V1::Provider } - c.register([:"1", :provisioner]) { Plugin::V1::Provisioner } - - c.register(:"2") { Plugin::V2::Plugin } - c.register([:"2", :command]) { Plugin::V2::Command } - c.register([:"2", :communicator]) { Plugin::V2::Communicator } - c.register([:"2", :config]) { Plugin::V2::Config } - c.register([:"2", :guest]) { Plugin::V2::Guest } - c.register([:"2", :host]) { Plugin::V2::Host } - c.register([:"2", :provider]) { Plugin::V2::Provider } - c.register([:"2", :provisioner]) { Plugin::V2::Provisioner } - c.register([:"2", :synced_folder]) { Plugin::V2::SyncedFolder } - end - - # This returns a true/false showing whether we're running from the - # environment setup by the Vagrant installers. - # - # @return [Boolean] - def self.in_installer? - !!ENV["VAGRANT_INSTALLER_ENV"] - end - - # Configure a Vagrant environment. The version specifies the version - # of the configuration that is expected by the block. The block, based - # on that version, configures the environment. - # - # Note that the block isn't run immediately. Instead, the configuration - # block is stored until later, and is run when an environment is loaded. - # - # @param [String] version Version of the configuration - def self.configure(version, &block) - Config.run(version, &block) - end - - # This checks if a plugin with the given name is installed. This can - # be used from the Vagrantfile to easily branch based on plugin - # availability. - def self.has_plugin?(name) - manager = plugin("2").manager - - manager.required.any? { |gem_name| gem_name == name } || - manager.registered.any? { |plugin| plugin.name == name } - end - - # Returns a superclass to use when creating a plugin for Vagrant. - # Given a specific version, this returns a proper superclass to use - # to register plugins for that version. - # - # Optionally, if you give a specific component, then it will return - # the proper superclass for that component as well. - # - # Plugins and plugin components should subclass the classes returned by - # this method. This method lets Vagrant core control these superclasses - # and change them over time without affecting plugins. For example, if - # the V1 superclass happens to be "Vagrant::V1," future versions of - # Vagrant may move it to "Vagrant::Plugins::V1" and plugins will not be - # affected. - # - # @param [String] version - # @param [String] component - # @return [Class] - def self.plugin(version, component=nil) - # Build up the key and return a result - key = version.to_s.to_sym - key = [key, component.to_s.to_sym] if component - result = PLUGIN_COMPONENTS.get(key) - - # If we found our component then we return that - return result if result - - # If we didn't find a result, then raise an exception, depending - # on if we got a component or not. - raise ArgumentError, "Plugin superclass not found for version/component: " + - "#{version} #{component}" - end - - # @deprecated - def self.require_plugin(name) - puts "Vagrant.require_plugin is deprecated and has no effect any longer." - puts "Use `vagrant plugin` commands to manage plugins. This warning will" - puts "be removed in the next version of Vagrant." - end - - # This allows a Vagrantfile to specify the version of Vagrant that is - # required. You can specify a list of requirements which will all be checked - # against the running Vagrant version. - # - # This should be specified at the _top_ of any Vagrantfile. - # - # Examples are shown below: - # - # Vagrant.require_version(">= 1.3.5") - # Vagrant.require_version(">= 1.3.5", "< 1.4.0") - # Vagrant.require_version("~> 1.3.5") - # - def self.require_version(*requirements) - logger = Log4r::Logger.new("vagrant::root") - logger.info("Version requirements from Vagrantfile: #{requirements.inspect}") - - req = Gem::Requirement.new(*requirements) - if req.satisfied_by?(Gem::Version.new(VERSION)) - logger.info(" - Version requirements satisfied!") - return - end - - raise Errors::VagrantVersionBad, - requirements: requirements.join(", "), - version: VERSION - end -end - -# Default I18n to load the en locale -I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_root) - -if I18n.config.respond_to?(:enforce_available_locales=) - # Make sure only available locales are used. This will be the default in the - # future but we need this to silence a deprecation warning from 0.6.9 - I18n.config.enforce_available_locales = true -end - -# A lambda that knows how to load plugins from a single directory. -plugin_load_proc = lambda do |directory| - # We only care about directories - next false if !directory.directory? - - # If there is a plugin file in the top-level directory, then load - # that up. - plugin_file = directory.join("plugin.rb") - if plugin_file.file? - global_logger.debug("Loading core plugin: #{plugin_file}") - load(plugin_file) - next true - end -end - -# Go through the `plugins` directory and attempt to load any plugins. The -# plugins are allowed to be in a directory in `plugins` or at most one -# directory deep within the plugins directory. So a plugin can be at -# `plugins/foo` or also at `plugins/foo/bar`, but no deeper. -Vagrant.source_root.join("plugins").children(true).each do |directory| - # Ignore non-directories - next if !directory.directory? - - # Load from this directory, and exit if we successfully loaded a plugin - next if plugin_load_proc.call(directory) - - # Otherwise, attempt to load from sub-directories - directory.children(true).each(&plugin_load_proc) -end diff --git a/lib/vagrant/pre-rubygems.rb b/lib/vagrant/pre-rubygems.rb new file mode 100644 index 000000000..40ab97dd3 --- /dev/null +++ b/lib/vagrant/pre-rubygems.rb @@ -0,0 +1,24 @@ +# This file is to be loaded _before_ any RubyGems are loaded. This file +# initializes the Bundler context so that Vagrant and its associated plugins +# can load properly, and then execs out into Vagrant again. + +require_relative "bundler" +require_relative "plugin/manager" +require_relative "shared_helpers" + +if defined?(Bundler) + require "bundler/shared_helpers" + if Bundler::SharedHelpers.in_bundle? + puts "Vagrant appears to be running in a Bundler environment. Plugins" + puts "will not be loaded and plugin commands are disabled." + puts + ENV["VAGRANT_NO_PLUGINS"] = "1" + end +end + +plugins = [] +plugins = Vagrant::Plugin::Manager.instance.installed_plugins if Vagrant.plugins_enabled? +Vagrant::Bundler.instance.init!(plugins) + +ENV["VAGRANT_INTERNAL_BUNDLERIZED"] = "1" +Kernel.exec("vagrant", *ARGV) From 817f7f326efaa485642ad00caf020ac32c07ea86 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 15:27:03 -0800 Subject: [PATCH 12/54] fast path Vagrant --version --- bin/vagrant | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index 3636225c2..6f53a27bb 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -5,6 +5,12 @@ # initializing which have historically resulted in stack traces. Signal.trap("INT") { abort } +# Fast path the version of Vagrant +if argv.include?("-v") || argv.include?("--version") + puts "Vagrant #{Vagrant::VERSION}" + exit 0 +end + # First, make sure that we're executing using the proper Bundler context # with our plugins. If we're not, then load that and reload Vagrant. if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] @@ -99,12 +105,6 @@ argv.each do |arg| end end -# Fast path the version of Vagrant -if argv.include?("-v") || argv.include?("--version") - puts "Vagrant #{Vagrant::VERSION}" - exit 0 -end - # Recombine the arguments argv << "--" argv += argv_extra From 177bfc1d0c6fa5eccafc60ab610de3a0625378c7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 15:27:20 -0800 Subject: [PATCH 13/54] Lock to log4r < 1.1.11 because we got fucked 1.1.11 was released today and seriously broke backwards compatibility. While they never officially made any promise to follow semver, it is almost expected at this point, but log4r decided to just fuck that. 1.1.11 changed the arity of Log4r::Logger.initialize. That seriously breaks _everything_. Darwin awarddddddddd goes to... --- vagrant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vagrant.gemspec b/vagrant.gemspec index 6238af891..1b1bdc318 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.add_dependency "childprocess", "~> 0.3.7" s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "i18n", "~> 0.6.0" - s.add_dependency "log4r", "~> 1.1.9" + s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11" s.add_dependency "net-ssh", ">= 2.6.6", "< 2.8.0" s.add_dependency "net-scp", "~> 1.1.0" From e800743d06481aa57d4f959fb73975db2887469d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 15:32:19 -0800 Subject: [PATCH 14/54] fast-path vagrant --version --- bin/vagrant | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index 6f53a27bb..2e12f0d76 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -5,8 +5,17 @@ # initializing which have historically resulted in stack traces. Signal.trap("INT") { abort } +# Split arguments by "--" if its there, we'll recombine them later +argv = ARGV.dup +argv_extra = [] +if idx = argv.index("--") + argv_extra = argv.slice(idx+1, argv.length-2) + argv = argv.slice(0, idx) +end + # Fast path the version of Vagrant if argv.include?("-v") || argv.include?("--version") + require "vagrant/version" puts "Vagrant #{Vagrant::VERSION}" exit 0 end @@ -23,14 +32,6 @@ if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] raise "Fatal error: this line should never be reached" end -# Split arguments by "--" if its there, we'll recombine them later -argv = ARGV.dup -argv_extra = [] -if idx = argv.index("--") - argv_extra = argv.slice(idx+1, argv.length-2) - argv = argv.slice(0, idx) -end - # Set logging level to `debug`. This is done before loading 'vagrant', as it # sets up the logging system. if argv.include?("--debug") From 8cfa24143df0a04239df2c0e6e24621169c3580e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 15:57:10 -0800 Subject: [PATCH 15/54] Put plugins in their own Gemfile group so we can load on their own --- bin/vagrant | 4 ++++ lib/vagrant.rb | 2 +- lib/vagrant/bundler.rb | 7 ++++--- lib/vagrant/plugin/manager.rb | 4 ++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index 2e12f0d76..0f017ee69 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -39,6 +39,10 @@ if argv.include?("--debug") ENV["VAGRANT_LOG"] = "debug" end +# Setup our dependencies by initializing Bundler +require "bundler" +Bundler.setup + require 'log4r' require 'vagrant' require 'vagrant/cli' diff --git a/lib/vagrant.rb b/lib/vagrant.rb index b7c870526..83c801c89 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -261,4 +261,4 @@ Vagrant.source_root.join("plugins").children(true).each do |directory| end # If we have plugins enabled, then load those -Bundler.require(:default) if Vagrant.plugins_enabled? +Bundler.require(:plugins) if Vagrant.plugins_enabled? diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 4659a8085..956735be1 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -39,9 +39,8 @@ module Vagrant "#{Vagrant.user_data_path.join("gems")}#{::File::PATH_SEPARATOR}#{@gem_path}" Gem.clear_paths - # Load Bundler and setup our paths + # Load Bundler now require "bundler" - ::Bundler.setup # Do some additional Bundler initialization ::Bundler.ui = ::Bundler::UI.new @@ -70,7 +69,7 @@ module Vagrant # Clean up any unused/old gems runtime = ::Bundler::Runtime.new(root, definition) - runtime.clean + #runtime.clean definition.specs end @@ -85,9 +84,11 @@ module Vagrant gemfile.puts(%Q[source "https://rubygems.org"]) gemfile.puts(%Q[source "http://gems.hashicorp.com"]) gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) + gemfile.puts("group :plugins do") plugins.each do |plugin| gemfile.puts(%Q[gem "#{plugin}"]) end + gemfile.puts("end") gemfile.close end end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 20ff87d25..3f7d5beb9 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -25,6 +25,7 @@ module Vagrant # Installs another plugin into our gem directory. # # @param [String] name Name of the plugin (gem) + # @return [Gem::Specification] def install_plugin(name) result = nil Vagrant::Bundler.instance.install(installed_plugins.push(name)).each do |spec| @@ -33,6 +34,9 @@ module Vagrant result = spec end + # Add the plugin to the state file + @global_file.add_plugin(result.name) + result end From e1c94310857fc219b0e6e10a92a27353e8fcf59e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:02:00 -0800 Subject: [PATCH 16/54] commands/plugin: uninstall works again --- lib/vagrant/bundler.rb | 14 ++++++++++---- lib/vagrant/plugin/manager.rb | 10 ++++++++++ plugins/commands/plugin/action/uninstall_plugin.rb | 4 +++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 956735be1..3dc7b4514 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -67,13 +67,19 @@ module Vagrant ::Bundler::Installer.install(root, definition, opts) end - # Clean up any unused/old gems - runtime = ::Bundler::Runtime.new(root, definition) - #runtime.clean - definition.specs end + # Clean removes any unused gems. + def clean(plugins) + gemfile = build_gemfile(plugins) + lockfile = "#{gemfile.path}.lock" + definition = ::Bundler::Definition.build(gemfile, lockfile, nil) + root = File.dirname(gemfile.path) + runtime = ::Bundler::Runtime.new(root, definition) + runtime.clean + end + # Builds a valid Gemfile for use with Bundler given the list of # plugins. # diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 3f7d5beb9..a4ccce8fb 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -40,6 +40,16 @@ module Vagrant result end + # Uninstalls the plugin with the given name. + # + # @param [String] name + def uninstall_plugin(name) + @global_file.remove_plugin(name) + + # Clean the environment, removing any old plugins + Vagrant::Bundler.instance.clean(installed_plugins) + end + # This returns the list of plugins that should be enabled. # # @return [Array] diff --git a/plugins/commands/plugin/action/uninstall_plugin.rb b/plugins/commands/plugin/action/uninstall_plugin.rb index f86675523..acf1ef2a3 100644 --- a/plugins/commands/plugin/action/uninstall_plugin.rb +++ b/plugins/commands/plugin/action/uninstall_plugin.rb @@ -13,7 +13,9 @@ module VagrantPlugins # Remove it! env[:ui].info(I18n.t("vagrant.commands.plugin.uninstalling", :name => env[:plugin_name])) - env[:plugin_state_file].remove_plugin(env[:plugin_name]) + + manager = Vagrant::Plugin::Manager.instance + manager.uninstall_plugin(env[:plugin_name]) @app.call(env) end From 8823f43f3a48a425d21184a0faf82d835b691a9a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:03:00 -0800 Subject: [PATCH 17/54] commands/plugin: error if uninstall non-existent plugin --- plugins/commands/plugin/action.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 2d0024915..04cfa5c9c 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -29,6 +29,7 @@ module VagrantPlugins # This middleware sequence will uninstall a plugin. def self.action_uninstall Vagrant::Action::Builder.new.tap do |b| + b.use PluginExistsCheck b.use UninstallPlugin end end From 76de267d1ec9051b137fe05e926016753b9e0843 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:04:53 -0800 Subject: [PATCH 18/54] Don't load plugins again on `vagrant plugin` commands --- bin/vagrant | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index 0f017ee69..ef6789565 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -39,6 +39,21 @@ if argv.include?("--debug") ENV["VAGRANT_LOG"] = "debug" end +# This is kind of hacky, and I'd love to find a better way to do this, but +# if we're accessing the plugin interface, we want to NOT load plugins +# for this run, because they can actually interfere with the function +# of the plugin interface. +argv.each do |arg| + if !arg.start_with?("-") + if arg == "plugin" + ENV["VAGRANT_NO_PLUGINS"] = "1" + ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" + end + + break + end +end + # Setup our dependencies by initializing Bundler require "bundler" Bundler.setup @@ -95,21 +110,6 @@ end # Default to colored output opts[:ui_class] ||= Vagrant::UI::Colored -# This is kind of hacky, and I'd love to find a better way to do this, but -# if we're accessing the plugin interface, we want to NOT load plugins -# for this run, because they can actually interfere with the function -# of the plugin interface. -argv.each do |arg| - if !arg.start_with?("-") - if arg == "plugin" - ENV["VAGRANT_NO_PLUGINS"] = "1" - ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" - end - - break - end -end - # Recombine the arguments argv << "--" argv += argv_extra From 73c71dbcc6456b94702708c532e912266bdf1e6f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:16:04 -0800 Subject: [PATCH 19/54] Work some things around so that Bundler is not setup with no plugins --- bin/vagrant | 41 +++++++++++++++++++++---------------- lib/vagrant.rb | 5 +++-- lib/vagrant/bundler.rb | 7 ++----- lib/vagrant/pre-rubygems.rb | 8 ++++---- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index ef6789565..ad4407d0b 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -20,9 +20,27 @@ if argv.include?("-v") || argv.include?("--version") exit 0 end +# This is kind of hacky, and I'd love to find a better way to do this, but +# if we're accessing the plugin interface, we want to NOT load plugins +# for this run, because they can actually interfere with the function +# of the plugin interface. +argv.each do |arg| + if !arg.start_with?("-") + if arg == "plugin" + ENV["VAGRANT_NO_PLUGINS"] = "1" + ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" + end + + break + end +end + +# Require some stuff that is NOT dependent on RubyGems +require "vagrant/shared_helpers" + # First, make sure that we're executing using the proper Bundler context # with our plugins. If we're not, then load that and reload Vagrant. -if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] +if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] && Vagrant.plugins_enabled? require "rbconfig" ruby_path = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"]) Kernel.exec( @@ -39,25 +57,12 @@ if argv.include?("--debug") ENV["VAGRANT_LOG"] = "debug" end -# This is kind of hacky, and I'd love to find a better way to do this, but -# if we're accessing the plugin interface, we want to NOT load plugins -# for this run, because they can actually interfere with the function -# of the plugin interface. -argv.each do |arg| - if !arg.start_with?("-") - if arg == "plugin" - ENV["VAGRANT_NO_PLUGINS"] = "1" - ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" - end - - break - end +# Setup our dependencies by initializing Bundler if we're using plugins +if Vagrant.plugins_enabled? + require "bundler" + Bundler.setup end -# Setup our dependencies by initializing Bundler -require "bundler" -Bundler.setup - require 'log4r' require 'vagrant' require 'vagrant/cli' diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 83c801c89..263234301 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -3,7 +3,9 @@ ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = "/Applications/Vagrant/embedded" -if !defined?(Bundler) +require "vagrant/shared_helpers" + +if Vagrant.plugins_enabled? && !defined?(Bundler) puts "It appears that Vagrant was not properly loaded. Specifically," puts "the bundler context Vagrant requires was not setup. Please execute" puts "vagrant using only the `vagrant` executable." @@ -80,7 +82,6 @@ end # just require them explicitly here. require "vagrant/plugin" require "vagrant/registry" -require "vagrant/shared_helpers" module Vagrant autoload :Action, 'vagrant/action' diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 3dc7b4514..785f74f27 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -1,6 +1,8 @@ require "pathname" require "tempfile" +require "bundler" + require_relative "shared_helpers" require_relative "version" @@ -21,8 +23,6 @@ module Vagrant # Initializes Bundler and the various gem paths so that we can begin # loading gems. This must only be called once. def init!(plugins) - raise "Bundler already initialized" if defined?(::Bundler) - # Setup the Bundler configuration @configfile = File.open(Tempfile.new("vagrant").path + "1", "w+") @configfile.close @@ -39,9 +39,6 @@ module Vagrant "#{Vagrant.user_data_path.join("gems")}#{::File::PATH_SEPARATOR}#{@gem_path}" Gem.clear_paths - # Load Bundler now - require "bundler" - # Do some additional Bundler initialization ::Bundler.ui = ::Bundler::UI.new if !::Bundler.ui.respond_to?(:silence) diff --git a/lib/vagrant/pre-rubygems.rb b/lib/vagrant/pre-rubygems.rb index 40ab97dd3..f90b7cc40 100644 --- a/lib/vagrant/pre-rubygems.rb +++ b/lib/vagrant/pre-rubygems.rb @@ -2,10 +2,6 @@ # initializes the Bundler context so that Vagrant and its associated plugins # can load properly, and then execs out into Vagrant again. -require_relative "bundler" -require_relative "plugin/manager" -require_relative "shared_helpers" - if defined?(Bundler) require "bundler/shared_helpers" if Bundler::SharedHelpers.in_bundle? @@ -16,6 +12,10 @@ if defined?(Bundler) end end +require_relative "bundler" +require_relative "plugin/manager" +require_relative "shared_helpers" + plugins = [] plugins = Vagrant::Plugin::Manager.instance.installed_plugins if Vagrant.plugins_enabled? Vagrant::Bundler.instance.init!(plugins) From 88615105203ab11a26ec1796ec53291cac41150d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:17:39 -0800 Subject: [PATCH 20/54] Lower some branching logic --- bin/vagrant | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index ad4407d0b..96e7bd154 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -35,12 +35,9 @@ argv.each do |arg| end end -# Require some stuff that is NOT dependent on RubyGems -require "vagrant/shared_helpers" - # First, make sure that we're executing using the proper Bundler context # with our plugins. If we're not, then load that and reload Vagrant. -if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] && Vagrant.plugins_enabled? +if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] require "rbconfig" ruby_path = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"]) Kernel.exec( @@ -57,6 +54,9 @@ if argv.include?("--debug") ENV["VAGRANT_LOG"] = "debug" end +# Require some stuff that is NOT dependent on RubyGems +require "vagrant/shared_helpers" + # Setup our dependencies by initializing Bundler if we're using plugins if Vagrant.plugins_enabled? require "bundler" From f8b49afe8af91120ea8780140e79f5379077747f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:28:05 -0800 Subject: [PATCH 21/54] Nice error message on plugin install conflict --- lib/vagrant/bundler.rb | 3 +++ lib/vagrant/errors.rb | 4 ++++ templates/locales/en.yml | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 785f74f27..ea9639566 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -65,6 +65,9 @@ module Vagrant end definition.specs + rescue ::Bundler::VersionConflict => e + raise Errors::PluginInstallVersionConflict, + conflicts: e.to_s.gsub("Bundler", "Vagrant") end # Clean removes any unused gems. diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index a4fd6090b..938b4a9ac 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -440,6 +440,10 @@ module Vagrant error_key(:plugin_install_not_found) end + class PluginInstallVersionConflict < VagrantError + error_key(:plugin_install_version_conflict) + end + class PluginLoadError < VagrantError error_key(:plugin_load_error) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index e02c1c30c..7a4679e00 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -486,6 +486,16 @@ en: plugin_install_not_found: |- The plugin '%{name}' could not be found in local or remote repositories. Please check the name of the plugin and try again. + plugin_install_version_conflict: |- + The plugin(s) can't be installed due to the version conflicts below. + This means that the plugins depend on a library version that conflicts + with other plugins or Vagrant itself, creating an impossible situation + where Vagrant wouldn't be able to load the plugins. + + You can fix the issue by either removing a conflicting plugin or + by contacting a plugin author to see if they can address the conflict. + + %{conflicts} plugin_load_error: |- The plugin "%{plugin}" could not be found. Please make sure that it is properly installed via `vagrant plugin`. Note that plugins made for From e69723b63c5f1677ae45ec40d9e02a7a31f0e15c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:31:49 -0800 Subject: [PATCH 22/54] core: Plugin::Manager.installed_specs doesn't use Bundler --- lib/vagrant/plugin/manager.rb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index a4ccce8fb..3986f1b79 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -1,3 +1,5 @@ +require "set" + require_relative "../bundler" require_relative "../shared_helpers" require_relative "state_file" @@ -62,7 +64,24 @@ module Vagrant # # @return [Array] def installed_specs - ::Bundler.load.specs + installed = Set.new(installed_plugins) + + # Go through the plugins installed in this environment and + # get the latest version of each. + installed_map = {} + Gem::Specification.find_all.each do |spec| + # Ignore specs that aren't in our installed list + next if !installed.include?(spec.name) + + # If we already have a newer version in our list of installed, + # then ignore it + next if installed_map.has_key?(spec.name) && + installed_map[spec.name].version >= spec.version + + installed_map[spec.name] = spec + end + + installed_map.values end end end From f7e4c4df6b04b01a41492f8dcec77492c8ddfdfc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:43:33 -0800 Subject: [PATCH 23/54] setup the Bundler path always --- bin/vagrant | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index 96e7bd154..6f72807f5 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -57,11 +57,12 @@ end # Require some stuff that is NOT dependent on RubyGems require "vagrant/shared_helpers" -# Setup our dependencies by initializing Bundler if we're using plugins -if Vagrant.plugins_enabled? - require "bundler" - Bundler.setup -end +# Setup our dependencies by initializing Bundler. If we're using plugins, +# then also initialize the paths to the plugins. +groups = [:default] +groups << :plugins if Vagrant.plugins_enabled? +require "bundler" +Bundler.setup(*groups) require 'log4r' require 'vagrant' From 36f64db874fed5a9ed345935b39fbd20337253ae Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:49:25 -0800 Subject: [PATCH 24/54] Always setup plugins, but just don't always require them --- bin/vagrant | 4 +--- lib/vagrant/pre-rubygems.rb | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bin/vagrant b/bin/vagrant index 6f72807f5..448803bd7 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -59,10 +59,8 @@ require "vagrant/shared_helpers" # Setup our dependencies by initializing Bundler. If we're using plugins, # then also initialize the paths to the plugins. -groups = [:default] -groups << :plugins if Vagrant.plugins_enabled? require "bundler" -Bundler.setup(*groups) +Bundler.setup require 'log4r' require 'vagrant' diff --git a/lib/vagrant/pre-rubygems.rb b/lib/vagrant/pre-rubygems.rb index f90b7cc40..ab381f431 100644 --- a/lib/vagrant/pre-rubygems.rb +++ b/lib/vagrant/pre-rubygems.rb @@ -16,8 +16,7 @@ require_relative "bundler" require_relative "plugin/manager" require_relative "shared_helpers" -plugins = [] -plugins = Vagrant::Plugin::Manager.instance.installed_plugins if Vagrant.plugins_enabled? +plugins = Vagrant::Plugin::Manager.instance.installed_plugins Vagrant::Bundler.instance.init!(plugins) ENV["VAGRANT_INTERNAL_BUNDLERIZED"] = "1" From fe8842c795f2f7643a13a31aebee581781305ba1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:54:10 -0800 Subject: [PATCH 25/54] Fix failing tests --- test/unit/vagrant/plugin/manager_test.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index d886656f1..34fdf66b6 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -45,11 +45,14 @@ describe Vagrant::Plugin::Manager do describe "#installed_specs" do it "has the plugins" do - runtime = double("runtime") - runtime.stub(specs: ["foo"]) - ::Bundler.stub(:load => runtime) + # We just add "i18n" because it is a dependency of Vagrant and + # we know it will be there. + sf = Vagrant::Plugin::StateFile.new(path) + sf.add_plugin("i18n") - expect(subject.installed_specs).to eql(["foo"]) + specs = subject.installed_specs + expect(specs.length).to eql(1) + expect(specs.first.name).to eql("i18n") end end end From eabc0f04fa0bbd0ecd85ce8022d7b9e0d8dbc8f8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 16:57:51 -0800 Subject: [PATCH 26/54] friendly errors if a non-existent gem install is tried --- lib/vagrant/errors.rb | 4 ++++ lib/vagrant/plugin/manager.rb | 2 ++ templates/locales/en.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 938b4a9ac..f78dd9b63 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -428,6 +428,10 @@ module Vagrant error_key(:plugin_gem_error) end + class PluginGemNotFound < VagrantError + error_key(:plugin_gem_not_found) + end + class PluginInstallBadEntryPoint < VagrantError error_key(:plugin_install_bad_entry_point) end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 3986f1b79..7c67d4372 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -40,6 +40,8 @@ module Vagrant @global_file.add_plugin(result.name) result + rescue ::Bundler::GemNotFound + raise Errors::PluginGemNotFound, name: name end # Uninstalls the plugin with the given name. diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 7a4679e00..c8c900375 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -474,6 +474,9 @@ en: manage Vagrant plugins. The output of the errors are shown below: %{output} + plugin_gem_not_found: |- + The plugin '%{name}' could not be installed because it could not + be found. Please double check the name and try again. plugin_install_bad_entry_point: |- Attempting to load the plugin '%{name}' failed, because the entry point doesn't exist. The entry point attempted was From 2fd144611e44c2fe3345d1139a5653ad78addf2e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 17:04:50 -0800 Subject: [PATCH 27/54] Cleaning gems properly only removes them from the local dir --- lib/vagrant/bundler.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index ea9639566..444668672 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -64,6 +64,9 @@ module Vagrant ::Bundler::Installer.install(root, definition, opts) end + # Clean out the unused gems, if we have any + clean(plugins) + definition.specs rescue ::Bundler::VersionConflict => e raise Errors::PluginInstallVersionConflict, @@ -76,8 +79,11 @@ module Vagrant lockfile = "#{gemfile.path}.lock" definition = ::Bundler::Definition.build(gemfile, lockfile, nil) root = File.dirname(gemfile.path) - runtime = ::Bundler::Runtime.new(root, definition) - runtime.clean + + with_isolated_gem do + runtime = ::Bundler::Runtime.new(root, definition) + runtime.clean + end end # Builds a valid Gemfile for use with Bundler given the list of From d368b3cf6294832feb7f6ea330ada082249d16e5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 17:11:24 -0800 Subject: [PATCH 28/54] core: Tolerate syntax errors in ~/.gemrc [GH-2760] --- plugins/commands/plugin/gem_helper.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb index b937570b8..e1a1fff91 100644 --- a/plugins/commands/plugin/gem_helper.rb +++ b/plugins/commands/plugin/gem_helper.rb @@ -29,7 +29,15 @@ module VagrantPlugins # Set a custom configuration to avoid loading ~/.gemrc loads and # /etc/gemrc and so on. - old_config = Gem.configuration + old_config = nil + begin + old_config = Gem.configuration + rescue Psych::SyntaxError + # Just ignore this. This means that the ".gemrc" file has + # an invalid syntax and can't be loaded. We don't care, because + # when we set Gem.configuration to nil later, it'll force a reload + # if it is needed. + end Gem.configuration = NilGemConfig.new # Clear the sources so that installation uses custom sources From a57122243192c970d29c1539ed84a310fd176a2a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 17:12:01 -0800 Subject: [PATCH 29/54] core: tolerate errors in gemrc --- lib/vagrant/bundler.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 444668672..0bec2a058 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -123,7 +123,15 @@ module Vagrant # Set a custom configuration to avoid loading ~/.gemrc loads and # /etc/gemrc and so on. - old_config = Gem.configuration + old_config = nil + begin + old_config = Gem.configuration + rescue Psych::SyntaxError + # Just ignore this. This means that the ".gemrc" file has + # an invalid syntax and can't be loaded. We don't care, because + # when we set Gem.configuration to nil later, it'll force a reload + # if it is needed. + end Gem.configuration = NilGemConfig.new # Use a silent UI so that we have no output From 0c73a5ee051e942985a7de717ee00aa67d481519 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 17:40:38 -0800 Subject: [PATCH 30/54] remove clean on install... doesn't work right now --- lib/vagrant/bundler.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 0bec2a058..9a43c5bbf 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -64,8 +64,8 @@ module Vagrant ::Bundler::Installer.install(root, definition, opts) end - # Clean out the unused gems, if we have any - clean(plugins) + # TODO(mitchellh): clean gems here... for some reason when I put + # it in on install, we get a GemNotFound exception. Gotta investigate. definition.specs rescue ::Bundler::VersionConflict => e From 9dc1307b7c288b37c52f9b147a649a153673cc14 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 17:46:20 -0800 Subject: [PATCH 31/54] commands/plugin: remove --plugin-prerelease --- plugins/commands/plugin/action/install_gem.rb | 2 -- plugins/commands/plugin/command/install.rb | 1 - plugins/commands/plugin/command/mixin_install_opts.rb | 6 +++++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index ac91754e3..82e963d24 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -23,7 +23,6 @@ module VagrantPlugins def call(env) plugin_name = env[:plugin_name] - prerelease = env[:plugin_prerelease] version = env[:plugin_version] # Determine the plugin name we'll look for in the installed set @@ -46,7 +45,6 @@ module VagrantPlugins # Install the gem plugin_name_label = plugin_name - plugin_name_label += ' --prerelease' if prerelease plugin_name_label += " --version '#{version}'" if version env[:ui].info(I18n.t("vagrant.commands.plugin.installing", :name => plugin_name_label)) diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb index 6667fd062..c4f11d739 100644 --- a/plugins/commands/plugin/command/install.rb +++ b/plugins/commands/plugin/command/install.rb @@ -26,7 +26,6 @@ module VagrantPlugins # Install the gem action(Action.action_install, { :plugin_entry_point => options[:entry_point], - :plugin_prerelease => options[:plugin_prerelease], :plugin_version => options[:plugin_version], :plugin_sources => options[:plugin_sources], :plugin_name => argv[0] diff --git a/plugins/commands/plugin/command/mixin_install_opts.rb b/plugins/commands/plugin/command/mixin_install_opts.rb index 96c879968..0b1b0973a 100644 --- a/plugins/commands/plugin/command/mixin_install_opts.rb +++ b/plugins/commands/plugin/command/mixin_install_opts.rb @@ -8,9 +8,13 @@ module VagrantPlugins options[:entry_point] = entry_point end + # @deprecated o.on("--plugin-prerelease", "Allow prerelease versions of this plugin.") do |plugin_prerelease| - options[:plugin_prerelease] = plugin_prerelease + puts "--plugin-prelease is deprecated and will be removed in the next" + puts "version of Vagrant. It has no effect now. Use the '--plugin-version'" + puts "flag to get a specific pre-release version." + puts end o.on("--plugin-source PLUGIN_SOURCE", String, From 8904319beb9b60fbda19dec8a885cc5ed5656bc4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 20:47:02 -0800 Subject: [PATCH 32/54] commands/plugin: install version and entrypoints work --- lib/vagrant/bundler.rb | 14 ++++++++++++-- lib/vagrant/plugin/manager.rb | 17 ++++++++++++----- lib/vagrant/plugin/state_file.rb | 14 +++++++------- plugins/commands/plugin/action/install_gem.rb | 4 +++- test/unit/vagrant/plugin/manager_test.rb | 4 +++- test/unit/vagrant/plugin/state_file_test.rb | 7 +++++++ 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 9a43c5bbf..e44d58f10 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -97,9 +97,19 @@ module Vagrant gemfile.puts(%Q[source "http://gems.hashicorp.com"]) gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) gemfile.puts("group :plugins do") - plugins.each do |plugin| - gemfile.puts(%Q[gem "#{plugin}"]) + + plugins.each do |name, plugin| + version = plugin["gem_version"] + version = nil if version == "" + + opts = {} + if plugin["require"] && plugin["require"] != "" + opts[:require] = plugin["require"] + end + + gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}]) end + gemfile.puts("end") gemfile.close end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 7c67d4372..21672cc1d 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -28,16 +28,23 @@ module Vagrant # # @param [String] name Name of the plugin (gem) # @return [Gem::Specification] - def install_plugin(name) + def install_plugin(name, **opts) + plugins = installed_plugins + plugins[name] = { + "require" => opts[:require], + "gem_version" => opts[:version], + } + result = nil - Vagrant::Bundler.instance.install(installed_plugins.push(name)).each do |spec| + Vagrant::Bundler.instance.install(plugins).each do |spec| next if spec.name != name next if result && result.version >= spec.version result = spec end # Add the plugin to the state file - @global_file.add_plugin(result.name) + @global_file.add_plugin( + result.name, version: opts[:version], require: opts[:require]) result rescue ::Bundler::GemNotFound @@ -58,7 +65,7 @@ module Vagrant # # @return [Array] def installed_plugins - @global_file.installed_plugins.keys + @global_file.installed_plugins end # This returns the list of plugins that are installed as @@ -66,7 +73,7 @@ module Vagrant # # @return [Array] def installed_specs - installed = Set.new(installed_plugins) + installed = Set.new(installed_plugins.keys) # Go through the plugins installed in this environment and # get the latest version of each. diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index a73bedb5a..7263e9ee1 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -27,13 +27,13 @@ module Vagrant # Add a plugin that is installed to the state file. # # @param [String] name The name of the plugin - def add_plugin(name) - if !@data["installed"].has_key?(name) - @data["installed"][name] = { - "ruby_version" => RUBY_VERSION, - "vagrant_version" => Vagrant::VERSION, - } - end + def add_plugin(name, **opts) + @data["installed"][name] = { + "ruby_version" => RUBY_VERSION, + "vagrant_version" => Vagrant::VERSION, + "gem_version" => opts[:version] || "", + "require" => opts[:require] || "", + } save! end diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index 82e963d24..bb5d37423 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -22,6 +22,7 @@ module VagrantPlugins end def call(env) + entrypoint = env[:plugin_entry_point] plugin_name = env[:plugin_name] version = env[:plugin_version] @@ -51,7 +52,8 @@ module VagrantPlugins # TODO: support version, pre-release, custom sources manager = Vagrant::Plugin::Manager.instance - plugin_spec = manager.install_plugin(plugin_name) + plugin_spec = manager.install_plugin( + plugin_name, version: version, require: entrypoint) # Tell the user env[:ui].success(I18n.t("vagrant.commands.plugin.installed", diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index 34fdf66b6..c32c0ce90 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -39,7 +39,9 @@ describe Vagrant::Plugin::Manager do describe "#installed_plugins" do it "has the plugins" do - expect(subject.installed_plugins).to eql(["foo"]) + plugins = subject.installed_plugins + expect(plugins.length).to eql(1) + expect(plugins).to have_key("foo") end end diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index 8e94a9f52..4dc97fdc6 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -32,6 +32,8 @@ describe Vagrant::Plugin::StateFile do expect(plugins["foo"]).to eql({ "ruby_version" => RUBY_VERSION, "vagrant_version" => Vagrant::VERSION, + "gem_version" => "", + "require" => "", }) end @@ -50,6 +52,11 @@ describe Vagrant::Plugin::StateFile do instance = described_class.new(path) expect(instance.installed_plugins.keys).to eql(["foo"]) end + + it "should store metadata" do + subject.add_plugin("foo", version: "1.2.3") + expect(subject.installed_plugins["foo"]["gem_version"]).to eql("1.2.3") + end end context "with an old-style file" do From 5fe29940054184d74f7195e093cd3069a7067c06 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 20:50:25 -0800 Subject: [PATCH 33/54] commands/plugin: convert all actions to use the new classes --- lib/vagrant/plugin/manager.rb | 2 +- plugins/commands/plugin/action.rb | 1 + .../commands/plugin/action/license_plugin.rb | 9 ------ .../commands/plugin/action/list_plugins.rb | 28 ++++--------------- .../plugin/action/plugin_exists_check.rb | 7 ++--- plugins/commands/plugin/command/base.rb | 5 ---- plugins/commands/plugin/plugin.rb | 1 - 7 files changed, 11 insertions(+), 42 deletions(-) diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 21672cc1d..b58e2ffd7 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -63,7 +63,7 @@ module Vagrant # This returns the list of plugins that should be enabled. # - # @return [Array] + # @return [Hash] def installed_plugins @global_file.installed_plugins end diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 04cfa5c9c..89b58e0c2 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -15,6 +15,7 @@ module VagrantPlugins # This middleware sequence licenses paid addons. def self.action_license Vagrant::Action::Builder.new.tap do |b| + b.use PluginExistsCheck b.use LicensePlugin end end diff --git a/plugins/commands/plugin/action/license_plugin.rb b/plugins/commands/plugin/action/license_plugin.rb index 518c1824c..f4566c94c 100644 --- a/plugins/commands/plugin/action/license_plugin.rb +++ b/plugins/commands/plugin/action/license_plugin.rb @@ -17,15 +17,6 @@ module VagrantPlugins end def call(env) - # Get the list of installed plugins according to the state file - installed = env[:plugin_state_file].installed_plugins.keys - - # If the plugin we're trying to license doesn't exist in the - # state file, then it is an error. - if !installed.include?(env[:plugin_name]) - raise Vagrant::Errors::PluginNotFound, :name => env[:plugin_name] - end - # Verify the license file exists license_file = Pathname.new(env[:plugin_license_path]) if !license_file.file? diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 8c173e6b3..76ad5a561 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -16,31 +16,15 @@ module VagrantPlugins end def call(env) - # Get the list of installed plugins according to the state file - installed = env[:plugin_state_file].installed_plugins.keys - - # Go through the plugins installed in this environment and - # get the latest version of each. - installed_map = {} - Vagrant::Plugin::Manager.instance.installed_specs.each do |spec| - # Ignore specs that aren't in our installed list - next if !installed.include?(spec.name) - - # If we already have a newer version in our list of installed, - # then ignore it - next if installed_map.has_key?(spec.name) && - installed_map[spec.name].version >= spec.version - - installed_map[spec.name] = spec - end + specs = Vagrant::Plugin::Manager.instance.installed_specs # Output! - if installed_map.empty? + if specs.empty? env[:ui].info(I18n.t("vagrant.commands.plugin.no_plugins")) - else - installed_map.values.each do |spec| - env[:ui].info "#{spec.name} (#{spec.version})" - end + end + + specs.each do |spec| + env[:ui].info "#{spec.name} (#{spec.version})" end @app.call(env) diff --git a/plugins/commands/plugin/action/plugin_exists_check.rb b/plugins/commands/plugin/action/plugin_exists_check.rb index abe8d43cf..6616abf0e 100644 --- a/plugins/commands/plugin/action/plugin_exists_check.rb +++ b/plugins/commands/plugin/action/plugin_exists_check.rb @@ -1,4 +1,4 @@ -require "set" +require "vagrant/plugin/manager" module VagrantPlugins module CommandPlugin @@ -11,9 +11,8 @@ module VagrantPlugins end def call(env) - # Get the list of installed plugins according to the state file - installed = env[:plugin_state_file].installed_plugins.keys - if !installed.include?(env[:plugin_name]) + installed = Vagrant::Plugin::Manager.instance.installed_specs + if !installed.has_key?(env[:plugin_name]) raise Vagrant::Errors::PluginNotInstalled, name: env[:plugin_name] end diff --git a/plugins/commands/plugin/command/base.rb b/plugins/commands/plugin/command/base.rb index 770d71697..5cf522fd2 100644 --- a/plugins/commands/plugin/command/base.rb +++ b/plugins/commands/plugin/command/base.rb @@ -11,11 +11,6 @@ module VagrantPlugins # @param [Object] callable the Middleware callable # @param [Hash] env Extra environment hash that is merged in. def action(callable, env=nil) - env = { - :gem_helper => GemHelper.new(@env.gems_path), - :plugin_state_file => Vagrant::Plugin::StateFile.new(@env.home_path.join("plugins.json")) - }.merge(env || {}) - @env.action_runner.run(callable, env) end end diff --git a/plugins/commands/plugin/plugin.rb b/plugins/commands/plugin/plugin.rb index a738d01f1..0fd05973b 100644 --- a/plugins/commands/plugin/plugin.rb +++ b/plugins/commands/plugin/plugin.rb @@ -16,6 +16,5 @@ DESC end autoload :Action, File.expand_path("../action", __FILE__) - autoload :GemHelper, File.expand_path("../gem_helper", __FILE__) end end From aeb0d1a4801061ea56e903809dcc639fc0c670b6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 20:57:55 -0800 Subject: [PATCH 34/54] commands/plugin: fix plugin existence middleware, add tests --- .../plugin/action/plugin_exists_check.rb | 2 +- .../plugin/action/plugin_exists_check_test.rb | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 test/unit/plugins/commands/plugin/action/plugin_exists_check_test.rb diff --git a/plugins/commands/plugin/action/plugin_exists_check.rb b/plugins/commands/plugin/action/plugin_exists_check.rb index 6616abf0e..7a369976e 100644 --- a/plugins/commands/plugin/action/plugin_exists_check.rb +++ b/plugins/commands/plugin/action/plugin_exists_check.rb @@ -11,7 +11,7 @@ module VagrantPlugins end def call(env) - installed = Vagrant::Plugin::Manager.instance.installed_specs + installed = Vagrant::Plugin::Manager.instance.installed_plugins if !installed.has_key?(env[:plugin_name]) raise Vagrant::Errors::PluginNotInstalled, name: env[:plugin_name] diff --git a/test/unit/plugins/commands/plugin/action/plugin_exists_check_test.rb b/test/unit/plugins/commands/plugin/action/plugin_exists_check_test.rb new file mode 100644 index 000000000..3a442e9ae --- /dev/null +++ b/test/unit/plugins/commands/plugin/action/plugin_exists_check_test.rb @@ -0,0 +1,31 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe VagrantPlugins::CommandPlugin::Action::PluginExistsCheck do + let(:app) { lambda {} } + let(:env) { {} } + + let(:manager) { double("manager") } + + subject { described_class.new(app, env) } + + before do + Vagrant::Plugin::Manager.stub(instance: manager) + end + + it "should raise an exception if the plugin doesn't exist" do + manager.stub(installed_plugins: { "foo" => {} }) + app.should_not_receive(:call) + + env[:plugin_name] = "bar" + expect { subject.call(env) }. + to raise_error(Vagrant::Errors::PluginNotInstalled) + end + + it "should call the app if the plugin is installed" do + manager.stub(installed_plugins: { "bar" => {} }) + app.should_receive(:call).once.with(env) + + env[:plugin_name] = "bar" + subject.call(env) + end +end From bef7051943c37974c00380b479dcdb7e0d137626 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 21:08:55 -0800 Subject: [PATCH 35/54] commands/plugin: InstallGem tests --- plugins/commands/plugin/action/install_gem.rb | 4 +- .../plugin/action/install_gem_test.rb | 83 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 test/unit/plugins/commands/plugin/action/install_gem_test.rb diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index bb5d37423..c9e6e967a 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -50,11 +50,13 @@ module VagrantPlugins env[:ui].info(I18n.t("vagrant.commands.plugin.installing", :name => plugin_name_label)) - # TODO: support version, pre-release, custom sources manager = Vagrant::Plugin::Manager.instance plugin_spec = manager.install_plugin( plugin_name, version: version, require: entrypoint) + # Record it so we can uninstall if something goes wrong + @installed_plugin_name = plugin_spec.name + # Tell the user env[:ui].success(I18n.t("vagrant.commands.plugin.installed", :name => plugin_spec.name, diff --git a/test/unit/plugins/commands/plugin/action/install_gem_test.rb b/test/unit/plugins/commands/plugin/action/install_gem_test.rb new file mode 100644 index 000000000..918c63cdc --- /dev/null +++ b/test/unit/plugins/commands/plugin/action/install_gem_test.rb @@ -0,0 +1,83 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe VagrantPlugins::CommandPlugin::Action::InstallGem do + let(:app) { lambda { |env| } } + let(:env) {{ + ui: Vagrant::UI::Silent.new + }} + + let(:manager) { double("manager") } + + subject { described_class.new(app, env) } + + before do + Vagrant::Plugin::Manager.stub(instance: manager) + end + + describe "#call" do + it "should install the plugin" do + spec = Gem::Specification.new + manager.should_receive(:install_plugin).with( + "foo", version: nil, require: nil).once.and_return(spec) + + app.should_receive(:call).with(env).once + + env[:plugin_name] = "foo" + subject.call(env) + end + + it "should specify the version if given" do + spec = Gem::Specification.new + manager.should_receive(:install_plugin).with( + "foo", version: "bar", require: nil).once.and_return(spec) + + app.should_receive(:call).with(env).once + + env[:plugin_name] = "foo" + env[:plugin_version] = "bar" + subject.call(env) + end + + it "should specify the entrypoint if given" do + spec = Gem::Specification.new + manager.should_receive(:install_plugin).with( + "foo", version: "bar", require: "baz").once.and_return(spec) + + app.should_receive(:call).with(env).once + + env[:plugin_entry_point] = "baz" + env[:plugin_name] = "foo" + env[:plugin_version] = "bar" + subject.call(env) + end + end + + describe "#recover" do + it "should do nothing by default" do + subject.recover(env) + end + + context "with a successful plugin install" do + let(:action_runner) { double("action_runner") } + + before do + spec = Gem::Specification.new + spec.name = "foo" + manager.stub(install_plugin: spec) + + env[:plugin_name] = "foo" + subject.call(env) + + env[:action_runner] = action_runner + end + + it "should uninstall the plugin" do + action_runner.should_receive(:run).with do |action, newenv| + expect(newenv[:plugin_name]).to eql("foo") + end + + subject.recover(env) + end + end + end +end From e231890e7e2f5df65b2b2c2b90e05774e76e7a95 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 21:10:43 -0800 Subject: [PATCH 36/54] commands/plugin: UninstallPlugin tests --- .../plugin/action/uninstall_plugin_test.rb | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb diff --git a/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb b/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb new file mode 100644 index 000000000..ffe17122e --- /dev/null +++ b/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb @@ -0,0 +1,24 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe VagrantPlugins::CommandPlugin::Action::UninstallPlugin do + let(:app) { lambda { |env| } } + let(:env) {{ + ui: Vagrant::UI::Silent.new, + }} + + let(:manager) { double("manager") } + + subject { described_class.new(app, env) } + + before do + Vagrant::Plugin::Manager.stub(instance: manager) + end + + it "uninstalls the specified plugin" do + manager.should_receive(:uninstall_plugin).with("bar").ordered + app.should_receive(:call).ordered + + env[:plugin_name] = "bar" + subject.call(env) + end +end From 561e65ec72b05f1056624a70c62b8f1d39ca9b75 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 21:16:41 -0800 Subject: [PATCH 37/54] commands/plugin: list shows version constraints and entrypoints --- .../commands/plugin/action/list_plugins.rb | 23 ++++++++++++++++++- templates/locales/en.yml | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 76ad5a561..8a8ecd312 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -16,15 +16,36 @@ module VagrantPlugins end def call(env) - specs = Vagrant::Plugin::Manager.instance.installed_specs + manager = Vagrant::Plugin::Manager.instance + plugins = manager.installed_plugins + specs = manager.installed_specs # Output! if specs.empty? env[:ui].info(I18n.t("vagrant.commands.plugin.no_plugins")) + return @app.call(env) end specs.each do |spec| env[:ui].info "#{spec.name} (#{spec.version})" + + # Grab the plugin. Note that the check for whether it exists + # shouldn't be necessary since installed_specs checks that but + # its nice to be certain. + plugin = plugins[spec.name] + next if !plugin + + if plugin["gem_version"] && plugin["gem_version"] != "" + env[:ui].info(I18n.t( + "vagrant.commands.plugin.plugin_version", + version: plugin["gem_version"])) + end + + if plugin["require"] && plugin["require"] != "" + env[:ui].info(I18n.t( + "vagrant.commands.plugin.plugin_require", + require: plugin["require"])) + end end @app.call(env) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index c8c900375..a80e8cb7f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -919,6 +919,8 @@ en: Installing license for '%{name}'... no_plugins: |- No plugins installed. + plugin_require: " - Custom entrypoint: %{require}" + plugin_version: " - Version Constraint: %{version}" installed: |- Installed the plugin '%{name} (%{version})'! installing: |- From ad0651a29b06c69b0f9018c5236f1d572b512dad Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 21:28:16 -0800 Subject: [PATCH 38/54] website/docs: update docs on `vagrant plugin` --- website/docs/source/v2/cli/plugin.html.md | 36 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/website/docs/source/v2/cli/plugin.html.md b/website/docs/source/v2/cli/plugin.html.md index 6a6d159ac..ee5651a5c 100644 --- a/website/docs/source/v2/cli/plugin.html.md +++ b/website/docs/source/v2/cli/plugin.html.md @@ -28,6 +28,25 @@ repositories, usually [RubyGems](http://rubygems.org). This command will also update a plugin if it is already installed, but you can also use `vagrant plugin update` for that. +This command accepts optional command-line flags: + +* `--entry-point ENTRYPOINT` - By default, installed plugins are loaded + internally by loading an initialization file of the same name as the plugin. + Most of the time, this is correct. If the plugin you're installing has + another entrypoint, this flag can be used to specify it. + +* `--plugin-source SOURCE` - Adds a source from which to fetch a plugin. Note + that this doesn't only affect the single plugin being installed, by all future + plugin as well. This is a limitation of the underlying plugin installer + Vagrant uses. + +* `--plugin-version VERSION` - The version of the plugin to install. By default, + this command will install the latest version. You can constrain the version + using this flag. You can set it to a specific version, such as "1.2.3" or + you can set it to a version contraint, such as "> 1.0.2". You can set it + to a more complex constraint by comma-separating multiple constraints: + "> 1.0.2, < 1.1.0" (don't forget to quote these on the command-line). + # Plugin License **Command: `vagrant plugin license `** @@ -39,7 +58,10 @@ such as the [VMware Fusion provider](/v2/vmware/index.html). **Command: `vagrant plugin list`** -This lists all installed plugins and their respective versions. +This lists all installed plugins and their respective installed versions. +If a version constraint was specified for a plugin when installing it, the +constraint will be listed as well. Other plugin-specific information may +be shown, too. # Plugin Uninstall @@ -50,7 +72,13 @@ plugin will also be uninstalled assuming no other plugin needs them. # Plugin Update -**Command: `vagrant plugin update `** +**Command: `vagrant plugin update []`** -This updates the plugin with the given name. If the plugin isn't already -installed, this will not install it. +This updates the plugins that are installed within Vagrant. If you specified +version constraints when installing the plugin, this command will respect +those constraints. If you want to change a version constraint, re-install +the plugin using `vagrant plugin install`. + +If a name is specified, only that single plugin will be updated. If a +name is specified of a plugin that is not installed, this command will not +install it. From f778d706f7dc311e05d3125d77108590ee18d5bb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 21:37:24 -0800 Subject: [PATCH 39/54] core: set the Bundler UI in initialization --- lib/vagrant/bundler.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index e44d58f10..8bda18f1e 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -18,6 +18,16 @@ module Vagrant def initialize @gem_home = ENV["GEM_HOME"] @gem_path = ENV["GEM_PATH"] + + # Set the Bundler UI to be a silent UI. We have to add the + # `silence` method to it because Bundler UI doesn't have it. + ::Bundler.ui = ::Bundler::UI.new + if !::Bundler.ui.respond_to?(:silence) + ui = ::Bundler.ui + def ui.silence(*args) + yield + end + end end # Initializes Bundler and the various gem paths so that we can begin @@ -38,15 +48,6 @@ module Vagrant ENV["GEM_PATH"] = "#{Vagrant.user_data_path.join("gems")}#{::File::PATH_SEPARATOR}#{@gem_path}" Gem.clear_paths - - # Do some additional Bundler initialization - ::Bundler.ui = ::Bundler::UI.new - if !::Bundler.ui.respond_to?(:silence) - ui = ::Bundler.ui - def ui.silence(*args) - yield - end - end end # Installs the list of plugins. From 576075f1aca1f328fe59ae25e93187bad24a04d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 21:37:39 -0800 Subject: [PATCH 40/54] core: remove "update" => true because its not needed --- lib/vagrant/bundler.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 8bda18f1e..af53d6a5d 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -59,7 +59,6 @@ module Vagrant definition = ::Bundler::Definition.build(gemfile, lockfile, nil) root = File.dirname(gemfile.path) opts = {} - opts["update"] = true with_isolated_gem do ::Bundler::Installer.install(root, definition, opts) From 0117521744711fe2e9e8739a3ad0e1426b5f7760 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 22:33:05 -0800 Subject: [PATCH 41/54] commands/plugin: vagrant update is fancier now (see website docs) --- lib/vagrant/bundler.rb | 57 +++++++++++++------ lib/vagrant/plugin/manager.rb | 5 ++ plugins/commands/plugin/action.rb | 4 +- plugins/commands/plugin/action/update_gems.rb | 51 +++++++++++++++++ plugins/commands/plugin/command/update.rb | 12 +--- templates/locales/en.yml | 8 +++ 6 files changed, 108 insertions(+), 29 deletions(-) create mode 100644 plugins/commands/plugin/action/update_gems.rb diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index af53d6a5d..b9739ffb0 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -52,25 +52,22 @@ module Vagrant # Installs the list of plugins. # + # @param [Hash] plugins # @return [Array] def install(plugins) - gemfile = build_gemfile(plugins) - lockfile = "#{gemfile.path}.lock" - definition = ::Bundler::Definition.build(gemfile, lockfile, nil) - root = File.dirname(gemfile.path) - opts = {} + internal_install(plugins, nil) + end - with_isolated_gem do - ::Bundler::Installer.install(root, definition, opts) - end - - # TODO(mitchellh): clean gems here... for some reason when I put - # it in on install, we get a GemNotFound exception. Gotta investigate. - - definition.specs - rescue ::Bundler::VersionConflict => e - raise Errors::PluginInstallVersionConflict, - conflicts: e.to_s.gsub("Bundler", "Vagrant") + # Update updates the given plugins, or every plugin if none is given. + # + # @param [Hash] plugins + # @param [Array] specific Specific plugin names to update. If + # empty or nil, all plugins will be updated. + def update(plugins, specific) + specific ||= [] + update = true + update = { gems: specific } if !specific.empty? + internal_install(plugins, update) end # Clean removes any unused gems. @@ -86,6 +83,8 @@ module Vagrant end end + protected + # Builds a valid Gemfile for use with Bundler given the list of # plugins. # @@ -115,7 +114,31 @@ module Vagrant end end - protected + # This installs a set of plugins and optionally updates those gems. + # + # @param [Hash] plugins + # @param [Hash, Boolean] update If true, updates all plugins, otherwise + # can be a hash of options. See Bundler.definition. + # @return [Array] + def internal_install(plugins, update) + gemfile = build_gemfile(plugins) + lockfile = "#{gemfile.path}.lock" + definition = ::Bundler::Definition.build(gemfile, lockfile, update) + root = File.dirname(gemfile.path) + opts = {} + + with_isolated_gem do + ::Bundler::Installer.install(root, definition, opts) + end + + # TODO(mitchellh): clean gems here... for some reason when I put + # it in on install, we get a GemNotFound exception. Gotta investigate. + + definition.specs + rescue ::Bundler::VersionConflict => e + raise Errors::PluginInstallVersionConflict, + conflicts: e.to_s.gsub("Bundler", "Vagrant") + end def with_isolated_gem # Remove bundler settings so that Bundler isn't loaded when building diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index b58e2ffd7..9cb016411 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -61,6 +61,11 @@ module Vagrant Vagrant::Bundler.instance.clean(installed_plugins) end + # Updates all or a specific set of plugins. + def update_plugins(specific) + Vagrant::Bundler.instance.update(installed_plugins, specific) + end + # This returns the list of plugins that should be enabled. # # @return [Hash] diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 89b58e0c2..832bc3508 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -38,8 +38,7 @@ module VagrantPlugins # This middleware sequence will update a plugin. def self.action_update Vagrant::Action::Builder.new.tap do |b| - b.use PluginExistsCheck - b.use InstallGem + b.use UpdateGems end end @@ -50,6 +49,7 @@ module VagrantPlugins autoload :ListPlugins, action_root.join("list_plugins") autoload :PluginExistsCheck, action_root.join("plugin_exists_check") autoload :UninstallPlugin, action_root.join("uninstall_plugin") + autoload :UpdateGems, action_root.join("update_gems") end end end diff --git a/plugins/commands/plugin/action/update_gems.rb b/plugins/commands/plugin/action/update_gems.rb new file mode 100644 index 000000000..57414273f --- /dev/null +++ b/plugins/commands/plugin/action/update_gems.rb @@ -0,0 +1,51 @@ +require "vagrant/plugin/manager" + +module VagrantPlugins + module CommandPlugin + module Action + class UpdateGems + def initialize(app, env) + @app = app + end + + def call(env) + names = env[:plugin_name] || [] + + if names.empty? + env[:ui].info(I18n.t("vagrant.commands.plugin.updating")) + else + env[:ui].info(I18n.t("vagrant.commands.plugin.updating_specific", + names: names.join(", "))) + end + + manager = Vagrant::Plugin::Manager.instance + installed_specs = manager.installed_specs + new_specs = manager.update_plugins(names) + + updated = {} + installed_specs.each do |ispec| + new_specs.each do |uspec| + next if uspec.name != ispec.name + next if ispec.version >= uspec.version + next if updated[uspec.name] && updated[uspec.name].version >= uspec.version + + updated[uspec.name] = uspec + end + end + + if updated.empty? + env[:ui].success(I18n.t("vagrant.commands.plugin.up_to_date")) + end + + updated.values.each do |spec| + env[:ui].success(I18n.t("vagrant.commands.plugin.updated", + name: spec.name, version: spec.version.to_s)) + end + + # Continue + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/command/update.rb b/plugins/commands/plugin/command/update.rb index ca4bbb746..a0b832238 100644 --- a/plugins/commands/plugin/command/update.rb +++ b/plugins/commands/plugin/command/update.rb @@ -10,26 +10,18 @@ module VagrantPlugins include MixinInstallOpts def execute - options = {} - opts = OptionParser.new do |o| - o.banner = "Usage: vagrant plugin update [-h]" + o.banner = "Usage: vagrant plugin update [names...] [-h]" o.separator "" - build_install_opts(o, options) end # Parse the options argv = parse_options(opts) return if !argv - raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 1 # Update the gem action(Action.action_update, { - :plugin_entry_point => options[:entry_point], - :plugin_prerelease => options[:plugin_prerelease], - :plugin_version => options[:plugin_version], - :plugin_sources => options[:plugin_sources], - :plugin_name => argv[0] + :plugin_name => argv, }) # Success, exit status 0 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index a80e8cb7f..b0c3a4b82 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -927,6 +927,14 @@ en: Installing the '%{name}' plugin. This can take a few minutes... uninstalling: |- Uninstalling the '%{name}' plugin... + up_to_date: |- + All plugins are up to date. + updated: |- + Updated '%{name}' to version '%{version}'! + updating: |- + Updating installed plugins... + updating_specific: |- + Updating plugins: %{names}. This may take a few minutes... post_install: |- Post install message from the '%{name}' plugin: From 35d711c91b444a55f9b2fe8cd44815d8548fafec Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 22:37:29 -0800 Subject: [PATCH 42/54] commands/plugin: add tests for UpdateGems --- .../plugin/action/update_gems_test.rb | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/unit/plugins/commands/plugin/action/update_gems_test.rb diff --git a/test/unit/plugins/commands/plugin/action/update_gems_test.rb b/test/unit/plugins/commands/plugin/action/update_gems_test.rb new file mode 100644 index 000000000..f92b26990 --- /dev/null +++ b/test/unit/plugins/commands/plugin/action/update_gems_test.rb @@ -0,0 +1,33 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe VagrantPlugins::CommandPlugin::Action::UpdateGems do + let(:app) { lambda { |env| } } + let(:env) {{ + ui: Vagrant::UI::Silent.new + }} + + let(:manager) { double("manager") } + + subject { described_class.new(app, env) } + + before do + Vagrant::Plugin::Manager.stub(instance: manager) + manager.stub(installed_specs: []) + end + + describe "#call" do + it "should update all plugins if none are specified" do + manager.should_receive(:update_plugins).with([]).once.and_return([]) + app.should_receive(:call).with(env).once + subject.call(env) + end + + it "should update specified plugins" do + manager.should_receive(:update_plugins).with(["foo"]).once.and_return([]) + app.should_receive(:call).with(env).once + + env[:plugin_name] = ["foo"] + subject.call(env) + end + end +end From 84ecca5c154b9cf34cf87fc47a8e971eb1a08499 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 22:50:55 -0800 Subject: [PATCH 43/54] core: statefile can track sources, not sure if we'll use it though --- lib/vagrant/plugin/state_file.rb | 26 +++++++++++++++++++++ test/unit/vagrant/plugin/state_file_test.rb | 23 ++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index 7263e9ee1..d8d7ecb0d 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -38,6 +38,15 @@ module Vagrant save! end + # Adds a RubyGems index source to look up gems. + # + # @param [String] url URL of the source. + def add_source(url) + @data["sources"] ||= [] + @data["sources"] << url if !@data["sources"].include?(url) + save! + end + # This returns a hash of installed plugins according to the state # file. Note that this may _not_ directly match over to actually # installed gems. @@ -55,6 +64,23 @@ module Vagrant save! end + # Remove a source for RubyGems. + # + # @param [String] url URL of the source + def remove_source(url) + @data["sources"] ||= [] + @data["sources"].delete(url) + save! + end + + # Returns the list of RubyGems sources that will be searched for + # plugins. + # + # @return [Array] + def sources + @data["sources"] || [] + end + # This saves the state back into the state file. def save! @path.open("w+") do |f| diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index 4dc97fdc6..993d2c415 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -57,6 +57,29 @@ describe Vagrant::Plugin::StateFile do subject.add_plugin("foo", version: "1.2.3") expect(subject.installed_plugins["foo"]["gem_version"]).to eql("1.2.3") end + + describe "sources" do + it "should have no sources" do + expect(subject.sources).to be_empty + end + + it "should add sources" do + subject.add_source("foo") + expect(subject.sources).to eql(["foo"]) + end + + it "should de-dup sources" do + subject.add_source("foo") + subject.add_source("foo") + expect(subject.sources).to eql(["foo"]) + end + + it "can remove sources" do + subject.add_source("foo") + subject.remove_source("foo") + expect(subject.sources).to be_empty + end + end end context "with an old-style file" do From f612ec754993fd0fd66885e9845a7955ac78d27f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 23:13:49 -0800 Subject: [PATCH 44/54] core: support plugin sources, and mask Bundler errors --- lib/vagrant/bundler.rb | 15 +++- lib/vagrant/errors.rb | 4 ++ lib/vagrant/plugin/manager.rb | 13 +++- lib/vagrant/plugin/state_file.rb | 1 + plugins/commands/plugin/action/install_gem.rb | 6 +- templates/locales/en.yml | 7 ++ .../plugin/action/install_gem_test.rb | 18 ++++- test/unit/vagrant/plugin/manager_test.rb | 72 ++++++++++++++++++- test/unit/vagrant/plugin/state_file_test.rb | 1 + 9 files changed, 128 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index b9739ffb0..e78ff0180 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -1,4 +1,5 @@ require "pathname" +require "set" require "tempfile" require "bundler" @@ -94,9 +95,15 @@ module Vagrant f.tap do |gemfile| gemfile.puts(%Q[source "https://rubygems.org"]) gemfile.puts(%Q[source "http://gems.hashicorp.com"]) - gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) - gemfile.puts("group :plugins do") + sources = plugins.values.map { |p| p["sources"] }.flatten.compact.uniq + sources.each do |source| + next if source == "" + gemfile.puts(%Q[source "#{source}"]) + end + gemfile.puts(%Q[gem "vagrant", "= #{Vagrant::VERSION}"]) + + gemfile.puts("group :plugins do") plugins.each do |name, plugin| version = plugin["gem_version"] version = nil if version == "" @@ -108,9 +115,11 @@ module Vagrant gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}]) end - gemfile.puts("end") + gemfile.close + + puts File.read(gemfile.path) end end diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index f78dd9b63..ce21aef7b 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -168,6 +168,10 @@ module Vagrant error_key(:failed, "vagrant.actions.box.verify") end + class BundlerError < VagrantError + error_key(:bundler_error) + end + class CFEngineBootstrapFailed < VagrantError error_key(:cfengine_bootstrap_failed) end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 9cb016411..db37423ba 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -33,6 +33,7 @@ module Vagrant plugins[name] = { "require" => opts[:require], "gem_version" => opts[:version], + "sources" => opts[:sources], } result = nil @@ -44,11 +45,17 @@ module Vagrant # Add the plugin to the state file @global_file.add_plugin( - result.name, version: opts[:version], require: opts[:require]) + result.name, + version: opts[:version], + require: opts[:require], + sources: opts[:sources], + ) result rescue ::Bundler::GemNotFound raise Errors::PluginGemNotFound, name: name + rescue ::Bundler::BundlerError => e + raise Errors::BundlerError, message: e.to_s end # Uninstalls the plugin with the given name. @@ -59,11 +66,15 @@ module Vagrant # Clean the environment, removing any old plugins Vagrant::Bundler.instance.clean(installed_plugins) + rescue ::Bundler::BundlerError => e + raise Errors::BundlerError, message: e.to_s end # Updates all or a specific set of plugins. def update_plugins(specific) Vagrant::Bundler.instance.update(installed_plugins, specific) + rescue ::Bundler::BundlerError => e + raise Errors::BundlerError, message: e.to_s end # This returns the list of plugins that should be enabled. diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index d8d7ecb0d..b9161672f 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -33,6 +33,7 @@ module Vagrant "vagrant_version" => Vagrant::VERSION, "gem_version" => opts[:version] || "", "require" => opts[:require] || "", + "sources" => opts[:sources] || [], } save! diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index c9e6e967a..a6295ad6b 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -24,6 +24,7 @@ module VagrantPlugins def call(env) entrypoint = env[:plugin_entry_point] plugin_name = env[:plugin_name] + sources = env[:plugin_sources] version = env[:plugin_version] # Determine the plugin name we'll look for in the installed set @@ -52,7 +53,10 @@ module VagrantPlugins manager = Vagrant::Plugin::Manager.instance plugin_spec = manager.install_plugin( - plugin_name, version: version, require: entrypoint) + plugin_name, + version: version, + require: entrypoint, + sources: sources,) # Record it so we can uninstall if something goes wrong @installed_plugin_name = plugin_spec.name diff --git a/templates/locales/en.yml b/templates/locales/en.yml index b0c3a4b82..384d1220d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -254,6 +254,13 @@ en: The box '%{name}' is still stored on disk in the Vagrant 1.0.x format. This box must be upgraded in order to work properly with this version of Vagrant. + bundler_error: |- + Bundler, the underlying system Vagrant uses to install plugins, + reported an error. The error is shown below. These errors are usually + caused by misconfigured plugin installations or transient network + issues. The error from Bundler is: + + %{message} cfengine_bootstrap_failed: |- Failed to bootstrap CFEngine. Please see the output above to see what went wrong and address the issue. diff --git a/test/unit/plugins/commands/plugin/action/install_gem_test.rb b/test/unit/plugins/commands/plugin/action/install_gem_test.rb index 918c63cdc..5b046b434 100644 --- a/test/unit/plugins/commands/plugin/action/install_gem_test.rb +++ b/test/unit/plugins/commands/plugin/action/install_gem_test.rb @@ -18,7 +18,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should install the plugin" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: nil, require: nil).once.and_return(spec) + "foo", version: nil, require: nil, sources: nil).once.and_return(spec) app.should_receive(:call).with(env).once @@ -29,7 +29,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the version if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: "bar", require: nil).once.and_return(spec) + "foo", version: "bar", require: nil, sources: nil).once.and_return(spec) app.should_receive(:call).with(env).once @@ -41,7 +41,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the entrypoint if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: "bar", require: "baz").once.and_return(spec) + "foo", version: "bar", require: "baz", sources: nil).once.and_return(spec) app.should_receive(:call).with(env).once @@ -50,6 +50,18 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do env[:plugin_version] = "bar" subject.call(env) end + + it "should specify the sources if given" do + spec = Gem::Specification.new + manager.should_receive(:install_plugin).with( + "foo", version: nil, require: nil, sources: ["foo"]).once.and_return(spec) + + app.should_receive(:call).with(env).once + + env[:plugin_name] = "foo" + env[:plugin_sources] = ["foo"] + subject.call(env) + end end describe "#recover" do diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index c32c0ce90..3d6e728b4 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -16,12 +16,83 @@ describe Vagrant::Plugin::Manager do Pathname.new(p) end + let(:bundler) { double("bundler") } + after do path.unlink if path.file? end + before do + Vagrant::Bundler.stub(instance: bundler) + end + subject { described_class.new(path) } + describe "#install_plugin" do + it "installs the plugin and adds it to the state file" do + specs = Array.new(5) { Gem::Specification.new } + specs[3].name = "foo" + bundler.should_receive(:install).once.with do |plugins| + expect(plugins).to have_key("foo") + end.and_return(specs) + + result = subject.install_plugin("foo") + + # It should return the spec of the installed plugin + expect(result).to eql(specs[3]) + + # It should've added the plugin to the state + expect(subject.installed_plugins).to have_key("foo") + end + + it "masks GemNotFound with our error" do + bundler.should_receive(:install).and_raise(Bundler::GemNotFound) + + expect { subject.install_plugin("foo") }. + to raise_error(Vagrant::Errors::PluginGemNotFound) + end + + it "masks bundler errors with our own error" do + bundler.should_receive(:install).and_raise(Bundler::InstallError) + + expect { subject.install_plugin("foo") }. + to raise_error(Vagrant::Errors::BundlerError) + end + end + + describe "#uninstall_plugin" do + it "removes the plugin from the state" do + sf = Vagrant::Plugin::StateFile.new(path) + sf.add_plugin("foo") + + # Sanity + expect(subject.installed_plugins).to have_key("foo") + + # Test + bundler.should_receive(:clean).once.with({}) + + # Remove it + subject.uninstall_plugin("foo") + expect(subject.installed_plugins).to_not have_key("foo") + end + + it "masks bundler errors with our own error" do + bundler.should_receive(:clean).and_raise(Bundler::InstallError) + + expect { subject.uninstall_plugin("foo") }. + to raise_error(Vagrant::Errors::BundlerError) + end + end + + describe "#update_plugins" do + it "masks bundler errors with our own error" do + bundler.should_receive(:update).and_raise(Bundler::InstallError) + + expect { subject.update_plugins([]) }. + to raise_error(Vagrant::Errors::BundlerError) + end + end + context "without state" do describe "#installed_plugins" do it "is empty initially" do @@ -30,7 +101,6 @@ describe Vagrant::Plugin::Manager do end end - context "with state" do before do sf = Vagrant::Plugin::StateFile.new(path) diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index 993d2c415..6b37198ef 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -34,6 +34,7 @@ describe Vagrant::Plugin::StateFile do "vagrant_version" => Vagrant::VERSION, "gem_version" => "", "require" => "", + "sources" => [], }) end From 677275e43c2a49fff684c7b87a9a130dad5c4e21 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 5 Jan 2014 23:14:43 -0800 Subject: [PATCH 45/54] core: whoops, don't print Gemfile --- lib/vagrant/bundler.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index e78ff0180..184064cf3 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -118,8 +118,6 @@ module Vagrant gemfile.puts("end") gemfile.close - - puts File.read(gemfile.path) end end From 3f9fb2ef03a78bc9177a52d4629a5e80e6e42b2e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Jan 2014 09:27:26 -0800 Subject: [PATCH 46/54] core: reset the specification lookup when isolating gems --- lib/vagrant/bundler.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 184064cf3..28810d48c 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -161,7 +161,10 @@ module Vagrant # Clear paths so that it reads the new GEM_HOME setting Gem.paths = ENV - # Set a custom configuration to avoid loading ~/.gemrc loads and + # Reset the all specs override that Bundler does + old_all = Gem::Specification._all + Gem::Specification.all = nil + # /etc/gemrc and so on. old_config = nil begin @@ -185,6 +188,7 @@ module Vagrant Gem.configuration = old_config Gem.paths = ENV + Gem::Specification.all = old_all end # This is pretty hacky but it is a custom implementation of From 86cab61c275ae52a89d649f53a58185b699c5de9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Jan 2014 09:27:37 -0800 Subject: [PATCH 47/54] commands/plugin: support installing from file --- lib/vagrant/bundler.rb | 53 +++++++++++++++++++ lib/vagrant/plugin/manager.rb | 23 ++++++-- plugins/commands/plugin/action/install_gem.rb | 31 ++--------- plugins/commands/plugin/command/install.rb | 9 +++- 4 files changed, 82 insertions(+), 34 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 28810d48c..7a903a2cc 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -1,3 +1,4 @@ +require "monitor" require "pathname" require "set" require "tempfile" @@ -17,6 +18,8 @@ module Vagrant end def initialize + @monitor = Monitor.new + @gem_home = ENV["GEM_HOME"] @gem_path = ENV["GEM_PATH"] @@ -59,6 +62,41 @@ module Vagrant internal_install(plugins, nil) end + # Installs a local '*.gem' file so that Bundler can find it. + # + # @param [String] path Path to a local gem file. + # @return [Gem::Specification] + def install_local(path) + # We have to do this load here because this file can be loaded + # before RubyGems is actually loaded. + require "rubygems/dependency_installer" + begin + require "rubygems/format" + rescue LoadError + # rubygems 2.x + end + + # If we're installing from a gem file, determine the name + # based on the spec in the file. + pkg = if defined?(Gem::Format) + # RubyGems 1.x + Gem::Format.from_file_by_path(path) + else + # RubyGems 2.x + Gem::Package.new(path) + end + + # Install the gem manually. If the gem exists locally, then + # Bundler shouldn't attempt to get it remotely. + with_isolated_gem do + installer = Gem::DependencyInstaller.new( + :document => [], :prerelease => false) + installer.install(path, "= #{pkg.spec.version}") + end + + pkg.spec + end + # Update updates the given plugins, or every plugin if none is given. # # @param [Hash] plugins @@ -84,6 +122,21 @@ module Vagrant end end + # During the duration of the yielded block, Bundler loud output + # is enabled. + def verbose + @monitor.synchronize do + begin + old_ui = ::Bundler.ui + require 'bundler/vendored_thor' + ::Bundler.ui = ::Bundler::UI::Shell.new + yield + ensure + ::Bundler.ui = old_ui + end + end + end + protected # Builds a valid Gemfile for use with Bundler given the list of diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index db37423ba..acb2352d1 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -29,6 +29,13 @@ module Vagrant # @param [String] name Name of the plugin (gem) # @return [Gem::Specification] def install_plugin(name, **opts) + if name =~ /\.gem$/ + # If this is a gem file, then we install that gem locally. + local_spec = Vagrant::Bundler.instance.install_local(name) + name = local_spec.name + opts[:version] = "= #{local_spec.version}" + end + plugins = installed_plugins plugins[name] = { "require" => opts[:require], @@ -37,10 +44,18 @@ module Vagrant } result = nil - Vagrant::Bundler.instance.install(plugins).each do |spec| - next if spec.name != name - next if result && result.version >= spec.version - result = spec + install_lambda = lambda do + Vagrant::Bundler.instance.install(plugins).each do |spec| + next if spec.name != name + next if result && result.version >= spec.version + result = spec + end + end + + if opts[:verbose] + Vagrant::Bundler.instance.verbose(&install_lambda) + else + install_lambda.call end # Add the plugin to the state file diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index a6295ad6b..9f553a0ac 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -1,12 +1,3 @@ -require "rubygems" -require "rubygems/dependency_installer" - -begin - require "rubygems/format" -rescue LoadError - # rubygems 2.x -end - require "log4r" require "vagrant/plugin/manager" @@ -27,24 +18,6 @@ module VagrantPlugins sources = env[:plugin_sources] version = env[:plugin_version] - # Determine the plugin name we'll look for in the installed set - # in order to determine the version and all that. - find_plugin_name = plugin_name - if plugin_name =~ /\.gem$/ - # If we're installing from a gem file, determine the name - # based on the spec in the file. - pkg = if defined?(Gem::Format) - # RubyGems 1.x - Gem::Format.from_file_by_path(plugin_name) - else - # RubyGems 2.x - Gem::Package.new(plugin_name) - end - - find_plugin_name = pkg.spec.name - version = pkg.spec.version - end - # Install the gem plugin_name_label = plugin_name plugin_name_label += " --version '#{version}'" if version @@ -56,7 +29,9 @@ module VagrantPlugins plugin_name, version: version, require: entrypoint, - sources: sources,) + sources: sources, + verbose: !!env[:plugin_verbose], + ) # Record it so we can uninstall if something goes wrong @installed_plugin_name = plugin_spec.name diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb index c4f11d739..b38a08e25 100644 --- a/plugins/commands/plugin/command/install.rb +++ b/plugins/commands/plugin/command/install.rb @@ -10,12 +10,16 @@ module VagrantPlugins include MixinInstallOpts def execute - options = {} + options = { verbose: false } opts = OptionParser.new do |o| o.banner = "Usage: vagrant plugin install [-h]" o.separator "" build_install_opts(o, options) + + o.on("--verbose", "Enable verbose output for plugin installation") do |v| + options[:verbose] = v + end end # Parse the options @@ -28,7 +32,8 @@ module VagrantPlugins :plugin_entry_point => options[:entry_point], :plugin_version => options[:plugin_version], :plugin_sources => options[:plugin_sources], - :plugin_name => argv[0] + :plugin_name => argv[0], + :plugin_verbose => options[:verbose] }) # Success, exit status 0 From 5197d3d86fc0778b789b96b51d04e8d7cf816b2a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Jan 2014 10:43:20 -0800 Subject: [PATCH 48/54] core: generate bogus gemfile so that a random lockfile isn't loaded --- lib/vagrant/bundler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 7a903a2cc..1773081de 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -205,7 +205,7 @@ module Vagrant # native extensions because it causes all sorts of problems. old_rubyopt = ENV["RUBYOPT"] old_gemfile = ENV["BUNDLE_GEMFILE"] - ENV["BUNDLE_GEMFILE"] = nil + ENV["BUNDLE_GEMFILE"] = Tempfile.new("vagrant-gemfile").path ENV["RUBYOPT"] = (ENV["RUBYOPT"] || "").gsub(/-rbundler\/setup\s*/, "") # Set the GEM_HOME so gems are installed only to our local gem dir From 3cefcda1e3bcdb2f6cc2f56edfef7630e3cb5ba2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Jan 2014 10:52:14 -0800 Subject: [PATCH 49/54] core: when installig local plugins, don't fetch remote --- lib/vagrant/bundler.rb | 7 ++++--- lib/vagrant/plugin/manager.rb | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 1773081de..92a06fa0c 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -58,8 +58,8 @@ module Vagrant # # @param [Hash] plugins # @return [Array] - def install(plugins) - internal_install(plugins, nil) + def install(plugins, local=false) + internal_install(plugins, nil, local: local) end # Installs a local '*.gem' file so that Bundler can find it. @@ -180,12 +180,13 @@ module Vagrant # @param [Hash, Boolean] update If true, updates all plugins, otherwise # can be a hash of options. See Bundler.definition. # @return [Array] - def internal_install(plugins, update) + def internal_install(plugins, update, **extra) gemfile = build_gemfile(plugins) lockfile = "#{gemfile.path}.lock" definition = ::Bundler::Definition.build(gemfile, lockfile, update) root = File.dirname(gemfile.path) opts = {} + opts["local"] = true if extra[:local] with_isolated_gem do ::Bundler::Installer.install(root, definition, opts) diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index acb2352d1..5b708c88a 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -29,11 +29,13 @@ module Vagrant # @param [String] name Name of the plugin (gem) # @return [Gem::Specification] def install_plugin(name, **opts) + local = false if name =~ /\.gem$/ # If this is a gem file, then we install that gem locally. local_spec = Vagrant::Bundler.instance.install_local(name) name = local_spec.name opts[:version] = "= #{local_spec.version}" + local = true end plugins = installed_plugins @@ -45,7 +47,7 @@ module Vagrant result = nil install_lambda = lambda do - Vagrant::Bundler.instance.install(plugins).each do |spec| + Vagrant::Bundler.instance.install(plugins, local).each do |spec| next if spec.name != name next if result && result.version >= spec.version result = spec From 198e1427947b042d9bca2ddbea5054a6bf9445fd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 6 Jan 2014 10:55:34 -0800 Subject: [PATCH 50/54] commands/plugin: add more tests for installgem --- .../plugins/commands/plugin/action/install_gem_test.rb | 8 ++++---- test/unit/vagrant/plugin/manager_test.rb | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/unit/plugins/commands/plugin/action/install_gem_test.rb b/test/unit/plugins/commands/plugin/action/install_gem_test.rb index 5b046b434..df5362555 100644 --- a/test/unit/plugins/commands/plugin/action/install_gem_test.rb +++ b/test/unit/plugins/commands/plugin/action/install_gem_test.rb @@ -18,7 +18,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should install the plugin" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: nil, require: nil, sources: nil).once.and_return(spec) + "foo", version: nil, require: nil, sources: nil, verbose: false).once.and_return(spec) app.should_receive(:call).with(env).once @@ -29,7 +29,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the version if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: "bar", require: nil, sources: nil).once.and_return(spec) + "foo", version: "bar", require: nil, sources: nil, verbose: false).once.and_return(spec) app.should_receive(:call).with(env).once @@ -41,7 +41,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the entrypoint if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: "bar", require: "baz", sources: nil).once.and_return(spec) + "foo", version: "bar", require: "baz", sources: nil, verbose: false).once.and_return(spec) app.should_receive(:call).with(env).once @@ -54,7 +54,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should specify the sources if given" do spec = Gem::Specification.new manager.should_receive(:install_plugin).with( - "foo", version: nil, require: nil, sources: ["foo"]).once.and_return(spec) + "foo", version: nil, require: nil, sources: ["foo"], verbose: false).once.and_return(spec) app.should_receive(:call).with(env).once diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index 3d6e728b4..d44127804 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -32,8 +32,9 @@ describe Vagrant::Plugin::Manager do it "installs the plugin and adds it to the state file" do specs = Array.new(5) { Gem::Specification.new } specs[3].name = "foo" - bundler.should_receive(:install).once.with do |plugins| + bundler.should_receive(:install).once.with do |plugins, local| expect(plugins).to have_key("foo") + expect(local).to be_false end.and_return(specs) result = subject.install_plugin("foo") From b353865da1b7d1b5ffd47a0f8207e602f903eadd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 10:13:30 -0800 Subject: [PATCH 51/54] core: specific versions "0.1.0" don't equate to "= 0.1.0" --- lib/vagrant/plugin/manager.rb | 4 +++ test/unit/vagrant/plugin/manager_test.rb | 40 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 5b708c88a..b36f0b264 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -60,6 +60,10 @@ module Vagrant install_lambda.call end + # If the version constraint is just a specific version, don't + # store the constraint. + opts.delete(:version) if opts[:version] && opts[:version] =~ /^\d/ + # Add the plugin to the state file @global_file.add_plugin( result.name, diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index d44127804..70d6dbb44 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -59,6 +59,46 @@ describe Vagrant::Plugin::Manager do expect { subject.install_plugin("foo") }. to raise_error(Vagrant::Errors::BundlerError) end + + describe "installation options" do + let(:specs) do + specs = Array.new(5) { Gem::Specification.new } + specs[3].name = "foo" + specs + end + + before do + bundler.stub(:install).and_return(specs) + end + + it "installs a version with constraints" do + bundler.should_receive(:install).once.with do |plugins, local| + expect(plugins).to have_key("foo") + expect(plugins["foo"]["gem_version"]).to eql(">= 0.1.0") + expect(local).to be_false + end.and_return(specs) + + subject.install_plugin("foo", version: ">= 0.1.0") + + plugins = subject.installed_plugins + expect(plugins).to have_key("foo") + expect(plugins["foo"]["gem_version"]).to eql(">= 0.1.0") + end + + it "installs with an exact version but doesn't constrain" do + bundler.should_receive(:install).once.with do |plugins, local| + expect(plugins).to have_key("foo") + expect(plugins["foo"]["gem_version"]).to eql("0.1.0") + expect(local).to be_false + end.and_return(specs) + + subject.install_plugin("foo", version: "0.1.0") + + plugins = subject.installed_plugins + expect(plugins).to have_key("foo") + expect(plugins["foo"]["gem_version"]).to eql("") + end + end end describe "#uninstall_plugin" do From 582e1096e47d0535d7f969057cdcfdb93c96fc3c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 10:50:10 -0800 Subject: [PATCH 52/54] Allow forcing plugins with VAGRANT_FORCE_PLUGINS --- lib/vagrant/pre-rubygems.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/vagrant/pre-rubygems.rb b/lib/vagrant/pre-rubygems.rb index ab381f431..9cea06038 100644 --- a/lib/vagrant/pre-rubygems.rb +++ b/lib/vagrant/pre-rubygems.rb @@ -5,10 +5,17 @@ if defined?(Bundler) require "bundler/shared_helpers" if Bundler::SharedHelpers.in_bundle? - puts "Vagrant appears to be running in a Bundler environment. Plugins" - puts "will not be loaded and plugin commands are disabled." - puts - ENV["VAGRANT_NO_PLUGINS"] = "1" + if ENV["VAGRANT_FORCE_PLUGINS"] + puts "Vagrant appears to be running in a Bundler environment. Normally," + puts "plugins would not be loaded, but VAGRANT_FORCE_PLUGINS is enabled," + puts "so they will be." + puts + else + puts "Vagrant appears to be running in a Bundler environment. Plugins" + puts "will not be loaded and plugin commands are disabled." + puts + ENV["VAGRANT_NO_PLUGINS"] = "1" + end end end From 79602ab37b160f0cef8b7eafe85b61eeb17739e0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 10:51:01 -0800 Subject: [PATCH 53/54] update vagrant-spec config --- vagrant-spec.config.example.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vagrant-spec.config.example.rb b/vagrant-spec.config.example.rb index 59a39520d..dd0dbfe36 100644 --- a/vagrant-spec.config.example.rb +++ b/vagrant-spec.config.example.rb @@ -1,3 +1,5 @@ +ENV["VAGRANT_FORCE_PLUGINS"] = "1" + require_relative "test/acceptance/base" Vagrant::Spec::Acceptance.configure do |c| From 4f623f642235f923594451180451837402fa43a9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Jan 2014 10:52:07 -0800 Subject: [PATCH 54/54] Remove temporary line --- lib/vagrant.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 263234301..fcb102d5a 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -1,8 +1,6 @@ # This file is load before RubyGems are loaded, and allow us to actually # resolve plugin dependencies and load the proper versions of everything. -ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = "/Applications/Vagrant/embedded" - require "vagrant/shared_helpers" if Vagrant.plugins_enabled? && !defined?(Bundler)