class Vagrant::Plugin::Manager

The Manager helps with installing, listing, and initializing plugins.

Attributes

local_file[R]
system_file[R]
user_file[R]

Public Class Methods

instance() click to toggle source
# File lib/vagrant/plugin/manager.rb, line 26
def self.instance
  @instance ||= self.new(user_plugins_file)
end
new(user_file) click to toggle source

@param [Pathname] user_file

# File lib/vagrant/plugin/manager.rb, line 35
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
  @globalized = @localized = false
end
system_plugins_file() click to toggle source

Returns the path to the [StateFile] for system plugins.

# File lib/vagrant/plugin/manager.rb, line 20
def self.system_plugins_file
  dir = Vagrant.installer_embedded_dir
  return nil if !dir
  Pathname.new(dir).join("plugins.json")
end
user_plugins_file() click to toggle source

Returns the path to the [StateFile] for user plugins.

@return [Pathname]

# File lib/vagrant/plugin/manager.rb, line 15
def self.user_plugins_file
  Vagrant.user_data_path.join("plugins.json")
end

Public Instance Methods

bundler_init(plugins, **opts) click to toggle source

Initialize bundler with given plugins

@param [Hash] plugins List of plugins @return [nil]

# File lib/vagrant/plugin/manager.rb, line 83
def bundler_init(plugins, **opts)
  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"]
    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, **opts)
  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
globalize!() click to toggle source

Enable global plugins

@return [Hash] list of plugins

# File lib/vagrant/plugin/manager.rb, line 50
def globalize!
  @globalized = true
  @logger.debug("Enabling globalized plugins")
  plugins = installed_plugins
  bundler_init(plugins, global: user_file.path)
  plugins
end
install_plugin(name, **opts) click to toggle source

Installs another plugin into our gem directory.

@param [String] name Name of the plugin (gem) @return [Gem::Specification]

# File lib/vagrant/plugin/manager.rb, line 116
def install_plugin(name, **opts)
  if opts[:env_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)
    name       = local_spec.name
    opts[:version] = local_spec.version.to_s
  end

  plugins = installed_plugins
  plugins[name] = {
    "require"     => opts[:require],
    "gem_version" => opts[:version],
    "sources"     => opts[:sources],
  }

  if local_spec.nil?
    result = nil
    install_lambda = lambda do
      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
      end
    end

    if opts[:verbose]
      Vagrant::Bundler.instance.verbose(&install_lambda)
    else
      install_lambda.call
    end
  else
    result = local_spec
  end

  if result
    # Add the plugin to the state 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],
      env_local: !!opts[:env_local],
      installed_gem_version: result.version.to_s
    )
  else
    r = Gem::Dependency.new(name, opts[:version])
    result = Gem::Specification.find { |s|
      s.satisfies_requirement?(r) &&
        s.activated?
    }
    raise Errors::PluginInstallFailed,
      name: name if result.nil?
    @logger.warn("Plugin install returned no result as no new plugins were installed.")
  end
  # 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, local: !!opts[:local])
  result
rescue Gem::GemNotFoundException
  raise Errors::PluginGemNotFound, name: name
rescue Gem::Exception => err
  @logger.warn("Failed to install plugin: #{err}")
  @logger.debug("#{err.class}: #{err}\n#{err.backtrace.join("\n")}")
  # Try and determine a cause for the failure
  case err.message
  when /install development tools first/
    raise Errors::PluginNeedsDeveloperTools
  when /library not found in default locations/
    lib = err.message.match(/(\w+) library not found in default locations/)
    if lib.nil?
      raise Errors::BundlerError, message: err.message
    end
    raise Errors::PluginMissingLibrary,
      library: lib.captures.first,
      name: name
  when /find header files for ruby/
    raise Errors::PluginMissingRubyDev
  else
    raise Errors::BundlerError, message: err.message
  end
end
installed_plugins() click to toggle source

This returns the list of plugins that should be enabled.

@return [Hash]

# File lib/vagrant/plugin/manager.rb, line 266
def installed_plugins
  system = {}
  if @system_file
    @system_file.installed_plugins.each do |k, v|
      system[k] = v.merge("system" => true)
    end
  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|
      [plugin_name, plugin_info]
    }.sort_by(&:first)
  ]
end
installed_specs() click to toggle source

This returns the list of plugins that are installed as Gem::Specifications.

@return [Array<Gem::Specification>]

# File lib/vagrant/plugin/manager.rb, line 292
def installed_specs
  installed_plugin_info = installed_plugins
  installed = Set.new(installed_plugin_info.keys)
  installed_versions = Hash[
    installed_plugin_info.map{|plugin_name, plugin_info|
      gem_version = plugin_info["gem_version"].to_s
      gem_version = "> 0" if gem_version.empty?
      [plugin_name, Gem::Requirement.new(gem_version)]
    }
  ]

  # Go through the plugins installed in this environment and
  # get the latest version of each.
  installed_map = {}
  Gem::Specification.find_all.each do |spec|
    # Ignore specs that aren't in our installed list
    next if !installed.include?(spec.name)

    next if installed_versions[spec.name] &&
      !installed_versions[spec.name].satisfied_by?(spec.version)

    # If we already have a newer version in our list of installed,
    # then ignore it
    next if installed_map.key?(spec.name) &&
      installed_map[spec.name].version >= spec.version

    installed_map[spec.name] = spec
  end

  installed_map.values
end
load_plugins(plugins) click to toggle source

Loads the requested plugins into the Vagrant runtime

@param [Hash] plugins List of plugins to load @return [nil]

# File lib/vagrant/plugin/manager.rb, line 328
def load_plugins(plugins)
  if !Vagrant.plugins_enabled?
    @logger.warn("Plugin loading is disabled")
    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|
      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
  nil
end
localize!(env) click to toggle source

Enable environment local plugins

@param [Environment] env Vagrant environment @return [Hash, nil] list of plugins

# File lib/vagrant/plugin/manager.rb, line 62
def localize!(env)
  @localized = true
  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, local: local_file.path)
    plugins
  end
end
plugin_installed?(name, version=nil) click to toggle source

Check if the requested plugin is installed

@param [String] name Name of plugin @param [String] version Specific version of the plugin @return [Boolean]

# File lib/vagrant/plugin/manager.rb, line 381
def plugin_installed?(name, version=nil)
  # Make the requirement object
  version = Gem::Requirement.new([version.to_s]) if version

  # If plugins are loaded, check for match in loaded specs
  if ready?
    return installed_specs.any? do |s|
      match = s.name == name
      next match if !version
      next match && version.satisfied_by?(s.version)
    end
  end

  # Plugins are not loaded yet so check installed plugin data
  plugin_info = installed_plugins[name]
  return false if !plugin_info
  return !!plugin_info if version.nil? || plugin_info["installed_gem_version"].nil?
  installed_version = Gem::Version.new(plugin_info["installed_gem_version"])
  version.satisfied_by?(installed_version)
end
ready?() click to toggle source

@return [Boolean] local and global plugins are loaded

# File lib/vagrant/plugin/manager.rb, line 75
def ready?
  @globalized && @localized
end
uninstall_plugin(name, **opts) click to toggle source

Uninstalls the plugin with the given name.

@param [String] name

# File lib/vagrant/plugin/manager.rb, line 207
def uninstall_plugin(name, **opts)
  if @system_file
    if !@user_file.has_plugin?(name) && @system_file.has_plugin?(name)
      raise Errors::PluginUninstallSystem,
        name: name
    end
  end

  if opts[:env_local] && @local_file.nil?
    raise Errors::PluginNoLocalError
  end

  plugin_file = opts[:env_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)
rescue Gem::Exception => e
  raise Errors::BundlerError, message: e.to_s
end
update_plugins(specific, **opts) click to toggle source

Updates all or a specific set of plugins.

# File lib/vagrant/plugin/manager.rb, line 235
def update_plugins(specific, **opts)
  if opts[:env_local] && @local_file.nil?
    raise Errors::PluginNoLocalError
  end

  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|
    matching_spec = result.detect{|s| s.name == name}
    info = Hash[
      info.map do |key, value|
        [key.to_sym, value]
      end
    ]
    if matching_spec
      plugin_file.add_plugin(name, **info.merge(
        version: "> 0",
        installed_gem_version: matching_spec.version.to_s
      ))
    end
  end
  Vagrant::Bundler.instance.clean(installed_plugins)
  result
rescue Gem::Exception => e
  raise Errors::BundlerError, message: e.to_s
end