forked from elastic/logstash
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugins_docs_dependencies.rake
209 lines (179 loc) · 7.76 KB
/
plugins_docs_dependencies.rake
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# 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 'json'
class PluginVersionWorking
EXPORT_FILE = ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "plugins_version_docs.json"))
PLUGIN_METADATA = JSON.parse(IO.read(::File.expand_path(::File.join(::File.dirname(__FILE__), "plugins-metadata.json"))))
PLUGIN_PACKAGE_TYPES = %w(
input
output
codec
filter
integration
).freeze
PLUGIN_PACKAGE_NAME_PATTERN = %r{^logstash-#{Regexp::union(PLUGIN_PACKAGE_TYPES)}-}
# Simple class to make sure we get the right version for the document
# since we will record multiple versions for one plugin
class VersionDependencies
attr_reader :version, :priority, :from
def initialize(version, from)
@version = version
@from = from
@priority = from == :default ? 1 : -1
end
def eql?(other)
version == other.version && priority == other.priority
end
def <=>(other)
if eql?(other)
0
else
[priority, version] <=> [other.priority, other.version]
end
end
def to_hash(hash = {})
{
"version" => version,
"from" => from
}
end
def to_s
"from:#{from}, version: #{version}"
end
end
def measure_execution(label)
started_at = Time.now
response = yield
puts "Execution of label: #{label}, #{Time.now - started_at}s"
response
end
def all_plugins
measure_execution("Fetch all available plugins on `logstash-plugins`") do
LogStash::RakeLib.fetch_all_plugins.delete_if { |name| name =~ /^logstash-mixin-/ }
end
end
# We us a brute force strategy to get the highest version possible for all the community plugins.
# We take each plugin and we add it to the current dependencies and we try to resolve the tree, if it work we
# record the version installed.
def retrieve_definitions
builder = Bundler::Dsl.new
gemfile = LogStash::Gemfile.new(File.new(LogStash::Environment::GEMFILE_PATH, "r+")).load
successful_dependencies = {}
failures = {}
builder.eval_gemfile("bundler file", gemfile.generate())
definition = builder.to_definition(LogStash::Environment::LOCKFILE, {})
extract_versions(definition, successful_dependencies, :default)
plugins_to_install = (all_plugins - successful_dependencies.keys).delete_if { |name| name =~ /^logstash-core/ }
measure_execution("batch install of plugins") do
install_plugins_sequential(plugins_to_install, successful_dependencies, failures)
end
return [successful_dependencies, failures]
end
def install_plugins_sequential(plugins_to_install, successful_dependencies, failures)
total = plugins_to_install.size + successful_dependencies.size
puts "Default installed: #{successful_dependencies.size} Total available plugins: #{total}"
plugins_to_install.each do |plugin|
begin
try_plugin(plugin, successful_dependencies)
puts "Successfully installed: #{plugin}"
rescue => e
puts "Failed to install: #{plugin}"
failures[plugin] = {
"klass" => e.class,
"message" => e.message
}
end
end
puts "Successful: #{successful_dependencies.size}/#{total}"
puts "Failures: #{failures.size}/#{total}"
end
def try_plugin(plugin, successful_dependencies)
builder = Bundler::Dsl.new
gemfile = LogStash::Gemfile.new(File.new(LogStash::Environment::GEMFILE_PATH, "r+")).load
gemfile.update(plugin)
builder.eval_gemfile("bundler file", gemfile.generate())
definition = builder.to_definition(LogStash::Environment::LOCKFILE, {})
definition.resolve_remotely!
from = PLUGIN_METADATA.fetch(plugin, {}).fetch("default-plugins", false) ? :default : :missing
extract_versions(definition, successful_dependencies, from)
end
def extract_versions(definition, dependencies, from)
#definition.specs.select { |spec| spec.metadata && spec.metadata["logstash_plugin"] == "true" }.each do |spec|
#
# Bundler doesn't seem to provide us with `spec.metadata` for remotely
# discovered plugins (via rubygems.org api), so we have to choose by
# a name pattern instead of by checking spec.metadata["logstash_plugin"]
definition.resolve.select { |spec| spec.name =~ PLUGIN_PACKAGE_NAME_PATTERN }.each do |spec|
dependencies[spec.name] ||= []
dependencies[spec.name] << VersionDependencies.new(spec.version, from)
end
end
def generate
specs, failures = retrieve_definitions
filtered = specs.each_with_object({}) { |(k, v), h| h[k] = v.max.to_hash }
result = JSON.pretty_generate({ "successful" => filtered, "failures" => failures})
puts "Generating: #{EXPORT_FILE}"
IO.write(EXPORT_FILE, result)
end
end
task :generate_plugins_version do
require "bundler"
require "bundler/dsl"
require "json"
require "pluginmanager/gemfile"
require "bootstrap/environment"
# This patch comes after an investigation of `generate_plugins_version`
# causing OOM during `./gradlew generatePluginsVersion`.
# Why does this patch fix the issue? Hang on, this is going to be wild ride:
# In this rake task we compute a manifest that tells us, for each logstash plugin,
# what is the latest version that can be installed.
# We do this by (again for each plugin):
# * adding the plugin to the current Gemfile
# * instantiate a `Bundler::Dsl` instance with said Gemfile
# * retrieve a Bundler::Definition by passing in the Gemfile.lock
# * call `definition.resolve_remotely!
#
# Now, these repeated calls to `resolve_remotely!` on new instances of Definitions
# cause the out of memory. Resolving remote dependencies uses Bundler::Worker instances
# who trap the SIGINT signal in their `initializer` [1]. This shared helper method creates a closure that is
# passed to `Signal.trap`, and capture the return [2], which is the previous proc (signal handler).
# Since the variable that stores the return from `Signal.trap` is present in the binding, multiple calls
# to this helper cause each new closures to reference the previous one. The size of each binding
# accumulates and OOM occurs after 70-100 iterations.
# This is easy to replicate by looping over `Bundler::SharedHelpers.trap("INT") { 1 }`.
#
# This workaround removes the capture of the previous binding. Not calling all the previous handlers
# may cause some threads to not be cleaned up, but this rake task has a short life so everything
# ends up being cleaned up on exit anyway.
# We're confining this patch to this task only as this is the only place where we need to resolve
# dependencies many many times.
#
# You're still here? You're awesome :) Thanks for reading!
#
# [1] https://github.com/bundler/bundler/blob/d9d75807196b91f454de48d5afd0c43b395243a3/lib/bundler/worker.rb#L29
# [2] https://github.com/bundler/bundler/blob/d9d75807196b91f454de48d5afd0c43b395243a3/lib/bundler/shared_helpers.rb#L173
module ::Bundler
module SharedHelpers
def trap(signal, override = false, &block)
Signal.trap(signal) do
block.call
end
end
end
end
PluginVersionWorking.new.generate
end