Merge pull request #2769 from mitchellh/f-bundlerize
Plugin dependency management revamp This is a huge revamp of how plugin dependency management is done. To understand the changes here, a brief history lesson is in order: Since Vagrant 1.1, plugins have been loaded as RubyGems. Once Vagrant was loaded, it would iterate through a list of installed plugins, and `require` that plugin. This mostly worked okay. But the devil is in the details, and the edge cases were _really_ bad. In addition to the edge cases (mentioned below), building things like updaters, version constraints (">= 1.0", "< 1.1"), etc. all had to be done manually. This seemed silly, since RubyGems itself (and Bundler) do these sort of things for you. Why reinvent the wheel? As for edge cases: the primary edge case is that since the dependencies of Vagrant and its respective plugins weren't resolved as a whole, you can run into cases where plugin installation succeeded, but plugin loading failed because Vagrant already loaded a common dependency with the wrong version. An example explains this best: * Vagrant depends on "A >= 1.0, < 1.2" * vagrant-plugin depends on "A = 1.1" * When you run Vagrant, it loads the latest possible matching dependencies, so it would load A 1.2 * When Vagrant loads vagrant-plugin, it can't load, because A 1.2 is active, so A 1.1 can't be loaded. The error above should never happen: the versions available for A should satisfy both Vagrant and vagrant-plugin (by loading v1.1 for both). With this new branch, all plugin installation, dependency resolution, updating, etc. is managed by [Bundler](http://gembundler.com). This has yielded numerous benefits: * Vagrant now resolves dependencies before Vagrant is even loaded. This ensures that all plugins will be able to load. No more conflicts at run-time. * Conflicts are detected at `vagrant plugin install` time. This means that if there would be a crash if that plugin were to load, the plugin won't even install and a human-friendly error is shown to the end user. * `vagrant plugin install` now accepts complex version constraints such as "~> 1.0.0" or ">= 1.0, < 1.1". Vagrant stores these constraints for updating, which leads to the next point. * `vagrant plugin update` without arguments now updates all installed plugins, respecting the constraints specified by `vagrant plugin install`. * `vagrant plugin update NAME` will only update that gem (still respecting constraints). * Internally, there are a lot more unit tests. /cc @phinze :) The goal of this branch was to replace the _existing_ system and functionality with Bundler-ized management. It did not introduce any new features except where they naturally fell into place (version constraints). However, with this new system, many new possibilities are also available: * Vagrant environment local plugins (i.e. a Gemfile but for a specific Vagrant environment). * Plugin installation from git I'm sure those will be pursued at some point in the future. This fixes: #2612, #2406, #2428
This commit is contained in:
commit
ba85627c21
78
bin/vagrant
78
bin/vagrant
|
@ -13,6 +13,40 @@ if idx = argv.index("--")
|
|||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# Set logging level to `debug`. This is done before loading 'vagrant', as it
|
||||
# sets up the logging system.
|
||||
if argv.include?("--debug")
|
||||
|
@ -20,6 +54,14 @@ 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,
|
||||
# then also initialize the paths to the plugins.
|
||||
require "bundler"
|
||||
Bundler.setup
|
||||
|
||||
require 'log4r'
|
||||
require 'vagrant'
|
||||
require 'vagrant/cli'
|
||||
|
@ -72,27 +114,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
|
||||
|
||||
# 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
|
||||
|
@ -105,21 +126,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
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
require 'log4r'
|
||||
# This file is load before RubyGems are loaded, and allow us to actually
|
||||
# resolve plugin dependencies and load the proper versions of everything.
|
||||
|
||||
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."
|
||||
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
|
||||
|
@ -66,6 +78,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
|
||||
|
@ -118,12 +131,6 @@ module Vagrant
|
|||
!!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.
|
||||
|
@ -178,72 +185,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 : "<unknown>"
|
||||
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
|
||||
|
@ -312,3 +258,6 @@ Vagrant.source_root.join("plugins").children(true).each do |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(:plugins) if Vagrant.plugins_enabled?
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
require "monitor"
|
||||
require "pathname"
|
||||
require "set"
|
||||
require "tempfile"
|
||||
|
||||
require "bundler"
|
||||
|
||||
require_relative "shared_helpers"
|
||||
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
|
||||
|
||||
def initialize
|
||||
@monitor = Monitor.new
|
||||
|
||||
@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
|
||||
# loading gems. This must only be called once.
|
||||
def init!(plugins)
|
||||
# Setup the Bundler configuration
|
||||
@configfile = File.open(Tempfile.new("vagrant").path + "1", "w+")
|
||||
@configfile.close
|
||||
|
||||
# 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 = 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}#{@gem_path}"
|
||||
Gem.clear_paths
|
||||
end
|
||||
|
||||
# Installs the list of plugins.
|
||||
#
|
||||
# @param [Hash] plugins
|
||||
# @return [Array<Gem::Specification>]
|
||||
def install(plugins, local=false)
|
||||
internal_install(plugins, nil, local: local)
|
||||
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
|
||||
# @param [Array<String>] 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.
|
||||
def clean(plugins)
|
||||
gemfile = build_gemfile(plugins)
|
||||
lockfile = "#{gemfile.path}.lock"
|
||||
definition = ::Bundler::Definition.build(gemfile, lockfile, nil)
|
||||
root = File.dirname(gemfile.path)
|
||||
|
||||
with_isolated_gem do
|
||||
runtime = ::Bundler::Runtime.new(root, definition)
|
||||
runtime.clean
|
||||
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
|
||||
# plugins.
|
||||
#
|
||||
# @return [Tempfile]
|
||||
def build_gemfile(plugins)
|
||||
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"])
|
||||
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 == ""
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
# 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<Gem::Specification>]
|
||||
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)
|
||||
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
|
||||
# native extensions because it causes all sorts of problems.
|
||||
old_rubyopt = ENV["RUBYOPT"]
|
||||
old_gemfile = ENV["BUNDLE_GEMFILE"]
|
||||
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
|
||||
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
|
||||
|
||||
# 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
|
||||
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
|
||||
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
|
||||
Gem::Specification.all = old_all
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -428,6 +432,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
|
||||
|
@ -440,6 +448,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
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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 :Manager, "vagrant/plugin/manager"
|
||||
autoload :StateFile, "vagrant/plugin/state_file"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
require "set"
|
||||
|
||||
require_relative "../bundler"
|
||||
require_relative "../shared_helpers"
|
||||
require_relative "state_file"
|
||||
|
||||
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
|
||||
|
||||
# Installs another plugin into our gem directory.
|
||||
#
|
||||
# @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
|
||||
plugins[name] = {
|
||||
"require" => opts[:require],
|
||||
"gem_version" => opts[:version],
|
||||
"sources" => opts[:sources],
|
||||
}
|
||||
|
||||
result = nil
|
||||
install_lambda = lambda do
|
||||
Vagrant::Bundler.instance.install(plugins, local).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
|
||||
|
||||
# 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,
|
||||
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.
|
||||
#
|
||||
# @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)
|
||||
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.
|
||||
#
|
||||
# @return [Hash]
|
||||
def installed_plugins
|
||||
@global_file.installed_plugins
|
||||
end
|
||||
|
||||
# This returns the list of plugins that are installed as
|
||||
# Gem::Specifications.
|
||||
#
|
||||
# @return [Array<Gem::Specification>]
|
||||
def installed_specs
|
||||
installed = Set.new(installed_plugins.keys)
|
||||
|
||||
# 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
|
||||
end
|
|
@ -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
|
||||
|
@ -27,17 +27,27 @@ module VagrantPlugins
|
|||
# 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] || "",
|
||||
"sources" => opts[:sources] || [],
|
||||
}
|
||||
|
||||
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 +65,23 @@ module VagrantPlugins
|
|||
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<String>]
|
||||
def sources
|
||||
@data["sources"] || []
|
||||
end
|
||||
|
||||
# This saves the state back into the state file.
|
||||
def save!
|
||||
@path.open("w+") do |f|
|
|
@ -0,0 +1,30 @@
|
|||
# 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.
|
||||
|
||||
if defined?(Bundler)
|
||||
require "bundler/shared_helpers"
|
||||
if Bundler::SharedHelpers.in_bundle?
|
||||
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
|
||||
|
||||
require_relative "bundler"
|
||||
require_relative "plugin/manager"
|
||||
require_relative "shared_helpers"
|
||||
|
||||
plugins = Vagrant::Plugin::Manager.instance.installed_plugins
|
||||
Vagrant::Bundler.instance.init!(plugins)
|
||||
|
||||
ENV["VAGRANT_INTERNAL_BUNDLERIZED"] = "1"
|
||||
Kernel.exec("vagrant", *ARGV)
|
|
@ -0,0 +1,34 @@
|
|||
require "pathname"
|
||||
|
||||
module Vagrant
|
||||
# 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
|
||||
|
||||
# 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
|
|
@ -8,16 +8,14 @@ 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
|
||||
end
|
||||
|
||||
# This middleware sequence licenses paid addons.
|
||||
def self.action_license
|
||||
Vagrant::Action::Builder.new.tap do |b|
|
||||
b.use BundlerCheck
|
||||
b.use PluginExistsCheck
|
||||
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,31 +30,26 @@ 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 PluginExistsCheck
|
||||
b.use UninstallPlugin
|
||||
b.use PruneGems
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
b.use UpdateGems
|
||||
end
|
||||
end
|
||||
|
||||
# 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")
|
||||
autoload :PluginExistsCheck, action_root.join("plugin_exists_check")
|
||||
autoload :PruneGems, action_root.join("prune_gems")
|
||||
autoload :UninstallPlugin, action_root.join("uninstall_plugin")
|
||||
autoload :UpdateGems, action_root.join("update_gems")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -1,13 +1,5 @@
|
|||
require "rubygems"
|
||||
require "rubygems/dependency_installer"
|
||||
|
||||
begin
|
||||
require "rubygems/format"
|
||||
rescue LoadError
|
||||
# rubygems 2.x
|
||||
end
|
||||
|
||||
require "log4r"
|
||||
require "vagrant/plugin/manager"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommandPlugin
|
||||
|
@ -21,69 +13,29 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def call(env)
|
||||
entrypoint = env[:plugin_entry_point]
|
||||
plugin_name = env[:plugin_name]
|
||||
prerelease = env[:plugin_prerelease]
|
||||
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 += ' --prerelease' if prerelease
|
||||
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)
|
||||
manager = Vagrant::Plugin::Manager.instance
|
||||
plugin_spec = manager.install_plugin(
|
||||
plugin_name,
|
||||
version: version,
|
||||
require: entrypoint,
|
||||
sources: sources,
|
||||
verbose: !!env[:plugin_verbose],
|
||||
)
|
||||
|
||||
# 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.
|
||||
# Record it so we can uninstall if something goes 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)
|
||||
|
||||
# Tell the user
|
||||
env[:ui].success(I18n.t("vagrant.commands.plugin.installed",
|
||||
:name => plugin_spec.name,
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require "rubygems"
|
||||
require "set"
|
||||
require "vagrant/plugin/manager"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommandPlugin
|
||||
|
@ -17,32 +16,35 @@ 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 = {}
|
||||
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)
|
||||
|
||||
# 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
|
||||
end
|
||||
manager = Vagrant::Plugin::Manager.instance
|
||||
plugins = manager.installed_plugins
|
||||
specs = manager.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})"
|
||||
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
|
||||
|
||||
|
|
|
@ -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_plugins
|
||||
if !installed.has_key?(env[:plugin_name])
|
||||
raise Vagrant::Errors::PluginNotInstalled,
|
||||
name: env[:plugin_name]
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -1,3 +1,5 @@
|
|||
require "vagrant/plugin/state_file"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommandPlugin
|
||||
module Command
|
||||
|
@ -9,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 => StateFile.new(@env.home_path.join("plugins.json"))
|
||||
}.merge(env || {})
|
||||
|
||||
@env.action_runner.run(callable, env)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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 <name> [-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
|
||||
|
@ -26,10 +30,10 @@ 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]
|
||||
:plugin_name => argv[0],
|
||||
:plugin_verbose => options[:verbose]
|
||||
})
|
||||
|
||||
# Success, exit status 0
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -10,26 +10,18 @@ module VagrantPlugins
|
|||
include MixinInstallOpts
|
||||
|
||||
def execute
|
||||
options = {}
|
||||
|
||||
opts = OptionParser.new do |o|
|
||||
o.banner = "Usage: vagrant plugin update <name> [-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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,5 @@ DESC
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
@ -259,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.
|
||||
|
@ -479,6 +481,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
|
||||
|
@ -491,6 +496,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
|
||||
|
@ -911,12 +926,22 @@ 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: |-
|
||||
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:
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
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, sources: nil, verbose: false).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, sources: nil, verbose: false).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", sources: nil, verbose: false).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
|
||||
|
||||
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"], verbose: false).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
|
||||
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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,172 @@
|
|||
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
|
||||
|
||||
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, local|
|
||||
expect(plugins).to have_key("foo")
|
||||
expect(local).to be_false
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
plugins = subject.installed_plugins
|
||||
expect(plugins.length).to eql(1)
|
||||
expect(plugins).to have_key("foo")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#installed_specs" do
|
||||
it "has the plugins" do
|
||||
# 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")
|
||||
|
||||
specs = subject.installed_specs
|
||||
expect(specs.length).to eql(1)
|
||||
expect(specs.first.name).to eql("i18n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
@ -32,6 +32,9 @@ describe VagrantPlugins::CommandPlugin::StateFile do
|
|||
expect(plugins["foo"]).to eql({
|
||||
"ruby_version" => RUBY_VERSION,
|
||||
"vagrant_version" => Vagrant::VERSION,
|
||||
"gem_version" => "",
|
||||
"require" => "",
|
||||
"sources" => [],
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -50,6 +53,34 @@ describe VagrantPlugins::CommandPlugin::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
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
ENV["VAGRANT_FORCE_PLUGINS"] = "1"
|
||||
|
||||
require_relative "test/acceptance/base"
|
||||
|
||||
Vagrant::Spec::Acceptance.configure do |c|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <name> <license-file>`**
|
||||
|
@ -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 <name>`**
|
||||
**Command: `vagrant plugin update [<name>]`**
|
||||
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue