Merge pull request #10037 from chrisroberts/e-vagrant-plugins-local

Define project specific plugins
This commit is contained in:
Chris Roberts 2018-07-27 09:57:41 -07:00 committed by GitHub
commit a993cbce4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1001 additions and 141 deletions

View File

@ -44,7 +44,9 @@ argv.each_index do |i|
# Do not load plugins when performing plugin operations
if arg == "plugin"
opts[:vagrantfile_name] = ""
if argv.none?{|a| a == "--local" } && !ENV["VAGRANT_LOCAL_PLUGINS_LOAD"]
opts[:vagrantfile_name] = ""
end
ENV['VAGRANT_NO_PLUGINS'] = "1"
# Only initialize plugins when listing installed plugins
if argv[i+1] != "list"

View File

@ -3,6 +3,7 @@ require "vagrant/shared_helpers"
require "rubygems"
require "log4r"
require "vagrant/util"
require "vagrant/plugin/manager"
# Enable logging if it is requested. We do this before
# anything else so that we can setup the output before
@ -262,35 +263,6 @@ else
global_logger.warn("resolv replacement has not been enabled!")
end
# Setup the plugin manager and load any defined plugins
require_relative "vagrant/plugin/manager"
plugins = Vagrant::Plugin::Manager.instance.installed_plugins
global_logger.info("Plugins:")
plugins.each do |plugin_name, plugin_info|
installed_version = plugin_info["installed_gem_version"]
version_constraint = plugin_info["gem_version"]
installed_version = 'undefined' if installed_version.to_s.empty?
version_constraint = '> 0' if version_constraint.to_s.empty?
global_logger.info(
" - #{plugin_name} = [installed: " \
"#{installed_version} constraint: " \
"#{version_constraint}]"
)
end
if Vagrant.plugins_init?
begin
Vagrant::Bundler.instance.init!(plugins)
rescue StandardError, ScriptError => e
global_logger.error("Plugin initialization error - #{e.class}: #{e}")
e.backtrace.each do |backtrace_line|
global_logger.debug(backtrace_line)
end
raise Vagrant::Errors::PluginInitError, message: e.to_s
end
end
# A lambda that knows how to load plugins from a single directory.
plugin_load_proc = lambda do |directory|
# We only care about directories
@ -320,43 +292,3 @@ 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
if Vagrant.plugins_enabled?
begin
global_logger.info("Loading plugins!")
plugins.each do |plugin_name, plugin_info|
if plugin_info["require"].to_s.empty?
begin
global_logger.info("Loading plugin `#{plugin_name}` with default require: `#{plugin_name}`")
require plugin_name
rescue LoadError => err
if plugin_name.include?("-")
plugin_slash = plugin_name.gsub("-", "/")
global_logger.error("Failed to load plugin `#{plugin_name}` with default require. - #{err.class}: #{err}")
global_logger.info("Loading plugin `#{plugin_name}` with slash require: `#{plugin_slash}`")
require plugin_slash
else
raise
end
end
else
global_logger.debug("Loading plugin `#{plugin_name}` with custom require: `#{plugin_info["require"]}`")
require plugin_info["require"]
end
global_logger.debug("Successfully loaded plugin `#{plugin_name}`.")
end
if defined?(::Bundler)
global_logger.debug("Bundler detected in use. Loading `:plugins` group.")
::Bundler.require(:plugins)
end
rescue ScriptError, StandardError => err
global_logger.error("Plugin loading error: #{err.class} - #{err}")
err.backtrace.each do |backtrace_line|
global_logger.debug(backtrace_line)
end
raise Vagrant::Errors::PluginLoadError, message: err.to_s
end
else
global_logger.debug("Plugin loading is currently disabled.")
end

View File

@ -32,22 +32,42 @@ module Vagrant
@bundler ||= self.new
end
# @return [Pathname] Global plugin path
attr_reader :plugin_gem_path
# @return [Pathname] Vagrant environment specific plugin path
attr_reader :env_plugin_gem_path
def initialize
@plugin_gem_path = Vagrant.user_data_path.join("gems", RUBY_VERSION).freeze
@logger = Log4r::Logger.new("vagrant::bundler")
end
# Enable Vagrant environment specific plugins at given data path
#
# @param [Pathname] Path to Vagrant::Environment data directory
# @return [Pathname] Path to environment specific gem directory
def environment_path=(env_data_path)
@env_plugin_gem_path = env_data_path.join("plugins", "gems", RUBY_VERSION).freeze
end
# Initializes Bundler and the various gem paths so that we can begin
# loading gems. This must only be called once.
# loading gems.
def init!(plugins, repair=false)
if !@initial_specifications
@initial_specifications = Gem::Specification.find_all{true}
else
Gem::Specification.all = @initial_specifications
Gem::Specification.reset
end
# Add HashiCorp RubyGems source
Gem.sources << HASHICORP_GEMSTORE
if !Gem.sources.include?(HASHICORP_GEMSTORE)
Gem.sources << HASHICORP_GEMSTORE
end
# Generate dependencies for all registered plugins
plugin_deps = plugins.map do |name, info|
Gem::Dependency.new(name, info['gem_version'].to_s.empty? ? '> 0' : info['gem_version'])
Gem::Dependency.new(name, info['installed_gem_version'].to_s.empty? ? '> 0' : info['installed_gem_version'])
end
@logger.debug("Current generated plugin dependency list: #{plugin_deps}")
@ -78,7 +98,7 @@ module Vagrant
# Activate the gems
activate_solution(solution)
full_vagrant_spec_list = Gem::Specification.find_all{true} +
full_vagrant_spec_list = @initial_specifications +
solution.map(&:full_spec)
if(defined?(::Bundler))
@ -91,6 +111,7 @@ module Vagrant
end
Gem::Specification.reset
nil
end
# Removes any temporary files created by init
@ -101,9 +122,10 @@ module Vagrant
# Installs the list of plugins.
#
# @param [Hash] plugins
# @param [Boolean] env_local Environment local plugin install
# @return [Array<Gem::Specification>]
def install(plugins, local=false)
internal_install(plugins, nil, local: local)
def install(plugins, env_local=false)
internal_install(plugins, nil, env_local: env_local)
end
# Installs a local '*.gem' file so that Bundler can find it.
@ -120,7 +142,7 @@ module Vagrant
}
}
@logger.debug("Installing local plugin - #{plugin_info}")
internal_install(plugin_info, {})
internal_install(plugin_info, nil, env_local: opts[:env_local])
plugin_source.spec
end
@ -129,14 +151,14 @@ module Vagrant
# @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)
def update(plugins, specific, **opts)
specific ||= []
update = {gems: specific.empty? ? true : specific}
update = opts.merge({gems: specific.empty? ? true : specific})
internal_install(plugins, update)
end
# Clean removes any unused gems.
def clean(plugins)
def clean(plugins, **opts)
@logger.debug("Cleaning Vagrant plugins of stale gems.")
# Generate dependencies for all registered plugins
plugin_deps = plugins.map do |name, info|
@ -163,6 +185,13 @@ module Vagrant
Gem::Specification.load(spec_path)
end
# Include environment specific specification if enabled
if env_plugin_gem_path
plugin_specs += Dir.glob(env_plugin_gem_path.join('specifications/*.gemspec').to_s).map do |spec_path|
Gem::Specification.load(spec_path)
end
end
@logger.debug("Generating current plugin state solution set.")
# Resolve the request set to ensure proper activation order
@ -171,11 +200,27 @@ module Vagrant
solution_full_names = solution_specs.map(&:full_name)
# Find all specs installed to plugins directory that are not
# found within the solution set
# found within the solution set.
plugin_specs.delete_if do |spec|
solution_full_names.include?(spec.full_name)
end
if env_plugin_gem_path
# If we are cleaning locally, remove any global specs. If
# not, remove any local specs
if opts[:env_local]
@logger.debug("Removing specifications that are not environment local")
plugin_specs.delete_if do |spec|
spec.full_gem_path.to_s.include?(plugin_gem_path.realpath.to_s)
end
else
@logger.debug("Removing specifications that are environment local")
plugin_specs.delete_if do |spec|
spec.full_gem_path.to_s.include?(env_plugin_gem_path.realpath.to_s)
end
end
end
@logger.debug("Specifications to be removed - #{plugin_specs.map(&:full_name)}")
# Now delete all unused specs
@ -318,18 +363,37 @@ module Vagrant
# as we know the dependencies are satisfied and it will attempt to validate a gem's
# dependencies are satisfied by gems in the install directory (which will likely not
# be true)
result = request_set.install_into(plugin_gem_path.to_s, true,
install_path = extra[:env_local] ? env_plugin_gem_path : plugin_gem_path
result = request_set.install_into(install_path.to_s, true,
ignore_dependencies: true,
prerelease: Vagrant.prerelease?,
wrappers: true
)
result = result.map(&:full_spec)
result.each do |spec|
existing_paths = $LOAD_PATH.find_all{|s| s.include?(spec.full_name) }
if !existing_paths.empty?
@logger.debug("Removing existing LOAD_PATHs for #{spec.full_name} - " +
existing_paths.join(", "))
existing_paths.each{|s| $LOAD_PATH.delete(s) }
end
spec.full_require_paths.each do |r_path|
if !$LOAD_PATH.include?(r_path)
@logger.debug("Adding path to LOAD_PATH - #{r_path}")
$LOAD_PATH.unshift(r_path)
end
end
end
result
end
# Generate the composite resolver set totally all of vagrant (builtin + plugin set)
def generate_vagrant_set
Gem::Resolver.compose_sets(generate_builtin_set, generate_plugin_set)
sets = [generate_builtin_set, generate_plugin_set]
if env_plugin_gem_path && env_plugin_gem_path.exist?
sets << generate_plugin_set(env_plugin_gem_path)
end
Gem::Resolver.compose_sets(*sets)
end
# @return [Array<[Gem::Specification, String]>] spec and directory pairs
@ -387,10 +451,16 @@ module Vagrant
# Generate the plugin resolver set. Optionally provide specification names (short or
# full) that should be ignored
def generate_plugin_set(skip=[])
#
# @param [Pathname] path to plugins
# @param [Array<String>] gems to skip
# @return [PluginSet]
def generate_plugin_set(*args)
plugin_path = args.detect{|i| i.is_a?(Pathname) } || plugin_gem_path
skip = args.detect{|i| i.is_a?(Array) } || []
plugin_set = PluginSet.new
@logger.debug("Generating new plugin set instance. Skip gems - #{skip}")
Dir.glob(plugin_gem_path.join('specifications/*.gemspec').to_s).each do |spec_path|
Dir.glob(plugin_path.join('specifications/*.gemspec').to_s).each do |spec_path|
spec = Gem::Specification.load(spec_path)
desired_spec_path = File.join(spec.gem_dir, "#{spec.name}.gemspec")
# Vendor set requires the spec to be within the gem directory. Some gems will package their

View File

@ -8,6 +8,7 @@ require 'log4r'
require 'vagrant/util/file_mode'
require 'vagrant/util/platform'
require 'vagrant/util/hash_with_indifferent_access'
require "vagrant/util/silence_warnings"
require "vagrant/vagrantfile"
require "vagrant/version"
@ -146,6 +147,7 @@ module Vagrant
if opts[:local_data_path]
@local_data_path = Pathname.new(File.expand_path(opts[:local_data_path], @cwd))
end
@logger.debug("Effective local data path: #{@local_data_path}")
# If we have a root path, load the ".vagrantplugins" file.
@ -163,6 +165,19 @@ module Vagrant
@default_private_key_path = @home_path.join("insecure_private_key")
copy_insecure_private_key
# Initialize localized plugins
plugins = Vagrant::Plugin::Manager.instance.localize!(self)
if !vagrantfile.config.vagrant.plugins.empty?
plugins = process_configured_plugins
end
# Load any environment local plugins
Vagrant::Plugin::Manager.instance.load_plugins(plugins)
plugins = Vagrant::Plugin::Manager.instance.globalize!
Vagrant::Plugin::Manager.instance.load_plugins(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))
@ -898,6 +913,64 @@ module Vagrant
protected
# Check for any local plugins defined within the Vagrantfile. If
# found, validate they are available. If they are not available,
# request to install them, or raise an exception
#
# @return [Hash] plugin list for loading
def process_configured_plugins
return if !Vagrant.plugins_enabled?
errors = vagrantfile.config.vagrant.validate(nil)
if !errors["vagrant"].empty?
raise Errors::ConfigInvalid,
errors: Util::TemplateRenderer.render(
"config/validation_failed",
errors: errors)
end
# Check if defined plugins are installed
installed = Plugin::Manager.instance.installed_plugins
needs_install = []
config_plugins = vagrantfile.config.vagrant.plugins
config_plugins.each do |name, info|
if !installed[name]
needs_install << name
end
end
if !needs_install.empty?
ui.warn(I18n.t("vagrant.plugins.local.uninstalled_plugins",
plugins: needs_install.sort.join(", ")))
if !Vagrant.auto_install_local_plugins?
answer = nil
until ["y", "n"].include?(answer)
answer = ui.ask(I18n.t("vagrant.plugins.local.request_plugin_install") + " [N]: ")
answer.strip!.downcase!
answer = "n" if answer.to_s.empty?
end
if answer == "n"
raise Errors::PluginMissingLocalError,
plugins: needs_install.sort.join(", ")
end
end
needs_install.each do |name|
pconfig = Util::HashWithIndifferentAccess.new(config_plugins[name])
ui.info(I18n.t("vagrant.commands.plugin.installing", name: name))
options = {sources: Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup, env_local: true}
options[:sources] = pconfig[:sources] if pconfig[:sources]
options[:require] = pconfig[:entry_point] if pconfig[:entry_point]
options[:version] = pconfig[:version] if pconfig[:version]
spec = Plugin::Manager.instance.install_plugin(name, options)
ui.info(I18n.t("vagrant.commands.plugin.installed",
name: spec.name, version: spec.version.to_s))
end
ui.info("\n")
Vagrant::Plugin::Manager.instance.localize!(self)
end
Vagrant::Plugin::Manager.instance.local_file.installed_plugins
end
# This method copies the private key into the home directory if it
# doesn't already exist.
#

View File

@ -640,6 +640,14 @@ module Vagrant
error_key(:plugin_source_error)
end
class PluginNoLocalError < VagrantError
error_key(:plugin_no_local_error)
end
class PluginMissingLocalError < VagrantError
error_key(:plugin_missing_local_error)
end
class PushesNotDefined < VagrantError
error_key(:pushes_not_defined)
end

View File

@ -27,13 +27,78 @@ module Vagrant
@instance ||= self.new(user_plugins_file)
end
attr_reader :user_file
attr_reader :system_file
attr_reader :local_file
# @param [Pathname] user_file
def initialize(user_file)
@logger = Log4r::Logger.new("vagrant::plugin::manager")
@user_file = StateFile.new(user_file)
system_path = self.class.system_plugins_file
@system_file = nil
@system_file = StateFile.new(system_path) if system_path && system_path.file?
@local_file = nil
end
# Enable global plugins
#
# @return [Hash] list of plugins
def globalize!
@logger.debug("Enabling globalized plugins")
plugins = installed_plugins
bundler_init(plugins)
plugins
end
# Enable environment local plugins
#
# @param [Environment] env Vagrant environment
# @return [Hash] list of plugins
def localize!(env)
if env.local_data_path
@logger.debug("Enabling localized plugins")
@local_file = StateFile.new(env.local_data_path.join("plugins.json"))
Vagrant::Bundler.instance.environment_path = env.local_data_path
plugins = local_file.installed_plugins
bundler_init(plugins)
plugins
end
end
# Initialize bundler with given plugins
#
# @param [Hash] plugins List of plugins
# @return [nil]
def bundler_init(plugins)
if !Vagrant.plugins_init?
@logger.warn("Plugin initialization is disabled")
return nil
end
@logger.info("Plugins:")
plugins.each do |plugin_name, plugin_info|
installed_version = plugin_info["installed_gem_version"]
version_constraint = plugin_info["gem_version"]
installed_version = 'undefined' if installed_version.to_s.empty?
version_constraint = '> 0' if version_constraint.to_s.empty?
@logger.info(
" - #{plugin_name} = [installed: " \
"#{installed_version} constraint: " \
"#{version_constraint}]"
)
end
begin
Vagrant::Bundler.instance.init!(plugins)
rescue StandardError, ScriptError => err
@logger.error("Plugin initialization error - #{err.class}: #{err}")
err.backtrace.each do |backtrace_line|
@logger.debug(backtrace_line)
end
raise Vagrant::Errors::PluginInitError, message: err.to_s
end
end
# Installs another plugin into our gem directory.
@ -41,7 +106,10 @@ module Vagrant
# @param [String] name Name of the plugin (gem)
# @return [Gem::Specification]
def install_plugin(name, **opts)
local = false
if opts[:env_local] && @local_file.nil?
raise Errors::PluginNoLocalError
end
if name =~ /\.gem$/
# If this is a gem file, then we install that gem locally.
local_spec = Vagrant::Bundler.instance.install_local(name, opts)
@ -59,7 +127,7 @@ module Vagrant
if local_spec.nil?
result = nil
install_lambda = lambda do
Vagrant::Bundler.instance.install(plugins, local).each do |spec|
Vagrant::Bundler.instance.install(plugins, opts[:env_local]).each do |spec|
next if spec.name != name
next if result && result.version >= spec.version
result = spec
@ -75,18 +143,20 @@ module Vagrant
result = local_spec
end
# Add the plugin to the state file
@user_file.add_plugin(
plugin_file = opts[:env_local] ? @local_file : @user_file
plugin_file.add_plugin(
result.name,
version: opts[:version],
require: opts[:require],
sources: opts[:sources],
env_local: !!opts[:env_local],
installed_gem_version: result.version.to_s
)
# After install clean plugin gems to remove any cruft. This is useful
# for removing outdated dependencies or other versions of an installed
# plugin if the plugin is upgraded/downgraded
Vagrant::Bundler.instance.clean(installed_plugins)
Vagrant::Bundler.instance.clean(installed_plugins, local: !!opts[:local])
result
rescue Gem::GemNotFoundException
raise Errors::PluginGemNotFound, name: name
@ -97,7 +167,7 @@ module Vagrant
# Uninstalls the plugin with the given name.
#
# @param [String] name
def uninstall_plugin(name)
def uninstall_plugin(name, **opts)
if @system_file
if !@user_file.has_plugin?(name) && @system_file.has_plugin?(name)
raise Errors::PluginUninstallSystem,
@ -105,7 +175,18 @@ module Vagrant
end
end
@user_file.remove_plugin(name)
if opts[:env_local] && @local_file.nil?
raise Errors::PluginNoLocalError
end
plugin_file = opts[:env_local] ? @local_file : @user_file
if !plugin_file.has_plugin?(name)
raise Errors::PluginNotInstalled,
name: name
end
plugin_file.remove_plugin(name)
# Clean the environment, removing any old plugins
Vagrant::Bundler.instance.clean(installed_plugins)
@ -114,9 +195,15 @@ module Vagrant
end
# Updates all or a specific set of plugins.
def update_plugins(specific)
result = Vagrant::Bundler.instance.update(installed_plugins, specific)
installed_plugins.each do |name, info|
def update_plugins(specific, **opts)
if opts[:env_local] && @local_file.nil?
raise Errors::PluginNoLocalError
end
plugin_file = opts[:env_local] ? @local_file : @user_file
result = Vagrant::Bundler.instance.update(plugin_file.installed_plugins, specific)
plugin_file.installed_plugins.each do |name, info|
matching_spec = result.detect{|s| s.name == name}
info = Hash[
info.map do |key, value|
@ -124,7 +211,7 @@ module Vagrant
end
]
if matching_spec
@user_file.add_plugin(name, **info.merge(
plugin_file.add_plugin(name, **info.merge(
version: "> 0",
installed_gem_version: matching_spec.version.to_s
))
@ -148,6 +235,11 @@ module Vagrant
end
plugin_list = Util::DeepMerge.deep_merge(system, @user_file.installed_plugins)
if @local_file
plugin_list = Util::DeepMerge.deep_merge(plugin_list,
@local_file.installed_plugins)
end
# Sort plugins by name
Hash[
plugin_list.map{|plugin_name, plugin_info|
@ -191,6 +283,58 @@ module Vagrant
installed_map.values
end
# Loads the requested plugins into the Vagrant runtime
#
# @param [Hash] plugins List of plugins to load
# @return [nil]
def load_plugins(plugins)
if !Vagrant.plugins_enabled?
@logger.warn("Plugin loading is disabled")
return
end
if plugins.nil?
@logger.debug("No plugins provided for loading")
return
end
begin
@logger.info("Loading plugins...")
plugins.each do |plugin_name, plugin_info|
if plugin_info["require"].to_s.empty?
begin
@logger.info("Loading plugin `#{plugin_name}` with default require: `#{plugin_name}`")
require plugin_name
rescue LoadError => err
if plugin_name.include?("-")
plugin_slash = plugin_name.gsub("-", "/")
@logger.error("Failed to load plugin `#{plugin_name}` with default require. - #{err.class}: #{err}")
@logger.info("Loading plugin `#{plugin_name}` with slash require: `#{plugin_slash}`")
require plugin_slash
else
raise
end
end
else
@logger.debug("Loading plugin `#{plugin_name}` with custom require: `#{plugin_info["require"]}`")
require plugin_info["require"]
end
@logger.debug("Successfully loaded plugin `#{plugin_name}`.")
end
if defined?(::Bundler)
@logger.debug("Bundler detected in use. Loading `:plugins` group.")
::Bundler.require(:plugins)
end
rescue ScriptError, StandardError => err
@logger.error("Plugin loading error: #{err.class} - #{err}")
err.backtrace.each do |backtrace_line|
@logger.debug(backtrace_line)
end
raise Vagrant::Errors::PluginLoadError, message: err.to_s
end
nil
end
end
end
end

View File

@ -7,6 +7,10 @@ module Vagrant
# 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
# @return [Pathname] path to file
attr_reader :path
def initialize(path)
@path = path
@ -36,7 +40,8 @@ module Vagrant
"gem_version" => opts[:version] || "",
"require" => opts[:require] || "",
"sources" => opts[:sources] || [],
"installed_gem_version" => opts[:installed_gem_version]
"installed_gem_version" => opts[:installed_gem_version],
"env_local" => !!opts[:env_local]
}
save!

View File

@ -130,6 +130,18 @@ module Vagrant
end
end
# Automatically install locally defined plugins instead of
# waiting for user confirmation.
#
# @return [Boolean]
def self.auto_install_local_plugins?
if ENV["VAGRANT_INSTALL_LOCAL_PLUGINS"]
true
else
false
end
end
# Use Ruby Resolv in place of libc
#
# @return [boolean] enabled or not

View File

@ -41,6 +41,13 @@ module VagrantPlugins
end
end
# This middleware sequence will repair installed local plugins.
def self.action_repair_local
Vagrant::Action::Builder.new.tap do |b|
b.use RepairPluginsLocal
end
end
# This middleware sequence will uninstall a plugin.
def self.action_uninstall
Vagrant::Action::Builder.new.tap do |b|
@ -64,6 +71,7 @@ module VagrantPlugins
autoload :ListPlugins, action_root.join("list_plugins")
autoload :PluginExistsCheck, action_root.join("plugin_exists_check")
autoload :RepairPlugins, action_root.join("repair_plugins")
autoload :RepairPluginsLocal, action_root.join("repair_plugins")
autoload :UninstallPlugin, action_root.join("uninstall_plugin")
autoload :UpdateGems, action_root.join("update_gems")
end

View File

@ -42,17 +42,25 @@ module VagrantPlugins
end
if !abort_action
plugins_json = File.join(env[:home_path], "plugins.json")
plugins_gems = env[:gems_path]
files = []
dirs = []
if File.exist?(plugins_json)
FileUtils.rm(plugins_json)
# Do not include global paths if local only
if !env[:env_local_only] || env[:global_only]
files << Vagrant::Plugin::Manager.instance.user_file.path
dirs << Vagrant::Bundler.instance.plugin_gem_path
end
if File.directory?(plugins_gems)
FileUtils.rm_rf(plugins_gems)
# Add local paths if they exist
if Vagrant::Plugin::Manager.instance.local_file && (env[:env_local_only] || !env[:global_only])
files << Vagrant::Plugin::Manager.instance.local_file.path
dirs << Vagrant::Bundler.instance.env_plugin_gem_path
end
# Expunge files and directories
files.find_all(&:exist?).map(&:delete)
dirs.find_all(&:exist?).map(&:rmtree)
env[:ui].info(I18n.t("vagrant.commands.plugin.expunge_complete"))
@app.call(env)

View File

@ -18,6 +18,7 @@ module VagrantPlugins
plugin_name = env[:plugin_name]
sources = env[:plugin_sources]
version = env[:plugin_version]
env_local = env[:plugin_env_local]
# Install the gem
plugin_name_label = plugin_name
@ -28,10 +29,11 @@ module VagrantPlugins
manager = Vagrant::Plugin::Manager.instance
plugin_spec = manager.install_plugin(
plugin_name,
version: version,
require: entrypoint,
sources: sources,
verbose: !!env[:plugin_verbose],
version: version,
require: entrypoint,
sources: sources,
verbose: !!env[:plugin_verbose],
env_local: env_local
)
# Record it so we can uninstall if something goes wrong

View File

@ -35,13 +35,16 @@ module VagrantPlugins
spec = specs[plugin_name]
next if spec.nil?
system = ""
system = ", system" if plugin && plugin["system"]
env[:ui].info "#{spec.name} (#{spec.version}#{system})"
meta = ", global"
if plugin
meta = ", system" if plugin["system"]
meta = ", local" if plugin["env_local"]
end
env[:ui].info "#{spec.name} (#{spec.version}#{meta})"
env[:ui].machine("plugin-name", spec.name)
env[:ui].machine(
"plugin-version",
"#{spec.version}#{system}",
"#{spec.version}#{meta}",
target: spec.name)
if plugin["gem_version"] && plugin["gem_version"] != ""

View File

@ -19,11 +19,13 @@ module VagrantPlugins
def call(env)
env[:ui].info(I18n.t("vagrant.commands.plugin.repairing"))
plugins = Vagrant::Plugin::Manager.instance.installed_plugins
plugins = Vagrant::Plugin::Manager.instance.globalize!
begin
ENV["VAGRANT_DISABLE_PLUGIN_INIT"] = nil
Vagrant::Bundler.instance.init!(plugins, :repair)
ENV["VAGRANT_DISABLE_PLUGIN_INIT"] = "1"
env[:ui].info(I18n.t("vagrant.commands.plugin.repair_complete"))
rescue Exception => e
rescue => e
@logger.error("Failed to repair user installed plugins: #{e.class} - #{e}")
e.backtrace.each do |backtrace_line|
@logger.debug(backtrace_line)
@ -34,6 +36,29 @@ module VagrantPlugins
@app.call(env)
end
end
class RepairPluginsLocal
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::plugins::plugincommand::repair_local")
end
def call(env)
env[:ui].info(I18n.t("vagrant.commands.plugin.repairing_local"))
Vagrant::Plugin::Manager.instance.localize!(env[:env]).each_pair do |pname, pinfo|
env[:env].action_runner.run(Action.action_install,
plugin_name: pname,
plugin_entry_point: pinfo["require"],
plugin_sources: pinfo["sources"],
plugin_version: pinfo["gem_version"],
plugin_env_local: true
)
end
env[:ui].info(I18n.t("vagrant.commands.plugin.repair_local_complete"))
# Continue
@app.call(env)
end
end
end
end
end

View File

@ -15,7 +15,7 @@ module VagrantPlugins
name: env[:plugin_name]))
manager = Vagrant::Plugin::Manager.instance
manager.uninstall_plugin(env[:plugin_name])
manager.uninstall_plugin(env[:plugin_name], env_local: env[:env_local])
@app.call(env)
end

View File

@ -16,6 +16,18 @@ module VagrantPlugins
options[:force] = force
end
o.on("--local", "Include plugins from local project for expunge") do |l|
options[:env_local] = l
end
o.on("--local-only", "Only expunge local project plugins") do |l|
options[:env_local_only] = l
end
o.on("--global-only", "Only expunge global plugins") do |l|
options[:global_only] = l
end
o.on("--reinstall", "Reinstall current plugins after expunge") do |reinstall|
options[:reinstall] = reinstall
end

View File

@ -9,6 +9,8 @@ module VagrantPlugins
class Install < Base
include MixinInstallOpts
LOCAL_INSTALL_PAUSE = 3
def execute
options = { verbose: false }
@ -17,6 +19,10 @@ module VagrantPlugins
o.separator ""
build_install_opts(o, options)
o.on("--local", "Install plugin for local project only") do |l|
options[:env_local] = l
end
o.on("--verbose", "Enable verbose output for plugin installation") do |v|
options[:verbose] = v
end
@ -25,17 +31,51 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length < 1
# Install the gem
argv.each do |name|
action(Action.action_install, {
plugin_entry_point: options[:entry_point],
plugin_version: options[:plugin_version],
plugin_sources: options[:plugin_sources],
plugin_name: name,
plugin_verbose: options[:verbose]
})
if argv.length < 1
raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if !options[:env_local]
errors = @env.vagrantfile.config.vagrant.validate(nil)
if !errors["vagrant"].empty?
raise Errors::ConfigInvalid,
errors: Util::TemplateRenderer.render(
"config/validation_failed",
errors: errors)
end
local_plugins = @env.vagrantfile.config.vagrant.plugins
plugin_list = local_plugins.map do |name, info|
"#{name} (#{info.fetch(:version, "> 0")})"
end.join("\n")
@env.ui.info(I18n.t("vagrant.plugins.local.install_all",
plugins: plugin_list) + "\n")
# Pause to allow user to cancel
sleep(LOCAL_INSTALL_PAUSE)
local_plugins.each do |name, info|
action(Action.action_install,
plugin_entry_point: info[:entry_point],
plugin_version: info[:version],
plugin_sources: info[:sources] || Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup,
plugin_name: name,
plugin_env_local: true
)
end
else
# Install the gem
argv.each do |name|
action(Action.action_install,
plugin_entry_point: options[:entry_point],
plugin_version: options[:plugin_version],
plugin_sources: options[:plugin_sources],
plugin_name: name,
plugin_verbose: options[:verbose],
plugin_env_local: options[:env_local]
)
end
end
# Success, exit status 0

View File

@ -9,6 +9,9 @@ module VagrantPlugins
def execute
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant plugin list [-h]"
# Stub option to allow Vagrantfile loading
o.on("--local", "Include local project plugins"){|_|}
end
# Parse the options

View File

@ -7,8 +7,14 @@ module VagrantPlugins
module Command
class Repair < Base
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant plugin repair [-h]"
o.on("--local", "Repair plugins in local project") do |l|
options[:env_local] = l
end
end
# Parse the options
@ -16,8 +22,12 @@ module VagrantPlugins
return if !argv
raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0
if Vagrant::Plugin::Manager.instance.local_file
action(Action.action_repair_local, env: @env)
end
# Attempt to repair installed plugins
action(Action.action_repair)
action(Action.action_repair, options)
# Success, exit status 0
0

View File

@ -7,8 +7,13 @@ module VagrantPlugins
module Command
class Uninstall < Base
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant plugin uninstall <name> [<name2> <name3> ...] [-h]"
o.on("--local", "Remove plugin from local project") do |l|
options[:env_local] = l
end
end
# Parse the options
@ -18,7 +23,7 @@ module VagrantPlugins
# Uninstall the gems
argv.each do |gem|
action(Action.action_uninstall, plugin_name: gem)
action(Action.action_uninstall, plugin_name: gem, env_local: options[:env_local])
end
# Success, exit status 0

View File

@ -10,9 +10,14 @@ module VagrantPlugins
include MixinInstallOpts
def execute
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant plugin update [names...] [-h]"
o.separator ""
o.on("--local", "Update plugin in local project") do |l|
options[:env_local] = l
end
end
# Parse the options
@ -22,6 +27,7 @@ module VagrantPlugins
# Update the gem
action(Action.action_update, {
plugin_name: argv,
env_local: options[:env_local]
})
# Success, exit status 0

View File

@ -5,16 +5,25 @@ module VagrantPlugins
class VagrantConfig < Vagrant.plugin("2", :config)
attr_accessor :host
attr_accessor :sensitive
attr_accessor :plugins
VALID_PLUGIN_KEYS = [:sources, :version, :entry_point].freeze
def initialize
@host = UNSET_VALUE
@sensitive = UNSET_VALUE
@plugins = UNSET_VALUE
end
def finalize!
@host = :detect if @host == UNSET_VALUE
@host = @host.to_sym if @host
@sensitive = nil if @sensitive == UNSET_VALUE
if @plugins == UNSET_VALUE
@plugins = {}
else
@plugins = format_plugins(@plugins)
end
if @sensitive.is_a?(Array) || @sensitive.is_a?(String)
Array(@sensitive).each do |value|
@ -23,18 +32,48 @@ module VagrantPlugins
end
end
# Validate the configuration
#
# @param [Vagrant::Machine, NilClass] machine Machine instance or nil
# @return [Hash]
def validate(machine)
errors = _detected_errors
if @sensitive && (!@sensitive.is_a?(Array) && !@sensitive.is_a?(String))
errors << I18n.t("vagrant.config.root.sensitive_bad_type")
end
@plugins.each do |plugin_name, plugin_info|
invalid_keys = plugin_info.keys - VALID_PLUGIN_KEYS
if !invalid_keys.empty?
errors << I18n.t("vagrant.config.root.plugins_bad_key",
plugin_name: plugin_name,
plugin_key: invalid_keys.join(", ")
)
end
end
{"vagrant" => errors}
end
def to_s
"Vagrant"
end
def format_plugins(val)
result = case val
when String
{val => {}}
when Array
Hash[val.map{|item| [item.to_s, {}]}]
else
val
end
result.keys.each do |key|
result[key] = Hash[result[key].map{|k,v| [k.to_sym, v]}]
end
result
end
end
end
end

View File

@ -410,6 +410,23 @@ en:
Backup: %{backup_path}
plugins:
local:
uninstalled_plugins: |-
Vagrant has detected project local plugins configured for this
project which are not installed.
%{plugins}
request_plugin_install: |-
Install local plugins (Y/N)
install_all: |-
Vagrant will now install the following plugins to the local project
which have been defined in current Vagrantfile:
%{plugins}
Press ctrl-c to cancel...
#-------------------------------------------------------------------------------
# Translations for exception classes
#-------------------------------------------------------------------------------
@ -1092,7 +1109,7 @@ en:
%{message}
plugin_not_installed: |-
The plugin '%{name}' is not installed. Please install it first.
The plugin '%{name}' is not currently installed.
plugin_state_file_not_parsable: |-
Failed to parse the state file "%{path}":
%{message}
@ -1117,6 +1134,20 @@ en:
%{error_msg}
Source: %{source}
plugin_no_local_error: |-
Vagrant is not currently working within a Vagrant project directory. Local
plugins are only supported within a Vagrant project directory.
plugin_missing_local_error: |-
Vagrant is missing plugins required by the currently loaded Vagrantfile.
Please install the configured plugins and run this command again. The
following plugins are currently missing:
%{plugins}
To install the plugins configured in the current Vagrantfile run the
following command:
vagrant plugin install --local
powershell_not_found: |-
Failed to locate the powershell executable on the available PATH. Please
ensure powershell is installed and available on the local PATH, then
@ -1711,6 +1742,10 @@ en:
sensitive_bad_type: |-
Invalid type provided for `sensitive`. The sensitive option expects a string
or an array of strings.
plugins_bad_key: |-
Invalid plugin configuration detected for `%{plugin_name}` plugin.
Unknown keys: %{plugin_key}
bad_key: |-
Unknown configuration section '%{key}'.
ssh:
@ -1889,9 +1924,13 @@ en:
%{message}
repairing: |-
Repairing currently installed plugins. This may take a few minutes...
Repairing currently installed global plugins. This may take a few minutes...
repairing_local: |-
Repairing currently installed local project plugins. This may take a few minutes...
repair_complete: |-
Installed plugins successfully repaired!
repair_local_complete: |-
Local project plugins successfully repaired!
repair_failed: |-
Failed to automatically repair installed Vagrant plugins. To fix this
problem remove all user installed plugins and reinstall. Vagrant can

View File

@ -121,7 +121,10 @@ describe "vagrant bin" do
context "plugin commands" do
let(:argv) { ["plugin"] }
before { allow(ENV).to receive(:[]=) }
before do
allow(ENV).to receive(:[]=)
allow(ENV).to receive(:[])
end
it "should unset vagrantfile" do
expect(Vagrant::Environment).to receive(:new).
@ -143,5 +146,23 @@ describe "vagrant bin" do
expect(ENV).not_to receive(:[]=).with("VAGRANT_DISABLE_PLUGIN_INIT", "1")
end
end
context "--local" do
let(:argv) { ["plugin", "install", "--local"] }
it "should not unset vagrantfile" do
expect(Vagrant::Environment).to receive(:new).
with(hash_excluding(vagrantfile_name: "")).and_return(env)
end
end
context "with VAGRANT_LOCAL_PLUGINS_LOAD enabled" do
before { expect(ENV).to receive(:[]).with("VAGRANT_LOCAL_PLUGINS_LOAD").and_return("1") }
it "should not unset vagrantfile" do
expect(Vagrant::Environment).to receive(:new).
with(hash_excluding(vagrantfile_name: "")).and_return(env)
end
end
end
end

View File

@ -5,21 +5,33 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do
let(:home_path){ '/fake/file/path/.vagrant.d' }
let(:gems_path){ "#{home_path}/gems" }
let(:force){ true }
let(:env_local){ false }
let(:env_local_only){ nil }
let(:global_only){ nil }
let(:env) {{
ui: Vagrant::UI::Silent.new,
home_path: home_path,
gems_path: gems_path,
force: force
force: force,
env_local: env_local,
env_local_only: env_local_only,
global_only: global_only
}}
let(:manager) { double("manager") }
let(:user_file) { double("user_file", path: user_file_pathname) }
let(:user_file_pathname) { double("user_file_pathname", exist?: true, delete: true) }
let(:local_file) { nil }
let(:bundler) { double("bundler", plugin_gem_path: plugin_gem_path,
env_plugin_gem_path: env_plugin_gem_path) }
let(:plugin_gem_path) { double("plugin_gem_path", exist?: true, rmtree: true) }
let(:env_plugin_gem_path) { nil }
let(:manager) { double("manager", user_file: user_file, local_file: local_file) }
let(:expect_to_receive) do
lambda do
allow(File).to receive(:exist?).with(File.join(home_path, 'plugins.json')).and_return(true)
allow(File).to receive(:directory?).with(gems_path).and_return(true)
expect(FileUtils).to receive(:rm).with(File.join(home_path, 'plugins.json'))
expect(FileUtils).to receive(:rm_rf).with(gems_path)
expect(app).to receive(:call).with(env).once
end
end
@ -28,6 +40,7 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do
before do
allow(Vagrant::Plugin::Manager).to receive(:instance).and_return(manager)
allow(Vagrant::Bundler).to receive(:instance).and_return(bundler)
end
describe "#call" do
@ -36,6 +49,8 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do
end
it "should delete all plugins" do
expect(user_file_pathname).to receive(:delete)
expect(plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
@ -60,5 +75,94 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do
end
end
end
context "when local option is set" do
let(:env_local) { true }
it "should delete plugins" do
expect(user_file_pathname).to receive(:delete)
expect(plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
end
context "when local plugins exist" do
let(:local_file) { double("local_file", path: local_file_pathname) }
let(:local_file_pathname) { double("local_file_pathname", exist?: true, delete: true) }
let(:env_plugin_gem_path) { double("env_plugin_gem_path", exist?: true, rmtree: true) }
it "should delete user and local plugins" do
expect(user_file_pathname).to receive(:delete)
expect(local_file_pathname).to receive(:delete)
expect(plugin_gem_path).to receive(:rmtree)
expect(env_plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
context "when local option is set" do
let(:env_local) { true }
it "should delete local plugins" do
expect(local_file_pathname).to receive(:delete)
expect(env_plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
it "should delete user plugins" do
expect(user_file_pathname).to receive(:delete)
expect(plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
context "when local only option is set" do
let(:env_local_only) { true }
it "should delete local plugins" do
expect(local_file_pathname).to receive(:delete)
expect(env_plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
it "should not delete user plugins" do
expect(user_file_pathname).not_to receive(:delete)
expect(plugin_gem_path).not_to receive(:rmtree)
subject.call(env)
end
end
context "when global only option is set" do
let(:global_only) { true }
it "should not delete local plugins" do
expect(local_file_pathname).not_to receive(:delete)
expect(env_plugin_gem_path).not_to receive(:rmtree)
subject.call(env)
end
it "should delete user plugins" do
expect(user_file_pathname).to receive(:delete)
expect(plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
end
context "when global and local only options are set" do
let(:env_local_only) { true }
let(:global_only) { true }
it "should delete local plugins" do
expect(local_file_pathname).to receive(:delete)
expect(env_plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
it "should delete user plugins" do
expect(user_file_pathname).to receive(:delete)
expect(plugin_gem_path).to receive(:rmtree)
subject.call(env)
end
end
end
end
end
end

View File

@ -18,7 +18,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do
it "should install the plugin" do
spec = Gem::Specification.new
expect(manager).to receive(:install_plugin).with(
"foo", version: nil, require: nil, sources: nil, verbose: false).once.and_return(spec)
"foo", version: nil, require: nil, sources: nil, verbose: false, env_local: nil).once.and_return(spec)
expect(app).to 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
expect(manager).to receive(:install_plugin).with(
"foo", version: "bar", require: nil, sources: nil, verbose: false).once.and_return(spec)
"foo", version: "bar", require: nil, sources: nil, verbose: false, env_local: nil).once.and_return(spec)
expect(app).to 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
expect(manager).to receive(:install_plugin).with(
"foo", version: "bar", require: "baz", sources: nil, verbose: false).once.and_return(spec)
"foo", version: "bar", require: "baz", sources: nil, verbose: false, env_local: nil).once.and_return(spec)
expect(app).to 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
expect(manager).to receive(:install_plugin).with(
"foo", version: nil, require: nil, sources: ["foo"], verbose: false).once.and_return(spec)
"foo", version: nil, require: nil, sources: ["foo"], verbose: false, env_local: nil).once.and_return(spec)
expect(app).to receive(:call).with(env).once

View File

@ -15,7 +15,7 @@ describe VagrantPlugins::CommandPlugin::Action::UninstallPlugin do
end
it "uninstalls the specified plugin" do
expect(manager).to receive(:uninstall_plugin).with("bar").ordered
expect(manager).to receive(:uninstall_plugin).with("bar", any_args).ordered
expect(app).to receive(:call).ordered
env[:plugin_name] = "bar"

View File

@ -0,0 +1,129 @@
require "tmpdir"
require_relative "../base"
require "vagrant/bundler"
describe Vagrant::Bundler do
include_context "unit"
let(:iso_env) { isolated_environment }
let(:env) { iso_env.create_vagrant_env }
before do
@tmpdir = Dir.mktmpdir("vagrant-bundler-test")
@vh = ENV["VAGRANT_HOME"]
ENV["VAGRANT_HOME"] = @tmpdir
end
after do
ENV["VAGRANT_HOME"] = @vh
FileUtils.rm_rf(@tmpdir)
end
it "should isolate gem path based on Ruby version" do
expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)
end
it "should not have an env_plugin_gem_path by default" do
expect(subject.env_plugin_gem_path).to be_nil
end
describe "#deinit" do
it "should provide method for backwards compatibility" do
subject.deinit
end
end
describe "#install" do
let(:plugins){ {"my-plugin" => {"gem_version" => "> 0"}} }
it "should pass plugin information hash to internal install" do
expect(subject).to receive(:internal_install).with(plugins, any_args)
subject.install(plugins)
end
it "should not include any update plugins" do
expect(subject).to receive(:internal_install).with(anything, nil, any_args)
subject.install(plugins)
end
it "should flag local when local is true" do
expect(subject).to receive(:internal_install).with(any_args, env_local: true)
subject.install(plugins, true)
end
it "should not flag local when local is not set" do
expect(subject).to receive(:internal_install).with(any_args, env_local: false)
subject.install(plugins)
end
end
describe "#install_local" do
let(:plugin_source){ double("plugin_source", spec: plugin_spec) }
let(:plugin_spec){ double("plugin_spec", name: plugin_name, version: plugin_version) }
let(:plugin_name){ "PLUGIN_NAME" }
let(:plugin_version){ "1.0.0" }
let(:plugin_path){ "PLUGIN_PATH" }
let(:sources){ "SOURCES" }
before do
allow(Gem::Source::SpecificFile).to receive(:new).and_return(plugin_source)
allow(subject).to receive(:internal_install)
end
it "should return plugin gem specification" do
expect(subject.install_local(plugin_path)).to eq(plugin_spec)
end
it "should set custom sources" do
expect(subject).to receive(:internal_install) do |info, update, opts|
expect(info[plugin_name]["sources"]).to eq(sources)
end
subject.install_local(plugin_path, sources: sources)
end
it "should not set the update parameter" do
expect(subject).to receive(:internal_install) do |info, update, opts|
expect(update).to be_nil
end
subject.install_local(plugin_path)
end
it "should not set plugin as environment local by default" do
expect(subject).to receive(:internal_install) do |info, update, opts|
expect(opts[:env_local]).to be_falsey
end
subject.install_local(plugin_path)
end
it "should set if plugin is environment local" do
expect(subject).to receive(:internal_install) do |info, update, opts|
expect(opts[:env_local]).to be_truthy
end
subject.install_local(plugin_path, env_local: true)
end
end
describe "#update" do
let(:plugins){ :plugins }
let(:specific){ [] }
after{ subject.update(plugins, specific) }
it "should mark update as true" do
expect(subject).to receive(:internal_install) do |info, update, opts|
expect(update).to be_truthy
end
end
context "with specific plugins named" do
let(:specific){ ["PLUGIN_NAME"] }
it "should set update to specific names" do
expect(subject).to receive(:internal_install) do |info, update, opts|
expect(update[:gems]).to eq(specific)
end
end
end
end
end

View File

@ -398,7 +398,13 @@ describe Vagrant::Machine do
callable = lambda { |_env| }
allow(provider).to receive(:action).with(action_name).and_return(callable)
allow(Vagrant::Plugin::Manager.instance).to receive(:installed_plugins)
# The first call here is to allow the environment to setup with attempting
# to load a plugin that does not exist
expect(Vagrant::Plugin::Manager.instance).to receive(:installed_plugins)
.and_return({})
expect(Vagrant::Plugin::Manager.instance).to receive(:installed_plugins)
.and_return({"vagrant-triggers"=>"stuff"})
expect(instance.instance_variable_get(:@triggers)).not_to receive(:fire_triggers)

View File

@ -26,13 +26,98 @@ describe Vagrant::Plugin::Manager do
subject { described_class.new(path) }
describe "#globalize!" do
let(:plugins) { double("plugins") }
before do
allow(subject).to receive(:bundler_init)
allow(subject).to receive(:installed_plugins).and_return(plugins)
end
it "should init bundler with installed plugins" do
expect(subject).to receive(:bundler_init).with(plugins)
subject.globalize!
end
it "should return installed plugins" do
expect(subject.globalize!).to eq(plugins)
end
end
describe "#localize!" do
let(:env) { double("env", local_data_path: local_data_path) }
let(:local_data_path) { double("local_data_path") }
let(:plugins) { double("plugins") }
let(:state_file) { double("state_file", installed_plugins: plugins) }
before do
allow(Vagrant::Plugin::StateFile).to receive(:new).and_return(state_file)
allow(bundler).to receive(:environment_path=)
allow(local_data_path).to receive(:join).and_return(local_data_path)
allow(subject).to receive(:bundler_init)
end
context "without local data path defined" do
let(:local_data_path) { nil }
it "should not do any initialization" do
expect(subject).not_to receive(:bundler_init)
subject.localize!(env)
end
it "should return nil" do
expect(subject.localize!(env)).to be_nil
end
end
it "should run bundler initialization" do
expect(subject).to receive(:bundler_init).with(plugins)
subject.localize!(env)
end
it "should return plugins" do
expect(subject.localize!(env)).to eq(plugins)
end
end
describe "#bundler_init" do
let(:plugins) { {"plugin_name" => {}} }
before do
allow(Vagrant).to receive(:plugins_init?).and_return(true)
allow(bundler).to receive(:init!)
end
it "should init the bundler instance with plugins" do
expect(bundler).to receive(:init!).with(plugins)
subject.bundler_init(plugins)
end
it "should return nil" do
expect(subject.bundler_init(plugins)).to be_nil
end
context "with plugin init disabled" do
before { expect(Vagrant).to receive(:plugins_init?).and_return(false) }
it "should return nil" do
expect(subject.bundler_init(plugins)).to be_nil
end
it "should not init the bundler instance" do
expect(bundler).not_to receive(:init!).with(plugins)
subject.bundler_init(plugins)
end
end
end
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"
expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|
expect(plugins).to have_key("foo")
expect(local).to be(false)
expect(local).to be_falsey
}.and_return(specs)
expect(bundler).to receive(:clean)
@ -95,7 +180,7 @@ describe Vagrant::Plugin::Manager do
expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eql(">= 0.1.0")
expect(local).to be(false)
expect(local).to be_falsey
}.and_return(specs)
expect(bundler).to receive(:clean)
@ -110,7 +195,7 @@ describe Vagrant::Plugin::Manager do
expect(bundler).to receive(:install).once.with(any_args) { |plugins, local|
expect(plugins).to have_key("foo")
expect(plugins["foo"]["gem_version"]).to eql("0.1.0")
expect(local).to be(false)
expect(local).to be_falsey
}.and_return(specs)
expect(bundler).to receive(:clean)
@ -140,6 +225,8 @@ describe Vagrant::Plugin::Manager do
end
it "masks bundler errors with our own error" do
sf = Vagrant::Plugin::StateFile.new(path)
sf.add_plugin("foo")
expect(bundler).to receive(:clean).and_raise(Gem::InstallError)
expect { subject.uninstall_plugin("foo") }.

View File

@ -32,6 +32,7 @@ describe Vagrant::Plugin::StateFile do
"require" => "",
"sources" => [],
"installed_gem_version" => nil,
"env_local" => false,
})
end

View File

@ -45,6 +45,9 @@ $ vagrant plugin expunge --reinstall
This command accepts optional command-line flags:
* `--force` - Do not prompt for confirmation prior to removal
* `--global-only` - Only expunge global plugins
* `--local` - Include plugins in local project
* `--local-only` - Only expunge local project plugins
* `--reinstall` - Attempt to reinstall plugins after removal
# Plugin Install
@ -79,6 +82,8 @@ This command accepts optional command-line flags:
Most of the time, this is correct. If the plugin you are installing has
another entrypoint, this flag can be used to specify it.
* `--local` - Install plugin to the local Vagrant project only.
* `--plugin-clean-sources` - Clears all sources that have been defined so
far. This is an advanced feature. The use case is primarily for corporate
firewalls that prevent access to RubyGems.org.
@ -111,6 +116,10 @@ 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.
This command accepts optional command-line flags:
* `--local` - Include local project plugins.
# Plugin Repair
Vagrant may fail to properly initialize user installed custom plugins. This can
@ -121,6 +130,10 @@ to automatically repair the problem.
If automatic repair is not successful, refer to the [expunge](#plugin-expunge)
command
This command accepts optional command-line flags:
* `--local` - Repair local project plugins.
# Plugin Uninstall
**Command: `vagrant plugin uninstall <name> [<name2> <name3> ...]`**
@ -130,6 +143,10 @@ plugin will also be uninstalled assuming no other plugin needs them.
If multiple plugins are given, multiple plugins will be uninstalled.
This command accepts optional command-line flags:
* `--local` - Uninstall plugin from local project.
# Plugin Update
**Command: `vagrant plugin update [<name>]`**
@ -142,3 +159,7 @@ 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.
This command accepts optional command-line flags:
* `--local` - Update plugin from local project.

View File

@ -161,6 +161,19 @@ may be desirable to ignore inaccessible sources and continue with the
plugin installation. Enabling this value will cause Vagrant to simply log
the plugin source error and continue.
## `VAGRANT_INSTALL_LOCAL_PLUGINS`
If this is set to any value, Vagrant will not prompt for confirmation
prior to installing local plugins which have been defined within the
local Vagrantfile.
## `VAGRANT_LOCAL_PLUGINS_LOAD`
If this is set Vagrant will not stub the Vagrantfile when running
`vagrant plugin` commands. When this environment variable is set the
`--local` flag will not be required by `vagrant plugin` commands to
enable local project plugins.
## `VAGRANT_NO_PARALLEL`
If this is set, Vagrant will not perform any parallel operations (such as

View File

@ -22,6 +22,38 @@ the host. Vagrant needs to know this information in order to perform some
host-specific things, such as preparing NFS folders if they're enabled.
You should only manually set this if auto-detection fails.
`config.vagrant.plugins` - (string, array, hash) - Define plugin, list of
plugins, or definition of plugins to install for the local project. Vagrant
will require these plugins be installed and available for the project. If
the plugins are not available, it will attempt to automatically install
them into the local project. When requiring a single plugin, a string can
be provided:
```ruby
config.vagrant.plugins = "vagrant-plugin"
```
If multiple plugins are required, they can be provided as an array:
```ruby
config.vagrant.plugins = ["vagrant-plugin", "vagrant-other-plugin"]
```
Plugins can also be defined as a Hash, which supports setting extra options
for the plugins. When a Hash is used, the key is the name of the plugin, and
the value is a Hash of options for the plugin. For example, to set an explicit
version of a plugin to install:
```ruby
config.vagrant.plugins = {"vagrant-scp" => {"version" => "1.0.0"}}
```
Supported options are:
* `entry_point` - Path for Vagrant to load plugin
* `sources` - Custom sources for downloading plugin
* `version` - Version constraint for plugin
`config.vagrant.sensitive` - (string, array) - Value or list of values that
should not be displayed in Vagrant's output. Value(s) will be removed from
Vagrant's normal UI output as well as logger output.