From db2e27bab68f5f79c2ba212c8a173b6c485c8bea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 17:12:46 -0800 Subject: [PATCH 01/21] Initial plugin command plugin --- plugins/commands/plugin/command/root.rb | 56 +++++++++++++++++++++++++ plugins/commands/plugin/plugin.rb | 18 ++++++++ 2 files changed, 74 insertions(+) create mode 100644 plugins/commands/plugin/command/root.rb create mode 100644 plugins/commands/plugin/plugin.rb diff --git a/plugins/commands/plugin/command/root.rb b/plugins/commands/plugin/command/root.rb new file mode 100644 index 000000000..bb10557d5 --- /dev/null +++ b/plugins/commands/plugin/command/root.rb @@ -0,0 +1,56 @@ +require 'optparse' + +module VagrantPlugins + module CommandPlugin + module Command + class Root < Vagrant.plugin("2", :command) + def initialize(argv, env) + super + + @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) + + @subcommands = Vagrant::Registry.new + end + + def execute + if @main_args.include?("-h") || @main_args.include?("--help") + # Print the help for all the sub-commands. + return help + end + + # If we reached this far then we must have a subcommand. If not, + # then we also just print the help and exit. + command_class = @subcommands.get(@sub_command.to_sym) if @sub_command + return help if !command_class || !@sub_command + @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") + + # Initialize and execute the command class + command_class.new(@sub_args, @env).execute + end + + # Prints the help out for this command + def help + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin []" + o.separator "" + o.separator "Available subcommands:" + + # Add the available subcommands as separators in order to print them + # out as well. + keys = [] + @subcommands.each { |key, value| keys << key.to_s } + + keys.sort.each do |key| + o.separator " #{key}" + end + + o.separator "" + o.separator "For help on any individual command run `vagrant plugin COMMAND -h`" + end + + @env.ui.info(opts.help, :prefix => false) + end + end + end + end +end diff --git a/plugins/commands/plugin/plugin.rb b/plugins/commands/plugin/plugin.rb new file mode 100644 index 000000000..4739cfab1 --- /dev/null +++ b/plugins/commands/plugin/plugin.rb @@ -0,0 +1,18 @@ +require "vagrant" + +module VagrantPlugins + module CommandPlugin + class Plugin < Vagrant.plugin("2") + name "plugin command" + description <<-DESC + This command helps manage and install plugins within the + Vagrant environment. +DESC + + command("plugin") do + require File.expand_path("../command/root", __FILE__) + Command::Root + end + end + end +end From a2ef7790deb6b6cfba3b80b5282994aef1aef09b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 18:42:04 -0800 Subject: [PATCH 02/21] Start creating the plugin middleware sequences --- lib/vagrant/environment.rb | 1 + plugins/commands/gem/command.rb | 2 +- plugins/commands/plugin/action.rb | 22 +++++++++ .../commands/plugin/action/bundler_check.rb | 25 ++++++++++ plugins/commands/plugin/action/install_gem.rb | 47 +++++++++++++++++++ plugins/commands/plugin/command/install.rb | 29 ++++++++++++ plugins/commands/plugin/command/root.rb | 4 ++ plugins/commands/plugin/plugin.rb | 3 ++ plugins/commands/plugin/state_file.rb | 32 +++++++++++++ templates/locales/en.yml | 3 ++ 10 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 plugins/commands/plugin/action.rb create mode 100644 plugins/commands/plugin/action/bundler_check.rb create mode 100644 plugins/commands/plugin/action/install_gem.rb create mode 100644 plugins/commands/plugin/command/install.rb create mode 100644 plugins/commands/plugin/state_file.rb diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 37554a066..72f5e0748 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -411,6 +411,7 @@ module Vagrant :box_collection => boxes, :global_config => config_global, :host => host, + :gems_path => gems_path, :root_path => root_path, :tmp_path => tmp_path, :ui => @ui diff --git a/plugins/commands/gem/command.rb b/plugins/commands/gem/command.rb index f9b47b842..b3c35828e 100644 --- a/plugins/commands/gem/command.rb +++ b/plugins/commands/gem/command.rb @@ -15,7 +15,7 @@ module VagrantPlugins if defined?(Bundler) require 'bundler/shared_helpers' if Bundler::SharedHelpers.in_bundle? - raise Errors::GemCommandInBundler + raise Vagrant::Errors::GemCommandInBundler end end diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb new file mode 100644 index 000000000..a38608ac4 --- /dev/null +++ b/plugins/commands/plugin/action.rb @@ -0,0 +1,22 @@ +require "pathname" + +require "vagrant/action/builder" + +module VagrantPlugins + module CommandPlugin + module Action + # This middleware sequence will install a plugin. + def self.action_install + Vagrant::Action::Builder.new.tap do |b| + b.use BundlerCheck + b.use InstallGem + end + end + + # The autoload farm + action_root = Pathname.new(File.expand_path("../action", __FILE__)) + autoload :BundlerCheck, action_root.join("bundler_check") + autoload :InstallGem, action_root.join("install_gem") + end + end +end diff --git a/plugins/commands/plugin/action/bundler_check.rb b/plugins/commands/plugin/action/bundler_check.rb new file mode 100644 index 000000000..b53a83c5d --- /dev/null +++ b/plugins/commands/plugin/action/bundler_check.rb @@ -0,0 +1,25 @@ +module VagrantPlugins + module CommandPlugin + module Action + class BundlerCheck + def initialize(app, env) + @app = app + end + + def call(env) + # Bundler sets up its own custom gem load paths such that our + # own gems are never loaded. Therefore, give an error if a user + # tries to install gems while within a Bundler-managed environment. + if defined?(Bundler) + require 'bundler/shared_helpers' + if Bundler::SharedHelpers.in_bundle? + raise Vagrant::Errors::GemCommandInBundler + end + end + + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb new file mode 100644 index 000000000..1abd010cf --- /dev/null +++ b/plugins/commands/plugin/action/install_gem.rb @@ -0,0 +1,47 @@ +require "rubygems" +require "rubygems/gem_runner" + +require "log4r" + +module VagrantPlugins + module CommandPlugin + module Action + # This action takes the `:plugin_name` variable in the environment + # and installs it using the RubyGems API. + class InstallGem + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::installgem") + end + + def call(env) + plugin_name = env[:plugin_name] + + # First, install the gem + begin + # Set the GEM_HOME so that it is installed into our local gems path + old_gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = env[:gems_path].to_s + p ENV["GEM_PATH"] + @logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}") + + @logger.info("Installing gem: #{plugin_name}") + env[:ui].info( + I18n.t("vagrant.commands.plugin.installing", :name => plugin_name)) + Gem.clear_paths + Gem::GemRunner.new.run( + ["install", plugin_name, "--no-ri", "--no-rdoc"]) + ensure + ENV["GEM_HOME"] = old_gem_home + end + + # Mark that we installed the gem + env[:plugin_state_file].add_plugin(plugin_name) + + # Continue + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb new file mode 100644 index 000000000..ffa137672 --- /dev/null +++ b/plugins/commands/plugin/command/install.rb @@ -0,0 +1,29 @@ +require 'optparse' + +module VagrantPlugins + module CommandPlugin + module Command + class Install < Vagrant.plugin("2", :command) + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin install [-h]" + end + + # 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 + @env.action_runner.run(Action.action_install, { + :plugin_name => argv[0], + :plugin_state_file => StateFile.new(@env.data_dir.join("plugins.json")) + }) + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/command/root.rb b/plugins/commands/plugin/command/root.rb index bb10557d5..d2fc4b059 100644 --- a/plugins/commands/plugin/command/root.rb +++ b/plugins/commands/plugin/command/root.rb @@ -10,6 +10,10 @@ module VagrantPlugins @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) @subcommands = Vagrant::Registry.new + @subcommands.register(:install) do + require_relative "install" + Install + end end def execute diff --git a/plugins/commands/plugin/plugin.rb b/plugins/commands/plugin/plugin.rb index 4739cfab1..c7cf5e24f 100644 --- a/plugins/commands/plugin/plugin.rb +++ b/plugins/commands/plugin/plugin.rb @@ -14,5 +14,8 @@ DESC Command::Root end end + + autoload :Action, File.expand_path("../action", __FILE__) + autoload :StateFile, File.expand_path("../state_file", __FILE__) end end diff --git a/plugins/commands/plugin/state_file.rb b/plugins/commands/plugin/state_file.rb new file mode 100644 index 000000000..5282ee480 --- /dev/null +++ b/plugins/commands/plugin/state_file.rb @@ -0,0 +1,32 @@ +require "json" + +module VagrantPlugins + module CommandPlugin + # 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 + def initialize(path) + @path = path + + @data = {} + @data = JSON.parse(@path.read) if @path.exist? + end + + # Add a plugin that is installed to the state file. + # + # @param [String] name The name of the plugin + def add_plugin(name) + @data["installed"] ||= [] + @data["installed"] << name + save! + end + + # This saves the state back into the state file. + def save! + @path.open("w+") do |f| + f.write(JSON.dump(@data)) + end + end + end + end +end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 2c243c2dd..1ce45e7e6 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -411,6 +411,9 @@ en: ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant. + plugin: + installing: |- + Installing the '%{name}' plugin... status: aborted: |- The VM is in an aborted state. This means that it was abruptly From fa50f06a4c34c6bbab0c8ee43099ed3051121081 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 23:31:53 -0800 Subject: [PATCH 03/21] Error if RubyGems failed. --- lib/vagrant/errors.rb | 4 ++ plugins/commands/plugin/action/install_gem.rb | 21 ++------- plugins/commands/plugin/command/install.rb | 1 + plugins/commands/plugin/gem_helper.rb | 47 +++++++++++++++++++ plugins/commands/plugin/plugin.rb | 1 + templates/locales/en.yml | 5 ++ 6 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 plugins/commands/plugin/gem_helper.rb diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index a90c0b21b..22ea2e09e 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -336,6 +336,10 @@ module Vagrant error_key(:provider_not_found) end + class PluginGemError < VagrantError + error_key(:plugin_gem_error) + end + class PluginLoadError < VagrantError status_code(81) error_key(:plugin_load_error) diff --git a/plugins/commands/plugin/action/install_gem.rb b/plugins/commands/plugin/action/install_gem.rb index 1abd010cf..71be71b0c 100644 --- a/plugins/commands/plugin/action/install_gem.rb +++ b/plugins/commands/plugin/action/install_gem.rb @@ -17,23 +17,10 @@ module VagrantPlugins def call(env) plugin_name = env[:plugin_name] - # First, install the gem - begin - # Set the GEM_HOME so that it is installed into our local gems path - old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = env[:gems_path].to_s - p ENV["GEM_PATH"] - @logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}") - - @logger.info("Installing gem: #{plugin_name}") - env[:ui].info( - I18n.t("vagrant.commands.plugin.installing", :name => plugin_name)) - Gem.clear_paths - Gem::GemRunner.new.run( - ["install", plugin_name, "--no-ri", "--no-rdoc"]) - ensure - ENV["GEM_HOME"] = old_gem_home - end + # Install the gem + env[:ui].info(I18n.t("vagrant.commands.plugin.installing", + :name => plugin_name)) + env[:gem_helper].cli(["install", plugin_name, "--no-ri", "--no-rdoc"]) # Mark that we installed the gem env[:plugin_state_file].add_plugin(plugin_name) diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb index ffa137672..dc5b2fa6b 100644 --- a/plugins/commands/plugin/command/install.rb +++ b/plugins/commands/plugin/command/install.rb @@ -16,6 +16,7 @@ module VagrantPlugins # Install the gem @env.action_runner.run(Action.action_install, { + :gem_helper => GemHelper.new(@env.gems_path), :plugin_name => argv[0], :plugin_state_file => StateFile.new(@env.data_dir.join("plugins.json")) }) diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb new file mode 100644 index 000000000..d7fd81b49 --- /dev/null +++ b/plugins/commands/plugin/gem_helper.rb @@ -0,0 +1,47 @@ +require "rubygems" +require "rubygems/gem_runner" + +require "log4r" + +module VagrantPlugins + module CommandPlugin + # This class provides methods to help with calling out to the + # `gem` command but using the RubyGems API. + class GemHelper + def initialize(gem_home) + @gem_home = gem_home.to_s + @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::gemhelper") + end + + def cli(argv) + # Initialize the UI to use for RubyGems. This allows us to capture + # the stdout/stderr without actually going to the real STDOUT/STDERR. + # The final "false" here tells RubyGems we're not a TTY, so don't + # ask us things. + gem_ui = Gem::StreamUI.new(StringIO.new, StringIO.new, StringIO.new, false) + + # Set the GEM_HOME so that it is installed into our local gems path + old_gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = @gem_home + @logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}") + @logger.info("Calling gem with argv: #{argv.inspect}") + Gem.clear_paths + Gem::DefaultUserInteraction.use_ui(gem_ui) do + Gem::GemRunner.new.run(argv) + end + rescue Gem::SystemExitException => e + # This means that something forced an exit within RubyGems. + # We capture this to check whether it succeeded or not by + # checking the "exit_code" + raise Vagrant::Errors::PluginGemError, :output => gem_ui.errs.string.chomp if e != 0 + ensure + # Restore the old GEM_HOME + ENV["GEM_HOME"] = old_gem_home + + # Log the output properly + @logger.debug("Gem STDOUT: #{gem_ui.outs.string}") + @logger.debug("Gem STDERR: #{gem_ui.errs.string}") + end + end + end +end diff --git a/plugins/commands/plugin/plugin.rb b/plugins/commands/plugin/plugin.rb index c7cf5e24f..db9763319 100644 --- a/plugins/commands/plugin/plugin.rb +++ b/plugins/commands/plugin/plugin.rb @@ -16,6 +16,7 @@ DESC end autoload :Action, File.expand_path("../action", __FILE__) + autoload :GemHelper, File.expand_path("../gem_helper", __FILE__) autoload :StateFile, File.expand_path("../state_file", __FILE__) end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1ce45e7e6..214bbd1a3 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -165,6 +165,11 @@ en: no_env: |- A Vagrant environment is required to run this command. Run `vagrant init` to set one up. + plugin_gem_error: |- + An error occurred within RubyGems, the underlying system used to + manage Vagrant plugins. The output of the errors are shown below: + + %{output} plugin_load_error: |- The plugin "%{plugin}" could not be found. Please make sure that it is properly installed via `vagrant gem`. From 150cae86b7068296c13ffbb14ff45c73cf35534d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 23:33:52 -0800 Subject: [PATCH 04/21] Only show error if exit code != 0 of gem --- plugins/commands/plugin/gem_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb index d7fd81b49..8fdeee6ef 100644 --- a/plugins/commands/plugin/gem_helper.rb +++ b/plugins/commands/plugin/gem_helper.rb @@ -33,7 +33,8 @@ module VagrantPlugins # This means that something forced an exit within RubyGems. # We capture this to check whether it succeeded or not by # checking the "exit_code" - raise Vagrant::Errors::PluginGemError, :output => gem_ui.errs.string.chomp if e != 0 + raise Vagrant::Errors::PluginGemError, + :output => gem_ui.errs.string.chomp if e.exit_code != 0 ensure # Restore the old GEM_HOME ENV["GEM_HOME"] = old_gem_home From 53667d44dbc5cd83cc32d08c749e0421e65b83a8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 23:38:44 -0800 Subject: [PATCH 05/21] Have a base class to invoke actions on the plugins --- plugins/commands/plugin/command/base.rb | 22 +++++++++++++++++ plugins/commands/plugin/command/install.rb | 10 ++++---- plugins/commands/plugin/command/list.rb | 28 ++++++++++++++++++++++ plugins/commands/plugin/command/root.rb | 5 ++++ 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 plugins/commands/plugin/command/base.rb create mode 100644 plugins/commands/plugin/command/list.rb diff --git a/plugins/commands/plugin/command/base.rb b/plugins/commands/plugin/command/base.rb new file mode 100644 index 000000000..7c98b38b6 --- /dev/null +++ b/plugins/commands/plugin/command/base.rb @@ -0,0 +1,22 @@ +module VagrantPlugins + module CommandPlugin + module Command + class Base < Vagrant.plugin("2", :command) + # This is a helper for executing an action sequence with the proper + # environment hash setup so that the plugin specific helpers are + # in. + # + # @param [Object] callable the Middleware callable + # @param [Hash] env Extra environment hash that is merged in. + def action(callable, env=nil) + env = { + :gem_helper => GemHelper.new(@env.gems_path), + :plugin_state_file => StateFile.new(@env.data_dir.join("plugins.json")) + }.merge(env || {}) + + @env.action_runner.run(callable, env) + end + end + end + end +end diff --git a/plugins/commands/plugin/command/install.rb b/plugins/commands/plugin/command/install.rb index dc5b2fa6b..295285cc2 100644 --- a/plugins/commands/plugin/command/install.rb +++ b/plugins/commands/plugin/command/install.rb @@ -1,9 +1,11 @@ require 'optparse' +require_relative "base" + module VagrantPlugins module CommandPlugin module Command - class Install < Vagrant.plugin("2", :command) + class Install < Base def execute opts = OptionParser.new do |o| o.banner = "Usage: vagrant plugin install [-h]" @@ -15,11 +17,7 @@ module VagrantPlugins raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 1 # Install the gem - @env.action_runner.run(Action.action_install, { - :gem_helper => GemHelper.new(@env.gems_path), - :plugin_name => argv[0], - :plugin_state_file => StateFile.new(@env.data_dir.join("plugins.json")) - }) + action(Action.action_install, :plugin_name => argv[0]) # Success, exit status 0 0 diff --git a/plugins/commands/plugin/command/list.rb b/plugins/commands/plugin/command/list.rb new file mode 100644 index 000000000..61027c216 --- /dev/null +++ b/plugins/commands/plugin/command/list.rb @@ -0,0 +1,28 @@ +require 'optparse' + +require_relative "base" + +module VagrantPlugins + module CommandPlugin + module Command + class List < Base + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin list [-h]" + end + + # Parse the options + argv = parse_options(opts) + return if !argv + raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length > 0 + + # List the installed plugins + action(Action.action_list) + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/command/root.rb b/plugins/commands/plugin/command/root.rb index d2fc4b059..3b1d29aa5 100644 --- a/plugins/commands/plugin/command/root.rb +++ b/plugins/commands/plugin/command/root.rb @@ -14,6 +14,11 @@ module VagrantPlugins require_relative "install" Install end + + @subcommands.register(:list) do + require_relative "list" + List + end end def execute From d404eee770d91ae5784f3041f9ec220d091990d7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 23:41:18 -0800 Subject: [PATCH 06/21] Make the bundler environment error more correct --- templates/locales/en.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 214bbd1a3..3eb42b136 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -114,11 +114,11 @@ en: occurring. Please wait for the other instance of Vagrant to end and then try again. gem_command_in_bundler: |- - You cannot run the `vagrant gem` command while in a bundler environment. - Bundler messes around quite a bit with the RubyGem load paths and gems - installed via `vagrant gem` are excluded by Bundler. - - Instead, please include your Vagrant plugins in your Gemfile itself. + You cannot run the `vagrant plugin` command while in a bundler environment. + This should generally never happen unless Vagrant is installed outside + of the official installers or another gem is wrongly attempting to + use Vagrant internals directly. Please properly install Vagrant to + fix this. If this error persists, please contact support. guest: invalid_class: |- The specified guest class does not inherit from a proper guest From 8ac7b620751036ee05943031bd539ed45ff766a0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 23:52:34 -0800 Subject: [PATCH 07/21] A really basic "list" command --- plugins/commands/plugin/action.rb | 9 +++++ .../commands/plugin/action/list_plugins.rb | 25 +++++++++++++ plugins/commands/plugin/gem_helper.rb | 37 ++++++++++++++----- 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 plugins/commands/plugin/action/list_plugins.rb diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index a38608ac4..6a0fee7f3 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -13,10 +13,19 @@ module VagrantPlugins end end + # This middleware sequence will list all installed plugins. + def self.action_list + Vagrant::Action::Builder.new.tap do |b| + b.use BundlerCheck + b.use ListPlugins + end + end + # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) autoload :BundlerCheck, action_root.join("bundler_check") autoload :InstallGem, action_root.join("install_gem") + autoload :ListPlugins, action_root.join("list_plugins") end end end diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb new file mode 100644 index 000000000..515d39a4c --- /dev/null +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -0,0 +1,25 @@ +require "rubygems" + +module VagrantPlugins + module CommandPlugin + module Action + class ListPlugins + def initialize(app, env) + @app = app + end + + def call(env) + env[:gem_helper].with_environment do + specs = Gem::Specification.find_all + + specs.each do |spec| + env[:ui].info spec.name + end + end + + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb index 8fdeee6ef..0ce73efab 100644 --- a/plugins/commands/plugin/gem_helper.rb +++ b/plugins/commands/plugin/gem_helper.rb @@ -13,6 +13,11 @@ module VagrantPlugins @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::gemhelper") end + # This executes the `gem` command with the given arguments. Under + # the covers this is actually using the RubyGems API directly, + # instead of shelling out, which allows for more fine-grained control. + # + # @param [Array] argv The arguments to send to the `gem` command. def cli(argv) # Initialize the UI to use for RubyGems. This allows us to capture # the stdout/stderr without actually going to the real STDOUT/STDERR. @@ -21,13 +26,11 @@ module VagrantPlugins gem_ui = Gem::StreamUI.new(StringIO.new, StringIO.new, StringIO.new, false) # Set the GEM_HOME so that it is installed into our local gems path - old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = @gem_home - @logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}") - @logger.info("Calling gem with argv: #{argv.inspect}") - Gem.clear_paths - Gem::DefaultUserInteraction.use_ui(gem_ui) do - Gem::GemRunner.new.run(argv) + with_environment do + @logger.info("Calling gem with argv: #{argv.inspect}") + Gem::DefaultUserInteraction.use_ui(gem_ui) do + Gem::GemRunner.new.run(argv) + end end rescue Gem::SystemExitException => e # This means that something forced an exit within RubyGems. @@ -36,13 +39,27 @@ module VagrantPlugins raise Vagrant::Errors::PluginGemError, :output => gem_ui.errs.string.chomp if e.exit_code != 0 ensure - # Restore the old GEM_HOME - ENV["GEM_HOME"] = old_gem_home - # Log the output properly @logger.debug("Gem STDOUT: #{gem_ui.outs.string}") @logger.debug("Gem STDERR: #{gem_ui.errs.string}") end + + # This will yield the given block with the proper ENV setup so + # that RubyGems only sees the gems in the Vagrant-managed gem + # path. + def with_environment + old_gem_home = ENV["GEM_HOME"] + ENV["GEM_HOME"] = @gem_home + @logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}") + + # Clear paths so that it reads the new GEM_HOME setting + Gem.clear_paths + + yield + ensure + # Restore the old GEM_HOME + ENV["GEM_HOME"] = old_gem_home + end end end end From f257d1211fa43a051e54abaaa9cc51ffca4a48c4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Feb 2013 23:59:48 -0800 Subject: [PATCH 08/21] List actually compares state with gems --- .../commands/plugin/action/list_plugins.rb | 24 ++++++++++++++++--- plugins/commands/plugin/gem_helper.rb | 2 +- plugins/commands/plugin/state_file.rb | 10 ++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 515d39a4c..5123ff0a0 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -1,18 +1,36 @@ require "rubygems" +require "set" module VagrantPlugins module CommandPlugin module Action + # This middleware lists all the installed plugins. + # + # This is a bit more complicated than simply listing installed + # gems or what is in the state file as installed. Instead, this + # actually compares installed gems with what the state file claims + # is installed, and outputs the appropriate truly installed + # plugins. class ListPlugins def initialize(app, env) @app = app end def call(env) - env[:gem_helper].with_environment do - specs = Gem::Specification.find_all + # Get the list of installed plugins according to the state file + installed = Set.new(env[:plugin_state_file].installed_plugins) - specs.each do |spec| + # Get the actual specifications of installed gems + specs = env[:gem_helper].with_environment do + Gem::Specification.find_all + end + + # Go through each spec and if it is an installed plugin, then + # output it. This means that both the installed state and + # gem match up. + specs.each do |spec| + if installed.include?(spec.name) + # TODO: Formatting env[:ui].info spec.name end end diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb index 0ce73efab..ff1eec780 100644 --- a/plugins/commands/plugin/gem_helper.rb +++ b/plugins/commands/plugin/gem_helper.rb @@ -55,7 +55,7 @@ module VagrantPlugins # Clear paths so that it reads the new GEM_HOME setting Gem.clear_paths - yield + return yield ensure # Restore the old GEM_HOME ENV["GEM_HOME"] = old_gem_home diff --git a/plugins/commands/plugin/state_file.rb b/plugins/commands/plugin/state_file.rb index 5282ee480..ce045507f 100644 --- a/plugins/commands/plugin/state_file.rb +++ b/plugins/commands/plugin/state_file.rb @@ -21,6 +21,16 @@ module VagrantPlugins save! end + # This returns a list of installed plugins according to the state + # file. Note that this may _not_ directly match over to actually + # installed gems. + # + # @return [Array] + def installed_plugins + @data["installed"] ||= [] + @data["installed"] + end + # This saves the state back into the state file. def save! @path.open("w+") do |f| From 472d4182c17efd6faa9e373fe7d0f14790eeed9e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:15:46 -0800 Subject: [PATCH 09/21] Basic logic behind prune action is good. --- plugins/commands/plugin/action.rb | 2 + plugins/commands/plugin/action/prune_gems.rb | 131 +++++++++++++++++++ plugins/commands/plugin/gem_helper.rb | 12 +- 3 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 plugins/commands/plugin/action/prune_gems.rb diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 6a0fee7f3..2c6522e29 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -10,6 +10,7 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| b.use BundlerCheck b.use InstallGem + b.use PruneGems end end @@ -26,6 +27,7 @@ module VagrantPlugins autoload :BundlerCheck, action_root.join("bundler_check") autoload :InstallGem, action_root.join("install_gem") autoload :ListPlugins, action_root.join("list_plugins") + autoload :PruneGems, action_root.join("prune_gems") end end end diff --git a/plugins/commands/plugin/action/prune_gems.rb b/plugins/commands/plugin/action/prune_gems.rb new file mode 100644 index 000000000..e06511464 --- /dev/null +++ b/plugins/commands/plugin/action/prune_gems.rb @@ -0,0 +1,131 @@ +require "rubygems" +require "set" + +require "log4r" + +module VagrantPlugins + module CommandPlugin + module Action + # This class prunes any unnecessary gems from the Vagrant-managed + # gem folder. This keeps the gem folder to the absolute minimum set + # of required gems and doesn't let it blow up out of control. + # + # A high-level description of how this works: + # + # 1. Get the list of installed plugins. Vagrant maintains this + # list on its own. + # 2. Get the list of installed RubyGems. + # 3. Find the latest version of each RubyGem that matches an installed + # plugin. These are our root RubyGems that must be installed. + # 4. Go through each root and mark all dependencies recursively as + # necessary. + # 5. Set subtraction between all gems and necessary gems yields a + # list of gems that aren't needed. Uninstall them. + # + class PruneGems + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::prune") + end + + def call(env) + @logger.info("Pruning gems...") + + # Get the list of installed plugins according to the state file + installed = Set.new(env[:plugin_state_file].installed_plugins) + + # Get the actual specifications of installed gems + all_specs = env[:gem_helper].with_environment do + result = [] + Gem::Specification.find_all { |s| result << s } + result + end + + # The list of specs to prune initially starts out as all of them + all_specs = Set.new(all_specs) + + # Go through each spec and find the latest version of the installed + # gems, since we want to keep those. + installed_specs = {} + + @logger.debug("Collecting installed plugin gems...") + all_specs.each do |spec| + # If this isn't a spec that we claim is installed, skip it + next if !installed.include?(spec.name) + + # If it is already in the specs, then we need to make sure we + # have the latest version. + if installed_specs.has_key?(spec.name) + if installed_specs[spec.name].version > spec.version + next + end + end + + @logger.debug(" -- #{spec.name} (#{spec.version})") + installed_specs[spec.name] = spec + end + + # Recursive dependency checker to keep all dependencies and remove + # all non-crucial gems from the prune list. + good_specs = Set.new + to_check = installed_specs.values + + while true + # If we're out of gems to check then we break out + break if to_check.empty? + + # Get a random (first) element to check + spec = to_check.shift + + # If we already checked this, then do the next one + next if good_specs.include?(spec) + + # Find all the dependencies and add the latest compliant gem + # to the `to_check` list. + if spec.dependencies.length > 0 + @logger.debug("Finding dependencies for '#{spec.name}' to mark as good...") + spec.dependencies.each do |dep| + # Ignore non-runtime dependencies + next if dep.type != :runtime + @logger.debug("Searching for: '#{dep.name}'") + + latest_matching = nil + + all_specs.each do |prune_spec| + if dep =~ prune_spec + # If we have a matching one already and this one isn't newer + # then we ditch it. + next if latest_matching && + prune_spec.version <= latest_matching.version + + latest_matching = prune_spec + end + end + + if latest_matching.nil? + @logger.error("Missing dependency for '#{spec.name}': #{dep.name}") + next + end + + @logger.debug("Latest matching dep: '#{latest_matching.name}' (#{latest_matching.version})") + to_check << latest_matching + end + end + + # Add ito the list of checked things so we don't accidentally + # re-check it + good_specs.add(spec) + end + + # Figure out the gems we need to prune + prune_specs = all_specs - good_specs + @logger.info("Gems to prune: #{prune_specs.inspect}") + + # TODO: Prune + + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb index ff1eec780..d91be4442 100644 --- a/plugins/commands/plugin/gem_helper.rb +++ b/plugins/commands/plugin/gem_helper.rb @@ -49,16 +49,22 @@ module VagrantPlugins # path. def with_environment old_gem_home = ENV["GEM_HOME"] + old_gem_path = ENV["GEM_PATH"] ENV["GEM_HOME"] = @gem_home - @logger.debug("Set GEM_HOME to: #{ENV["GEM_HOME"]}") + ENV["GEM_PATH"] = @gem_home + @logger.debug("Set GEM_* to: #{ENV["GEM_HOME"]}") # Clear paths so that it reads the new GEM_HOME setting - Gem.clear_paths + Gem.paths = ENV return yield ensure - # Restore the old GEM_HOME + # Restore the old GEM_* settings ENV["GEM_HOME"] = old_gem_home + ENV["GEM_PATH"] = old_gem_path + + # Reset everything + Gem.paths = ENV end end end From 572142df7ecce95e899b6998d6437055fd8e8ccd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:30:52 -0800 Subject: [PATCH 10/21] Perform gem uninstallation for prune --- plugins/commands/plugin/action/prune_gems.rb | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/action/prune_gems.rb b/plugins/commands/plugin/action/prune_gems.rb index e06511464..db9750baa 100644 --- a/plugins/commands/plugin/action/prune_gems.rb +++ b/plugins/commands/plugin/action/prune_gems.rb @@ -1,4 +1,5 @@ require "rubygems" +require "rubygems/uninstaller" require "set" require "log4r" @@ -121,7 +122,29 @@ module VagrantPlugins prune_specs = all_specs - good_specs @logger.info("Gems to prune: #{prune_specs.inspect}") - # TODO: Prune + if prune_specs.length > 0 + env[:gem_helper].with_environment do + prune_specs.each do |prune_spec| + uninstaller = Gem::Uninstaller.new(prune_spec.name, { + :executables => true, + :force => true, + :version => prune_spec.version.version + }) + + # This is sad, but there is no way to force this to be true + # so I just monkey-patch here. Vagrant has a pretty strict + # version check on the RubyGems version so this should be okay. + # In the future, let's try to get a pull request into RubyGems + # to fix this. + def uninstaller.ask_if_ok(spec) + true + end + + @logger.info("Uninstalling: #{prune_spec.name} (#{prune_spec.version})") + uninstaller.uninstall + end + end + end @app.call(env) end From 60d21e35c97cdee0b97f3cb8e2aa80943413b78a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:32:31 -0800 Subject: [PATCH 11/21] Be silent when uninstalling gems --- plugins/commands/plugin/gem_helper.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/gem_helper.rb b/plugins/commands/plugin/gem_helper.rb index d91be4442..2dc260e5e 100644 --- a/plugins/commands/plugin/gem_helper.rb +++ b/plugins/commands/plugin/gem_helper.rb @@ -57,7 +57,10 @@ module VagrantPlugins # Clear paths so that it reads the new GEM_HOME setting Gem.paths = ENV - return yield + # Use a silent UI so that we have no output + Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do + return yield + end ensure # Restore the old GEM_* settings ENV["GEM_HOME"] = old_gem_home From 491356c938ef1169f89e5155861f04c865bf4a11 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:33:39 -0800 Subject: [PATCH 12/21] Better logging for prune --- plugins/commands/plugin/action/prune_gems.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/action/prune_gems.rb b/plugins/commands/plugin/action/prune_gems.rb index db9750baa..e90ede8f9 100644 --- a/plugins/commands/plugin/action/prune_gems.rb +++ b/plugins/commands/plugin/action/prune_gems.rb @@ -120,7 +120,8 @@ module VagrantPlugins # Figure out the gems we need to prune prune_specs = all_specs - good_specs - @logger.info("Gems to prune: #{prune_specs.inspect}") + @logger.debug("Gems to prune: #{prune_specs.inspect}") + @logger.info("Pruning #{prune_specs.length} gems.") if prune_specs.length > 0 env[:gem_helper].with_environment do From 76457eff167f9e4ef0928e8c3bf6c5c869e8fa93 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:34:43 -0800 Subject: [PATCH 13/21] Make sure the state file only contains unique fields --- plugins/commands/plugin/state_file.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/commands/plugin/state_file.rb b/plugins/commands/plugin/state_file.rb index ce045507f..0371f74cd 100644 --- a/plugins/commands/plugin/state_file.rb +++ b/plugins/commands/plugin/state_file.rb @@ -10,15 +10,17 @@ module VagrantPlugins @data = {} @data = JSON.parse(@path.read) if @path.exist? + @data["installed"] ||= [] end # Add a plugin that is installed to the state file. # # @param [String] name The name of the plugin def add_plugin(name) - @data["installed"] ||= [] - @data["installed"] << name - save! + if !@data["installed"].include?(name) + @data["installed"] << name + save! + end end # This returns a list of installed plugins according to the state @@ -27,12 +29,15 @@ module VagrantPlugins # # @return [Array] def installed_plugins - @data["installed"] ||= [] @data["installed"] end # This saves the state back into the state file. def save! + # Scrub some fields + @data["installed"].uniq! + + # Save @path.open("w+") do |f| f.write(JSON.dump(@data)) end From e055bc893ba1b3d310026ccdc2c4eaf01a4d03fc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:35:59 -0800 Subject: [PATCH 14/21] Sort the state file as well for sanity --- plugins/commands/plugin/state_file.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/commands/plugin/state_file.rb b/plugins/commands/plugin/state_file.rb index 0371f74cd..3d6792d04 100644 --- a/plugins/commands/plugin/state_file.rb +++ b/plugins/commands/plugin/state_file.rb @@ -19,8 +19,9 @@ module VagrantPlugins def add_plugin(name) if !@data["installed"].include?(name) @data["installed"] << name - save! end + + save! end # This returns a list of installed plugins according to the state @@ -35,6 +36,7 @@ module VagrantPlugins # This saves the state back into the state file. def save! # Scrub some fields + @data["installed"].sort! @data["installed"].uniq! # Save From 0d7322578b020f5547960bc8dbd2703c1a53e7b3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:47:01 -0800 Subject: [PATCH 15/21] `vagrant plugin uninstall` --- plugins/commands/plugin/action.rb | 9 ++++++ .../plugin/action/uninstall_plugin.rb | 23 +++++++++++++++ plugins/commands/plugin/command/root.rb | 5 ++++ plugins/commands/plugin/command/uninstall.rb | 28 +++++++++++++++++++ plugins/commands/plugin/state_file.rb | 8 ++++++ templates/locales/en.yml | 2 ++ 6 files changed, 75 insertions(+) create mode 100644 plugins/commands/plugin/action/uninstall_plugin.rb create mode 100644 plugins/commands/plugin/command/uninstall.rb diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 2c6522e29..a23a8617d 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -22,12 +22,21 @@ module VagrantPlugins end end + # This middleware sequence will uninstall a plugin. + def self.action_uninstall + Vagrant::Action::Builder.new.tap do |b| + b.use BundlerCheck + b.use UninstallPlugin + b.use PruneGems + end + end # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) autoload :BundlerCheck, action_root.join("bundler_check") autoload :InstallGem, action_root.join("install_gem") autoload :ListPlugins, action_root.join("list_plugins") autoload :PruneGems, action_root.join("prune_gems") + autoload :UninstallPlugin, action_root.join("uninstall_plugin") end end end diff --git a/plugins/commands/plugin/action/uninstall_plugin.rb b/plugins/commands/plugin/action/uninstall_plugin.rb new file mode 100644 index 000000000..f86675523 --- /dev/null +++ b/plugins/commands/plugin/action/uninstall_plugin.rb @@ -0,0 +1,23 @@ +module VagrantPlugins + module CommandPlugin + module Action + # This middleware uninstalls a plugin by simply removing it from + # the state file. Running a {PruneGems} after should properly remove + # it from the gem index. + class UninstallPlugin + def initialize(app, env) + @app = app + end + + def call(env) + # Remove it! + env[:ui].info(I18n.t("vagrant.commands.plugin.uninstalling", + :name => env[:plugin_name])) + env[:plugin_state_file].remove_plugin(env[:plugin_name]) + + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/command/root.rb b/plugins/commands/plugin/command/root.rb index 3b1d29aa5..fe7f9b339 100644 --- a/plugins/commands/plugin/command/root.rb +++ b/plugins/commands/plugin/command/root.rb @@ -19,6 +19,11 @@ module VagrantPlugins require_relative "list" List end + + @subcommands.register(:uninstall) do + require_relative "uninstall" + Uninstall + end end def execute diff --git a/plugins/commands/plugin/command/uninstall.rb b/plugins/commands/plugin/command/uninstall.rb new file mode 100644 index 000000000..bbe78bd1d --- /dev/null +++ b/plugins/commands/plugin/command/uninstall.rb @@ -0,0 +1,28 @@ +require 'optparse' + +require_relative "base" + +module VagrantPlugins + module CommandPlugin + module Command + class Uninstall < Base + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin uninstall [-h]" + end + + # Parse the options + argv = parse_options(opts) + return if !argv + raise Vagrant::Errors::CLIInvalidUsage, :help => opts.help.chomp if argv.length < 1 + + # Uninstall the gem + action(Action.action_uninstall, :plugin_name => argv[0]) + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/state_file.rb b/plugins/commands/plugin/state_file.rb index 3d6792d04..f57ab9f20 100644 --- a/plugins/commands/plugin/state_file.rb +++ b/plugins/commands/plugin/state_file.rb @@ -33,6 +33,14 @@ module VagrantPlugins @data["installed"] end + # Remove a plugin that is installed from the state file. + # + # @param [String] name The name of the plugin. + def remove_plugin(name) + @data["installed"].delete(name) + save! + end + # This saves the state back into the state file. def save! # Scrub some fields diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 3eb42b136..928d2a1e4 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -419,6 +419,8 @@ en: plugin: installing: |- Installing the '%{name}' plugin... + uninstalling: |- + Uninstalling the '%{name}' plugin... status: aborted: |- The VM is in an aborted state. This means that it was abruptly From b6821a4fc722a926f16b8ba0448b6357c990a871 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 10:49:26 -0800 Subject: [PATCH 16/21] Fix some include requirements for pruning --- plugins/commands/plugin/action/prune_gems.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/commands/plugin/action/prune_gems.rb b/plugins/commands/plugin/action/prune_gems.rb index e90ede8f9..9d3a76f00 100644 --- a/plugins/commands/plugin/action/prune_gems.rb +++ b/plugins/commands/plugin/action/prune_gems.rb @@ -1,4 +1,5 @@ require "rubygems" +require "rubygems/user_interaction" require "rubygems/uninstaller" require "set" From bc54875a256191490c3bfdd17c156c414901126f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 12:49:21 -0800 Subject: [PATCH 17/21] Move the plugins.json to the home directory --- plugins/commands/plugin/command/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/commands/plugin/command/base.rb b/plugins/commands/plugin/command/base.rb index 7c98b38b6..5180dfd5d 100644 --- a/plugins/commands/plugin/command/base.rb +++ b/plugins/commands/plugin/command/base.rb @@ -11,7 +11,7 @@ module VagrantPlugins def action(callable, env=nil) env = { :gem_helper => GemHelper.new(@env.gems_path), - :plugin_state_file => StateFile.new(@env.data_dir.join("plugins.json")) + :plugin_state_file => StateFile.new(@env.home_path.join("plugins.json")) }.merge(env || {}) @env.action_runner.run(callable, env) From 693b825eb109fac4ef5c1aca2241dc3eecd981dc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 12:53:52 -0800 Subject: [PATCH 18/21] Don't use the RC file, use the JSOn state file --- lib/vagrant/environment.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 72f5e0748..5d0bccc37 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -15,7 +15,6 @@ module Vagrant class Environment DEFAULT_HOME = "~/.vagrant.d" DEFAULT_LOCAL_DATA = ".vagrant" - DEFAULT_RC = "~/.vagrantrc" # The `cwd` that this environment represents attr_reader :cwd @@ -598,12 +597,14 @@ module Vagrant ::Gem.clear_paths # Load the plugins - rc_path = File.expand_path(ENV["VAGRANT_RC"] || DEFAULT_RC) - if File.file?(rc_path) && @@loaded_rc.add?(rc_path) - @logger.debug("Loading RC file: #{rc_path}") - load rc_path - else - @logger.debug("RC file not found. Not loading: #{rc_path}") + plugins_json_file = @home_path.join("plugins.json") + @logger.debug("Loading plugins from: #{plugins_json_file}") + if plugins_json_file.file? + data = JSON.parse(plugins_json_file.read) + data["installed"].each do |plugin| + @logger.info("Loading plugin from JSON: #{plugin}") + Vagrant.require_plugin(plugin) + end end end From 9690754983133248756cd00010d2ed9c813ed632 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 12:59:32 -0800 Subject: [PATCH 19/21] Plugin listing is a bit more sophisticated, shows version --- .../commands/plugin/action/list_plugins.rb | 23 +++++++++++++------ templates/locales/en.yml | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 5123ff0a0..838c7754b 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -25,14 +25,23 @@ module VagrantPlugins Gem::Specification.find_all end - # Go through each spec and if it is an installed plugin, then - # output it. This means that both the installed state and - # gem match up. + # Get the latest version of the installed plugins + installed_map = {} specs.each do |spec| - if installed.include?(spec.name) - # TODO: Formatting - env[:ui].info spec.name - end + # Ignore specs that aren't in our installed list + next if !installed.include?(spec.name) + + # If we already have a newer version in our list of installed, + # then ignore it + next if installed_map.has_key?(spec.name) && + installed_map[spec.name].version >= spec.version + + installed_map[spec.name] = spec + end + + # Output! + installed_map.values.each do |spec| + env[:ui].info "#{spec.name} (#{spec.version})" end @app.call(env) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 928d2a1e4..4b0a5ce7d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -418,7 +418,7 @@ en: `vagrantup.com` for more information on using Vagrant. plugin: installing: |- - Installing the '%{name}' plugin... + Installing the '%{name}' plugin. This can take a few minutes... uninstalling: |- Uninstalling the '%{name}' plugin... status: From 5882d5dad6209e25f5f0d39823016ae85feb74a4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 13:03:00 -0800 Subject: [PATCH 20/21] VAGRANT_NO_PLUGINS can be used to skip plugin loading --- lib/vagrant/environment.rb | 7 +++++++ plugins/commands/plugin/action.rb | 1 + 2 files changed, 8 insertions(+) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 5d0bccc37..fafa7d224 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -591,6 +591,13 @@ module Vagrant # Loads the Vagrant plugins by properly setting up RubyGems so that # our private gem repository is on the path. def load_plugins + if ENV["VAGRANT_NO_PLUGINS"] + # If this key exists, then we don't load any plugins. It is a "safe + # mode" of sorts. + @logger.warn("VAGRANT_NO_PLUGINS is set. Not loading 3rd party plugins.") + return + end + # Add our private gem path to the gem path and reset the paths # that Rubygems knows about. ENV["GEM_PATH"] = "#{@gems_path}#{::File::PATH_SEPARATOR}#{ENV["GEM_PATH"]}" diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index a23a8617d..4f14c2cf6 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -30,6 +30,7 @@ module VagrantPlugins b.use PruneGems end end + # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) autoload :BundlerCheck, action_root.join("bundler_check") From 455745277c822f466f188c5dbad44085e26e8958 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 3 Feb 2013 13:13:22 -0800 Subject: [PATCH 21/21] List plugins should have output when no plugins --- plugins/commands/plugin/action/list_plugins.rb | 8 ++++++-- templates/locales/en.yml | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 838c7754b..3da2cd5f6 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -40,8 +40,12 @@ module VagrantPlugins end # Output! - installed_map.values.each do |spec| - env[:ui].info "#{spec.name} (#{spec.version})" + if installed_map.empty? + env[:ui].info(I18n.t("vagrant.commands.plugin.no_plugins")) + else + installed_map.values.each do |spec| + env[:ui].info "#{spec.name} (#{spec.version})" + end end @app.call(env) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 4b0a5ce7d..12bc3be6c 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -417,6 +417,8 @@ en: the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant. plugin: + no_plugins: |- + No plugins installed. installing: |- Installing the '%{name}' plugin. This can take a few minutes... uninstalling: |-