foreman-tasks 4.1.6 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|