From 40f4e6f67ec2b3e0d5d886a63909c6fc2be9c947 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 10 May 2018 12:03:21 -0700 Subject: [PATCH 01/27] 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. --- lib/vagrant.rb | 70 +-------- lib/vagrant/bundler.rb | 94 ++++++++++-- lib/vagrant/environment.rb | 63 ++++++++ lib/vagrant/errors.rb | 8 + lib/vagrant/plugin/manager.rb | 144 ++++++++++++++++-- lib/vagrant/plugin/state_file.rb | 7 +- .../commands/plugin/action/expunge_plugins.rb | 20 ++- plugins/commands/plugin/action/install_gem.rb | 2 + .../commands/plugin/action/list_plugins.rb | 11 +- .../plugin/action/uninstall_plugin.rb | 2 +- plugins/commands/plugin/command/expunge.rb | 4 + plugins/commands/plugin/command/install.rb | 60 ++++++-- plugins/commands/plugin/command/uninstall.rb | 7 +- plugins/kernel_v2/config/vagrant.rb | 39 +++++ templates/locales/en.yml | 37 ++++- 15 files changed, 452 insertions(+), 116 deletions(-) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index c775b5f37..64183c092 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -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 diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 301e40e37..18b734064 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -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] 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] 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 diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 60eb4a685..946b5c1a8 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -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. # diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 7eef2d6d9..8fe7d9e71 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -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 diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 64891bd37..4487b143f 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -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 diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index 85db50b92..c8e118d4b 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -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! diff --git a/plugins/commands/plugin/action/expunge_plugins.rb b/plugins/commands/plugin/action/expunge_plugins.rb index 072ef81a6..bb011c544 100644 --- a/plugins/commands/plugin/action/expunge_plugins.rb +++ b/plugins/commands/plugin/action/expunge_plugins.rb @@ -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) diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index 320f95e83..2612a4db4 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -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 diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index f5f8d41bf..3eb8aeb00 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -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"] != "" diff --git a/plugins/commands/plugin/action/uninstall_plugin.rb b/plugins/commands/plugin/action/uninstall_plugin.rb index 0aa9b4a9a..8e988d1aa 100644 --- a/plugins/commands/plugin/action/uninstall_plugin.rb +++ b/plugins/commands/plugin/action/uninstall_plugin.rb @@ -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 diff --git a/plugins/commands/plugin/command/expunge.rb b/plugins/commands/plugin/command/expunge.rb index 6ec16ea9d..a59b68a7f 100644 --- a/plugins/commands/plugin/command/expunge.rb +++ b/plugins/commands/plugin/command/expunge.rb @@ -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 diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb index 441500387..6e9ae9428 100644 --- a/plugins/commands/plugin/command/install.rb +++ b/plugins/commands/plugin/command/install.rb @@ -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 diff --git a/plugins/commands/plugin/command/uninstall.rb b/plugins/commands/plugin/command/uninstall.rb index ec032a5d9..57534358f 100644 --- a/plugins/commands/plugin/command/uninstall.rb +++ b/plugins/commands/plugin/command/uninstall.rb @@ -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 [ ...] [-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 diff --git a/plugins/kernel_v2/config/vagrant.rb b/plugins/kernel_v2/config/vagrant.rb index 951383db7..2e097d61b 100644 --- a/plugins/kernel_v2/config/vagrant.rb +++ b/plugins/kernel_v2/config/vagrant.rb @@ -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 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 0fd99ccc8..6c28b16d8 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -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: From 84c0aafe7182f77ab538b10a599913416c4dd2a9 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 22 Jun 2018 10:06:41 -0700 Subject: [PATCH 02/27] Support non-interactive local plugin install --- lib/vagrant/environment.rb | 18 +++--- lib/vagrant/plugin/manager.rb | 9 ++- lib/vagrant/plugin/state_file.rb | 2 +- lib/vagrant/shared_helpers.rb | 12 ++++ .../commands/plugin/action/expunge_plugins.rb | 4 +- .../plugin/action/expunge_plugins_test.rb | 57 +++++++++++++++++-- .../plugin/action/install_gem_test.rb | 8 +-- .../plugin/action/uninstall_plugin_test.rb | 2 +- test/unit/vagrant/machine_test.rb | 8 ++- test/unit/vagrant/plugin/manager_test.rb | 8 ++- test/unit/vagrant/plugin/state_file_test.rb | 1 + .../other/environmental-variables.html.md | 6 ++ 12 files changed, 109 insertions(+), 26 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 946b5c1a8..4d2fb6d56 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -938,14 +938,16 @@ module Vagrant 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(", ") + if !Vagrant.auto_install_local_plugins? + 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 end needs_install.each do |name| ui.info(I18n.t("vagrant.commands.plugin.installing", name: name)) diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 4487b143f..d5d2d3de8 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -192,8 +192,8 @@ module Vagrant 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| + 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| @@ -280,6 +280,11 @@ module Vagrant 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| diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index c8e118d4b..2e475a87a 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -41,7 +41,7 @@ module Vagrant "require" => opts[:require] || "", "sources" => opts[:sources] || [], "installed_gem_version" => opts[:installed_gem_version], - "local" => opts[:local] + "local" => !!opts[:local] } save! diff --git a/lib/vagrant/shared_helpers.rb b/lib/vagrant/shared_helpers.rb index 0bdcd7fa5..ada85f3ba 100644 --- a/lib/vagrant/shared_helpers.rb +++ b/lib/vagrant/shared_helpers.rb @@ -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 diff --git a/plugins/commands/plugin/action/expunge_plugins.rb b/plugins/commands/plugin/action/expunge_plugins.rb index bb011c544..8347a9eef 100644 --- a/plugins/commands/plugin/action/expunge_plugins.rb +++ b/plugins/commands/plugin/action/expunge_plugins.rb @@ -47,13 +47,13 @@ module VagrantPlugins # Do not include global paths if local only if !env[:local] - files << Vagrant::Plugin::Manager.instance.user_file.path + files << Vagrant::Plugin::Manager.instance.user_file dirs << Vagrant::Bundler.instance.plugin_gem_path end # Add local paths if they exist if Vagrant::Plugin::Manager.instance.local_file - files << Vagrant::Plugin::Manager.instance.local_file.path + files << Vagrant::Plugin::Manager.instance.local_file dirs << Vagrant::Bundler.instance.env_plugin_gem_path end diff --git a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb index a4b0a4644..c271eb2b3 100644 --- a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb +++ b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb @@ -5,21 +5,28 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do let(:home_path){ '/fake/file/path/.vagrant.d' } let(:gems_path){ "#{home_path}/gems" } let(:force){ true } + let(:local){ false } let(:env) {{ ui: Vagrant::UI::Silent.new, home_path: home_path, gems_path: gems_path, - force: force + force: force, + local: local }} - let(:manager) { double("manager") } + let(:user_file) { double("user_file", 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 +35,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 +44,8 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do end it "should delete all plugins" do + expect(user_file).to receive(:delete) + expect(plugin_gem_path).to receive(:rmtree) subject.call(env) end @@ -60,5 +70,44 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do end end end + + context "when local option is set" do + let(:local) { true } + + it "should not delete plugins" do + expect(user_file).not_to receive(:delete) + expect(plugin_gem_path).not_to receive(:rmtree) + subject.call(env) + end + end + + context "when local plugins exist" do + let(:local_file) { double("local_file", 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).to receive(:delete) + expect(local_file).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(:local) { true } + + it "should delete local plugins" do + expect(local_file).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).not_to receive(:delete) + expect(plugin_gem_path).not_to receive(:rmtree) + subject.call(env) + end + end + end end end diff --git a/test/unit/plugins/commands/plugin/action/install_gem_test.rb b/test/unit/plugins/commands/plugin/action/install_gem_test.rb index 908473577..7c88695e9 100644 --- a/test/unit/plugins/commands/plugin/action/install_gem_test.rb +++ b/test/unit/plugins/commands/plugin/action/install_gem_test.rb @@ -18,7 +18,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should install the plugin" do spec = Gem::Specification.new 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, 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, 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, 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, local: nil).once.and_return(spec) expect(app).to receive(:call).with(env).once diff --git a/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb b/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb index 757d75be0..39b2790aa 100644 --- a/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb +++ b/test/unit/plugins/commands/plugin/action/uninstall_plugin_test.rb @@ -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" diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index 0e22b8a06..2cc904d88 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -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) diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index de42c7ec8..a5d909ce5 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -32,7 +32,7 @@ describe Vagrant::Plugin::Manager do 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 +95,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 +110,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 +140,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") }. diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index 57818064d..efdd8cbe3 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -32,6 +32,7 @@ describe Vagrant::Plugin::StateFile do "require" => "", "sources" => [], "installed_gem_version" => nil, + "local" => false, }) end diff --git a/website/source/docs/other/environmental-variables.html.md b/website/source/docs/other/environmental-variables.html.md index 35fbbb8c0..b38ae52a2 100644 --- a/website/source/docs/other/environmental-variables.html.md +++ b/website/source/docs/other/environmental-variables.html.md @@ -161,6 +161,12 @@ 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_NO_PARALLEL` If this is set, Vagrant will not perform any parallel operations (such as From b71054502e41f840bc61d30b1b38017d78048382 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Mon, 25 Jun 2018 15:57:32 -0700 Subject: [PATCH 03/27] Update local usage to env_local for clarity --- lib/vagrant/bundler.rb | 11 ++++++----- plugins/commands/plugin/action/expunge_plugins.rb | 2 +- plugins/commands/plugin/action/install_gem.rb | 12 ++++++------ plugins/commands/plugin/action/uninstall_plugin.rb | 2 +- plugins/commands/plugin/command/expunge.rb | 2 +- plugins/commands/plugin/command/install.rb | 8 ++++---- plugins/commands/plugin/command/uninstall.rb | 4 ++-- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 18b734064..4f1d145d2 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -121,9 +121,10 @@ module Vagrant # Installs the list of plugins. # # @param [Hash] plugins + # @param [Boolean] env_local Environment local plugin install # @return [Array] - 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. @@ -140,7 +141,7 @@ module Vagrant } } @logger.debug("Installing local plugin - #{plugin_info}") - internal_install(plugin_info, nil, local: opts[:local]) + internal_install(plugin_info, nil, env_local: opts[:env_local]) plugin_source.spec end @@ -206,7 +207,7 @@ module Vagrant if env_plugin_gem_path # If we are cleaning locally, remove any global specs. If # not, remove any local specs - if opts[:local] + 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) @@ -361,7 +362,7 @@ 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) - install_path = extra[:local] ? env_plugin_gem_path : plugin_gem_path + 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?, diff --git a/plugins/commands/plugin/action/expunge_plugins.rb b/plugins/commands/plugin/action/expunge_plugins.rb index 8347a9eef..97d5e907c 100644 --- a/plugins/commands/plugin/action/expunge_plugins.rb +++ b/plugins/commands/plugin/action/expunge_plugins.rb @@ -46,7 +46,7 @@ module VagrantPlugins dirs = [] # Do not include global paths if local only - if !env[:local] + if !env[:env_local] files << Vagrant::Plugin::Manager.instance.user_file dirs << Vagrant::Bundler.instance.plugin_gem_path end diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index 2612a4db4..6ffabb5ab 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -18,7 +18,7 @@ module VagrantPlugins plugin_name = env[:plugin_name] sources = env[:plugin_sources] version = env[:plugin_version] - local = env[:plugin_local] + env_local = env[:plugin_env_local] # Install the gem plugin_name_label = plugin_name @@ -29,11 +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], - local: local + version: version, + require: entrypoint, + sources: sources, + verbose: !!env[:plugin_verbose], + env_local: env_local ) # Record it so we can uninstall if something goes wrong diff --git a/plugins/commands/plugin/action/uninstall_plugin.rb b/plugins/commands/plugin/action/uninstall_plugin.rb index 8e988d1aa..5518581f0 100644 --- a/plugins/commands/plugin/action/uninstall_plugin.rb +++ b/plugins/commands/plugin/action/uninstall_plugin.rb @@ -15,7 +15,7 @@ module VagrantPlugins name: env[:plugin_name])) manager = Vagrant::Plugin::Manager.instance - manager.uninstall_plugin(env[:plugin_name], local: env[:local]) + manager.uninstall_plugin(env[:plugin_name], env_local: env[:env_local]) @app.call(env) end diff --git a/plugins/commands/plugin/command/expunge.rb b/plugins/commands/plugin/command/expunge.rb index a59b68a7f..afe4e12ea 100644 --- a/plugins/commands/plugin/command/expunge.rb +++ b/plugins/commands/plugin/command/expunge.rb @@ -17,7 +17,7 @@ module VagrantPlugins end o.on("--local", "Remove local project plugins only") do |l| - options[:local] = l + options[:env_local] = l end o.on("--reinstall", "Reinstall current plugins after expunge") do |reinstall| diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb index 6e9ae9428..3e3e907ac 100644 --- a/plugins/commands/plugin/command/install.rb +++ b/plugins/commands/plugin/command/install.rb @@ -20,7 +20,7 @@ module VagrantPlugins build_install_opts(o, options) o.on("--local", "Install plugin for local project only") do |l| - options[:local] = l + options[:env_local] = l end o.on("--verbose", "Enable verbose output for plugin installation") do |v| @@ -33,7 +33,7 @@ module VagrantPlugins return if !argv if argv.length < 1 - raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if !options[:local] + raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if !options[:env_local] errors = @env.vagrantfile.config.vagrant.validate(nil) if !errors["vagrant"].empty? @@ -61,7 +61,7 @@ module VagrantPlugins plugin_version: info[:version], plugin_sources: info[:sources] || Vagrant::Bundler::DEFAULT_GEM_SOURCES.dup, plugin_name: name, - plugin_local: true + plugin_env_local: true ) end else @@ -73,7 +73,7 @@ module VagrantPlugins plugin_sources: options[:plugin_sources], plugin_name: name, plugin_verbose: options[:verbose], - plugin_local: options[:local] + plugin_env_local: options[:env_local] ) end end diff --git a/plugins/commands/plugin/command/uninstall.rb b/plugins/commands/plugin/command/uninstall.rb index 57534358f..341f4f893 100644 --- a/plugins/commands/plugin/command/uninstall.rb +++ b/plugins/commands/plugin/command/uninstall.rb @@ -12,7 +12,7 @@ module VagrantPlugins o.banner = "Usage: vagrant plugin uninstall [ ...] [-h]" o.on("--local", "Remove plugin from local project") do |l| - options[:local] = l + options[:env_local] = l end end @@ -23,7 +23,7 @@ module VagrantPlugins # Uninstall the gems argv.each do |gem| - action(Action.action_uninstall, plugin_name: gem, local: options[:local]) + action(Action.action_uninstall, plugin_name: gem, env_local: options[:env_local]) end # Success, exit status 0 From a410b0af5170c55f9efa6ec32cf5843ba99e6918 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Mon, 25 Jun 2018 15:57:52 -0700 Subject: [PATCH 04/27] Start adding bundler coverage --- test/unit/vagrant/bundler_test.rb | 130 ++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 test/unit/vagrant/bundler_test.rb diff --git a/test/unit/vagrant/bundler_test.rb b/test/unit/vagrant/bundler_test.rb new file mode 100644 index 000000000..9f5c6638b --- /dev/null +++ b/test/unit/vagrant/bundler_test.rb @@ -0,0 +1,130 @@ +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 } + let(:subject) { Vagrant::Bundler.instance } + + 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 From 3223737734fe7b57bc5a4c7c5d8403abfa0f4120 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 17 Jul 2018 14:22:26 -0700 Subject: [PATCH 05/27] Use env_local consistently internally --- lib/vagrant/bundler.rb | 1 + lib/vagrant/environment.rb | 2 +- lib/vagrant/plugin/manager.rb | 31 ++++++++++++++----- lib/vagrant/plugin/state_file.rb | 2 +- .../commands/plugin/action/list_plugins.rb | 2 +- .../plugin/action/expunge_plugins_test.rb | 8 ++--- .../plugin/action/install_gem_test.rb | 8 ++--- test/unit/vagrant/plugin/state_file_test.rb | 2 +- 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index 4f1d145d2..c1613f612 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -111,6 +111,7 @@ module Vagrant end Gem::Specification.reset + nil end # Removes any temporary files created by init diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 4d2fb6d56..845036cf7 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -171,7 +171,7 @@ module Vagrant plugins = process_configured_plugins end - # Load any local plugins + # Load any environment local plugins Vagrant::Plugin::Manager.instance.load_plugins(plugins) plugins = Vagrant::Plugin::Manager.instance.globalize! diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index d5d2d3de8..21b60c575 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -43,6 +43,9 @@ module Vagrant @local_file = nil end + # Enable global plugins + # + # @return [Hash] list of plugins def globalize! @logger.debug("Enabling globalized plugins") if !Vagrant.plugins_init? @@ -55,7 +58,10 @@ module Vagrant 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") @@ -67,6 +73,10 @@ module Vagrant end end + # Initialize bundler with given plugins + # + # @param [Hash] plugins List of plugins + # @return [nil] def bundler_init(plugins) @logger.info("Plugins:") plugins.each do |plugin_name, plugin_info| @@ -96,7 +106,7 @@ module Vagrant # @param [String] name Name of the plugin (gem) # @return [Gem::Specification] def install_plugin(name, **opts) - if opts[:local] && @local_file.nil? + if opts[:env_local] && @local_file.nil? raise Errors::PluginNoLocalError end @@ -117,7 +127,7 @@ module Vagrant if local_spec.nil? result = nil install_lambda = lambda do - Vagrant::Bundler.instance.install(plugins, opts[: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 @@ -133,13 +143,13 @@ module Vagrant result = local_spec end # Add the plugin to the state file - plugin_file = opts[:local] ? @local_file : @user_file + plugin_file = opts[:env_local] ? @local_file : @user_file plugin_file.add_plugin( result.name, version: opts[:version], require: opts[:require], sources: opts[:sources], - local: !!opts[:local], + env_local: !!opts[:env_local], installed_gem_version: result.version.to_s ) @@ -165,11 +175,11 @@ module Vagrant end end - if opts[:local] && @local_file.nil? + if opts[:env_local] && @local_file.nil? raise Errors::PluginNoLocalError end - plugin_file = opts[:local] ? @local_file : @user_file + plugin_file = opts[:env_local] ? @local_file : @user_file if !plugin_file.has_plugin?(name) raise Errors::PluginNotInstalled, @@ -186,11 +196,11 @@ module Vagrant # Updates all or a specific set of plugins. def update_plugins(specific, **opts) - if opts[:local] && @local_file.nil? + if opts[:env_local] && @local_file.nil? raise Errors::PluginNoLocalError end - plugin_file = opts[:local] ? @local_file : @user_file + 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| @@ -274,6 +284,10 @@ 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") @@ -319,6 +333,7 @@ module Vagrant end raise Vagrant::Errors::PluginLoadError, message: err.to_s end + nil end end end diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index 2e475a87a..c6872d4fd 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -41,7 +41,7 @@ module Vagrant "require" => opts[:require] || "", "sources" => opts[:sources] || [], "installed_gem_version" => opts[:installed_gem_version], - "local" => !!opts[:local] + "env_local" => !!opts[:env_local] } save! diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 3eb8aeb00..4ab8f3982 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -38,7 +38,7 @@ module VagrantPlugins meta = ", global" if plugin meta = ", system" if plugin["system"] - meta = ", local" if plugin["local"] + meta = ", local" if plugin["env_local"] end env[:ui].info "#{spec.name} (#{spec.version}#{meta})" env[:ui].machine("plugin-name", spec.name) diff --git a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb index c271eb2b3..a758afbc0 100644 --- a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb +++ b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb @@ -5,13 +5,13 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do let(:home_path){ '/fake/file/path/.vagrant.d' } let(:gems_path){ "#{home_path}/gems" } let(:force){ true } - let(:local){ false } + let(:env_local){ false } let(:env) {{ ui: Vagrant::UI::Silent.new, home_path: home_path, gems_path: gems_path, force: force, - local: local + env_local: env_local }} let(:user_file) { double("user_file", exist?: true, delete: true) } @@ -72,7 +72,7 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do end context "when local option is set" do - let(:local) { true } + let(:env_local) { true } it "should not delete plugins" do expect(user_file).not_to receive(:delete) @@ -94,7 +94,7 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do end context "when local option is set" do - let(:local) { true } + let(:env_local) { true } it "should delete local plugins" do expect(local_file).to receive(:delete) diff --git a/test/unit/plugins/commands/plugin/action/install_gem_test.rb b/test/unit/plugins/commands/plugin/action/install_gem_test.rb index 7c88695e9..435595041 100644 --- a/test/unit/plugins/commands/plugin/action/install_gem_test.rb +++ b/test/unit/plugins/commands/plugin/action/install_gem_test.rb @@ -18,7 +18,7 @@ describe VagrantPlugins::CommandPlugin::Action::InstallGem do it "should install the plugin" do spec = Gem::Specification.new expect(manager).to receive(:install_plugin).with( - "foo", version: nil, require: nil, sources: nil, verbose: false, local: nil).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, local: nil).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, local: nil).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, local: nil).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 diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index efdd8cbe3..9546db7bc 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -32,7 +32,7 @@ describe Vagrant::Plugin::StateFile do "require" => "", "sources" => [], "installed_gem_version" => nil, - "local" => false, + "env_local" => false, }) end From 78bf131dc8a0df551e857d8cd7813124d6afbceb Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 17 Jul 2018 14:38:08 -0700 Subject: [PATCH 06/27] Do not use singleton for testing --- test/unit/vagrant/bundler_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/vagrant/bundler_test.rb b/test/unit/vagrant/bundler_test.rb index 9f5c6638b..d27474349 100644 --- a/test/unit/vagrant/bundler_test.rb +++ b/test/unit/vagrant/bundler_test.rb @@ -8,7 +8,6 @@ describe Vagrant::Bundler do let(:iso_env) { isolated_environment } let(:env) { iso_env.create_vagrant_env } - let(:subject) { Vagrant::Bundler.instance } before do @tmpdir = Dir.mktmpdir("vagrant-bundler-test") From 737ef0ededfc5ae0a3ec076a3d7c9f40bd3d7652 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 17 Jul 2018 15:00:38 -0700 Subject: [PATCH 07/27] Allow Vagrantfile to load when using --local --- bin/vagrant | 4 +++- test/unit/bin/vagrant_test.rb | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/vagrant b/bin/vagrant index 83fc5a887..ad4ffa7b5 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -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" } + opts[:vagrantfile_name] = "" + end ENV['VAGRANT_NO_PLUGINS'] = "1" # Only initialize plugins when listing installed plugins if argv[i+1] != "list" diff --git a/test/unit/bin/vagrant_test.rb b/test/unit/bin/vagrant_test.rb index 4b5dfbe5b..df7cc0e0c 100644 --- a/test/unit/bin/vagrant_test.rb +++ b/test/unit/bin/vagrant_test.rb @@ -143,5 +143,14 @@ 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 end end From 8baf7ced38b9cedc99bbd595f98cedc036baad7f Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 17 Jul 2018 15:16:38 -0700 Subject: [PATCH 08/27] Use path of state file, not state file itself --- .../commands/plugin/action/expunge_plugins.rb | 4 ++-- .../plugin/action/expunge_plugins_test.rb | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/plugins/commands/plugin/action/expunge_plugins.rb b/plugins/commands/plugin/action/expunge_plugins.rb index 97d5e907c..3327d57df 100644 --- a/plugins/commands/plugin/action/expunge_plugins.rb +++ b/plugins/commands/plugin/action/expunge_plugins.rb @@ -47,13 +47,13 @@ module VagrantPlugins # Do not include global paths if local only if !env[:env_local] - files << Vagrant::Plugin::Manager.instance.user_file + files << Vagrant::Plugin::Manager.instance.user_file.path dirs << Vagrant::Bundler.instance.plugin_gem_path end # Add local paths if they exist if Vagrant::Plugin::Manager.instance.local_file - files << Vagrant::Plugin::Manager.instance.local_file + files << Vagrant::Plugin::Manager.instance.local_file.path dirs << Vagrant::Bundler.instance.env_plugin_gem_path end diff --git a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb index a758afbc0..4b1d50257 100644 --- a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb +++ b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb @@ -14,7 +14,8 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do env_local: env_local }} - let(:user_file) { double("user_file", exist?: true, delete: true) } + 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) } @@ -44,7 +45,7 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do end it "should delete all plugins" do - expect(user_file).to receive(:delete) + expect(user_file_pathname).to receive(:delete) expect(plugin_gem_path).to receive(:rmtree) subject.call(env) end @@ -75,19 +76,20 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do let(:env_local) { true } it "should not delete plugins" do - expect(user_file).not_to receive(:delete) + expect(user_file_pathname).not_to receive(:delete) expect(plugin_gem_path).not_to receive(:rmtree) subject.call(env) end end context "when local plugins exist" do - let(:local_file) { double("local_file", exist?: true, delete: true) } + 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).to receive(:delete) - expect(local_file).to receive(:delete) + 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) @@ -97,13 +99,13 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do let(:env_local) { true } it "should delete local plugins" do - expect(local_file).to receive(:delete) + 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).not_to receive(:delete) + expect(user_file_pathname).not_to receive(:delete) expect(plugin_gem_path).not_to receive(:rmtree) subject.call(env) end From c605b7d8753dc3c6caf920a7ba49c75008ea427a Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 17 Jul 2018 15:16:53 -0700 Subject: [PATCH 09/27] Update option name for local plugin installation --- lib/vagrant/environment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 845036cf7..8aab2cf6a 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -953,7 +953,7 @@ module Vagrant 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)) + config_plugins[name]).merge(env_local: true)) ui.info(I18n.t("vagrant.commands.plugin.installed", name: spec.name, version: spec.version.to_s)) end From 9d191a2419b977b82a1d3d170581e3f0edc705ee Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 17 Jul 2018 15:17:05 -0700 Subject: [PATCH 10/27] Add local option stub to allow Vagrantfile loading --- plugins/commands/plugin/command/list.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/commands/plugin/command/list.rb b/plugins/commands/plugin/command/list.rb index 7949b5db8..1b850f52a 100644 --- a/plugins/commands/plugin/command/list.rb +++ b/plugins/commands/plugin/command/list.rb @@ -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 plugins"){|_|} end # Parse the options From c5a6790192e86cf3859a3ac26d575deebe3e2a12 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 10:48:22 -0700 Subject: [PATCH 11/27] Only run bundler initialization if plugin initialization is enabled --- lib/vagrant/plugin/manager.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index 21b60c575..d409ba788 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -48,12 +48,7 @@ module Vagrant # @return [Hash] list of plugins 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 + plugins = installed_plugins bundler_init(plugins) plugins end @@ -78,6 +73,11 @@ module Vagrant # @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"] From 3e22764ac88643b4ed45ba12bc81301e6bdf9479 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 10:48:57 -0700 Subject: [PATCH 12/27] Add test coverage on globalize and localize within plugin manager --- test/unit/vagrant/plugin/manager_test.rb | 85 ++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index a5d909ce5..aa6b11b7c 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -26,6 +26,91 @@ 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 } From 1cd8a4b9bec586e001cb51f2717e0fc7c7d8fb2e Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 10:50:06 -0700 Subject: [PATCH 13/27] Allow vagrantfile_name stub disable via environment variable --- bin/vagrant | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/vagrant b/bin/vagrant index ad4ffa7b5..7372a726d 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -44,7 +44,7 @@ argv.each_index do |i| # Do not load plugins when performing plugin operations if arg == "plugin" - if argv.none?{|a| a == "--local" } + if argv.none?{|a| a == "--local" } && !ENV["VAGRANT_LOCAL_PLUGINS_LOAD"] opts[:vagrantfile_name] = "" end ENV['VAGRANT_NO_PLUGINS'] = "1" From 564dff651e69bc216ba9434364994f16574d1524 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 10:50:41 -0700 Subject: [PATCH 14/27] Add coverage on vagrantfile_name stubbing behavior --- test/unit/bin/vagrant_test.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/unit/bin/vagrant_test.rb b/test/unit/bin/vagrant_test.rb index df7cc0e0c..08edcb20e 100644 --- a/test/unit/bin/vagrant_test.rb +++ b/test/unit/bin/vagrant_test.rb @@ -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). @@ -152,5 +155,14 @@ describe "vagrant bin" do 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 From 14edb8f4230cde78c3a50d1856378d792a970c68 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 13:43:52 -0700 Subject: [PATCH 15/27] Add local only and global only flags to plugin expunge command --- .../commands/plugin/action/expunge_plugins.rb | 4 +- plugins/commands/plugin/command/expunge.rb | 10 ++- .../plugin/action/expunge_plugins_test.rb | 67 +++++++++++++++++-- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/plugins/commands/plugin/action/expunge_plugins.rb b/plugins/commands/plugin/action/expunge_plugins.rb index 3327d57df..98b7f6014 100644 --- a/plugins/commands/plugin/action/expunge_plugins.rb +++ b/plugins/commands/plugin/action/expunge_plugins.rb @@ -46,13 +46,13 @@ module VagrantPlugins dirs = [] # Do not include global paths if local only - if !env[:env_local] + if !env[:env_local_only] || env[:global_only] files << Vagrant::Plugin::Manager.instance.user_file.path dirs << Vagrant::Bundler.instance.plugin_gem_path end # Add local paths if they exist - if Vagrant::Plugin::Manager.instance.local_file + 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 diff --git a/plugins/commands/plugin/command/expunge.rb b/plugins/commands/plugin/command/expunge.rb index afe4e12ea..1b23ab02e 100644 --- a/plugins/commands/plugin/command/expunge.rb +++ b/plugins/commands/plugin/command/expunge.rb @@ -16,10 +16,18 @@ module VagrantPlugins options[:force] = force end - o.on("--local", "Remove local project plugins only") do |l| + o.on("--local", "Include local project plugins 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 diff --git a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb index 4b1d50257..a58f38d33 100644 --- a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb +++ b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb @@ -6,12 +6,16 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do 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, - env_local: env_local + env_local: env_local, + env_local_only: env_local_only, + global_only: global_only }} let(:user_file) { double("user_file", path: user_file_pathname) } @@ -75,9 +79,9 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do context "when local option is set" do let(:env_local) { true } - it "should not delete plugins" do - expect(user_file_pathname).not_to receive(:delete) - expect(plugin_gem_path).not_to receive(:rmtree) + it "should delete plugins" do + expect(user_file_pathname).to receive(:delete) + expect(plugin_gem_path).to receive(:rmtree) subject.call(env) end end @@ -104,11 +108,60 @@ describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do 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) + 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 From ef0269c538c1b0cfcb4583383e75de678b552c91 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 13:45:15 -0700 Subject: [PATCH 16/27] Add action method for local plugin repair --- plugins/commands/plugin/action.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 5412b460f..d0f9bf8e9 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -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 From 3fd55dac23a6b22fb4d13f23bb9b7e7f62692ebd Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 13:46:17 -0700 Subject: [PATCH 17/27] Add local plugin repair support. Update global repair implementation. --- .../commands/plugin/action/repair_plugins.rb | 27 +++++++++++++++++-- plugins/commands/plugin/command/repair.rb | 12 ++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/plugins/commands/plugin/action/repair_plugins.rb b/plugins/commands/plugin/action/repair_plugins.rb index 9745d378b..0d13907dd 100644 --- a/plugins/commands/plugin/action/repair_plugins.rb +++ b/plugins/commands/plugin/action/repair_plugins.rb @@ -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,27 @@ 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) + 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 + # Continue + @app.call(env) + end + end end end end diff --git a/plugins/commands/plugin/command/repair.rb b/plugins/commands/plugin/command/repair.rb index 6deff6ae2..a531e01b2 100644 --- a/plugins/commands/plugin/command/repair.rb +++ b/plugins/commands/plugin/command/repair.rb @@ -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", "Install plugin for local project only") 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 options[:env_local] + 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 From 7a623d282684e352423e56e45e436c4aff4bb595 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 14:02:17 -0700 Subject: [PATCH 18/27] Include local flag for plugin update command --- plugins/commands/plugin/command/update.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/commands/plugin/command/update.rb b/plugins/commands/plugin/command/update.rb index 96bd1f678..6f7c9ff36 100644 --- a/plugins/commands/plugin/command/update.rb +++ b/plugins/commands/plugin/command/update.rb @@ -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", "Remove plugin from 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 From 8445b496d8bacd07805b33f3ef830073eddff6d5 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 14:19:29 -0700 Subject: [PATCH 19/27] Use consistent terms for describing local flag --- plugins/commands/plugin/command/expunge.rb | 2 +- plugins/commands/plugin/command/list.rb | 2 +- plugins/commands/plugin/command/repair.rb | 2 +- plugins/commands/plugin/command/update.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/commands/plugin/command/expunge.rb b/plugins/commands/plugin/command/expunge.rb index 1b23ab02e..721a44169 100644 --- a/plugins/commands/plugin/command/expunge.rb +++ b/plugins/commands/plugin/command/expunge.rb @@ -16,7 +16,7 @@ module VagrantPlugins options[:force] = force end - o.on("--local", "Include local project plugins for expunge") do |l| + o.on("--local", "Include plugins from local project for expunge") do |l| options[:env_local] = l end diff --git a/plugins/commands/plugin/command/list.rb b/plugins/commands/plugin/command/list.rb index 1b850f52a..8e176d6b2 100644 --- a/plugins/commands/plugin/command/list.rb +++ b/plugins/commands/plugin/command/list.rb @@ -11,7 +11,7 @@ module VagrantPlugins o.banner = "Usage: vagrant plugin list [-h]" # Stub option to allow Vagrantfile loading - o.on("--local", "Include local plugins"){|_|} + o.on("--local", "Include local project plugins"){|_|} end # Parse the options diff --git a/plugins/commands/plugin/command/repair.rb b/plugins/commands/plugin/command/repair.rb index a531e01b2..670d2b232 100644 --- a/plugins/commands/plugin/command/repair.rb +++ b/plugins/commands/plugin/command/repair.rb @@ -12,7 +12,7 @@ module VagrantPlugins opts = OptionParser.new do |o| o.banner = "Usage: vagrant plugin repair [-h]" - o.on("--local", "Install plugin for local project only") do |l| + o.on("--local", "Repair plugins in local project") do |l| options[:env_local] = l end end diff --git a/plugins/commands/plugin/command/update.rb b/plugins/commands/plugin/command/update.rb index 6f7c9ff36..03abf12f3 100644 --- a/plugins/commands/plugin/command/update.rb +++ b/plugins/commands/plugin/command/update.rb @@ -15,7 +15,7 @@ module VagrantPlugins o.banner = "Usage: vagrant plugin update [names...] [-h]" o.separator "" - o.on("--local", "Remove plugin from local project") do |l| + o.on("--local", "Update plugin in local project") do |l| options[:env_local] = l end end From 7a20c772eca9dea7e5a2e2f5e62e49a5451f5514 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 14:20:08 -0700 Subject: [PATCH 20/27] Add new flags for the plugin subcommands --- website/source/docs/cli/plugin.html.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/website/source/docs/cli/plugin.html.md b/website/source/docs/cli/plugin.html.md index dcadb73b8..59a388742 100644 --- a/website/source/docs/cli/plugin.html.md +++ b/website/source/docs/cli/plugin.html.md @@ -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 [ ...]`** @@ -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 []`** @@ -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. From 0a3d40bd333be5ef6bdec3495b4de0838e397552 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 14:20:39 -0700 Subject: [PATCH 21/27] Include documentation on local plugin load environment variable --- website/source/docs/other/environmental-variables.html.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/source/docs/other/environmental-variables.html.md b/website/source/docs/other/environmental-variables.html.md index b38ae52a2..5f1b4877a 100644 --- a/website/source/docs/other/environmental-variables.html.md +++ b/website/source/docs/other/environmental-variables.html.md @@ -167,6 +167,13 @@ 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 From ab39125570cea3836ca691f8504ef5e232a76e48 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 15:47:35 -0700 Subject: [PATCH 22/27] Set options directly instead of lazy merging --- lib/vagrant/environment.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 8aab2cf6a..53edce6b0 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -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" @@ -950,10 +951,16 @@ module Vagrant end end needs_install.each do |name| + pconfig = Util::HashWithIndifferentAccess.new(config_plugins[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(env_local: true)) + + 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 From 8e0e2fc53af8b5f7693c8783a9d80b217fa8919e Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 15:48:13 -0700 Subject: [PATCH 23/27] Add output for local repair --- plugins/commands/plugin/action/repair_plugins.rb | 2 ++ templates/locales/en.yml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/action/repair_plugins.rb b/plugins/commands/plugin/action/repair_plugins.rb index 0d13907dd..5421f000d 100644 --- a/plugins/commands/plugin/action/repair_plugins.rb +++ b/plugins/commands/plugin/action/repair_plugins.rb @@ -44,6 +44,7 @@ module VagrantPlugins 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, @@ -53,6 +54,7 @@ module VagrantPlugins plugin_env_local: true ) end + env[:ui].info(I18n.t("vagrant.commands.plugin.repair_local_complete")) # Continue @app.call(env) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 6c28b16d8..a1e0d4e97 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1920,9 +1920,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 From 7c9fb9a5d704378a8b8359170e76b3379ad7984f Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 15:48:26 -0700 Subject: [PATCH 24/27] Use availablity of local plugins file instead of option --- plugins/commands/plugin/command/repair.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/commands/plugin/command/repair.rb b/plugins/commands/plugin/command/repair.rb index 670d2b232..57f90cbb2 100644 --- a/plugins/commands/plugin/command/repair.rb +++ b/plugins/commands/plugin/command/repair.rb @@ -22,7 +22,7 @@ module VagrantPlugins return if !argv raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0 - if options[:env_local] + if Vagrant::Plugin::Manager.instance.local_file action(Action.action_repair_local, env: @env) end From e9623ca52b0834fa40b131787f8e372d6045a868 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 18 Jul 2018 15:48:55 -0700 Subject: [PATCH 25/27] Add documentation for plugins entry in Vagrantfile --- .../docs/vagrantfile/vagrant_settings.html.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/website/source/docs/vagrantfile/vagrant_settings.html.md b/website/source/docs/vagrantfile/vagrant_settings.html.md index e0a9de414..ac09ee011 100644 --- a/website/source/docs/vagrantfile/vagrant_settings.html.md +++ b/website/source/docs/vagrantfile/vagrant_settings.html.md @@ -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. From 0558a2f49ee827f6c5d20e490cac7e7fc36933f4 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 19 Jul 2018 09:52:34 -0700 Subject: [PATCH 26/27] Include missing assignment in docs --- website/source/docs/vagrantfile/vagrant_settings.html.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/vagrantfile/vagrant_settings.html.md b/website/source/docs/vagrantfile/vagrant_settings.html.md index ac09ee011..e8ff18eb7 100644 --- a/website/source/docs/vagrantfile/vagrant_settings.html.md +++ b/website/source/docs/vagrantfile/vagrant_settings.html.md @@ -30,13 +30,13 @@ them into the local project. When requiring a single plugin, a string can be provided: ```ruby -config.vagrant.plugins "vagrant-plugin" +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"] +config.vagrant.plugins = ["vagrant-plugin", "vagrant-other-plugin"] ``` Plugins can also be defined as a Hash, which supports setting extra options @@ -45,7 +45,7 @@ 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"}} +config.vagrant.plugins = {"vagrant-scp" => {"version" => "1.0.0"}} ``` Supported options are: From ae14f951248194ac933de508c347668ade54c92d Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 19 Jul 2018 10:31:32 -0700 Subject: [PATCH 27/27] Properly downcase answer for check. Default response to no. --- lib/vagrant/environment.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 53edce6b0..5fcc7654d 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -942,8 +942,9 @@ module Vagrant if !Vagrant.auto_install_local_plugins? answer = nil until ["y", "n"].include?(answer) - answer = ui.ask(I18n.t("vagrant.plugins.local.request_plugin_install") + ": ") - answer.strip.downcase! + 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,