logstash/lib/pluginmanager/install.rb
Pier-Hugues Pellerin 4dc2fd3ddd Remove unecessary dependency file-dependencies
To understand why we can now remove this gem we have to go back at the
history of LS 1.5 and the choice we made back them. In the begining
plugins depending on external files like the `user-agent` or the `filter-geoip`
were not bundling theses files in the gem.

The `file-dependencies` was providing a new hook when plugins were
installed to trigger the file download.

In the context of making the plugins work in an offline environment we
need to make sure that every plugins external resources are present in
the gem.

We have currently a few plugins that require external files and they all
bundle it. So I think its safe to remove that feature, also the hook was
not triggered correctly. So it was a NOOP.

Fixes #8298
2017-09-18 18:56:58 +00:00

192 lines
7.8 KiB
Ruby

# encoding: utf-8
require "pluginmanager/command"
require "pluginmanager/install_strategy_factory"
require "pluginmanager/ui"
require "pluginmanager/errors"
require "jar-dependencies"
require "jar_install_post_install_hook"
require "fileutils"
class LogStash::PluginManager::Install < LogStash::PluginManager::Command
parameter "[PLUGIN] ...", "plugin name(s) or file", :attribute_name => :plugins_arg
option "--version", "VERSION", "version of the plugin to install"
option "--[no-]verify", :flag, "verify plugin validity before installation", :default => true
option "--preserve", :flag, "preserve current gem options", :default => false
option "--development", :flag, "install all development dependencies of currently installed plugins", :default => false
option "--local", :flag, "force local-only plugin installation. see bin/logstash-plugin package|unpack", :default => false
# the install logic below support installing multiple plugins with each a version specification
# but the argument parsing does not support it for now so currently if specifying --version only
# one plugin name can be also specified.
def execute
# Turn off any jar dependencies lookup when running with `--local`
ENV["JARS_SKIP"] = "true" if local?
# This is a special flow for PACK related plugins,
# if we dont detect an pack we will just use the normal `Bundle install` Strategy`
# this could be refactored into his own strategy
begin
if strategy = LogStash::PluginManager::InstallStrategyFactory.create(plugins_arg)
LogStash::PluginManager.ui.debug("Installing with strategy: #{strategy.class}")
strategy.execute
return
end
rescue LogStash::PluginManager::InstallError => e
report_exception("An error occured when installing the: #{plugins_args_human}, to have more information about the error add a DEBUG=1 before running the command.", e.original_exception)
return
rescue LogStash::PluginManager::FileNotFoundError => e
report_exception("File not found for: #{plugins_args_human}", e)
return
rescue LogStash::PluginManager::InvalidPackError => e
report_exception("Invalid pack for: #{plugins_args_human}, reason: #{e.message}", e)
return
rescue => e
report_exception("Something went wrong when installing #{plugins_args_human}", e)
return
end
# TODO(ph): refactor this into his own strategy
validate_cli_options!
if local_gems?
gems = extract_local_gems_plugins
elsif development?
gems = plugins_development_gems
else
gems = plugins_gems
verify_remote!(gems) if !local? && verify?
end
install_gems_list!(gems)
remove_unused_locally_installed_gems!
end
private
def validate_cli_options!
if development?
signal_usage_error("Cannot specify plugin(s) with --development, it will add the development dependencies of the currently installed plugins") unless plugins_arg.empty?
else
signal_usage_error("No plugin specified") if plugins_arg.empty? && verify?
# TODO: find right syntax to allow specifying list of plugins with optional version specification for each
signal_usage_error("Only 1 plugin name can be specified with --version") if version && plugins_arg.size > 1
end
signal_error("File #{LogStash::Environment::GEMFILE_PATH} does not exist or is not writable, aborting") unless ::File.writable?(LogStash::Environment::GEMFILE_PATH)
end
# Check if the specified gems contains
# the logstash `metadata`
def verify_remote!(gems)
options = { :rubygems_source => gemfile.gemset.sources }
gems.each do |plugin, version|
puts("Validating #{[plugin, version].compact.join("-")}")
next if validate_plugin(plugin, version, options)
signal_error("Installation aborted, verification failed for #{plugin} #{version}")
end
end
def validate_plugin(plugin, version, options)
LogStash::PluginManager.logstash_plugin?(plugin, version, options)
rescue SocketError
false
end
def plugins_development_gems
# Get currently defined gems and their dev dependencies
specs = []
specs = LogStash::PluginManager.all_installed_plugins_gem_specs(gemfile)
# Construct the list of dependencies to add to the current gemfile
specs.each_with_object([]) do |spec, install_list|
dependencies = spec.dependencies
.select { |dep| dep.type == :development }
.map { |dep| [dep.name] + dep.requirement.as_list }
install_list.concat(dependencies)
end
end
def plugins_gems
version ? [plugins_arg << version] : plugins_arg.map { |plugin| [plugin, nil] }
end
# install_list will be an array of [plugin name, version, options] tuples, version it
# can be nil at this point we know that plugins_arg is not empty and if the
# --version is specified there is only one plugin in plugins_arg
#
def install_gems_list!(install_list)
# If something goes wrong during the installation `LogStash::Gemfile` will restore a backup version.
install_list = LogStash::PluginManager.merge_duplicates(install_list)
# Add plugins/gems to the current gemfile
puts("Installing" + (install_list.empty? ? "..." : " " + install_list.collect(&:first).join(", ")))
install_list.each do |plugin, version, options|
if preserve?
plugin_gem = gemfile.find(plugin)
puts("Preserving Gemfile gem options for plugin #{plugin}") if plugin_gem && !plugin_gem.options.empty?
gemfile.update(plugin, version, options)
else
gemfile.overwrite(plugin, version, options)
end
end
# Sync gemfiles changes to disk to make them available to the `bundler install`'s API
gemfile.save
bundler_options = {:install => true}
bundler_options[:without] = [] if development?
bundler_options[:rubygems_source] = gemfile.gemset.sources
bundler_options[:local] = true if local?
output = LogStash::Bundler.invoke!(bundler_options)
puts("Installation successful")
rescue => exception
gemfile.restore!
report_exception("Installation Aborted", exception)
ensure
display_bundler_output(output)
end
# Extract the specified local gems in a predefined local path
# Update the gemfile to use a relative path to this plugin and run
# Bundler, this will mark the gem not updatable by `bin/logstash-plugin update`
# This is the most reliable way to make it work in bundler without
# hacking with `how bundler works`
#
# Bundler 2.0, will have support for plugins source we could create a .gem source
# to support it.
def extract_local_gems_plugins
FileUtils.mkdir_p(LogStash::Environment::CACHE_PATH)
plugins_arg.collect do |plugin|
# We do the verify before extracting the gem so we dont have to deal with unused path
if verify?
puts("Validating #{plugin}")
signal_error("Installation aborted, verification failed for #{plugin}") unless LogStash::PluginManager.logstash_plugin?(plugin, version)
end
# Make the original .gem available for the prepare-offline-pack,
# paquet will lookup in the cache directory before going to rubygems.
FileUtils.cp(plugin, ::File.join(LogStash::Environment::CACHE_PATH, ::File.basename(plugin)))
package, path = LogStash::Rubygems.unpack(plugin, LogStash::Environment::LOCAL_GEM_PATH)
[package.spec.name, package.spec.version, { :path => relative_path(path) }]
end
end
# We cannot install both .gem and normal plugin in one call of `plugin install`
def local_gems?
return false if plugins_arg.empty?
local_gem = plugins_arg.collect { |plugin| ::File.extname(plugin) == ".gem" }.uniq
if local_gem.size == 1
return local_gem.first
else
signal_usage_error("Mixed source of plugins, you can't mix local `.gem` and remote gems")
end
end
def plugins_args_human
plugins_arg.join(", ")
end
end # class Logstash::PluginManager