logstash/lib/pluginmanager/update.rb
github-actions[bot] 77e355ed9f
plugin manager: add --level=[major|minor|patch] (default: minor) (#16899) (#16974)
* plugin manager: add `--level=[major|minor|patch]` (default: `minor`)

* docs: plugin manager update `--level` behavior

* Update docs/static/plugin-manager.asciidoc

Co-authored-by: João Duarte <jsvd@users.noreply.github.com>

* docs: plugin update major as subheading

* docs: intention-first in major plugin updates

* Update docs/static/plugin-manager.asciidoc

Co-authored-by: Karen Metts <35154725+karenzone@users.noreply.github.com>

---------

Co-authored-by: João Duarte <jsvd@users.noreply.github.com>
Co-authored-by: Karen Metts <35154725+karenzone@users.noreply.github.com>
(cherry picked from commit 6943df5570)

Co-authored-by: Ry Biesemeyer <yaauie@users.noreply.github.com>
2025-01-28 17:18:03 -08:00

167 lines
6.8 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 "jar-dependencies"
require "jar_install_post_install_hook"
class LogStash::PluginManager::Update < LogStash::PluginManager::Command
REJECTED_OPTIONS = [:path, :git, :github]
# These are local gems used by LS and needs to be filtered out of other plugin gems
NON_PLUGIN_LOCAL_GEMS = ["logstash-core", "logstash-core-plugin-api"]
SUPPORTED_LEVELS = %w(major minor patch)
parameter "[PLUGIN] ...", "Plugin name(s) to upgrade to latest version", :attribute_name => :plugins_arg
option "--level", "LEVEL", "restrict updates to given semantic version level (one of #{SUPPORTED_LEVELS})", :default => "minor" do |given_level|
fail("unsupported level `#{given_level}`; expected one of #{SUPPORTED_LEVELS}") unless SUPPORTED_LEVELS.include?(given_level)
given_level
end
option "--[no-]verify", :flag, "verify plugin validity before installation", :default => true
option "--local", :flag, "force local-only plugin update. see bin/logstash-plugin package|unpack", :default => false
option "--[no-]conservative", :flag, "do a conservative update of plugin's dependencies", :default => true
def execute
# Turn off any jar dependencies lookup when running with `--local`
ENV["JARS_SKIP"] = "true" if local?
# remove "system" local gems used by LS
local_gems = gemfile.locally_installed_gems.map(&:name) - NON_PLUGIN_LOCAL_GEMS
if local_gems.size > 0
if update_all?
plugins_with_path = local_gems
else
plugins_with_path = plugins_arg & local_gems
end
warn_local_gems(plugins_with_path) if plugins_with_path.size > 0
end
update_gems!
end
private
def update_all?
plugins_arg.size == 0
end
def warn_local_gems(plugins_with_path)
puts("Update is not supported for manually defined plugins or local .gem plugin installations, skipping: #{plugins_with_path.join(", ")}")
end
def update_gems!
# If any error is raise inside the block the Gemfile will restore a backup of the Gemfile
previous_gem_specs_map = find_latest_gem_specs
# remove any version constrain from the Gemfile so the plugin(s) can be updated to latest version
# calling update without requirements will remove any previous requirements
plugins = plugins_to_update(previous_gem_specs_map)
# Skipping the major version validation when using a local cache as we can have situations
# without internet connection.
filtered_plugins = plugins.map { |plugin| gemfile.find(plugin) }
.compact
.reject { |plugin| REJECTED_OPTIONS.any? { |key| plugin.options.has_key?(key) } }
.each { |plugin| gemfile.update(plugin.name) }
# force a disk sync before running bundler
gemfile.save
puts("Updating #{filtered_plugins.collect(&:name).join(", ")}") unless filtered_plugins.empty?
output = nil
# any errors will be logged to $stderr by invoke!
# Bundler cannot update and clean gems in one operation so we have to call the CLI twice.
Bundler.settings.temporary(:frozen => false) do # Unfreeze the bundle when updating gems
output = LogStash::Bundler.invoke! update: plugins,
level: level,
rubygems_source: gemfile.gemset.sources,
local: local?,
conservative: conservative?
output << LogStash::Bundler.genericize_platform unless output.nil?
end
# We currently dont removed unused gems from the logstash installation
# see: https://github.com/elastic/logstash/issues/6339
# output = LogStash::Bundler.invoke!(:clean => true)
display_updated_plugins(previous_gem_specs_map)
rescue => exception
gemfile.restore!
report_exception("Updated Aborted", exception)
ensure
display_bundler_output(output)
end
# create list of plugins to update
def plugins_to_update(previous_gem_specs_map)
if update_all?
previous_gem_specs_map.values.map {|spec| spec.name}
else
# If the plugins isn't available in the gemspec or in
# the gemfile defined with a local path, we assume the plugins is not
# installed.
not_installed = plugins_arg.select { |plugin| !previous_gem_specs_map.has_key?(plugin.downcase) && !gemfile.find(plugin) }
# find only the not installed that doesn't correspond to an alias
not_installed_aliases = not_installed.select { |plugin| LogStash::PluginManager::ALIASES.has_key?(plugin)}
not_installed -= not_installed_aliases
signal_error("Plugin #{not_installed.join(', ')} is not installed so it cannot be updated, aborting") unless not_installed.empty?
# resolve aliases that doesn't correspond to a real gem
plugins_to_update = plugins_arg.map do |plugin|
if not_installed_aliases.include?(plugin)
resolved_plugin = LogStash::PluginManager::ALIASES[plugin]
puts "Remapping alias #{plugin} to #{resolved_plugin}"
resolved_plugin
else
plugin
end
end
plugins_to_update
end
end
# We compare the before the update and after the update
def display_updated_plugins(previous_gem_specs_map)
update_count = 0
find_latest_gem_specs.values.each do |spec|
name = spec.name.downcase
if previous_gem_specs_map.has_key?(name)
if spec.version != previous_gem_specs_map[name].version
puts("Updated #{spec.name} #{previous_gem_specs_map[name].version.to_s} to #{spec.version.to_s}")
update_count += 1
end
else
puts("Installed #{spec.name} #{spec.version.to_s}")
update_count += 1
end
end
puts("No plugin updated") if update_count.zero?
end
# retrieve only the latest spec for all locally installed plugins
# @return [Hash] result hash {plugin_name.downcase => plugin_spec}
def find_latest_gem_specs
LogStash::PluginManager.all_installed_plugins_gem_specs(gemfile).inject({}) do |result, spec|
previous = result[spec.name.downcase]
result[spec.name.downcase] = previous ? [previous, spec].max_by {|s| s.version} : spec
result
end
end
end