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:
parent
1f279445ac
commit
40f4e6f67e
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"] != ""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue