Merge branch 'plugin-command'
This introduces a new `vagrant plugin` command and removes the old `vagrant gem` command. Technically, `vagrant plugin` is still just using RubyGems underneath to manage plugins, but it masks the entire RubyGem experience away from the end user. This merge introduces three commands; `list`, `install`, and `uninstall`. They do what they sound like they do. Future enhancements will add more information such as what components a plugin installs and activation/deactivation of plugins that you want to keep installed but don't want to run.
This commit is contained in:
commit
c8260162d4
|
@ -15,7 +15,6 @@ module Vagrant
|
||||||
class Environment
|
class Environment
|
||||||
DEFAULT_HOME = "~/.vagrant.d"
|
DEFAULT_HOME = "~/.vagrant.d"
|
||||||
DEFAULT_LOCAL_DATA = ".vagrant"
|
DEFAULT_LOCAL_DATA = ".vagrant"
|
||||||
DEFAULT_RC = "~/.vagrantrc"
|
|
||||||
|
|
||||||
# The `cwd` that this environment represents
|
# The `cwd` that this environment represents
|
||||||
attr_reader :cwd
|
attr_reader :cwd
|
||||||
|
@ -411,6 +410,7 @@ module Vagrant
|
||||||
:box_collection => boxes,
|
:box_collection => boxes,
|
||||||
:global_config => config_global,
|
:global_config => config_global,
|
||||||
:host => host,
|
:host => host,
|
||||||
|
:gems_path => gems_path,
|
||||||
:root_path => root_path,
|
:root_path => root_path,
|
||||||
:tmp_path => tmp_path,
|
:tmp_path => tmp_path,
|
||||||
:ui => @ui
|
:ui => @ui
|
||||||
|
@ -591,18 +591,27 @@ module Vagrant
|
||||||
# Loads the Vagrant plugins by properly setting up RubyGems so that
|
# Loads the Vagrant plugins by properly setting up RubyGems so that
|
||||||
# our private gem repository is on the path.
|
# our private gem repository is on the path.
|
||||||
def load_plugins
|
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
|
# Add our private gem path to the gem path and reset the paths
|
||||||
# that Rubygems knows about.
|
# that Rubygems knows about.
|
||||||
ENV["GEM_PATH"] = "#{@gems_path}#{::File::PATH_SEPARATOR}#{ENV["GEM_PATH"]}"
|
ENV["GEM_PATH"] = "#{@gems_path}#{::File::PATH_SEPARATOR}#{ENV["GEM_PATH"]}"
|
||||||
::Gem.clear_paths
|
::Gem.clear_paths
|
||||||
|
|
||||||
# Load the plugins
|
# Load the plugins
|
||||||
rc_path = File.expand_path(ENV["VAGRANT_RC"] || DEFAULT_RC)
|
plugins_json_file = @home_path.join("plugins.json")
|
||||||
if File.file?(rc_path) && @@loaded_rc.add?(rc_path)
|
@logger.debug("Loading plugins from: #{plugins_json_file}")
|
||||||
@logger.debug("Loading RC file: #{rc_path}")
|
if plugins_json_file.file?
|
||||||
load rc_path
|
data = JSON.parse(plugins_json_file.read)
|
||||||
else
|
data["installed"].each do |plugin|
|
||||||
@logger.debug("RC file not found. Not loading: #{rc_path}")
|
@logger.info("Loading plugin from JSON: #{plugin}")
|
||||||
|
Vagrant.require_plugin(plugin)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -336,6 +336,10 @@ module Vagrant
|
||||||
error_key(:provider_not_found)
|
error_key(:provider_not_found)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class PluginGemError < VagrantError
|
||||||
|
error_key(:plugin_gem_error)
|
||||||
|
end
|
||||||
|
|
||||||
class PluginLoadError < VagrantError
|
class PluginLoadError < VagrantError
|
||||||
status_code(81)
|
status_code(81)
|
||||||
error_key(:plugin_load_error)
|
error_key(:plugin_load_error)
|
||||||
|
|
|
@ -15,7 +15,7 @@ module VagrantPlugins
|
||||||
if defined?(Bundler)
|
if defined?(Bundler)
|
||||||
require 'bundler/shared_helpers'
|
require 'bundler/shared_helpers'
|
||||||
if Bundler::SharedHelpers.in_bundle?
|
if Bundler::SharedHelpers.in_bundle?
|
||||||
raise Errors::GemCommandInBundler
|
raise Vagrant::Errors::GemCommandInBundler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
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
|
||||||
|
b.use PruneGems
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
|
@ -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
|
|
@ -0,0 +1,34 @@
|
||||||
|
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]
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Continue
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,56 @@
|
||||||
|
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)
|
||||||
|
# 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
|
||||||
|
specs = env[:gem_helper].with_environment do
|
||||||
|
Gem::Specification.find_all
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the latest version of the installed plugins
|
||||||
|
installed_map = {}
|
||||||
|
specs.each do |spec|
|
||||||
|
# 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!
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,156 @@
|
||||||
|
require "rubygems"
|
||||||
|
require "rubygems/user_interaction"
|
||||||
|
require "rubygems/uninstaller"
|
||||||
|
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.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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
@ -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.home_path.join("plugins.json"))
|
||||||
|
}.merge(env || {})
|
||||||
|
|
||||||
|
@env.action_runner.run(callable, env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
require 'optparse'
|
||||||
|
|
||||||
|
require_relative "base"
|
||||||
|
|
||||||
|
module VagrantPlugins
|
||||||
|
module CommandPlugin
|
||||||
|
module Command
|
||||||
|
class Install < Base
|
||||||
|
def execute
|
||||||
|
opts = OptionParser.new do |o|
|
||||||
|
o.banner = "Usage: vagrant plugin install <name> [-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
|
||||||
|
action(Action.action_install, :plugin_name => argv[0])
|
||||||
|
|
||||||
|
# Success, exit status 0
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
|
@ -0,0 +1,70 @@
|
||||||
|
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
|
||||||
|
@subcommands.register(:install) do
|
||||||
|
require_relative "install"
|
||||||
|
Install
|
||||||
|
end
|
||||||
|
|
||||||
|
@subcommands.register(:list) do
|
||||||
|
require_relative "list"
|
||||||
|
List
|
||||||
|
end
|
||||||
|
|
||||||
|
@subcommands.register(:uninstall) do
|
||||||
|
require_relative "uninstall"
|
||||||
|
Uninstall
|
||||||
|
end
|
||||||
|
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 <command> [<args>]"
|
||||||
|
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
|
|
@ -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 <name> [-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
|
|
@ -0,0 +1,74 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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<String>] 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.
|
||||||
|
# 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
|
||||||
|
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.
|
||||||
|
# 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.exit_code != 0
|
||||||
|
ensure
|
||||||
|
# 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"]
|
||||||
|
old_gem_path = ENV["GEM_PATH"]
|
||||||
|
ENV["GEM_HOME"] = @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.paths = ENV
|
||||||
|
|
||||||
|
# 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
|
||||||
|
ENV["GEM_PATH"] = old_gem_path
|
||||||
|
|
||||||
|
# Reset everything
|
||||||
|
Gem.paths = ENV
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,22 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
|
@ -0,0 +1,57 @@
|
||||||
|
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?
|
||||||
|
@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)
|
||||||
|
if !@data["installed"].include?(name)
|
||||||
|
@data["installed"] << name
|
||||||
|
end
|
||||||
|
|
||||||
|
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<String>]
|
||||||
|
def installed_plugins
|
||||||
|
@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
|
||||||
|
@data["installed"].sort!
|
||||||
|
@data["installed"].uniq!
|
||||||
|
|
||||||
|
# Save
|
||||||
|
@path.open("w+") do |f|
|
||||||
|
f.write(JSON.dump(@data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -114,11 +114,11 @@ en:
|
||||||
occurring. Please wait for the other instance of Vagrant to end and then
|
occurring. Please wait for the other instance of Vagrant to end and then
|
||||||
try again.
|
try again.
|
||||||
gem_command_in_bundler: |-
|
gem_command_in_bundler: |-
|
||||||
You cannot run the `vagrant gem` command while in a bundler environment.
|
You cannot run the `vagrant plugin` command while in a bundler environment.
|
||||||
Bundler messes around quite a bit with the RubyGem load paths and gems
|
This should generally never happen unless Vagrant is installed outside
|
||||||
installed via `vagrant gem` are excluded by Bundler.
|
of the official installers or another gem is wrongly attempting to
|
||||||
|
use Vagrant internals directly. Please properly install Vagrant to
|
||||||
Instead, please include your Vagrant plugins in your Gemfile itself.
|
fix this. If this error persists, please contact support.
|
||||||
guest:
|
guest:
|
||||||
invalid_class: |-
|
invalid_class: |-
|
||||||
The specified guest class does not inherit from a proper guest
|
The specified guest class does not inherit from a proper guest
|
||||||
|
@ -165,6 +165,11 @@ en:
|
||||||
no_env: |-
|
no_env: |-
|
||||||
A Vagrant environment is required to run this command. Run `vagrant init`
|
A Vagrant environment is required to run this command. Run `vagrant init`
|
||||||
to set one up.
|
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: |-
|
plugin_load_error: |-
|
||||||
The plugin "%{plugin}" could not be found. Please make sure that it is
|
The plugin "%{plugin}" could not be found. Please make sure that it is
|
||||||
properly installed via `vagrant gem`.
|
properly installed via `vagrant gem`.
|
||||||
|
@ -411,6 +416,13 @@ en:
|
||||||
ready to `vagrant up` your first virtual environment! Please read
|
ready to `vagrant up` your first virtual environment! Please read
|
||||||
the comments in the Vagrantfile as well as documentation on
|
the comments in the Vagrantfile as well as documentation on
|
||||||
`vagrantup.com` for more information on using Vagrant.
|
`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: |-
|
||||||
|
Uninstalling the '%{name}' plugin...
|
||||||
status:
|
status:
|
||||||
aborted: |-
|
aborted: |-
|
||||||
The VM is in an aborted state. This means that it was abruptly
|
The VM is in an aborted state. This means that it was abruptly
|
||||||
|
|
Loading…
Reference in New Issue