mirror of
https://github.com/elastic/logstash.git
synced 2025-04-19 12:25:10 -04:00
* tests: integration tests for pluginmanager install --preserve * fix regression where pluginmanager's install --preserve flag didn't
287 lines
12 KiB
Ruby
287 lines
12 KiB
Ruby
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
# license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright
|
|
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
# the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
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
|
|
option "--[no-]conservative", :flag, "do a conservative update of plugin's dependencies", :default => true
|
|
|
|
# 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
|
|
gems = verify_remote!(gems) if !local? && verify?
|
|
end
|
|
|
|
check_for_integrations(gems)
|
|
update_logstash_mixin_dependencies(gems)
|
|
install_gems_list!(gems)
|
|
remove_unused_locally_installed_gems!
|
|
remove_unused_integration_overlaps!
|
|
remove_orphan_dependencies!
|
|
end
|
|
|
|
private
|
|
|
|
def remove_unused_integration_overlaps!
|
|
installed_plugin_specs = plugins_arg.flat_map do |plugin_arg|
|
|
if LogStash::PluginManager.plugin_file?(plugin_arg)
|
|
LogStash::PluginManager.plugin_file_spec(plugin_arg)
|
|
else
|
|
LogStash::PluginManager.find_plugins_gem_specs(plugin_arg)
|
|
end
|
|
end.select do |spec|
|
|
LogStash::PluginManager.integration_plugin_spec?(spec)
|
|
end.flat_map do |spec|
|
|
LogStash::PluginManager.integration_plugin_provides(spec)
|
|
end.select do |plugin_name|
|
|
LogStash::PluginManager.installed_plugin?(plugin_name, gemfile)
|
|
end.each do |plugin_name|
|
|
puts "Removing '#{plugin_name}' since it is provided by an integration plugin"
|
|
::Bundler::LogstashUninstall.uninstall!(plugin_name)
|
|
end
|
|
end
|
|
|
|
def check_for_integrations(gems)
|
|
gems.each do |plugin, _version|
|
|
integration_plugin = LogStash::PluginManager.which_integration_plugin_provides(plugin, gemfile)
|
|
if integration_plugin
|
|
signal_error("Installation aborted, plugin '#{plugin}' is already provided by '#{integration_plugin.name}'")
|
|
end
|
|
end
|
|
end
|
|
|
|
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)
|
|
gems_swap = {}
|
|
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_usage_error("Installs of an alias doesn't require version specification --version") if version
|
|
|
|
# if the plugin is an alias then fallback to the original name
|
|
if LogStash::PluginManager::ALIASES.has_key?(plugin)
|
|
resolved_plugin = LogStash::PluginManager::ALIASES[plugin]
|
|
if validate_plugin(resolved_plugin, version, options)
|
|
puts "Remapping alias #{plugin} to #{resolved_plugin}"
|
|
gems_swap[plugin] = resolved_plugin
|
|
next
|
|
end
|
|
end
|
|
signal_error("Installation aborted, verification failed for #{plugin} #{version}")
|
|
end
|
|
|
|
# substitute in gems the list the alias plugin with the original
|
|
gems.collect do |plugin, version|
|
|
[gems_swap.fetch(plugin, 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
|
|
|
|
def local_gem?
|
|
plugins_arg.any? { |plugin_arg| LogStash::PluginManager.plugin_file?(plugin_arg) }
|
|
end
|
|
|
|
def update_logstash_mixin_dependencies(install_list)
|
|
return if !verify? || preserve? || development? || local? || local_gem?
|
|
|
|
puts "Resolving mixin dependencies"
|
|
LogStash::Bundler.prepare
|
|
plugins_to_update = install_list.map(&:first)
|
|
unlock_dependencies = LogStash::Bundler.expand_logstash_mixin_dependencies(plugins_to_update) - plugins_to_update
|
|
|
|
if unlock_dependencies.any?
|
|
puts "Updating mixin dependencies #{unlock_dependencies.join(', ')}"
|
|
LogStash::Bundler.invoke! update: unlock_dependencies,
|
|
rubygems_source: gemfile.gemset.sources,
|
|
conservative: conservative?
|
|
end
|
|
|
|
unlock_dependencies
|
|
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|
|
|
plugin_gem = gemfile.find(plugin)
|
|
if preserve?
|
|
puts("Preserving Gemfile gem options for plugin #{plugin}") if plugin_gem && !plugin_gem.options.empty?
|
|
# if the plugin exists and no version was specified, keep the existing requirements
|
|
requirements = (plugin_gem && version.nil? ? plugin_gem.requirements : [version]).compact
|
|
gemfile.update(plugin, *requirements, 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 = nil
|
|
# Unfreeze the bundle when installing gems
|
|
Bundler.settings.temporary({:frozen => false}) do
|
|
output = LogStash::Bundler.invoke!(bundler_options)
|
|
output << LogStash::Bundler.genericize_platform.to_s
|
|
end
|
|
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) }, package.spec]
|
|
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
|