-
Notifications
You must be signed in to change notification settings - Fork 3
/
export_tasks_tolerant.rake
298 lines (259 loc) · 9.03 KB
/
export_tasks_tolerant.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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#
# export_tasks.rake is a debugging tool to extract tasks from the
# current foreman instance.
#
# Run "foreman-rake foreman_tasks:export_tasks" to export tasks
require 'csv'
namespace :foreman_tasks do
desc <<-DESC.strip_heredoc
Export dynflow tasks based on filter. ENV variables:
* TASK_SEARCH : scoped search filter (example: 'label = "Actions::Foreman::Host::ImportFacts"')
* TASK_FILE : file to export to
* TASK_FORMAT : format to use for the export (either html or csv)
* TASK_DAYS : number of days to go back
* SKIP_FAILED : skip tasks that fail to export (true or false[default])
If TASK_SEARCH is not defined, it defaults to all tasks in the past 7 days and
all unsuccessful tasks in the past 60 days. The default TASK_FORMAT is html
which requires a tar.gz file extension.
DESC
task :export_tasks_tolerant => :environment do
deprecated_options = { :tasks => 'TASK_SEARCH',
:days => 'TASK_DAYS',
:export => 'TASK_FILE' }
deprecated_options.each do |option, new_option|
raise "The #{option} option is deprecated. Please use #{new_option} instead" if ENV.include?(option.to_s)
end
class TaskRender
def initialize
@cache = {}
end
def h(foo)
foo
end
def url(foo)
foo
end
def render_task(task)
@plan = task.execution_plan
erb('show')
end
def world
ForemanTasks.dynflow.world
end
def template(filename)
File.join(Gem::Specification.find_by_name('dynflow').gem_dir, 'web', 'views', "#{filename}.erb") # rubocop:disable Rails/DynamicFindBy
end
def erb(file, options = {})
@cache[file] ||= Tilt.new(template(file))
@cache[file].render(self, options[:locals])
end
def prettify_value(value)
YAML.dump(value)
end
def prettyprint(value)
value = prettyprint_references(value)
if value
pretty_value = prettify_value(value)
<<-HTML
<pre class="prettyprint lang-yaml">#{h(pretty_value)}</pre>
HTML
else
''
end
end
def prettyprint_references(value)
case value
when Hash
value.reduce({}) do |h, (key, val)|
h.update(key => prettyprint_references(val))
end
when Array
value.map { |val| prettyprint_references(val) }
when Dynflow::ExecutionPlan::OutputReference
value.inspect
else
value
end
end
def duration_to_s(duration)
h('%0.2fs' % duration)
end
def load_action(step)
world.persistence.load_action_for_presentation(@plan, step.action_id, step)
end
def step_error(step)
if step.error
['<pre>',
"#{h(step.error.message)} (#{h(step.error.exception_class)})\n",
h(step.error.backtrace.join("\n")),
'</pre>'].join
end
end
def show_world(world_id)
if (registered_world = world.coordinator.find_worlds(false, id: world_id).first)
'%{world_id} %{world_meta}' % { world_id: world_id, world_meta: registered_world.meta.inspect }
else
world_id
end
end
def show_action_data(label, value)
value_html = prettyprint(value)
if !value_html.empty?
<<-HTML
<p>
<b>#{h(label)}</b>
#{value_html}
</p>
HTML
else
''
end
end
def atom_css_classes(atom)
classes = ['atom']
step = @plan.steps[atom.step_id]
case step.state
when :success
classes << 'success'
when :error
classes << 'error'
when :skipped, :skipping
classes << 'skipped'
end
classes.join(' ')
end
def flow_css_classes(flow, sub_flow = nil)
classes = []
case flow
when Dynflow::Flows::Sequence
classes << 'sequence'
when Dynflow::Flows::Concurrence
classes << 'concurrence'
when Dynflow::Flows::Atom
classes << atom_css_classes(flow)
else
raise "Unknown run plan #{run_plan.inspect}"
end
classes << atom_css_classes(sub_flow) if sub_flow.is_a? Dynflow::Flows::Atom
classes.join(' ')
end
def step_css_class(step)
case step.state
when :success
'success'
when :error
'important'
end
end
def progress_width(step)
if step.state == :error
100 # we want to show the red bar in full width
else
step.progress_done * 100
end
end
def step(step_id)
@plan.steps[step_id]
end
def updated_url(new_params)
url('?' + Rack::Utils.build_nested_query(params.merge(new_params.stringify_keys)))
end
end
class PageHelper
def self.pagify(template)
pre = <<-HTML
<html>
<head>
<title>Dynflow Console</title>
<script src="jquery.js"></script>
<link rel="stylesheet" type="text/css" href="bootstrap.css">
<link rel="stylesheet" type="text/css" href="application.css">
<script src="bootstrap.js"></script>
<script src="run_prettify.js"></script>
<script src="application.js"></script>
</head>
<body>
#{template}
<body>
</html>
HTML
end
def self.copy_assets(tmp_dir)
['vendor/bootstrap/js/bootstrap.js',
'vendor/jquery/jquery.js',
'vendor/jquery/jquery.js',
'javascripts/application.js',
'vendor/bootstrap/css/bootstrap.css',
'stylesheets/application.css'].each do |file|
filename = File.join(Gem::Specification.find_by_name('dynflow').gem_dir, 'web', 'assets', file) # rubocop:disable Rails/DynamicFindBy
FileUtils.copy_file(filename, File.join(tmp_dir, File.basename(file)))
end
end
def self.generate_index(tasks)
html = '<div><table class="table">'
tasks.order('started_at desc').all.each do |task|
html << "<tr><td><a href=\"#{task.id}.html\">#{task.label}</a></td><td>#{task.started_at}</td>\
<td>#{task.state}</td><td>#{task.result}</td></tr>"
end
html << '</table></div>'
end
end
filter = if ENV['TASK_SEARCH'].nil? && ENV['TASK_DAYS'].nil?
"started_at > \"#{7.days.ago.to_s(:db)}\" || " \
"(result != success && started_at > \"#{60.days.ago.to_s(:db)})\""
else
ENV['TASK_SEARCH'] || ''
end
if (days = ENV['TASK_DAYS'])
filter += ' && ' unless filter == ''
filter += "started_at > \"#{days.to_i.days.ago.to_s(:db)}\""
end
verbose = ENV['VERBOSE']
format = ENV['TASK_FORMAT'] || 'html'
export_filename = ENV['TASK_FILE'] || "/tmp/task-export-#{Time.now.to_i}.#{format == 'csv' ? 'csv' : 'tar.gz'}"
skip_errors = ENV['SKIP_FAILED'] == "true"
tasks = ForemanTasks::Task.search_for(filter)
puts _("Exporting all tasks matching filter #{filter}")
puts _("Gathering #{tasks.count} tasks.")
if format == 'html'
Dir.mktmpdir('task-export') do |tmp_dir|
PageHelper.copy_assets(tmp_dir)
renderer = TaskRender.new
total = tasks.count
tasks.each_with_index do |task, count|
File.open(File.join(tmp_dir, "#{task.id}.html"), 'w') { |file| file.write(PageHelper.pagify(renderer.render_task(task))) }
puts "#{count + 1}/#{total}"
rescue StandardError => e
puts "WARNING: task failed to export. Additional task details below."
puts task.inspect
if skip_errors
# Displaying task.id may result in another failure in case the task is severely broken (as seen on the field).
puts "WARNING: Skipping task #{count + 1} because it failed to export."
else
raise e
end
end
File.open(File.join(tmp_dir, 'index.html'), 'w') { |file| file.write(PageHelper.pagify(PageHelper.generate_index(tasks))) }
sh("tar cvzf #{export_filename} #{tmp_dir} > /dev/null")
end
elsif format == 'csv'
CSV.open(export_filename, 'wb') do |csv|
csv << %w[id state type label result parent_task_id started_at ended_at]
tasks.each do |task|
csv << [task.id, task.state, task.type, task.label, task.result,
task.parent_task_id, task.started_at, task.ended_at]
rescue StandardError => e
puts "WARNING: task failed to export. Additional task details below."
puts task.inspect
if skip_errors
# Displaying task.id may result in another failure in case the task is severely broken (as seen on the field).
puts "WARNING: Skipping task because it failed to export."
else
raise e
end
end
end
end
puts "Created #{export_filename}"
end
end