logstash/qa/integration/specs/cli/remove_spec.rb
Ry Biesemeyer 089558801e
plugins: improve remove command to support multiple plugins (#17030)
Removal works in a single pass by finding plugins that would have unmet
dependencies if all of the specified plugins were to be removed, and
proceeding with the removal only if no conflicts were created.

> ~~~
> ╭─{ rye@perhaps:~/src/elastic/logstash@main (pluginmanager-remove-multiple ✘) }
> ╰─● bin/logstash-plugin remove logstash-input-syslog logstash-filter-grok
> Using system java: /Users/rye/.jenv/shims/java
> Resolving dependencies......
> Successfully removed logstash-input-syslog
> Successfully removed logstash-filter-grok
> [success (00:00:05)]
~~~
2025-02-19 11:17:20 -08:00

239 lines
11 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_relative '../../framework/fixture'
require_relative '../../framework/settings'
require_relative '../../services/logstash_service'
require_relative '../../framework/helpers'
require "logstash/devutils/rspec/spec_helper"
describe "CLI > logstash-plugin remove" do
before(:each) do
@fixture = Fixture.new(__FILE__)
@logstash_plugin = @fixture.get_service("logstash").plugin_cli
end
if RbConfig::CONFIG["host_os"] == "linux"
context "without internet connection (linux seccomp wrapper)" do
let(:offline_wrapper_path) { File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "offline_wrapper")) }
let(:offline_wrapper_cmd) { File.join(offline_wrapper_path, "offline") }
before do
Dir.chdir(offline_wrapper_path) do
system("make clean")
system("make")
end
end
context "when no other plugins depends on this plugin" do
let(:test_plugin) { "logstash-filter-qatest" }
before :each do
@logstash_plugin.install(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "logstash-filter-qatest-0.1.1.gem"))
end
it "successfully remove the plugin" do
execute = @logstash_plugin.run_raw("#{offline_wrapper_cmd} bin/logstash-plugin remove #{test_plugin}")
expect(execute.exit_code).to eq(0)
expect(execute.stderr_and_stdout).to match(/Successfully removed #{test_plugin}/)
presence_check = @logstash_plugin.list(test_plugin)
expect(presence_check.exit_code).to eq(1)
expect(presence_check.stderr_and_stdout).to match(/ERROR: No plugins found/)
end
end
context "when other plugins depends on this plugin" do
it "refuses to remove the plugin and display the plugin that depends on it." do
execute = @logstash_plugin.run_raw("#{offline_wrapper_cmd} bin/logstash-plugin remove logstash-codec-json")
expect(execute.exit_code).to eq(1)
expect(execute.stderr_and_stdout).to match(/Failed to remove "logstash-codec-json"/)
expect(execute.stderr_and_stdout).to match(/logstash-integration-kafka/) # one of the dependency
expect(execute.stderr_and_stdout).to match(/logstash-output-udp/) # one of the dependency
presence_check = @logstash_plugin.list("logstash-codec-json")
expect(presence_check.exit_code).to eq(0)
expect(presence_check.stderr_and_stdout).to match(/logstash-codec-json/)
end
end
end
else
context "when no other plugins depends on this plugin" do
let(:test_plugin) { "logstash-filter-qatest" }
before :each do
@logstash_plugin.install(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "logstash-filter-qatest-0.1.1.gem"))
end
it "successfully remove the plugin" do
execute = @logstash_plugin.remove(test_plugin)
expect(execute.exit_code).to eq(0)
expect(execute.stderr_and_stdout).to match(/Successfully removed #{test_plugin}/)
presence_check = @logstash_plugin.list(test_plugin)
expect(presence_check.exit_code).to eq(1)
expect(presence_check.stderr_and_stdout).to match(/ERROR: No plugins found/)
end
end
context "when other plugins depends on this plugin" do
it "refuses to remove the plugin and display the plugin that depends on it." do
execute = @logstash_plugin.remove("logstash-codec-json")
expect(execute.exit_code).to eq(1)
expect(execute.stderr_and_stdout).to match(/Failed to remove "logstash-codec-json"/)
expect(execute.stderr_and_stdout).to match(/logstash-integration-kafka/) # one of the dependency
expect(execute.stderr_and_stdout).to match(/logstash-output-udp/) # one of the dependency
presence_check = @logstash_plugin.list("logstash-codec-json")
expect(presence_check.exit_code).to eq(0)
expect(presence_check.stderr_and_stdout).to match(/logstash-codec-json/)
end
end
context "multiple plugins" do
let(:setup_plugin_list) do
fail("spec must override `setup_plugin_list`")
end
before(:each) do
if setup_plugin_list.any?
search_dir = File.expand_path(File.join(__dir__, "..", "..", "fixtures", "plugins"))
plugin_paths = []
aggregate_failures('setup: resolve plugin paths') do
setup_plugin_list.each do |requested_plugin|
found = Dir.glob(File.join(search_dir, "#{requested_plugin}-*.gem"))
expect(found).to have_attributes(:size => 1), lambda { "expected exactly one `#{requested_plugin}` in `#{search_dir}`, got #{found.inspect}" }
plugin_paths << found.first
end
end
aggregate_failures('setup: installing plugins') do
puts "installing plugins #{plugin_paths.inspect}"
outcome = @logstash_plugin.install(*plugin_paths)
expect(outcome.exit_code).to eq(0)
expect(outcome.stderr_and_stdout).to match(/Installation successful/)
end
end
end
context "when a remaining plugin has a dependency on a removed plugin" do
let(:setup_plugin_list) do
%w(
logstash-filter-zero_no_dependencies
logstash-filter-one_no_dependencies
logstash-filter-two_depends_on_one
logstash-filter-three_no_dependencies
logstash-filter-four_depends_on_one_and_three
)
end
it "errors helpfully without removing any of the plugins" do
execute = @logstash_plugin.remove("logstash-filter-three_no_dependencies", "logstash-filter-zero_no_dependencies")
expect(execute.exit_code).to eq(1)
expect(execute.stderr_and_stdout).to include('Failed to remove "logstash-filter-three_no_dependencies"')
expect(execute.stderr_and_stdout).to include("* logstash-filter-four_depends_on_one_and_three") # one of the dependency
expect(execute.stderr_and_stdout).to include("No plugins were removed.")
aggregate_failures("list plugins") do
presence_check = @logstash_plugin.list
expect(presence_check.exit_code).to eq(0)
expect(presence_check.stderr_and_stdout).to include('logstash-filter-three_no_dependencies')
expect(presence_check.stderr_and_stdout).to include('logstash-filter-zero_no_dependencies')
end
end
end
context "when multiple remaining plugins have a dependency on a removed plugin" do
let(:setup_plugin_list) do
%w(
logstash-filter-zero_no_dependencies
logstash-filter-one_no_dependencies
logstash-filter-two_depends_on_one
logstash-filter-three_no_dependencies
logstash-filter-four_depends_on_one_and_three
)
end
it "errors helpfully without removing any of the plugins" do
execute = @logstash_plugin.remove("logstash-filter-one_no_dependencies", "logstash-filter-zero_no_dependencies")
expect(execute.exit_code).to eq(1)
expect(execute.stderr_and_stdout).to include('Failed to remove "logstash-filter-one_no_dependencies"')
expect(execute.stderr_and_stdout).to include("* logstash-filter-four_depends_on_one_and_three") # one of the dependency
expect(execute.stderr_and_stdout).to include("* logstash-filter-two_depends_on_one") # one of the dependency
expect(execute.stderr_and_stdout).to include("No plugins were removed.")
aggregate_failures("list plugins") do
presence_check = @logstash_plugin.list
expect(presence_check.exit_code).to eq(0)
expect(presence_check.stderr_and_stdout).to include('logstash-filter-one_no_dependencies')
expect(presence_check.stderr_and_stdout).to include('logstash-filter-zero_no_dependencies')
end
end
end
context "when removing plugins and all plugins that depend on them" do
let(:setup_plugin_list) do
%w(
logstash-filter-zero_no_dependencies
logstash-filter-one_no_dependencies
logstash-filter-two_depends_on_one
logstash-filter-three_no_dependencies
logstash-filter-four_depends_on_one_and_three
)
end
it "removes the plugins" do
plugins_to_remove = %w(
logstash-filter-one_no_dependencies
logstash-filter-two_depends_on_one
logstash-filter-three_no_dependencies
logstash-filter-four_depends_on_one_and_three
).shuffle #random order
execute = @logstash_plugin.remove(*plugins_to_remove)
aggregate_failures("removal action") do
expect(execute).to have_attributes(:exit_code => 0, :stderr_and_stdout => include("Success"))
plugins_to_remove.each do |gem_name|
expect(execute.stderr_and_stdout).to include("Successfully removed #{gem_name}")
end
end
aggregate_failures("list plugins") do
presence_check = @logstash_plugin.list
expect(presence_check.exit_code).to eq(0)
aggregate_failures("removed plugins") do
plugins_to_remove.each do |expected_removed_plugin|
expect(presence_check.stderr_and_stdout).to_not include(expected_removed_plugin)
end
end
aggregate_failures("non-removed plugins") do
(setup_plugin_list - plugins_to_remove).each do |expected_remaining_plugin|
expect(presence_check.stderr_and_stdout).to include(expected_remaining_plugin)
end
end
end
end
end
end
end
end