-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Script to run all plugins tests and try an install, rooting in local Logstash #15018
Changes from all commits
fb35ddd
c261d6a
619b348
baf6330
da06602
4a4af04
6f053c6
fcd3644
be85408
0d263fb
1324e65
e23677b
d3df637
b30ad38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
# encoding: utf-8 | ||
|
||
# Test the | ||
# - build (rspec), | ||
# - packaging (gem build) | ||
# - and deploy (bin/logstash-plugin install) | ||
# of a plugins inside the current Logstash, using its JRuby | ||
# Usage example: | ||
# bin/ruby ci/test_supported_plugins.rb -p logstash-integration-jdbc | ||
# bin/ruby ci/test_supported_plugins.rb -t tier1 -k input,codec,integration | ||
# | ||
# The script uses OS's temp folder unless the environment variable LOGSTASH_PLUGINS_TMP is specified. | ||
# The path of such variable should be absolute. | ||
|
||
require "open3" | ||
require "set" | ||
require 'optparse' | ||
|
||
ENV['LOGSTASH_PATH'] = File.expand_path('..', __dir__) | ||
ENV['LOGSTASH_SOURCE'] = '1' | ||
|
||
logstash_plugin_cli = ENV['LOGSTASH_PATH'] + "/bin/logstash-plugin" | ||
|
||
# it has to be out of logstash local close else plugins' Gradle script | ||
# would interfere with Logstash's one | ||
base_folder = ENV['LOGSTASH_PLUGINS_TMP'] || (require 'tmpdir'; Dir.tmpdir) | ||
puts "Using #{base_folder} as temporary clone folder" | ||
plugins_folder = File.join(base_folder, "plugin_clones") | ||
unless File.directory?(plugins_folder) | ||
Dir.mkdir(plugins_folder) | ||
end | ||
|
||
class Plugin | ||
attr_reader :plugins_folder, :plugin_name, :plugin_base_folder | ||
|
||
def initialize(plugins_folder, plugin_name) | ||
@plugin_name = plugin_name | ||
@plugins_folder = plugins_folder | ||
@plugin_base_folder = "#{plugins_folder}/#{plugin_name}" | ||
end | ||
|
||
def git_retrieve | ||
if File.directory?(plugin_name) | ||
puts "#{plugin_name} already cloned locally, proceed with updating..." | ||
Dir.chdir(plugin_name) do | ||
system("git restore -- .") | ||
puts "Cleaning following files" | ||
system("git clean -n ") | ||
puts "Proceed with cleaning" | ||
system("git clean -Xf") | ||
end | ||
puts "#{plugin_name} updated" | ||
return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, great idea thanks. Implemented with commit e23677b |
||
end | ||
|
||
puts "#{plugin_name} local clone doesn't exist, cloning..." | ||
|
||
plugin_repository = "[email protected]:logstash-plugins/#{plugin_name}.git" | ||
unless system("git clone #{plugin_repository}") | ||
puts "Can't clone #{plugin_repository}" | ||
exit 1 | ||
end | ||
puts "#{plugin_name} cloned" | ||
end | ||
|
||
# return true if successed | ||
def execute_rspec | ||
if File.exists?("#{plugin_base_folder}/build.gradle") | ||
system("#{plugin_base_folder}/gradlew vendor") | ||
end | ||
system("#{ENV['LOGSTASH_PATH']}/bin/ruby -S bundle install") | ||
spec_result = system("#{ENV['LOGSTASH_PATH']}/bin/ruby -S bundle exec rspec") | ||
#puts "DNABG>> spec_result #{spec_result}" | ||
return spec_result ? true : false | ||
end | ||
|
||
# Return nil in case of error or the file name of the generated gem file | ||
def build_gem | ||
system("gem build #{plugin_name}.gemspec") | ||
|
||
gem_name = Dir.glob("#{plugin_name}*.gem").first | ||
unless gem_name | ||
puts "**error** gem not generated for plugin #{plugin_name}" | ||
return nil | ||
end | ||
|
||
gem_file = File.join(plugin_base_folder, gem_name) | ||
puts "gem_file generated: #{gem_file}" | ||
gem_file | ||
end | ||
|
||
def install_gem(gem_file) | ||
logstash_plugin_cli = ENV['LOGSTASH_PATH'] + "/bin/logstash-plugin" | ||
stdout, stderr, status = Open3.capture3("#{logstash_plugin_cli} install #{gem_file}") | ||
reg_exp = /Installing .*\nInstallation successful$/ | ||
if status != 0 && !reg_exp.match(stdout) | ||
puts "Failed to install plugins:\n stdout:\n #{stdout} \nstderr:\n #{stderr}" | ||
return false | ||
else | ||
puts "plugin #{plugin_name} successfully installed" | ||
#system("#{logstash_plugin_cli} remove #{gem_name}") | ||
return true | ||
end | ||
end | ||
end | ||
|
||
|
||
# reason could be a symbol, describing the phase that broke: | ||
# :unit_test, :gem_build, :gem_install | ||
FailureDetail = Struct.new(:plugin_name, :reason) | ||
|
||
# contains set of FailureDetail | ||
failed_plugins = [].to_set | ||
|
||
PLUGIN_DEFINITIONS = { | ||
:tier1 => { | ||
:input => ["logstash-input-azure_event_hubs", "logstash-input-beats", "logstash-input-elasticsearch", "logstash-input-file", | ||
"logstash-input-generator", "logstash-input-heartbeat", "logstash-input-http", "logstash-input-http_poller", | ||
"logstash-input-redis", "logstash-input-s3", "logstash-input-stdin", "logstash-input-syslog", "logstash-input-udp", | ||
"logstash-input-elastic_agent"], | ||
:codec => ["logstash-codec-avro", "logstash-codec-cef", "logstash-codec-es_bulk", "logstash-codec-json", | ||
"logstash-codec-json_lines", "logstash-codec-line", "logstash-codec-multiline", "logstash-codec-plain", | ||
"logstash-codec-rubydebug"], | ||
:filter => ["logstash-filter-cidr", "logstash-filter-clone", "logstash-filter-csv", "logstash-filter-date", "logstash-filter-dissect", | ||
"logstash-filter-dns", "logstash-filter-drop", "logstash-filter-elasticsearch", "logstash-filter-fingerprint", | ||
"logstash-filter-geoip", "logstash-filter-grok", "logstash-filter-http", "logstash-filter-json", "logstash-filter-kv", | ||
"logstash-filter-memcached", "logstash-filter-mutate", "logstash-filter-prune", "logstash-filter-ruby", | ||
"logstash-filter-sleep", "logstash-filter-split", "logstash-filter-syslog_pri", "logstash-filter-translate", | ||
"logstash-filter-truncate", "logstash-filter-urldecode", "logstash-filter-useragent", "logstash-filter-uuid", | ||
"logstash-filter-xml"], | ||
:output => ["logstash-output-elasticsearch", "logstash-output-email", "logstash-output-file", "logstash-output-http", | ||
"logstash-output-redis", "logstash-output-s3", "logstash-output-stdout", "logstash-output-tcp", "logstash-output-udp"], | ||
:integration => ["logstash-integration-jdbc", "logstash-integration-kafka", "logstash-integration-rabbitmq", | ||
"logstash-integration-elastic_enterprise_search"] | ||
}, | ||
:tier2 => { | ||
:input => ["logstash-input-couchdb_changes", "logstash-input-gelf", "logstash-input-graphite", "logstash-input-jms", | ||
"logstash-input-snmp", "logstash-input-sqs", "logstash-input-twitter"], | ||
:codec => ["logstash-codec-collectd", "logstash-codec-dots", "logstash-codec-fluent", "logstash-codec-graphite", | ||
"logstash-codec-msgpack", "logstash-codec-netflow"], | ||
:filter => ["logstash-filter-aggregate", "logstash-filter-de_dot", "logstash-filter-throttle"], | ||
:output => ["logstash-output-csv", "logstash-output-graphite"] | ||
} | ||
} | ||
|
||
def validate_options!(options) | ||
raise "plugin and tiers or kinds can't be specified at the same time" if (options[:tiers] || options[:kinds]) && options[:plugin] | ||
|
||
options[:tiers].map! { |v| v.to_sym } if options[:tiers] | ||
options[:kinds].map! { |v| v.to_sym } if options[:kinds] | ||
|
||
raise "Invalid tier name expected tier1 or tier2" if options[:tiers] && !(options[:tiers] - [:tier1, :tier2]).empty? | ||
raise "Invalid kind name expected input, codec, filter, output, integration" if options[:kinds] && !(options[:kinds] - [:input, :codec, :filter, :output, :integration]).empty? | ||
end | ||
|
||
# @param tiers array of labels | ||
# @param kinds array of labels | ||
def select_by_tiers_and_kinds(tiers, kinds) | ||
selected = [] | ||
tiers.each do |tier| | ||
kinds.each do |kind| | ||
selected = selected + PLUGIN_DEFINITIONS[tier].fetch(kind, []) | ||
end | ||
end | ||
selected | ||
end | ||
|
||
def select_plugins_by_opts(options) | ||
select_plugins = [] | ||
if options[:plugin] | ||
select_plugins << options[:plugin] | ||
else | ||
selected_tiers = options.fetch(:tiers, [:tier1, :tier2]) | ||
selected_kinds = options.fetch(:kinds, [:input, :codec, :filter, :output, :integration]) | ||
select_plugins = select_plugins + select_by_tiers_and_kinds(selected_tiers, selected_kinds) | ||
end | ||
select_plugins | ||
end | ||
|
||
def snapshot_logstash_artifacts! | ||
stdout, stderr, status = Open3.capture3("git add --force -- Gemfile Gemfile.lock vendor/bundle") | ||
if status != 0 | ||
puts "Error snapshotting Logstash on path: #{Dir.pwd}" | ||
puts stderr | ||
exit 1 | ||
end | ||
end | ||
|
||
def cleanup_logstash_snapshot | ||
system("git restore --staged -- Gemfile Gemfile.lock vendor/bundle") | ||
end | ||
|
||
def restore_logstash_from_snapshot | ||
system("git restore -- Gemfile Gemfile.lock vendor/bundle") | ||
system("git clean -Xf -- Gemfile Gemfile.lock vendor/bundle") | ||
end | ||
|
||
def setup_logstash_for_development | ||
system("./gradlew installDevelopmentGems") | ||
end | ||
|
||
option_parser = OptionParser.new do |opts| | ||
opts.on '-t', '--tiers tier1, tier2', Array, 'Use to select which tier to test. If no provided mean "all"' | ||
opts.on '-k', '--kinds input, codec, filter, output', Array, 'Use to select which kind of plugin to test. If no provided mean "all"' | ||
opts.on '-pPLUGIN', '--plugin=PLUGIN', 'Use to select a specific plugin, conflict with either -t and -k' | ||
opts.on '-h', '--halt', 'Halt immediately on first error' | ||
end | ||
options = {} | ||
option_parser.parse!(into: options) | ||
|
||
validate_options!(options) | ||
|
||
plugins = select_plugins_by_opts(options) | ||
|
||
setup_logstash_for_development | ||
|
||
# save to local git for test isolation | ||
snapshot_logstash_artifacts! | ||
|
||
plugins.each do |plugin_name| | ||
restore_logstash_from_snapshot | ||
|
||
Dir.chdir(plugins_folder) do | ||
plugin = Plugin.new(plugins_folder, plugin_name) | ||
plugin.git_retrieve | ||
|
||
status = Dir.chdir(plugin_name) do | ||
unless plugin.execute_rspec | ||
failed_plugins << FailureDetail.new(plugin_name, :unit_test) | ||
break :error | ||
end | ||
|
||
# build the gem and install into Logstash | ||
gem_file = plugin.build_gem | ||
unless gem_file | ||
#puts "inserted into failed, because no gem file exists" | ||
failed_plugins << FailureDetail.new(plugin_name, :gem_build) | ||
break :error | ||
end | ||
|
||
# install the plugin | ||
unless plugin.install_gem(gem_file) | ||
#puts "inserted into failed, because the gem can't be installed" | ||
failed_plugins << FailureDetail.new(plugin_name, :gem_install) | ||
break :error | ||
end | ||
:success | ||
end | ||
|
||
# any of the verification subtask terminated with error | ||
if status == :error | ||
# break looping on plugins if immediate halt | ||
break if options[:halt] | ||
end | ||
end | ||
end | ||
|
||
# restore original git status to avoid to accidentally commit build artifacts | ||
cleanup_logstash_snapshot | ||
|
||
if failed_plugins | ||
puts "########################################" | ||
puts " Failed plugins:" | ||
puts "----------------------------------------" | ||
failed_plugins.each {|failure| puts "- #{failure}"} | ||
puts "########################################" | ||
else | ||
puts "NO ERROR ON PLUGINS!" | ||
end | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/bin/sh -ie | ||
export JRUBY_OPTS="-J-Xmx1g" | ||
export GRADLE_OPTS="-Xmx4g -Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dorg.gradle.logging.level=info -Dfile.encoding=UTF-8" | ||
|
||
./gradlew assemble | ||
|
||
bin/ruby ci/test_supported_plugins.rb $@ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 the usage is missing a note about needing a clean bootstrap +
installDevelopmentGems
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need the Logstash JRuby to be present, so at least here we should execute
./gradlew assemble
, to setup the environment to be able to run the Ruby script.We can split this in 2:
installDevelopmentGems
tasksThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handled with b30ad38