Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/static/plugin-manager.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/bootstrap/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,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]
Expand Down
7 changes: 7 additions & 0 deletions lib/pluginmanager/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand Down
29 changes: 28 additions & 1 deletion spec/unit/bootstrap/bundler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,41 @@
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 } }

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)
Expand Down
60 changes: 52 additions & 8 deletions spec/unit/plugin_manager/update_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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