From 93e8e50320557c188dd2c1cc876ba792debcbade Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Tue, 14 Jan 2025 06:57:01 +0000 Subject: [PATCH 1/6] plugin manager: add `--level=[major|minor|patch]` (default: `minor`) --- 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 +++++++++++++++++++++---- 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/lib/bootstrap/bundler.rb b/lib/bootstrap/bundler.rb index 3252220212b..02816171217 100644 --- a/lib/bootstrap/bundler.rb +++ b/lib/bootstrap/bundler.rb @@ -264,6 +264,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 From 9feb4b7a004ce963766b203910a1541f8af78917 Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Tue, 14 Jan 2025 19:06:39 +0000 Subject: [PATCH 2/6] docs: plugin manager update `--level` behavior --- docs/static/plugin-manager.asciidoc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/static/plugin-manager.asciidoc b/docs/static/plugin-manager.asciidoc index ab58f11ca08..08218176af0 100644 --- a/docs/static/plugin-manager.asciidoc +++ b/docs/static/plugin-manager.asciidoc @@ -112,6 +112,21 @@ bin/logstash-plugin update logstash-input-github <2> <1> updates all installed plugins <2> updates only the plugin you specify +[NOTE] +.Major-version plugin updates +============================== +By default, the plugin manager will consume _minor_ updates containing non-breaking features and fixes. +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-version breaking changes +<2> updates only the plugin you specify to latest, including major-version breaking changes +============================== + [discrete] [[removing-plugins]] === Removing plugins From 82bd65882b910d2ef251592300fe122978a839dc Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Wed, 15 Jan 2025 11:45:38 -0800 Subject: [PATCH 3/6] Update docs/static/plugin-manager.asciidoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Duarte --- docs/static/plugin-manager.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/plugin-manager.asciidoc b/docs/static/plugin-manager.asciidoc index 08218176af0..82c7fbc0416 100644 --- a/docs/static/plugin-manager.asciidoc +++ b/docs/static/plugin-manager.asciidoc @@ -115,7 +115,7 @@ bin/logstash-plugin update logstash-input-github <2> [NOTE] .Major-version plugin updates ============================== -By default, the plugin manager will consume _minor_ updates containing non-breaking features and fixes. +By default, the plugin manager will only update plugins for which newer _minor_ or _patch_ versions exist, to avoid the introduction of breaking changes. If you wish to also include breaking changes, specify `--level=major`. [source,shell] From 3e7ecc34b886adc18658909ee498f4c62f189f1c Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Tue, 21 Jan 2025 09:36:44 -0800 Subject: [PATCH 4/6] docs: plugin update major as subheading --- docs/static/plugin-manager.asciidoc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/static/plugin-manager.asciidoc b/docs/static/plugin-manager.asciidoc index 82c7fbc0416..9d8a21bd9a4 100644 --- a/docs/static/plugin-manager.asciidoc +++ b/docs/static/plugin-manager.asciidoc @@ -112,9 +112,10 @@ bin/logstash-plugin update logstash-input-github <2> <1> updates all installed plugins <2> updates only the plugin you specify -[NOTE] -.Major-version plugin updates -============================== +[discrete] +[[updating-plugins-major-versions]] +==== Major-version plugin updates + By default, the plugin manager will only update plugins for which newer _minor_ or _patch_ versions exist, to avoid the introduction of breaking changes. If you wish to also include breaking changes, specify `--level=major`. @@ -125,7 +126,7 @@ bin/logstash-plugin update --level=major logstash-input-github <2> ---------------------------------- <1> updates all installed plugins to latest, including major-version breaking changes <2> updates only the plugin you specify to latest, including major-version breaking changes -============================== + [discrete] [[removing-plugins]] From 3b2403e974f5ae992f2022126a04b28bff9e8f79 Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Tue, 21 Jan 2025 10:55:22 -0800 Subject: [PATCH 5/6] docs: intention-first in major plugin updates --- docs/static/plugin-manager.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/plugin-manager.asciidoc b/docs/static/plugin-manager.asciidoc index 9d8a21bd9a4..502c3815e49 100644 --- a/docs/static/plugin-manager.asciidoc +++ b/docs/static/plugin-manager.asciidoc @@ -116,7 +116,7 @@ bin/logstash-plugin update logstash-input-github <2> [[updating-plugins-major-versions]] ==== Major-version plugin updates -By default, the plugin manager will only update plugins for which newer _minor_ or _patch_ versions exist, to avoid the introduction of breaking changes. +To avoid introducing breaking changes, the plugin manager updates plugins for which newer minor or patch versions exist by default. If you wish to also include breaking changes, specify `--level=major`. [source,shell] From 9631f7dd865bacf23200294079df3ed9b3c1a719 Mon Sep 17 00:00:00 2001 From: Ry Biesemeyer Date: Tue, 21 Jan 2025 12:58:07 -0800 Subject: [PATCH 6/6] Update docs/static/plugin-manager.asciidoc Co-authored-by: Karen Metts <35154725+karenzone@users.noreply.github.com> --- docs/static/plugin-manager.asciidoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/static/plugin-manager.asciidoc b/docs/static/plugin-manager.asciidoc index 502c3815e49..464030ace33 100644 --- a/docs/static/plugin-manager.asciidoc +++ b/docs/static/plugin-manager.asciidoc @@ -113,10 +113,10 @@ bin/logstash-plugin update logstash-input-github <2> <2> updates only the plugin you specify [discrete] -[[updating-plugins-major-versions]] -==== Major-version plugin updates +[[updating-major]] +==== Major version plugin updates -To avoid introducing breaking changes, the plugin manager updates plugins for which newer minor or patch versions exist by default. +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] @@ -124,8 +124,8 @@ If you wish to also include breaking changes, specify `--level=major`. 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-version breaking changes -<2> updates only the plugin you specify to latest, including major-version breaking changes +<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]