-
Notifications
You must be signed in to change notification settings - Fork 13
/
plugindocs.rb
208 lines (173 loc) · 7.1 KB
/
plugindocs.rb
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
require "clamp"
require "fileutils"
require "json"
require "pmap" # Enumerable#peach
require "set"
require "stud/try"
require "time"
require "thread" # Mutex
require "yaml"
require_relative 'lib/logstash-docket'
class PluginDocs < Clamp::Command
option "--output-path", "OUTPUT", "Path to the top-level of the logstash-docs path to write the output.", required: true
option "--main", :flag, "Fetch the plugin's docs from main instead of the version found in PLUGINS_JSON", :default => false
option "--settings", "SETTINGS_YAML", "Path to the settings file.", :default => File.join(File.dirname(__FILE__), "settings.yml"), :attribute_name => :settings_path
option("--parallelism", "NUMBER", "for performance", default: 4) { |v| Integer(v) }
option "--skip-existing", :flag, "Don't generate documentation if asciidoc file exists"
parameter "PLUGINS_JSON", "The path to the file containing plugin versions json"
include LogstashDocket
# cache the processed plugins to prevent re-process with wrapped and alias plugins
@@processed_plugins = Set.new
# a mutex to provide safely sharing resources in threads
@@mutex = Mutex.new
def execute
settings = YAML.load(File.read(settings_path))
report = JSON.parse(File.read(plugins_json))
repositories = report["successful"]
alias_definitions = Util::AliasDefinitionsLoader.get_alias_definitions
# we need to sort to be sure we generate docs first for embedded plugins of integration plugins
# and skip the process for stand-alone plugin if already processed
sorted_repositories = repositories.sort_by { |name,_| name.include?('-integration-') ? 0 : 1 }
if parallelism > 1
$stderr.puts("WARN: --parallelism is temporarily constrained to 1\n")
end
# there is a race condition when embedded plugins of integrations have changes
# we are using cache mechanism to slightly improve the situation but doesn't 100% guarantee
# TODO: for the long term stick with {.peach(parallelism) do |repository_name, details| }
# to speed up the process
# quick thought: separate integration embedded plugin doc generation process
sorted_repositories.each do |repository_name, details|
next if plugin_processed?(repository_name)
cache_processed_plugin(repository_name)
if settings['skip'].include?(repository_name)
$stderr.puts("Skipping #{repository_name}\n")
next
end
is_default_plugin = details["from"] == "default"
version = main? ? nil : details['version']
released_plugin = ArtifactPlugin.from_rubygems(repository_name, version) do |gem_data|
github_source_from_gem_data(repository_name, gem_data)
end || fail("[repository:#{repository_name}]: failed to find release package `#{tag(version)}` via rubygems")
if released_plugin.type == 'integration' && !is_default_plugin
$stderr.puts("[repository:#{repository_name}]: Skipping non-default Integration Plugin\n")
next
end
release_tag = released_plugin.tag
release_date = released_plugin.release_date ?
released_plugin.release_date.strftime("%Y-%m-%d") :
"unreleased"
changelog_url = released_plugin.changelog_url
released_plugin.with_wrapped_plugins(alias_definitions).each do |plugin|
cache_processed_plugin(plugin.canonical_name)
write_doc_to_file(plugin, release_tag, release_date, changelog_url, is_default_plugin)
end
end
end
private
##
# Generates a doc based on plugin info and writes to output .asciidoc file.
#
# @param plugin [Plugin]
# @param release_tag [String]
# @param release_date [String]
# @param changelog_url [String]
# @param is_default_plugin [Boolean]
# @return [void]
def write_doc_to_file(plugin, release_tag, release_date, changelog_url, is_default_plugin)
$stderr.puts("#{plugin.desc}: fetching documentation\n")
content = plugin.documentation
if content.nil?
$stderr.puts("#{plugin.desc}: failed to fetch doc; skipping\n")
return
end
output_asciidoc = "#{output_path}/docs/plugins/#{plugin.type}s/#{plugin.name}.asciidoc"
directory = File.dirname(output_asciidoc)
FileUtils.mkdir_p(directory) if !File.directory?(directory)
# Replace %VERSION%, etc
content = content \
.gsub("%VERSION%", release_tag) \
.gsub("%RELEASE_DATE%", release_date || "unreleased") \
.gsub("%CHANGELOG_URL%", changelog_url)
# Inject contextual variables for docs build
injection_variables = Hash.new
injection_variables[:default_plugin] = (is_default_plugin ? 1 : 0)
content = inject_variables(content, injection_variables)
# Even if no version bump, sometimes generating content might be different.
# For this case, we skip to accept the changes.
# eg: https://github.com/elastic/logstash-docs/pull/983/commits
if skip_existing? && File.exist?(output_asciidoc) \
&& no_version_bump?(output_asciidoc, content)
$stderr.puts("#{plugin.desc}: skipping since no version bump and doc exists.\n")
return
end
# write the doc
File.write(output_asciidoc, content)
puts "#{plugin.canonical_name}@#{plugin.tag}: #{release_date}\n"
end
##
# Hack to inject variables after a known pattern (the type declaration)
#
# @param content [String]
# @param kv [Hash{#to_s,#to_s}]
# @return [String]
def inject_variables(content, kv)
kv_string = kv.map do |k, v|
":#{k}: #{v}"
end.join("\n")
content.sub(/^:type: .*/) do |type|
"#{type}\n#{kv_string}"
end
end
##
# Support for plugins that are sourced outside the logstash-plugins org,
# by means of the gem_data's `source_code_uri` metadata.
def github_source_from_gem_data(gem_name, gem_data)
known_source = gem_data.dig('metadata', 'source_code_uri')
if known_source
known_source =~ %r{\bgithub.com/(?<org>[^/]+)/(?<repo>[^/]+)} || fail("unsupported source `#{known_source}`")
org = Regexp.last_match(:org)
repo = Regexp.last_match(:repo)
else
org = ENV.fetch('PLUGIN_ORG','logstash-plugins')
repo = gem_name
end
Source::Github.new(org: org, repo: repo)
end
def tag(version)
version ? "v#{version}" : "main"
end
##
# Checks if no version bump and return true if so, false otherwise.
#
# @param output_asciidoc [String]
# @param content [String]
# @return [Boolean]
def no_version_bump?(output_asciidoc, content)
existing_file_content = File.read(output_asciidoc)
version_fetch_regex = /^\:version: (.*?)\n/
existing_file_content[version_fetch_regex, 1] == content[version_fetch_regex, 1]
end
##
# Checks if plugin cached.
#
# @param plugin_canonical_name [String]
# @return [Boolean]
def plugin_processed?(plugin_canonical_name)
@@mutex.synchronize do
return @@processed_plugins.include?(plugin_canonical_name)
end
end
##
# Adds a plugin to caching list.
#
# @param plugin_canonical_name [String]
# @return [void]
def cache_processed_plugin(plugin_canonical_name)
@@mutex.synchronize do
@@processed_plugins.add(plugin_canonical_name)
end
end
end
if __FILE__ == $0
PluginDocs.run
end