From 2a8898a395a94597a933c2a47ba6e4ce483336eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 17:17:16 -0800 Subject: [PATCH] plugin manager: add `--level=[major|minor|patch]` (default: `minor`) (#16899) (#16973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 Co-authored-by: Karen Metts <35154725+karenzone@users.noreply.github.com> (cherry picked from commit 6943df557085a276cef47bba1bead9a1820d3c05) Co-authored-by: Ry Biesemeyer --- docs/static/plugin-manager.asciidoc | 16 +++++++ lib/bootstrap/bundler.rb | 1 + lib/pluginmanager/update.rb | 7 +++ spec/unit/bootstrap/bundler_spec.rb | 29 +++++++++++- spec/unit/plugin_manager/update_spec.rb | 60 +++++++++++++++++++++---- 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/docs/static/plugin-manager.asciidoc b/docs/static/plugin-manager.asciidoc index ab58f11ca08..464030ace33 100644 --- a/docs/static/plugin-manager.asciidoc +++ b/docs/static/plugin-manager.asciidoc @@ -112,6 +112,22 @@ bin/logstash-plugin update logstash-input-github <2> <1> updates all installed plugins <2> updates only the plugin you specify +[discrete] +[[updating-major]] +==== Major version plugin updates + +To avoid introducing breaking changes, the plugin manager updates only plugins for which newer _minor_ or _patch_ versions exist by default. +If you wish to also include breaking changes, specify `--level=major`. + +[source,shell] +---------------------------------- +bin/logstash-plugin update --level=major <1> +bin/logstash-plugin update --level=major logstash-input-github <2> +---------------------------------- +<1> updates all installed plugins to latest, including major versions with breaking changes +<2> updates only the plugin you specify to latest, including major versions with breaking changes + + [discrete] [[removing-plugins]] === Removing plugins diff --git a/lib/bootstrap/bundler.rb b/lib/bootstrap/bundler.rb index 8cd56d0d3e8..b517836da89 100644 --- a/lib/bootstrap/bundler.rb +++ b/lib/bootstrap/bundler.rb @@ -266,6 +266,7 @@ def bundler_arguments(options = {}) elsif options[:update] arguments << "update" arguments << expand_logstash_mixin_dependencies(options[:update]) + arguments << "--#{options[:level] || 'minor'}" arguments << "--local" if options[:local] arguments << "--conservative" if options[:conservative] elsif options[:clean] diff --git a/lib/pluginmanager/update.rb b/lib/pluginmanager/update.rb index 4c3651abbd0..76130bfa584 100644 --- a/lib/pluginmanager/update.rb +++ b/lib/pluginmanager/update.rb @@ -24,7 +24,13 @@ class LogStash::PluginManager::Update < LogStash::PluginManager::Command # 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 @@ -82,6 +88,7 @@ def update_gems! # 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? diff --git a/spec/unit/bootstrap/bundler_spec.rb b/spec/unit/bootstrap/bundler_spec.rb index f635ba709f2..5571f1f73c7 100644 --- a/spec/unit/bootstrap/bundler_spec.rb +++ b/spec/unit/bootstrap/bundler_spec.rb @@ -140,6 +140,33 @@ end end + context "level: major" do + let(:options) { super().merge(:level => "major") } + it "invokes bundler with --minor" do + expect(bundler_arguments).to include("--major") + end + end + + context "level: minor" do + let(:options) { super().merge(:level => "minor") } + it "invokes bundler with --minor" do + expect(bundler_arguments).to include("--minor") + end + end + + context "level: patch" do + let(:options) { super().merge(:level => "patch") } + it "invokes bundler with --minor" do + expect(bundler_arguments).to include("--patch") + end + end + + context "level: unspecified" do + it "invokes bundler with --minor" do + expect(bundler_arguments).to include("--minor") + end + end + context 'with ecs_compatibility' do let(:plugin_name) { 'logstash-output-elasticsearch' } let(:options) { { :update => plugin_name } } @@ -147,7 +174,7 @@ it "also update dependencies" do expect(bundler_arguments).to include('logstash-mixin-ecs_compatibility_support', plugin_name) - mixin_libs = bundler_arguments - ["update", plugin_name] + mixin_libs = bundler_arguments - ["update", "--minor", plugin_name] mixin_libs.each do |gem_name| dep = ::Gem::Dependency.new(gem_name) expect(dep.type).to eq(:runtime) diff --git a/spec/unit/plugin_manager/update_spec.rb b/spec/unit/plugin_manager/update_spec.rb index e69fb91c2e7..ac6abeefb97 100644 --- a/spec/unit/plugin_manager/update_spec.rb +++ b/spec/unit/plugin_manager/update_spec.rb @@ -21,18 +21,24 @@ describe LogStash::PluginManager::Update do let(:cmd) { LogStash::PluginManager::Update.new("update") } let(:sources) { cmd.gemfile.gemset.sources } + let(:expect_preflight_error) { false } # hack to bypass before-hook expectations before(:each) do - expect(cmd).to receive(:find_latest_gem_specs).and_return({}) - allow(cmd).to receive(:warn_local_gems).and_return(nil) - expect(cmd).to receive(:display_updated_plugins).and_return(nil) + unless expect_preflight_error + expect(cmd).to receive(:find_latest_gem_specs).and_return({}) + allow(cmd).to receive(:warn_local_gems).and_return(nil) + expect(cmd).to receive(:display_updated_plugins).and_return(nil) + end end it "pass all gem sources to the bundle update command" do sources = cmd.gemfile.gemset.sources expect_any_instance_of(LogStash::Bundler).to receive(:invoke!).with( - :update => [], :rubygems_source => sources, - :conservative => true, :local => false + :update => [], + :rubygems_source => sources, + :conservative => true, + :local => false, + :level => "minor" # default ) cmd.execute end @@ -46,14 +52,52 @@ expect(cmd.gemfile).to receive(:save).and_return(nil) expect(cmd).to receive(:plugins_to_update).and_return([plugin]) expect_any_instance_of(LogStash::Bundler).to receive(:invoke!).with( - hash_including(:update => [plugin], :rubygems_source => sources) + hash_including(:update => [plugin], :rubygems_source => sources, :level => "minor") ).and_return(nil) end it "skips version verification when ask for it" do - cmd.verify = false expect(cmd).to_not receive(:validates_version) - cmd.execute + cmd.run(["--no-verify"]) + end + end + + context "with explicit `--level` flag" do + LogStash::PluginManager::Update::SUPPORTED_LEVELS.each do |level| + context "with --level=#{level} (valid)" do + let(:requested_level) { level } + + let(:cmd) { LogStash::PluginManager::Update.new("update") } + let(:plugin) { OpenStruct.new(:name => "dummy", :options => {}) } + + before(:each) do + cmd.verify = false + end + + it "propagates the level flag as an option to Bundler#invoke!" do + expect(cmd.gemfile).to receive(:find).with(plugin).and_return(plugin) + expect(cmd.gemfile).to receive(:save).and_return(nil) + expect(cmd).to receive(:plugins_to_update).and_return([plugin]) + expect_any_instance_of(LogStash::Bundler).to receive(:invoke!).with( + hash_including(:update => [plugin], :rubygems_source => sources, :level => requested_level) + ).and_return(nil) + + cmd.run(["--level=#{requested_level}"]) + end + end + end + + context "with --level=eVeRyThInG (invalid)" do + let(:requested_level) { "eVeRyThInG" } + let(:expect_preflight_error) { true } + + let(:cmd) { LogStash::PluginManager::Update.new("update") } + let(:plugin) { OpenStruct.new(:name => "dummy", :options => {}) } + + it "errors helpfully" do + expect { cmd.run(["--level=#{requested_level}"]) } + .to raise_error.with_message(including("unsupported level `#{requested_level}`")) + end end end end