Vagrant Environment isolated plugins

Adds support for plugins isolated to a specific `Vagrant::Environment`
which can be managed by the vagrant plugin command using the the
--local flag.
This commit is contained in:
Chris Roberts 2018-05-10 12:03:21 -07:00
parent 1f279445ac
commit 40f4e6f67e
15 changed files with 452 additions and 116 deletions

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))
@ -120,7 +140,7 @@ module Vagrant
}
}
@logger.debug("Installing local plugin - #{plugin_info}")
internal_install(plugin_info, {})
internal_install(plugin_info, nil, local: opts[:local])
plugin_source.spec
end
@ -129,14 +149,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 +183,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 +198,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[: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 +361,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[: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 +449,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

@ -146,6 +146,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 +164,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 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 +912,55 @@ 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(", ")))
answer = nil
until ["y", "n"].include?(answer)
answer = ui.ask(I18n.t("vagrant.plugins.local.request_plugin_install") + ": ")
answer.strip.downcase!
end
if answer == "n"
raise Errors::PluginMissingLocalError,
plugins: needs_install.sort.join(", ")
end
needs_install.each do |name|
ui.info(I18n.t("vagrant.commands.plugin.installing", name: name))
spec = Plugin::Manager.instance.install_plugin(name,
{sources: Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup}.merge(
config_plugins[name]).merge(local: true))
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,68 @@ 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
def globalize!
@logger.debug("Enabling globalized plugins")
if !Vagrant.plugins_init?
@logger.warn("Plugin initialization is disabled")
return {}
end
plugins = Vagrant::Plugin::Manager.instance.installed_plugins
bundler_init(plugins)
plugins
end
# @param [Environment] env Vagrant environment
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
def bundler_init(plugins)
@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 +96,10 @@ module Vagrant
# @param [String] name Name of the plugin (gem)
# @return [Gem::Specification]
def install_plugin(name, **opts)
local = false
if opts[: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 +117,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[:local]).each do |spec|
next if spec.name != name
next if result && result.version >= spec.version
result = spec
@ -75,18 +133,20 @@ module Vagrant
result = local_spec
end
# Add the plugin to the state file
@user_file.add_plugin(
plugin_file = opts[:local] ? @local_file : @user_file
plugin_file.add_plugin(
result.name,
version: opts[:version],
require: opts[:require],
sources: opts[:sources],
local: !!opts[: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 +157,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 +165,18 @@ module Vagrant
end
end
@user_file.remove_plugin(name)
if opts[:local] && @local_file.nil?
raise Errors::PluginNoLocalError
end
plugin_file = opts[: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 +185,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[:local] && @local_file.nil?
raise Errors::PluginNoLocalError
end
plugin_file = opts[:local] ? @local_file : @user_file
result = Vagrant::Bundler.instance.update(plugin_list.installed_plugins, specific)
plugin_list.installed_plugins.each do |name, info|
matching_spec = result.detect{|s| s.name == name}
info = Hash[
info.map do |key, value|
@ -124,7 +201,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 +225,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 +273,48 @@ module Vagrant
installed_map.values
end
def load_plugins(plugins)
if !Vagrant.plugins_enabled?
@logger.warn("Plugin loading is disabled")
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
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],
"local" => opts[:local]
}
save!

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[:local]
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
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]
local = env[:plugin_local]
# Install the gem
plugin_name_label = plugin_name
@ -32,6 +33,7 @@ module VagrantPlugins
require: entrypoint,
sources: sources,
verbose: !!env[:plugin_verbose],
local: 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["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

@ -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], local: env[:local])
@app.call(env)
end

View File

@ -16,6 +16,10 @@ module VagrantPlugins
options[:force] = force
end
o.on("--local", "Remove local project plugins only") do |l|
options[:local] = 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[: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[: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_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_local: options[:local]
)
end
end
# Success, exit status 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[: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, local: options[:local])
end
# 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: