foreman-tasks 4.1.6 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby_tests.yml +0 -1
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +2 -2
- data/app/controllers/foreman_tasks/tasks_controller.rb +7 -6
- data/app/graphql/types/recurring_logic.rb +18 -0
- data/app/graphql/types/task.rb +25 -0
- data/app/graphql/types/triggering.rb +16 -0
- data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
- data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +6 -2
- data/app/models/foreman_tasks/recurring_logic.rb +2 -0
- data/app/models/foreman_tasks/task/dynflow_task.rb +3 -8
- data/app/models/foreman_tasks/task.rb +28 -0
- data/app/models/foreman_tasks/triggering.rb +2 -0
- data/app/services/foreman_tasks/dashboard_table_filter.rb +56 -0
- data/foreman-tasks.gemspec +0 -1
- data/lib/foreman_tasks/continuous_output.rb +50 -0
- data/lib/foreman_tasks/engine.rb +6 -15
- data/lib/foreman_tasks/tasks/export_tasks.rake +46 -90
- data/lib/foreman_tasks/version.rb +1 -1
- data/lib/foreman_tasks.rb +2 -5
- data/test/controllers/api/tasks_controller_test.rb +0 -11
- data/test/graphql/queries/recurring_logic_test.rb +28 -0
- data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
- data/test/graphql/queries/task_query_test.rb +33 -0
- data/test/graphql/queries/tasks_query_test.rb +31 -0
- data/test/unit/dashboard_table_filter_test.rb +77 -0
- data/test/unit/task_test.rb +39 -8
- data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +4 -11
- data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.test.js +5 -27
- data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +0 -8
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +1 -6
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +1 -2
- data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +11 -22
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +16 -17
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +0 -3
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +1 -3
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -12
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +0 -5
- data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/selectionHeaderCellFormatter.test.js.snap +0 -1
- data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/selectionHeaderCellFormatter.test.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/formatters/selectionHeaderCellFormatter.js +0 -1
- data/webpack/ForemanTasks/Components/TasksTable/index.js +0 -2
- metadata +18 -27
- data/test/core/unit/dispatcher_test.rb +0 -43
- data/test/core/unit/runner_test.rb +0 -116
- data/test/core/unit/task_launcher_test.rb +0 -56
- data/test/foreman_tasks_core_test_helper.rb +0 -4
- data/test/unit/otp_manager_test.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e48966ae6049eb85b3051151b7d15d48beb91a55b3580525fe114a3e045ad0f
|
4
|
+
data.tar.gz: 93da446f348f1a27600a79223ba4daab0e5e83417db711919392cfab2eedf57a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 740ff700f75e43154068d3838611dc7c7306d7f5da91e3d2d41a6fce5ea0ccde84bd5f027545f24874b8fb112d9848505ae2a535386ee2f356852695dacd61a2
|
7
|
+
data.tar.gz: 8f61c3c868af4b7c3d3173b728858537c72ccf695e60b63971ec19f8a675a6ad9c19ecdd3dedb2549e2eb27b7cde62a2be67ead4d737bb43b774301abb303228
|
@@ -192,7 +192,7 @@ module ForemanTasks
|
|
192
192
|
params[:order] = "#{ordering_params[:sort_by]} #{ordering_params[:sort_order]}"
|
193
193
|
end
|
194
194
|
params[:order] ||= 'started_at DESC'
|
195
|
-
@tasks = resource_scope_for_index.order(params[:order].to_s)
|
195
|
+
@tasks = DashboardTableFilter.new(resource_scope_for_index, params).scope.order(params[:order].to_s)
|
196
196
|
end
|
197
197
|
|
198
198
|
def search_options
|
@@ -260,7 +260,7 @@ module ForemanTasks
|
|
260
260
|
raise BadRequest,
|
261
261
|
_('Resource search_params requires resource_type and resource_id to be specified')
|
262
262
|
end
|
263
|
-
scope.joins(:
|
263
|
+
scope.joins(:locks).where(foreman_tasks_locks:
|
264
264
|
{ resource_type: search_params[:resource_type],
|
265
265
|
resource_id: search_params[:resource_id] })
|
266
266
|
when 'task'
|
@@ -72,15 +72,17 @@ module ForemanTasks
|
|
72
72
|
def unlock
|
73
73
|
task = find_dynflow_task
|
74
74
|
if task.paused?
|
75
|
-
|
75
|
+
task.state = :stopped
|
76
|
+
task.save!
|
77
|
+
render json: { statusText: 'OK' }
|
76
78
|
else
|
77
79
|
render json: {}, status: :bad_request
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
81
|
-
def force_unlock
|
83
|
+
def force_unlock
|
84
|
+
task = find_dynflow_task
|
82
85
|
task.state = :stopped
|
83
|
-
task.locks.destroy_all
|
84
86
|
task.save!
|
85
87
|
render json: { statusText: 'OK' }
|
86
88
|
end
|
@@ -125,9 +127,8 @@ module ForemanTasks
|
|
125
127
|
end
|
126
128
|
|
127
129
|
def filter(scope, paginate: true)
|
128
|
-
|
129
|
-
|
130
|
-
scope = scope.search_for(search, order: params[:order])
|
130
|
+
scope = DashboardTableFilter.new(scope, params).scope
|
131
|
+
scope = scope.search_for(search_query, order: params[:order])
|
131
132
|
scope = scope.paginate(page: params[:page], per_page: params[:per_page]) if paginate
|
132
133
|
scope.distinct
|
133
134
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Types
|
2
|
+
class RecurringLogic < Types::BaseObject
|
3
|
+
description 'A Recurring Logic'
|
4
|
+
model_class ::ForemanTasks::RecurringLogic
|
5
|
+
|
6
|
+
global_id_field :id
|
7
|
+
field :cron_line, String
|
8
|
+
field :end_time, GraphQL::Types::ISO8601DateTime
|
9
|
+
field :max_iteration, Integer
|
10
|
+
field :iteration, Integer
|
11
|
+
field :state, String
|
12
|
+
belongs_to :triggering, Types::Triggering
|
13
|
+
|
14
|
+
def self.graphql_definition
|
15
|
+
super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::RecurringLogic') }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Types
|
2
|
+
class Task < Types::BaseObject
|
3
|
+
description 'A Task'
|
4
|
+
model_class ::ForemanTasks::Task
|
5
|
+
|
6
|
+
global_id_field :id
|
7
|
+
field :type, String
|
8
|
+
field :label, String
|
9
|
+
field :started_at, GraphQL::Types::ISO8601DateTime
|
10
|
+
field :ended_at, GraphQL::Types::ISO8601DateTime
|
11
|
+
field :state, String
|
12
|
+
field :result, String
|
13
|
+
field :external_id, String
|
14
|
+
field :parent_task_id, String
|
15
|
+
field :start_at, GraphQL::Types::ISO8601DateTime
|
16
|
+
field :start_before, GraphQL::Types::ISO8601DateTime
|
17
|
+
field :action, String
|
18
|
+
field :user_id, Integer
|
19
|
+
field :state_updated_at, GraphQL::Types::ISO8601DateTime
|
20
|
+
|
21
|
+
def self.graphql_definition
|
22
|
+
super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Task') }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Types
|
2
|
+
class Triggering < Types::BaseObject
|
3
|
+
description 'A Task Triggering'
|
4
|
+
model_class ::ForemanTasks::Triggering
|
5
|
+
|
6
|
+
global_id_field :id
|
7
|
+
field :mode, String
|
8
|
+
field :start_at, GraphQL::Types::ISO8601DateTime
|
9
|
+
field :start_before, GraphQL::Types::ISO8601DateTime
|
10
|
+
field :recurring_logic, Types::RecurringLogic
|
11
|
+
|
12
|
+
def self.graphql_definition
|
13
|
+
super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Triggering') }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -8,7 +8,7 @@ module Actions
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def continuous_output
|
11
|
-
continuous_output = ::
|
11
|
+
continuous_output = ::ForemanTasks::ContinuousOutput.new
|
12
12
|
continuous_output_providers.each do |continous_output_provider|
|
13
13
|
continous_output_provider.fill_continuous_output(continuous_output)
|
14
14
|
end
|
@@ -20,7 +20,9 @@ module Actions
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def set_clock
|
23
|
-
action.
|
23
|
+
action.world.clock.ping action.send(:suspended_action),
|
24
|
+
POLL_INTERVAL,
|
25
|
+
CheckOnProxyActions
|
24
26
|
end
|
25
27
|
|
26
28
|
def check_triggered
|
@@ -42,7 +44,9 @@ module Actions
|
|
42
44
|
|
43
45
|
def notify(event, tasks)
|
44
46
|
tasks.each do |task|
|
45
|
-
action.
|
47
|
+
action.world.event task.execution_plan_id,
|
48
|
+
task.step_id,
|
49
|
+
event
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
@@ -140,17 +140,12 @@ module ForemanTasks
|
|
140
140
|
|
141
141
|
def main_action
|
142
142
|
return @main_action if defined?(@main_action)
|
143
|
-
|
144
|
-
@main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
|
145
143
|
if active_job?
|
146
144
|
job_data = active_job_data
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
Foreman::Logging.exception("Failed to load ActiveJob for task #{id}", e, :logger => 'foreman-tasks')
|
151
|
-
end
|
145
|
+
@main_action = active_job_action(job_data['job_class'], job_data['arguments'])
|
146
|
+
else
|
147
|
+
@main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
|
152
148
|
end
|
153
|
-
@main_action
|
154
149
|
end
|
155
150
|
|
156
151
|
# The class for ActiveJob jobs in Dynflow, JobWrapper is not expected to
|
@@ -5,6 +5,8 @@ module ForemanTasks
|
|
5
5
|
include Authorizable
|
6
6
|
extend Search
|
7
7
|
|
8
|
+
graphql_type '::Types::Task'
|
9
|
+
|
8
10
|
def check_permissions_after_save
|
9
11
|
# there's no create_tasks permission, tasks are created as a result of internal actions, in such case we
|
10
12
|
# don't do authorization, that should have been performed on wrapping action level
|
@@ -255,6 +257,32 @@ module ForemanTasks
|
|
255
257
|
main_action.continuous_output.raw_outputs
|
256
258
|
end
|
257
259
|
|
260
|
+
def self.latest_tasks_by_resource_ids(label, resource_type, resource_ids)
|
261
|
+
tasks = arel_table
|
262
|
+
links = ForemanTasks::Link.arel_table
|
263
|
+
started_at = tasks[:started_at]
|
264
|
+
resource_id = links[:resource_id]
|
265
|
+
|
266
|
+
base_combined_table = tasks
|
267
|
+
.join(links).on(tasks[:id].eq(links[:task_id]))
|
268
|
+
.where(tasks[:label].eq(label)
|
269
|
+
.and(links[:resource_type].eq(resource_type))
|
270
|
+
.and(links[:resource_id].in(resource_ids)))
|
271
|
+
|
272
|
+
grouped = base_combined_table.project(
|
273
|
+
started_at.maximum.as('started_at_max'),
|
274
|
+
resource_id
|
275
|
+
).group(resource_id).order(resource_id).as('grouped')
|
276
|
+
|
277
|
+
max_per_resource_id = tasks
|
278
|
+
.join(links).on(tasks[:id].eq(links[:task_id]))
|
279
|
+
.join(grouped).on(grouped[:started_at_max].eq(started_at).and(grouped[:resource_id].eq(resource_id)))
|
280
|
+
.distinct
|
281
|
+
.project(tasks[Arel.star], grouped[:resource_id])
|
282
|
+
|
283
|
+
find_by_sql(max_per_resource_id.to_sql).index_by(&:resource_id)
|
284
|
+
end
|
285
|
+
|
258
286
|
protected
|
259
287
|
|
260
288
|
def generate_id
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ForemanTasks
|
2
|
+
# narrows the scope for the tasks table based on params coming from tasks dashboard
|
3
|
+
#
|
4
|
+
# Supported filters:
|
5
|
+
#
|
6
|
+
# * :result
|
7
|
+
# * :state
|
8
|
+
# * :time_horizon - expected format of Hxy, where the xy is the time horizon in hours we're interested in
|
9
|
+
# :time_mode can be set to 'recent' to filter the recent tasks, or 'older' (default) to filter earlier ones
|
10
|
+
class DashboardTableFilter
|
11
|
+
def initialize(scope, params)
|
12
|
+
@scope = scope
|
13
|
+
@params = params
|
14
|
+
end
|
15
|
+
|
16
|
+
def scope
|
17
|
+
@new_scope = @scope
|
18
|
+
scope_by(:result)
|
19
|
+
scope_by(:state)
|
20
|
+
scope_by_time
|
21
|
+
@new_scope
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def scope_by(field)
|
27
|
+
if (field == :result) && (@params[field] == 'other')
|
28
|
+
@new_scope = @new_scope.where(:result => ['cancelled', 'pending'])
|
29
|
+
elsif @params[field].present?
|
30
|
+
@new_scope = @new_scope.where(field => @params[field])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def scope_by_time
|
35
|
+
return if @params[:time_horizon].blank?
|
36
|
+
hours = if @params[:time_horizon].casecmp('week') == 0
|
37
|
+
24 * 7
|
38
|
+
else
|
39
|
+
@params[:time_horizon][/\AH(\d{1,2})$/i, 1]
|
40
|
+
end
|
41
|
+
|
42
|
+
unless hours
|
43
|
+
raise Foreman::Exception, 'Unexpected format of time: should be in form of "H24" or equal to "week"'
|
44
|
+
end
|
45
|
+
timestamp = Time.now.utc - hours.to_i.hours
|
46
|
+
case @params[:time_mode]
|
47
|
+
when 'recent'
|
48
|
+
operator = '>'
|
49
|
+
else
|
50
|
+
operator = '<'
|
51
|
+
search_suffix = 'OR state_updated_at IS NULL'
|
52
|
+
end
|
53
|
+
@new_scope = @new_scope.where("state_updated_at #{operator} ? #{search_suffix}", timestamp)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/foreman-tasks.gemspec
CHANGED
@@ -29,7 +29,6 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
|
|
29
29
|
s.extra_rdoc_files = Dir['README*', 'LICENSE']
|
30
30
|
|
31
31
|
s.add_dependency "dynflow", '>= 1.2.3'
|
32
|
-
s.add_dependency "foreman-tasks-core"
|
33
32
|
s.add_dependency "get_process_mem" # for memory polling
|
34
33
|
s.add_dependency "parse-cron", '~> 0.1.4'
|
35
34
|
s.add_dependency "sinatra" # for Dynflow web console
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ForemanTasks
|
2
|
+
class ContinuousOutput
|
3
|
+
attr_accessor :raw_outputs
|
4
|
+
|
5
|
+
def initialize(raw_outputs = [])
|
6
|
+
@raw_outputs = []
|
7
|
+
raw_outputs.each { |raw_output| add_raw_output(raw_output) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_raw_output(raw_output)
|
11
|
+
missing_args = %w[output_type output timestamp] - raw_output.keys
|
12
|
+
unless missing_args.empty?
|
13
|
+
raise ArgumentError, "Missing args for raw output: #{missing_args.inspect}"
|
14
|
+
end
|
15
|
+
@raw_outputs << raw_output
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
@raw_outputs.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def last_timestamp
|
23
|
+
return if @raw_outputs.empty?
|
24
|
+
@raw_outputs.last.fetch('timestamp')
|
25
|
+
end
|
26
|
+
|
27
|
+
def sort!
|
28
|
+
@raw_outputs.sort_by! { |record| record['timestamp'].to_f }
|
29
|
+
end
|
30
|
+
|
31
|
+
def humanize
|
32
|
+
sort!
|
33
|
+
raw_outputs.map { |output| output['output'] }.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_exception(context, exception, timestamp = Time.now.getlocal)
|
37
|
+
add_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_output(*args)
|
41
|
+
add_raw_output(self.class.format_output(*args))
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.format_output(message, type = 'debug', timestamp = Time.now.getlocal)
|
45
|
+
{ 'output_type' => type,
|
46
|
+
'output' => message,
|
47
|
+
'timestamp' => timestamp.to_f }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'foreman_tasks_core'
|
2
1
|
require 'fast_gettext'
|
3
2
|
require 'gettext_i18n_rails'
|
4
3
|
|
@@ -34,7 +33,7 @@ module ForemanTasks
|
|
34
33
|
|
35
34
|
initializer 'foreman_tasks.register_plugin', :before => :finisher_hook do |_app|
|
36
35
|
Foreman::Plugin.register :"foreman-tasks" do
|
37
|
-
requires_foreman '>= 2.
|
36
|
+
requires_foreman '>= 2.6.0'
|
38
37
|
divider :top_menu, :parent => :monitor_menu, :last => true, :caption => N_('Foreman Tasks')
|
39
38
|
menu :top_menu, :tasks,
|
40
39
|
:url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
|
@@ -66,6 +65,11 @@ module ForemanTasks
|
|
66
65
|
|
67
66
|
add_all_permissions_to_default_roles
|
68
67
|
|
68
|
+
register_graphql_query_field :task, '::Types::Task', :record_field
|
69
|
+
register_graphql_query_field :tasks, '::Types::Task', :collection_field
|
70
|
+
register_graphql_query_field :recurring_logic, '::Types::RecurringLogic', :record_field
|
71
|
+
register_graphql_query_field :recurring_logics, '::Types::RecurringLogic', :collection_field
|
72
|
+
|
69
73
|
logger :dynflow, :enabled => true
|
70
74
|
logger :action, :enabled => true
|
71
75
|
|
@@ -116,19 +120,6 @@ module ForemanTasks
|
|
116
120
|
world.middleware.use Actions::Middleware::KeepCurrentRequestID
|
117
121
|
world.middleware.use ::Actions::Middleware::LoadSettingValues if Gem::Version.new(::SETTINGS[:version]) >= Gem::Version.new('2.5')
|
118
122
|
end
|
119
|
-
|
120
|
-
::ForemanTasks.dynflow.config.on_init do |world|
|
121
|
-
ForemanTasksCore.dynflow_setup(world)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
initializer 'foreman_tasks.set_core_settings' do
|
126
|
-
ForemanTasksCore::SettingsLoader.settings_registry.each_key do |settings_keys|
|
127
|
-
settings = settings_keys.inject({}) do |h, settings_key|
|
128
|
-
h.merge(SETTINGS[settings_key] || {})
|
129
|
-
end
|
130
|
-
ForemanTasksCore::SettingsLoader.setup_settings(settings_keys.first, settings)
|
131
|
-
end
|
132
123
|
end
|
133
124
|
|
134
125
|
# to enable async Foreman operations using Dynflow
|
@@ -12,7 +12,7 @@ namespace :foreman_tasks do
|
|
12
12
|
|
13
13
|
* TASK_SEARCH : scoped search filter (example: 'label = "Actions::Foreman::Host::ImportFacts"')
|
14
14
|
* TASK_FILE : file to export to
|
15
|
-
* TASK_FORMAT : format to use for the export (either html
|
15
|
+
* TASK_FORMAT : format to use for the export (either html or csv)
|
16
16
|
* TASK_DAYS : number of days to go back
|
17
17
|
|
18
18
|
If TASK_SEARCH is not defined, it defaults to all tasks in the past 7 days and
|
@@ -185,27 +185,23 @@ namespace :foreman_tasks do
|
|
185
185
|
end
|
186
186
|
|
187
187
|
class PageHelper
|
188
|
-
def self.pagify(
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
188
|
+
def self.pagify(template)
|
189
|
+
pre = <<-HTML
|
190
|
+
<html>
|
191
|
+
<head>
|
192
|
+
<title>Dynflow Console</title>
|
193
|
+
<script src="jquery.js"></script>
|
194
|
+
<link rel="stylesheet" type="text/css" href="bootstrap.css">
|
195
|
+
<link rel="stylesheet" type="text/css" href="application.css">
|
196
|
+
<script src="bootstrap.js"></script>
|
197
|
+
<script src="run_prettify.js"></script>
|
198
|
+
<script src="application.js"></script>
|
199
|
+
</head>
|
200
|
+
<body>
|
201
|
+
#{template}
|
202
|
+
<body>
|
203
|
+
</html>
|
201
204
|
HTML
|
202
|
-
if block_given?
|
203
|
-
yield io
|
204
|
-
else
|
205
|
-
io.write template
|
206
|
-
end
|
207
|
-
ensure
|
208
|
-
io.write '</body></html>'
|
209
205
|
end
|
210
206
|
|
211
207
|
def self.copy_assets(tmp_dir)
|
@@ -220,64 +216,13 @@ namespace :foreman_tasks do
|
|
220
216
|
end
|
221
217
|
end
|
222
218
|
|
223
|
-
def self.
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
229
|
-
|
230
|
-
def self.generate_index_entry(io, task)
|
231
|
-
io << <<~HTML
|
232
|
-
<tr>
|
233
|
-
<td><a href=\"#{task.id}.html\">#{task.label}</a></td>
|
234
|
-
<td>#{task.started_at}</td>
|
235
|
-
<td>#{task.state}</td>
|
236
|
-
<td>#{task.result}</td>
|
237
|
-
</tr>
|
238
|
-
HTML
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def csv_export(export_filename, tasks)
|
243
|
-
CSV.open(export_filename, 'wb') do |csv|
|
244
|
-
csv << %w[id state type label result parent_task_id started_at ended_at]
|
245
|
-
tasks.find_each do |task|
|
246
|
-
csv << [task.id, task.state, task.type, task.label, task.result,
|
247
|
-
task.parent_task_id, task.started_at, task.ended_at]
|
219
|
+
def self.generate_index(tasks)
|
220
|
+
html = '<div><table class="table">'
|
221
|
+
tasks.order('started_at desc').all.each do |task|
|
222
|
+
html << "<tr><td><a href=\"#{task.id}.html\">#{task.label}</a></td><td>#{task.started_at}</td>\
|
223
|
+
<td>#{task.state}</td><td>#{task.result}</td></tr>"
|
248
224
|
end
|
249
|
-
|
250
|
-
end
|
251
|
-
|
252
|
-
def html_export(workdir, tasks)
|
253
|
-
PageHelper.copy_assets(workdir)
|
254
|
-
|
255
|
-
renderer = TaskRender.new
|
256
|
-
total = tasks.count
|
257
|
-
index = File.open(File.join(workdir, 'index.html'), 'w')
|
258
|
-
|
259
|
-
File.open(File.join(workdir, 'index.html'), 'w') do |index|
|
260
|
-
PageHelper.pagify(index) do |io|
|
261
|
-
PageHelper.generate_with_index(io) do |index|
|
262
|
-
tasks.find_each.each_with_index do |task, count|
|
263
|
-
File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, renderer.render_task(task)) }
|
264
|
-
PageHelper.generate_index_entry(index, task)
|
265
|
-
puts "#{count + 1}/#{total}"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
def generate_filename(format)
|
273
|
-
base = "/tmp/task-export-#{Time.now.to_i}"
|
274
|
-
case format
|
275
|
-
when 'html'
|
276
|
-
base + '.tar.gz'
|
277
|
-
when 'csv'
|
278
|
-
base + '.csv'
|
279
|
-
when 'html-dir'
|
280
|
-
base
|
225
|
+
html << '</table></div>'
|
281
226
|
end
|
282
227
|
end
|
283
228
|
|
@@ -294,25 +239,36 @@ namespace :foreman_tasks do
|
|
294
239
|
end
|
295
240
|
|
296
241
|
format = ENV['TASK_FORMAT'] || 'html'
|
297
|
-
export_filename = ENV['TASK_FILE'] ||
|
242
|
+
export_filename = ENV['TASK_FILE'] || "/tmp/task-export-#{Time.now.to_i}.#{format == 'csv' ? 'csv' : 'tar.gz'}"
|
298
243
|
|
299
|
-
tasks = ForemanTasks::Task.search_for(filter)
|
244
|
+
tasks = ForemanTasks::Task.search_for(filter)
|
300
245
|
|
301
246
|
puts _("Exporting all tasks matching filter #{filter}")
|
302
247
|
puts _("Gathering #{tasks.count} tasks.")
|
303
|
-
|
304
|
-
when 'html'
|
248
|
+
if format == 'html'
|
305
249
|
Dir.mktmpdir('task-export') do |tmp_dir|
|
306
|
-
|
250
|
+
PageHelper.copy_assets(tmp_dir)
|
251
|
+
|
252
|
+
renderer = TaskRender.new
|
253
|
+
total = tasks.count
|
254
|
+
|
255
|
+
tasks.find_each.with_index do |task, count|
|
256
|
+
File.open(File.join(tmp_dir, "#{task.id}.html"), 'w') { |file| file.write(PageHelper.pagify(renderer.render_task(task))) }
|
257
|
+
puts "#{count + 1}/#{total}"
|
258
|
+
end
|
259
|
+
|
260
|
+
File.open(File.join(tmp_dir, 'index.html'), 'w') { |file| file.write(PageHelper.pagify(PageHelper.generate_index(tasks))) }
|
261
|
+
|
307
262
|
system("tar", "czf", export_filename, tmp_dir)
|
308
263
|
end
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
264
|
+
elsif format == 'csv'
|
265
|
+
CSV.open(export_filename, 'wb') do |csv|
|
266
|
+
csv << %w[id state type label result parent_task_id started_at ended_at]
|
267
|
+
tasks.find_each do |task|
|
268
|
+
csv << [task.id, task.state, task.type, task.label, task.result,
|
269
|
+
task.parent_task_id, task.started_at, task.ended_at]
|
270
|
+
end
|
271
|
+
end
|
316
272
|
end
|
317
273
|
|
318
274
|
puts "Created #{export_filename}"
|
data/lib/foreman_tasks.rb
CHANGED
@@ -6,17 +6,14 @@ require 'foreman_tasks/dynflow/configuration'
|
|
6
6
|
require 'foreman_tasks/triggers'
|
7
7
|
require 'foreman_tasks/authorizer_ext'
|
8
8
|
require 'foreman_tasks/cleaner'
|
9
|
+
require 'foreman_tasks/continuous_output'
|
9
10
|
|
10
11
|
module ForemanTasks
|
11
12
|
extend Algebrick::TypeCheck
|
12
13
|
extend Algebrick::Matching
|
13
14
|
|
14
15
|
def self.dynflow
|
15
|
-
@dynflow ||=
|
16
|
-
world = ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
|
17
|
-
ForemanTasksCore.dynflow_setup(world) if defined?(ForemanTasksCore)
|
18
|
-
world
|
19
|
-
end
|
16
|
+
@dynflow ||= ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
|
20
17
|
end
|
21
18
|
|
22
19
|
def self.trigger(action, *args, &block)
|
@@ -68,17 +68,6 @@ module ForemanTasks
|
|
68
68
|
data = JSON.parse(response.body)
|
69
69
|
_(data[0]['results'][0]['id']).must_equal task.id
|
70
70
|
end
|
71
|
-
|
72
|
-
it 'can search for a specific resource' do
|
73
|
-
org = FactoryBot.create(:organization)
|
74
|
-
task = FactoryBot.create(:task_with_links, resource_id: org.id, resource_type: 'Organization')
|
75
|
-
|
76
|
-
post :bulk_search, params: { :searches => [{ :type => 'resource', :resource_id => org.id, :resource_type => 'Organization' }] }
|
77
|
-
|
78
|
-
assert_response :success
|
79
|
-
data = JSON.parse(response.body)
|
80
|
-
_(data[0]['results'][0]['id']).must_equal task.id
|
81
|
-
end
|
82
71
|
end
|
83
72
|
|
84
73
|
describe 'GET /api/tasks/show' do
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'foreman_tasks_test_helper'
|
2
|
+
|
3
|
+
module Queries
|
4
|
+
class RecurringLogicTest < GraphQLQueryTestCase
|
5
|
+
let(:query) do
|
6
|
+
<<-GRAPHQL
|
7
|
+
query($id: String!) {
|
8
|
+
recurringLogic(id: $id) {
|
9
|
+
id
|
10
|
+
cronLine
|
11
|
+
}
|
12
|
+
}
|
13
|
+
GRAPHQL
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:cron_line) { '5 4 3 2 1' }
|
17
|
+
let(:recurring_logic) { FactoryBot.create(:recurring_logic, :cron_line => cron_line) }
|
18
|
+
let(:global_id) { Foreman::GlobalId.for(recurring_logic) }
|
19
|
+
let(:variables) { { id: global_id } }
|
20
|
+
let(:data) { result['data']['recurringLogic'] }
|
21
|
+
|
22
|
+
test "should fetch recurring logic" do
|
23
|
+
assert_empty result['errors']
|
24
|
+
assert_equal global_id, data['id']
|
25
|
+
assert_equal cron_line, data['cronLine']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|