foreman-tasks 5.2.0 → 5.3.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/app/lib/actions/base.rb +1 -0
- data/app/lib/actions/foreman/host/import_facts.rb +1 -1
- data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +18 -11
- data/app/lib/actions/proxy_action.rb +1 -1
- data/app/lib/actions/task_synchronization.rb +65 -0
- data/app/lib/actions/trigger_proxy_batch.rb +1 -1
- data/app/models/foreman_tasks/remote_task.rb +10 -3
- data/app/models/foreman_tasks/task/dynflow_task.rb +20 -21
- data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
- data/config/routes.rb +1 -1
- data/db/migrate/20210708123832_add_parent_task_id_to_remote_tasks.foreman_tasks.rb +5 -0
- data/db/migrate/20211123170430_tasks_settings_to_dsl_category.rb +5 -0
- data/foreman-tasks.gemspec +1 -1
- data/lib/foreman_tasks/dynflow/configuration.rb +0 -5
- data/lib/foreman_tasks/engine.rb +56 -9
- data/lib/foreman_tasks/test_helpers.rb +1 -1
- data/lib/foreman_tasks/version.rb +1 -1
- data/test/factories/task_factory.rb +1 -1
- data/test/lib/actions/middleware/keep_current_taxonomies_test.rb +9 -1
- data/test/support/dummy_dynflow_action.rb +1 -1
- data/test/unit/actions/trigger_proxy_batch_test.rb +0 -1
- data/test/unit/troubleshooting_help_generator_test.rb +0 -1
- data/test/unit/ui_notifications_test.rb +0 -1
- data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +1 -1
- data/webpack/ForemanTasks/Components/TaskActions/index.js +1 -1
- data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +28 -14
- data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +50 -42
- data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +1 -1
- data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +1 -1
- data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js} +0 -0
- metadata +8 -7
- data/app/models/setting/foreman_tasks.rb +0 -29
- data/lib/foreman_tasks/dynflow/persistence.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f5ae3bb36274977790149d11f722477d6db0254b251ffa1dc79ec850a206183
|
4
|
+
data.tar.gz: bfd76d0fcc43a78e34c1831a392042e5770bd09276d95d126043f3a14db174b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2bde1cb649304ba13019b544c8db8f7fa72b2e0539d47b2db07d6e945364d08b4e5fa0b100b701aa7f080e8be308ad7a6af404eaee2865e7b13fd423f9a9300
|
7
|
+
data.tar.gz: 00a77aaf192bbfc12a70dc034aa75bfd728c18956b1b9377bbef3d7fce7491c68cfa520d9fd04de4789c721fcc2d2b2b605b63a9165601092841e44184219dfc
|
data/app/lib/actions/base.rb
CHANGED
@@ -26,7 +26,7 @@ module Actions
|
|
26
26
|
def run
|
27
27
|
::User.as :admin do
|
28
28
|
host = ::Host.find(input[:host][:id])
|
29
|
-
state = host.import_facts(input[:facts], proxy)
|
29
|
+
state = ::HostFactImporter.new(host).import_facts(input[:facts], proxy)
|
30
30
|
output[:state] = state
|
31
31
|
end
|
32
32
|
rescue ::Foreman::Exception => e
|
@@ -26,17 +26,26 @@ module Actions
|
|
26
26
|
def check_triggered
|
27
27
|
in_remote_task_batches(remote_tasks.triggered) do |batch|
|
28
28
|
batch.group_by(&:proxy_url).each do |(url, tasks)|
|
29
|
-
|
30
|
-
process_task_results tasks
|
29
|
+
results = poll_proxy_tasks(url, tasks)
|
30
|
+
process_task_results tasks, results
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def process_task_results(tasks)
|
36
|
-
|
35
|
+
def process_task_results(tasks, results)
|
36
|
+
possibly_missing, present = tasks.partition { |task| !results.key?(task.remote_task_id) }
|
37
|
+
missing = possibly_missing.select do |task|
|
38
|
+
# Really missing are tasks which are missing and:
|
39
|
+
# don't have a remote parent
|
40
|
+
# had a remote parent but the proxy doesn't have the remote parent anymore
|
41
|
+
# has a remote parent, proxy has the remote parent but it is stopped or paused
|
42
|
+
task.parent_task_id.nil? ||
|
43
|
+
!results.key?(task.parent_task_id) ||
|
44
|
+
%(stopped paused).include?(results[task.parent_task_id]['state'])
|
45
|
+
end
|
37
46
|
notify ::Actions::ProxyAction::ProxyActionMissing.new, missing if missing.any?
|
38
47
|
|
39
|
-
stopped = present.select { |task| %w[stopped paused].include? task.
|
48
|
+
stopped = present.select { |task| %w[stopped paused].include? results.dig(task.remote_task_id, 'state') }
|
40
49
|
notify ::Actions::ProxyAction::ProxyActionStopped.new, stopped if stopped.any?
|
41
50
|
end
|
42
51
|
|
@@ -52,15 +61,13 @@ module Actions
|
|
52
61
|
|
53
62
|
def poll_proxy_tasks(url, tasks)
|
54
63
|
proxy = ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => url)
|
55
|
-
|
56
|
-
tasks.map
|
57
|
-
|
58
|
-
task
|
59
|
-
end
|
64
|
+
# Get statuses of tasks and their optional parent tasks
|
65
|
+
ids = (tasks.map(&:remote_task_id) + tasks.map(&:parent_task_id)).uniq
|
66
|
+
proxy.task_states(ids)
|
60
67
|
rescue => e
|
61
68
|
# We could not reach the remote task, we'll try again next time
|
62
69
|
action.action_logger.warn(_('Failed to check on tasks on proxy at %{url}: %{exception}') % { :url => url, :exception => e.message })
|
63
|
-
|
70
|
+
{}
|
64
71
|
end
|
65
72
|
|
66
73
|
def in_remote_task_batches(scope)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Actions
|
2
|
+
# Examples:
|
3
|
+
|
4
|
+
# # Action A which emits an event when it successfully finishes.
|
5
|
+
# class A
|
6
|
+
# include ::Actions::ObservableAction
|
7
|
+
# # ... rest ...
|
8
|
+
# end
|
9
|
+
|
10
|
+
# # Action B which emits an event when it successfully finishes or fails.
|
11
|
+
# class B
|
12
|
+
# include ::Actions::ObservableAction
|
13
|
+
#
|
14
|
+
# execution_plan_hooks.use :emit_event_failure, :on => [:failure]
|
15
|
+
#
|
16
|
+
# def self.event_names
|
17
|
+
# super + [event_name_base + '_' + event_name_suffix(:failure)]
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def emit_event_failure(plan)
|
21
|
+
# emit_event(plan, :failure)
|
22
|
+
# end
|
23
|
+
# # ... rest ...
|
24
|
+
# end
|
25
|
+
module TaskSynchronization
|
26
|
+
def self.included(base)
|
27
|
+
base.execution_plan_hooks.use :sync_execution_plan_to_task, on: ::Dynflow::ExecutionPlan.states
|
28
|
+
end
|
29
|
+
|
30
|
+
def sync_execution_plan_to_task(plan)
|
31
|
+
return unless root_action?
|
32
|
+
on_execution_plan_save(plan)
|
33
|
+
rescue => e
|
34
|
+
::Foreman::Logging.exception('Error on on_execution_plan_save event', e,
|
35
|
+
:logger => 'dynflow')
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def on_execution_plan_save(execution_plan)
|
41
|
+
# We can load the data unless the execution plan was properly planned and saved
|
42
|
+
# including its steps
|
43
|
+
case execution_plan.state
|
44
|
+
when :pending
|
45
|
+
task = ForemanTasks::Task::DynflowTask.new_for_execution_plan(execution_plan)
|
46
|
+
task.start_at ||= Time.zone.now
|
47
|
+
task.save!
|
48
|
+
when :scheduled
|
49
|
+
delayed_plan = world.persistence.load_delayed_plan(execution_plan.id)
|
50
|
+
raise ::Foreman::Exception, 'Plan is delayed but the delay record is missing' if delayed_plan.nil?
|
51
|
+
task = ::ForemanTasks::Task::DynflowTask.find_by!(:external_id => execution_plan.id)
|
52
|
+
task.update_from_dynflow(execution_plan, delayed_plan)
|
53
|
+
when :planning
|
54
|
+
task = ::ForemanTasks::Task::DynflowTask.where(:external_id => execution_plan.id).first
|
55
|
+
task.update_from_dynflow(execution_plan)
|
56
|
+
else
|
57
|
+
if (task = ::ForemanTasks::Task::DynflowTask.where(:external_id => execution_plan.id).first)
|
58
|
+
unless task.state.to_s == execution_plan.state.to_s
|
59
|
+
task.update_from_dynflow(execution_plan)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -37,8 +37,8 @@ module Actions
|
|
37
37
|
# Group the tasks by operation, in theory there should be only one operation
|
38
38
|
batch.group_by(&:operation).each do |operation, group|
|
39
39
|
ForemanTasks::RemoteTask.batch_trigger(operation, group)
|
40
|
+
output[:planned_count] += group.size
|
40
41
|
end
|
41
|
-
output[:planned_count] += batch.size
|
42
42
|
rescue => e
|
43
43
|
action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
44
44
|
batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
|
@@ -32,20 +32,27 @@ module ForemanTasks
|
|
32
32
|
:action_class => remote_task.proxy_action_name })
|
33
33
|
end
|
34
34
|
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
35
|
-
remote_tasks.each
|
35
|
+
remote_tasks.each do |remote_task|
|
36
|
+
remote_task.update_from_batch_trigger results.fetch(remote_task.execution_plan_id, {}),
|
37
|
+
results.fetch('parent', {})
|
38
|
+
end
|
36
39
|
end
|
37
40
|
remote_tasks
|
38
41
|
end
|
39
42
|
|
40
|
-
def update_from_batch_trigger(data)
|
43
|
+
def update_from_batch_trigger(data, parent = {})
|
41
44
|
if data['result'] == 'success'
|
42
45
|
self.remote_task_id = data['task_id']
|
43
46
|
self.state = 'triggered'
|
47
|
+
elsif !parent.empty?
|
48
|
+
self.parent_task_id = parent['task_id']
|
49
|
+
self.state = 'parent-triggered'
|
44
50
|
else
|
45
51
|
# Tell the action the task on the smart proxy stopped
|
46
52
|
ForemanTasks.dynflow.world.event execution_plan_id,
|
47
53
|
step_id,
|
48
|
-
::Actions::ProxyAction::ProxyActionStopped.new
|
54
|
+
::Actions::ProxyAction::ProxyActionStopped.new,
|
55
|
+
optional: true
|
49
56
|
end
|
50
57
|
save!
|
51
58
|
end
|
@@ -5,15 +5,14 @@ module ForemanTasks
|
|
5
5
|
scope :for_action, ->(action_class) { where(label: action_class.name) }
|
6
6
|
after_validation :set_action_field
|
7
7
|
|
8
|
-
def update_from_dynflow(
|
9
|
-
|
10
|
-
self.
|
11
|
-
self.
|
12
|
-
self.
|
13
|
-
self.
|
14
|
-
self.
|
15
|
-
self.
|
16
|
-
self.start_before = string_to_time(utc_zone, data[:start_before]) if data[:start_before]
|
8
|
+
def update_from_dynflow(plan, delayed_plan = nil)
|
9
|
+
self.external_id = plan.id
|
10
|
+
self.result = map_result(plan).to_s
|
11
|
+
self.state = plan.state.to_s
|
12
|
+
self.started_at = plan.started_at unless plan.started_at.nil?
|
13
|
+
self.ended_at = plan.ended_at unless plan.ended_at.nil?
|
14
|
+
self.start_at = delayed_plan.start_at if delayed_plan
|
15
|
+
self.start_before = delayed_plan.start_before if delayed_plan
|
17
16
|
self.parent_task_id ||= begin
|
18
17
|
if main_action.try(:caller_execution_plan_id)
|
19
18
|
DynflowTask.where(:external_id => main_action.caller_execution_plan_id).first!.id
|
@@ -221,7 +220,7 @@ module ForemanTasks
|
|
221
220
|
fixed_count = 0
|
222
221
|
logger = Foreman::Logging.logger('foreman-tasks')
|
223
222
|
running.each do |task|
|
224
|
-
changes = task.update_from_dynflow(task.execution_plan
|
223
|
+
changes = task.update_from_dynflow(task.execution_plan)
|
225
224
|
unless changes.empty?
|
226
225
|
fixed_count += 1
|
227
226
|
logger.warn('Task %s updated at consistency check: %s' % [task.id, changes.inspect])
|
@@ -236,10 +235,10 @@ module ForemanTasks
|
|
236
235
|
fixed_count
|
237
236
|
end
|
238
237
|
|
239
|
-
def self.new_for_execution_plan(
|
240
|
-
new(:external_id =>
|
241
|
-
:state =>
|
242
|
-
:result =>
|
238
|
+
def self.new_for_execution_plan(execution_plan)
|
239
|
+
new(:external_id => execution_plan.id,
|
240
|
+
:state => execution_plan.state.to_s,
|
241
|
+
:result => execution_plan.result.to_s,
|
243
242
|
:user_id => User.current.try(:id))
|
244
243
|
end
|
245
244
|
|
@@ -258,20 +257,20 @@ module ForemanTasks
|
|
258
257
|
self.action = to_label
|
259
258
|
end
|
260
259
|
|
261
|
-
def map_result(
|
262
|
-
if state_result_transitioned?(%w[planned pending], %w[stopped error],
|
263
|
-
(
|
260
|
+
def map_result(plan)
|
261
|
+
if state_result_transitioned?(%w[planned pending], %w[stopped error], plan) ||
|
262
|
+
(plan.result == :error && cancelled?)
|
264
263
|
:cancelled
|
265
264
|
else
|
266
|
-
|
265
|
+
plan.result
|
267
266
|
end
|
268
267
|
end
|
269
268
|
|
270
|
-
def state_result_transitioned?(from, to,
|
269
|
+
def state_result_transitioned?(from, to, plan)
|
271
270
|
oldstate, oldresult = from
|
272
271
|
newstate, newresult = to
|
273
|
-
state == oldstate &&
|
274
|
-
result == oldresult &&
|
272
|
+
state == oldstate && plan.state.to_s == newstate &&
|
273
|
+
result == oldresult && plan.result.to_s == newresult
|
275
274
|
end
|
276
275
|
|
277
276
|
def cancelled?
|
data/config/routes.rb
CHANGED
@@ -70,7 +70,7 @@ Foreman::Application.routes.draw do
|
|
70
70
|
mount ForemanTasks.dynflow.web_console => '/dynflow'
|
71
71
|
if defined? ::Sidekiq
|
72
72
|
require 'sidekiq/web'
|
73
|
-
redis_url = SETTINGS.dig(:dynflow, :redis_url)
|
73
|
+
redis_url = ENV['DYNFLOW_REDIS_URL'] || SETTINGS.dig(:dynflow, :redis_url)
|
74
74
|
Sidekiq.redis = { url: redis_url }
|
75
75
|
Sidekiq::Web.set :sessions, false
|
76
76
|
mount Sidekiq::Web => '/sidekiq', :constraints => ForemanTasks::Dynflow::SidekiqConsoleConstraint.new
|
data/foreman-tasks.gemspec
CHANGED
@@ -26,7 +26,7 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
|
|
26
26
|
s.test_files = `git ls-files test`.split("\n")
|
27
27
|
s.extra_rdoc_files = Dir['README*', 'LICENSE']
|
28
28
|
|
29
|
-
s.add_dependency "dynflow", '>= 1.
|
29
|
+
s.add_dependency "dynflow", '>= 1.6.0'
|
30
30
|
s.add_dependency "get_process_mem" # for memory polling
|
31
31
|
s.add_dependency "parse-cron", '~> 0.1.4'
|
32
32
|
s.add_dependency "sinatra" # for Dynflow web console
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require File.expand_path('lib/foreman/dynflow/configuration', Rails.root)
|
2
|
-
require 'foreman_tasks/dynflow/persistence'
|
3
2
|
|
4
3
|
module ForemanTasks
|
5
4
|
# Import all Dynflow configuration from Foreman, and add our own for Tasks
|
@@ -34,9 +33,5 @@ module ForemanTasks
|
|
34
33
|
end
|
35
34
|
options
|
36
35
|
end
|
37
|
-
|
38
|
-
def persistence_class
|
39
|
-
ForemanTasks::Dynflow::Persistence
|
40
|
-
end
|
41
36
|
end
|
42
37
|
end
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -5,14 +5,6 @@ module ForemanTasks
|
|
5
5
|
class Engine < ::Rails::Engine
|
6
6
|
engine_name 'foreman_tasks'
|
7
7
|
|
8
|
-
initializer 'foreman_tasks.load_default_settings', :before => :load_config_initializers do
|
9
|
-
require_dependency File.expand_path('../../app/models/setting/foreman_tasks.rb', __dir__) if begin
|
10
|
-
Setting.table_exists?
|
11
|
-
rescue
|
12
|
-
false
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
8
|
assets_to_precompile = %w[foreman_tasks/foreman_tasks.css
|
17
9
|
foreman_tasks/foreman_tasks.js]
|
18
10
|
|
@@ -33,7 +25,7 @@ module ForemanTasks
|
|
33
25
|
|
34
26
|
initializer 'foreman_tasks.register_plugin', :before => :finisher_hook do |_app|
|
35
27
|
Foreman::Plugin.register :"foreman-tasks" do
|
36
|
-
requires_foreman '>=
|
28
|
+
requires_foreman '>= 3.0.0'
|
37
29
|
divider :top_menu, :parent => :monitor_menu, :last => true, :caption => N_('Foreman Tasks')
|
38
30
|
menu :top_menu, :tasks,
|
39
31
|
:url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
|
@@ -65,6 +57,60 @@ module ForemanTasks
|
|
65
57
|
|
66
58
|
add_all_permissions_to_default_roles
|
67
59
|
|
60
|
+
settings do
|
61
|
+
category(:tasks, N_('Tasks')) do
|
62
|
+
setting('foreman_tasks_sync_task_timeout',
|
63
|
+
type: :integer,
|
64
|
+
description: N_('Number of seconds to wait for synchronous task to finish.'),
|
65
|
+
default: 120,
|
66
|
+
full_name: N_('Sync task timeout'))
|
67
|
+
setting('dynflow_enable_console',
|
68
|
+
type: :boolean,
|
69
|
+
description: N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'),
|
70
|
+
default: true,
|
71
|
+
full_name: N_('Enable dynflow console'))
|
72
|
+
setting('dynflow_console_require_auth',
|
73
|
+
type: :boolean,
|
74
|
+
description: N_('Require user to be authenticated as user with admin rights when accessing dynflow console'),
|
75
|
+
default: true,
|
76
|
+
full_name: N_('Require auth for dynflow console'))
|
77
|
+
setting('foreman_tasks_proxy_action_retry_count',
|
78
|
+
type: :integer,
|
79
|
+
description: N_('Number of attempts to start a task on the smart proxy before failing'),
|
80
|
+
default: 4,
|
81
|
+
full_name: N_('Proxy action retry count'))
|
82
|
+
setting('foreman_tasks_proxy_action_retry_interval',
|
83
|
+
type: :integer,
|
84
|
+
description: N_('Time in seconds between retries'),
|
85
|
+
default: 15,
|
86
|
+
full_name: N_('Proxy action retry interval'))
|
87
|
+
setting('foreman_tasks_proxy_batch_trigger',
|
88
|
+
type: :boolean,
|
89
|
+
description: N_('Allow triggering tasks on the smart proxy in batches'),
|
90
|
+
default: true,
|
91
|
+
full_name: N_('Allow proxy batch tasks'))
|
92
|
+
setting('foreman_tasks_proxy_batch_size',
|
93
|
+
type: :integer,
|
94
|
+
description: N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'),
|
95
|
+
default: 100,
|
96
|
+
full_name: N_('Proxy tasks batch size'))
|
97
|
+
setting('foreman_tasks_troubleshooting_url',
|
98
|
+
type: :string,
|
99
|
+
description: N_('Url pointing to the task troubleshooting documentation. '\
|
100
|
+
'It should contain %{label} placeholder, that will be replaced with normalized task label '\
|
101
|
+
'(restricted to only alphanumeric characters)). %{version} placeholder is also available.'),
|
102
|
+
default: nil,
|
103
|
+
full_name: N_('Tasks troubleshooting URL'))
|
104
|
+
setting('foreman_tasks_polling_multiplier',
|
105
|
+
type: :integer,
|
106
|
+
description: N_('Polling multiplier which is used to multiply the default polling intervals. '\
|
107
|
+
'This can be used to prevent polling too frequently for long running tasks.'),
|
108
|
+
default: 1,
|
109
|
+
full_name: N_("Polling intervals multiplier"),
|
110
|
+
validate: { numericality: { greater_than: 0 } })
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
68
114
|
register_graphql_query_field :task, '::Types::Task', :record_field
|
69
115
|
register_graphql_query_field :tasks, '::Types::Task', :collection_field
|
70
116
|
register_graphql_query_field :recurring_logic, '::Types::RecurringLogic', :record_field
|
@@ -138,6 +184,7 @@ module ForemanTasks
|
|
138
184
|
Authorizer.prepend AuthorizerExt
|
139
185
|
User.include ForemanTasks::Concerns::UserExtensions
|
140
186
|
::Dynflow::Action::Polling.prepend ForemanTasks::Concerns::PollingActionExtensions
|
187
|
+
::Dynflow::ActiveJob::QueueAdapters::JobWrapper.include Actions::TaskSynchronization
|
141
188
|
end
|
142
189
|
|
143
190
|
rake_tasks do
|
@@ -11,7 +11,7 @@ module ForemanTasks
|
|
11
11
|
world_config = ForemanTasks.dynflow.config.world_config
|
12
12
|
if @use_in_memory_sqlite
|
13
13
|
world_config.persistence_adapter = lambda do |*_args|
|
14
|
-
::
|
14
|
+
::Dynflow::PersistenceAdapters::Sequel.new('adapter' => 'sqlite', 'database' => ':memory:')
|
15
15
|
end
|
16
16
|
end
|
17
17
|
@test_in_thread_world = ::Dynflow::Testing::InThreadWorld.new(world_config)
|
@@ -24,7 +24,7 @@ FactoryBot.define do
|
|
24
24
|
ForemanTasks::Task.where(:external_id => execution_plan.id).delete_all
|
25
25
|
task.update!(:external_id => execution_plan.id)
|
26
26
|
if evaluator.sync_with_dynflow
|
27
|
-
task.update_from_dynflow(execution_plan
|
27
|
+
task.update_from_dynflow(execution_plan)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -15,7 +15,7 @@ module Actions
|
|
15
15
|
middleware.use KeepCurrentTaxonomies
|
16
16
|
execution_plan_hooks.use :null_hook, :on => :planning
|
17
17
|
|
18
|
-
def null_hook; end
|
18
|
+
def null_hook(plan); end
|
19
19
|
end
|
20
20
|
|
21
21
|
before do
|
@@ -78,6 +78,14 @@ module Actions
|
|
78
78
|
Organization.stubs(:current=)
|
79
79
|
Location.stubs(:current=)
|
80
80
|
|
81
|
+
org_finder = mock('organization finder')
|
82
|
+
org_finder.stubs(:find).with(@org.id).returns(@org)
|
83
|
+
Organization.stubs(:unscoped).returns(org_finder)
|
84
|
+
|
85
|
+
loc_finder = mock('location finder')
|
86
|
+
loc_finder.stubs(:find).with(@loc.id).returns(@loc)
|
87
|
+
Location.stubs(:unscoped).returns(loc_finder)
|
88
|
+
|
81
89
|
triggered = ForemanTasks.trigger(TestHookAction)
|
82
90
|
task = ForemanTasks::Task.where(:external_id => triggered.id).first
|
83
91
|
wait_for { task.reload.state == 'stopped' }
|
@@ -46,7 +46,6 @@ module ForemanTasks
|
|
46
46
|
|
47
47
|
it 'fetches batch_size of tasks and triggers them' do
|
48
48
|
remote_tasks.expects(:first).with(batch_size).returns(remote_tasks)
|
49
|
-
remote_tasks.expects(:size).returns(batch_size)
|
50
49
|
triggered.expects(:remote_tasks).returns(remote_tasks)
|
51
50
|
ForemanTasks::RemoteTask.expects(:batch_trigger).with(proxy_operation_name, grouped_remote_batch)
|
52
51
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
2
|
-
import { addToast } from 'foremanReact/
|
2
|
+
import { addToast } from 'foremanReact/components/ToastsList';
|
3
3
|
import { TASKS_DASHBOARD_JS_QUERY_MODES } from '../TasksDashboard/TasksDashboardConstants';
|
4
4
|
import { timeToHoursNumber } from '../TasksDashboard/TasksDashboardHelper';
|
5
5
|
import {
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { sprintf } from 'foremanReact/common/I18n';
|
2
2
|
import { API } from 'foremanReact/redux/API';
|
3
|
-
import { addToast } from 'foremanReact/
|
3
|
+
import { addToast } from 'foremanReact/components/ToastsList';
|
4
4
|
import {
|
5
5
|
TASKS_RESUME_REQUEST,
|
6
6
|
TASKS_RESUME_SUCCESS,
|
@@ -3,6 +3,18 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { Alert, Card, Row, Col } from 'patternfly-react';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
|
6
|
+
const ConditionalLink = ({ children, link }) =>
|
7
|
+
link ? <a href={link}>{children}</a> : children;
|
8
|
+
|
9
|
+
ConditionalLink.propTypes = {
|
10
|
+
children: PropTypes.node.isRequired,
|
11
|
+
link: PropTypes.string,
|
12
|
+
};
|
13
|
+
|
14
|
+
ConditionalLink.defaultProps = {
|
15
|
+
link: null,
|
16
|
+
};
|
17
|
+
|
6
18
|
const Locks = ({ locks }) => (
|
7
19
|
<div>
|
8
20
|
<Alert type="info">
|
@@ -14,20 +26,22 @@ const Locks = ({ locks }) => (
|
|
14
26
|
<Row>
|
15
27
|
{locks.map((lock, key) => (
|
16
28
|
<Col xs={6} sm={4} md={4} key={key}>
|
17
|
-
<
|
18
|
-
<Card
|
19
|
-
<
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
<ConditionalLink link={lock.link}>
|
30
|
+
<Card className="card-pf-aggregate-status" accented>
|
31
|
+
<Card.Title>
|
32
|
+
<span
|
33
|
+
className={`fa ${
|
34
|
+
lock.exclusive ? 'fa-lock' : 'fa-unlock-alt'
|
35
|
+
}`}
|
36
|
+
/>
|
37
|
+
{lock.resource_type}
|
38
|
+
</Card.Title>
|
39
|
+
<Card.Body>
|
40
|
+
{`id:${lock.resource_id}`}
|
41
|
+
<br />
|
42
|
+
</Card.Body>
|
43
|
+
</Card>
|
44
|
+
</ConditionalLink>
|
31
45
|
</Col>
|
32
46
|
))}
|
33
47
|
</Row>
|
@@ -25,29 +25,33 @@ exports[`Locks rendering render with Props 1`] = `
|
|
25
25
|
sm={4}
|
26
26
|
xs={6}
|
27
27
|
>
|
28
|
-
<
|
29
|
-
|
30
|
-
aggregated={false}
|
31
|
-
aggregatedMini={false}
|
32
|
-
cardRef={null}
|
33
|
-
className="card-pf-aggregate-status"
|
34
|
-
matchHeight={false}
|
28
|
+
<ConditionalLink
|
29
|
+
link={null}
|
35
30
|
>
|
36
|
-
<
|
37
|
-
|
31
|
+
<Card
|
32
|
+
accented={true}
|
33
|
+
aggregated={false}
|
34
|
+
aggregatedMini={false}
|
35
|
+
cardRef={null}
|
36
|
+
className="card-pf-aggregate-status"
|
37
|
+
matchHeight={false}
|
38
38
|
>
|
39
|
-
<
|
40
|
-
className="
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
39
|
+
<CardTitle
|
40
|
+
className=""
|
41
|
+
>
|
42
|
+
<span
|
43
|
+
className="fa fa-unlock-alt"
|
44
|
+
/>
|
45
|
+
User
|
46
|
+
</CardTitle>
|
47
|
+
<CardBody
|
48
|
+
className=""
|
49
|
+
>
|
50
|
+
id:4
|
51
|
+
<br />
|
52
|
+
</CardBody>
|
53
|
+
</Card>
|
54
|
+
</ConditionalLink>
|
51
55
|
</Col>
|
52
56
|
<Col
|
53
57
|
bsClass="col"
|
@@ -57,29 +61,33 @@ exports[`Locks rendering render with Props 1`] = `
|
|
57
61
|
sm={4}
|
58
62
|
xs={6}
|
59
63
|
>
|
60
|
-
<
|
61
|
-
|
62
|
-
aggregated={false}
|
63
|
-
aggregatedMini={false}
|
64
|
-
cardRef={null}
|
65
|
-
className="card-pf-aggregate-status"
|
66
|
-
matchHeight={false}
|
64
|
+
<ConditionalLink
|
65
|
+
link={null}
|
67
66
|
>
|
68
|
-
<
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
</CardTitle>
|
76
|
-
<CardBody
|
77
|
-
className=""
|
67
|
+
<Card
|
68
|
+
accented={true}
|
69
|
+
aggregated={false}
|
70
|
+
aggregatedMini={false}
|
71
|
+
cardRef={null}
|
72
|
+
className="card-pf-aggregate-status"
|
73
|
+
matchHeight={false}
|
78
74
|
>
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
75
|
+
<CardTitle
|
76
|
+
className=""
|
77
|
+
>
|
78
|
+
<span
|
79
|
+
className="fa fa-unlock-alt"
|
80
|
+
/>
|
81
|
+
User
|
82
|
+
</CardTitle>
|
83
|
+
<CardBody
|
84
|
+
className=""
|
85
|
+
>
|
86
|
+
id:2
|
87
|
+
<br />
|
88
|
+
</CardBody>
|
89
|
+
</Card>
|
90
|
+
</ConditionalLink>
|
83
91
|
</Col>
|
84
92
|
</Row>
|
85
93
|
</CardGrid>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { API } from 'foremanReact/redux/API';
|
2
|
-
import { addToast } from 'foremanReact/
|
2
|
+
import { addToast } from 'foremanReact/components/ToastsList';
|
3
3
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
4
4
|
import {
|
5
5
|
BULK_CANCEL_PATH,
|
data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js}
RENAMED
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Nečas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dynflow
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.6.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.6.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: get_process_mem
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -166,6 +166,7 @@ files:
|
|
166
166
|
- app/lib/actions/proxy_action.rb
|
167
167
|
- app/lib/actions/recurring_action.rb
|
168
168
|
- app/lib/actions/serializers/active_record_serializer.rb
|
169
|
+
- app/lib/actions/task_synchronization.rb
|
169
170
|
- app/lib/actions/trigger_proxy_batch.rb
|
170
171
|
- app/lib/foreman_tasks/concerns/polling_action_extensions.rb
|
171
172
|
- app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb
|
@@ -188,7 +189,6 @@ files:
|
|
188
189
|
- app/models/foreman_tasks/task_group_member.rb
|
189
190
|
- app/models/foreman_tasks/task_groups/recurring_logic_task_group.rb
|
190
191
|
- app/models/foreman_tasks/triggering.rb
|
191
|
-
- app/models/setting/foreman_tasks.rb
|
192
192
|
- app/services/foreman_tasks/proxy_selector.rb
|
193
193
|
- app/services/foreman_tasks/troubleshooting_help_generator.rb
|
194
194
|
- app/services/ui_notifications/tasks.rb
|
@@ -250,7 +250,9 @@ files:
|
|
250
250
|
- db/migrate/20200517215015_rename_bookmarks_controller.rb
|
251
251
|
- db/migrate/20200519093217_drop_dynflow_allow_dangerous_actions_setting.foreman_tasks.rb
|
252
252
|
- db/migrate/20200611090846_add_task_lock_index_on_resource_type_and_task_id.rb
|
253
|
+
- db/migrate/20210708123832_add_parent_task_id_to_remote_tasks.foreman_tasks.rb
|
253
254
|
- db/migrate/20210720115251_add_purpose_to_recurring_logic.rb
|
255
|
+
- db/migrate/20211123170430_tasks_settings_to_dsl_category.rb
|
254
256
|
- db/seeds.d/20-foreman_tasks_permissions.rb
|
255
257
|
- db/seeds.d/30-notification_blueprints.rb
|
256
258
|
- db/seeds.d/60-dynflow_proxy_feature.rb
|
@@ -271,7 +273,6 @@ files:
|
|
271
273
|
- lib/foreman_tasks/dynflow.rb
|
272
274
|
- lib/foreman_tasks/dynflow/configuration.rb
|
273
275
|
- lib/foreman_tasks/dynflow/console_authorizer.rb
|
274
|
-
- lib/foreman_tasks/dynflow/persistence.rb
|
275
276
|
- lib/foreman_tasks/engine.rb
|
276
277
|
- lib/foreman_tasks/task_error.rb
|
277
278
|
- lib/foreman_tasks/tasks/cleanup.rake
|
@@ -572,6 +573,7 @@ files:
|
|
572
573
|
- webpack/__mocks__/foremanReact/components/ForemanModal/index.js
|
573
574
|
- webpack/__mocks__/foremanReact/components/Layout/LayoutActions.js
|
574
575
|
- webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js
|
576
|
+
- webpack/__mocks__/foremanReact/components/ToastsList/index.js
|
575
577
|
- webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js
|
576
578
|
- webpack/__mocks__/foremanReact/components/common/MessageBox.js
|
577
579
|
- webpack/__mocks__/foremanReact/components/common/dates/LongDateTime.js
|
@@ -581,7 +583,6 @@ files:
|
|
581
583
|
- webpack/__mocks__/foremanReact/constants.js
|
582
584
|
- webpack/__mocks__/foremanReact/redux/API/APISelectors.js
|
583
585
|
- webpack/__mocks__/foremanReact/redux/API/index.js
|
584
|
-
- webpack/__mocks__/foremanReact/redux/actions/toasts.js
|
585
586
|
- webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware.js
|
586
587
|
- webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js
|
587
588
|
- webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js
|
@@ -1,29 +0,0 @@
|
|
1
|
-
class Setting::ForemanTasks < Setting
|
2
|
-
def self.default_settings
|
3
|
-
[
|
4
|
-
set('foreman_tasks_sync_task_timeout', N_('Number of seconds to wait for synchronous task to finish.'), 120, N_('Sync task timeout')),
|
5
|
-
set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true, N_('Enable dynflow console')),
|
6
|
-
set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true, N_('Require auth for dynflow console')),
|
7
|
-
set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4, N_('Proxy action retry count')),
|
8
|
-
set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15, N_('Proxy action retry interval')),
|
9
|
-
set('foreman_tasks_proxy_batch_trigger', N_('Allow triggering tasks on the smart proxy in batches'), true, N_('Allow proxy batch tasks')),
|
10
|
-
set('foreman_tasks_proxy_batch_size', N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'), 100, N_('Proxy tasks batch size')),
|
11
|
-
set('foreman_tasks_troubleshooting_url',
|
12
|
-
N_('Url pointing to the task troubleshooting documentation. '\
|
13
|
-
'It should contain %{label} placeholder, that will be replaced with normalized task label '\
|
14
|
-
'(restricted to only alphanumeric characters)). %{version} placeholder is also available.'),
|
15
|
-
nil, N_('Tasks troubleshooting URL')),
|
16
|
-
set('foreman_tasks_polling_multiplier',
|
17
|
-
N_('Polling multiplier which is used to multiply the default polling intervals. '\
|
18
|
-
'This can be used to prevent polling too frequently for long running tasks.'),
|
19
|
-
1,
|
20
|
-
N_("Polling intervals multiplier")),
|
21
|
-
]
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.load_defaults
|
25
|
-
Setting::BLANK_ATTRS.push('foreman_tasks_troubleshooting_url')
|
26
|
-
Setting::NONZERO_ATTRS.push('foreman_tasks_polling_multiplier')
|
27
|
-
super
|
28
|
-
end
|
29
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module ForemanTasks
|
2
|
-
# Wrap the Dynflow persistence to reflect the changes to execution plan
|
3
|
-
# in the Task model. This is probably a temporary solution and
|
4
|
-
# Dynflow will probably get more events-based API but it should be enought
|
5
|
-
# for start, until the requiements on the API are clear enough.
|
6
|
-
class Dynflow::Persistence < ::Dynflow::PersistenceAdapters::Sequel
|
7
|
-
def save_execution_plan(execution_plan_id, value)
|
8
|
-
# clear connection only if not running in some active record transaction already
|
9
|
-
clear_connections = ActiveRecord::Base.connection.open_transactions.zero?
|
10
|
-
super.tap do
|
11
|
-
on_execution_plan_save(execution_plan_id, value)
|
12
|
-
rescue => e
|
13
|
-
Foreman::Logging.exception('Error on on_execution_plan_save event', e,
|
14
|
-
:logger => 'dynflow')
|
15
|
-
end
|
16
|
-
ensure
|
17
|
-
::ActiveRecord::Base.clear_active_connections! if clear_connections
|
18
|
-
end
|
19
|
-
|
20
|
-
def on_execution_plan_save(execution_plan_id, data)
|
21
|
-
# We can load the data unless the execution plan was properly planned and saved
|
22
|
-
# including its steps
|
23
|
-
case data[:state]
|
24
|
-
when :pending
|
25
|
-
task = ForemanTasks::Task::DynflowTask.new_for_execution_plan(execution_plan_id, data)
|
26
|
-
task.start_at ||= Time.zone.now
|
27
|
-
task.save!
|
28
|
-
when :scheduled
|
29
|
-
delayed_plan = load_delayed_plan(execution_plan_id)
|
30
|
-
raise Foreman::Exception, 'Plan is delayed but the delay record is missing' if delayed_plan.nil?
|
31
|
-
task = ::ForemanTasks::Task::DynflowTask.find_by!(:external_id => execution_plan_id)
|
32
|
-
task.update_from_dynflow(data.merge(:start_at => delayed_plan[:start_at],
|
33
|
-
:start_before => delayed_plan[:start_before]))
|
34
|
-
when :planning
|
35
|
-
task = ::ForemanTasks::Task::DynflowTask.where(:external_id => execution_plan_id).first
|
36
|
-
task.update_from_dynflow(data)
|
37
|
-
else
|
38
|
-
if (task = ::ForemanTasks::Task::DynflowTask.where(:external_id => execution_plan_id).first)
|
39
|
-
unless task.state.to_s == data[:state].to_s
|
40
|
-
task.update_from_dynflow(data)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|