foreman-tasks 5.1.1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -4
- data/.rubocop_todo.yml +0 -2
- data/README.md +8 -6
- data/app/graphql/mutations/recurring_logics/cancel.rb +27 -0
- data/app/graphql/types/recurring_logic.rb +2 -0
- data/app/lib/actions/base.rb +1 -0
- data/app/lib/actions/foreman/host/import_facts.rb +1 -1
- data/app/lib/actions/helpers/lifecycle_logging.rb +1 -1
- data/app/lib/actions/middleware/rails_executor_wrap.rb +2 -2
- data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +18 -11
- data/app/lib/actions/proxy_action.rb +4 -14
- data/app/lib/actions/task_synchronization.rb +65 -0
- data/app/lib/actions/trigger_proxy_batch.rb +1 -1
- data/app/models/foreman_tasks/lock.rb +1 -1
- data/app/models/foreman_tasks/remote_task.rb +12 -21
- data/app/models/foreman_tasks/task/dynflow_task.rb +20 -21
- data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
- data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +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/extra/foreman-tasks-cleanup.sh +19 -2
- data/extra/foreman-tasks-export.sh +7 -3
- data/foreman-tasks.gemspec +2 -4
- data/lib/foreman_tasks/dynflow/configuration.rb +1 -6
- data/lib/foreman_tasks/dynflow.rb +1 -1
- data/lib/foreman_tasks/engine.rb +58 -9
- data/lib/foreman_tasks/tasks/export_tasks.rake +24 -4
- data/lib/foreman_tasks/test_helpers.rb +1 -1
- data/lib/foreman_tasks/version.rb +1 -1
- data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/package.json +7 -9
- data/test/controllers/api/tasks_controller_test.rb +1 -2
- data/test/factories/recurring_logic_factory.rb +7 -1
- data/test/factories/task_factory.rb +1 -1
- data/test/graphql/mutations/recurring_logics/cancel_mutation_test.rb +66 -0
- data/test/lib/actions/middleware/keep_current_taxonomies_test.rb +9 -1
- data/test/support/dummy_dynflow_action.rb +1 -1
- data/test/support/dummy_proxy_action.rb +6 -0
- data/test/unit/actions/proxy_action_test.rb +20 -14
- data/test/unit/actions/trigger_proxy_batch_test.rb +0 -1
- data/test/unit/remote_task_test.rb +0 -8
- 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/ForemanTasks/Components/TasksTable/TasksTable.js +2 -28
- data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +5 -9
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +1 -5
- data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +2 -2
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +1 -4
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +1 -6
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +1 -6
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTable.test.js.snap +2 -22
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -12
- data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +1 -4
- data/webpack/ForemanTasks/Components/TasksTable/index.js +2 -2
- data/webpack/__mocks__/foremanReact/components/Pagination/index.js +2 -0
- data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js} +0 -0
- metadata +12 -8
- data/app/models/setting/foreman_tasks.rb +0 -29
- data/lib/foreman_tasks/dynflow/persistence.rb +0 -46
- data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a31c8423a352c5eeb6ff93b23ce8ed3d868ee4bdb16d456233f2c3df475342cd
|
4
|
+
data.tar.gz: c181fdc4c13d8c3cfb7e8d8469c9188a8972b97ca223e68cb50d9a901e23d552
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7f2b880bb70b4fc2781ab9c5bff2c7bcd0434bc067669fae601f1815f6720e7dd771a170cdde8a4922565f1bd87c61d5b3dc1c0b10192bd895508d123aa84da
|
7
|
+
data.tar.gz: a4652958efa52fec83b6c0a8093914b1d22ad574bc4763cb66c3bc41e82bd67e5b1448be0fd79ae3ce64225cb44d6c25faeb324cd10a846dfbe77abee5d55b02
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -11,7 +11,6 @@
|
|
11
11
|
# Include: **/*.gemspec
|
12
12
|
Gemspec/RequiredRubyVersion:
|
13
13
|
Exclude:
|
14
|
-
- 'foreman-tasks-core.gemspec'
|
15
14
|
- 'foreman-tasks.gemspec'
|
16
15
|
|
17
16
|
# Offense count: 1
|
@@ -37,7 +36,6 @@ Naming/MemoizedInstanceVariableName:
|
|
37
36
|
Exclude:
|
38
37
|
- 'app/controllers/foreman_tasks/recurring_logics_controller.rb'
|
39
38
|
- 'app/lib/actions/recurring_action.rb'
|
40
|
-
- 'lib/foreman_tasks_core/otp_manager.rb'
|
41
39
|
|
42
40
|
# Offense count: 11
|
43
41
|
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
data/README.md
CHANGED
@@ -6,7 +6,8 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
|
|
6
6
|
|
7
7
|
* Website: [TheForeman.org](http://theforeman.org)
|
8
8
|
* ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman)
|
9
|
-
* Issues: [
|
9
|
+
* Issues: [Foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
|
10
|
+
* Manual: [Foreman-tasks Manual](https://www.theforeman.org/plugins/foreman_tasks/0.8/index.html)
|
10
11
|
* Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About)
|
11
12
|
* Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net)
|
12
13
|
* Mailing lists:
|
@@ -25,6 +26,7 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
|
|
25
26
|
| >= 1.22 | ~> 0.15.0 |
|
26
27
|
| >= 2.0 | ~> 1.0.0 |
|
27
28
|
| >= 2.1 | ~> 2.0.0 |
|
29
|
+
| >= 2.6 | ~> 5.2.0 |
|
28
30
|
|
29
31
|
Installation
|
30
32
|
------------
|
@@ -154,9 +156,9 @@ rails root directory. See `-h` for more details and options
|
|
154
156
|
Tasks cleanup
|
155
157
|
-------------
|
156
158
|
|
157
|
-
Although
|
158
|
-
tasks can
|
159
|
-
|
159
|
+
Although the history of tasks has an auditing value, some kinds of
|
160
|
+
tasks can rapidly increase. Therefore, there is a mechanism for
|
161
|
+
cleaning up the tasks using a rake command. When running without
|
160
162
|
any arguments, the tasks are deleted based on the default parameters
|
161
163
|
defined in the code.
|
162
164
|
|
@@ -179,7 +181,7 @@ override the default configuration inside the configuration
|
|
179
181
|
```
|
180
182
|
:foreman-tasks:
|
181
183
|
:cleanup:
|
182
|
-
# the period after
|
184
|
+
# the period after which to delete all the tasks (by default, all tasks are not deleted after some period)
|
183
185
|
:after: 365d
|
184
186
|
# per action settings to override the default defined in the actions (cleanup_after method)
|
185
187
|
:actions:
|
@@ -194,7 +196,7 @@ to specify the search criteria for the cleanup manually:
|
|
194
196
|
* `TASK_SEARCH`: scoped search filter (example: 'label =
|
195
197
|
"Actions::Foreman::Host::ImportFacts"')
|
196
198
|
* `AFTER`: delete tasks created after `AFTER` period. Expected format
|
197
|
-
is a number followed by the time unit (`s`, `h`, `m`, `y`), such as
|
199
|
+
is a number followed by the time unit (`s`, `h`, `d`, `m`, `y`), such as
|
198
200
|
`10d` for 10 days (applicable only when the `TASK_SEARCH` option is
|
199
201
|
specified)
|
200
202
|
* `STATES`: comma separated list of task states to touch with the
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mutations
|
2
|
+
module RecurringLogics
|
3
|
+
class Cancel < BaseMutation
|
4
|
+
graphql_name 'CancelRecurringLogic'
|
5
|
+
description 'Cancels recurring logic and all its active tasks'
|
6
|
+
resource_class ::ForemanTasks::RecurringLogic
|
7
|
+
|
8
|
+
argument :id, ID, required: true
|
9
|
+
|
10
|
+
field :errors, [Types::AttributeError], null: false
|
11
|
+
field :recurring_logic, Types::RecurringLogic, null: true
|
12
|
+
|
13
|
+
def resolve(id:)
|
14
|
+
recurring_logic = load_object_by(id: id)
|
15
|
+
authorize!(recurring_logic, :edit)
|
16
|
+
task_errors = []
|
17
|
+
begin
|
18
|
+
recurring_logic.cancel
|
19
|
+
rescue => e
|
20
|
+
task_errors = [{ path: ['tasks'], message: "There has been an error when canceling one of the tasks: #{e}" }]
|
21
|
+
end
|
22
|
+
errors = recurring_logic.errors.any? ? map_errors_to_path(recurring_logic) : []
|
23
|
+
{ recurring_logic: recurring_logic, errors: (errors + task_errors) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
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
|
@@ -7,7 +7,7 @@ module Actions
|
|
7
7
|
|
8
8
|
def log_task_state_change(execution_plan)
|
9
9
|
return unless root_action?
|
10
|
-
logger = Rails.application.dynflow.world.action_logger
|
10
|
+
logger = ::Rails.application.dynflow.world.action_logger
|
11
11
|
task_id = ForemanTasks::Task::DynflowTask.where(external_id: execution_plan.id).pluck(:id).first
|
12
12
|
|
13
13
|
task_id_parts = []
|
@@ -10,13 +10,13 @@ module Actions
|
|
10
10
|
# for more details.
|
11
11
|
class RailsExecutorWrap < Dynflow::Middleware
|
12
12
|
def run(*args)
|
13
|
-
Rails.application.executor.wrap do
|
13
|
+
::Rails.application.executor.wrap do
|
14
14
|
pass(*args)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def finalize
|
19
|
-
Rails.application.executor.wrap do
|
19
|
+
::Rails.application.executor.wrap do
|
20
20
|
pass
|
21
21
|
end
|
22
22
|
end
|
@@ -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)
|
@@ -29,10 +29,10 @@ module Actions
|
|
29
29
|
default_connection_options.each do |key, value|
|
30
30
|
options[:connection_options][key] = value unless options[:connection_options].key?(key)
|
31
31
|
end
|
32
|
-
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s
|
32
|
+
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s))
|
33
33
|
# Just saving the RemoteTask is enough when using batch triggering
|
34
34
|
# It will be picked up by the ProxyBatchTriggering middleware
|
35
|
-
if input[:use_batch_triggering] &&
|
35
|
+
if input[:use_batch_triggering] && input.dig(:connection_options, :proxy_batch_triggering)
|
36
36
|
prepare_remote_task.save!
|
37
37
|
end
|
38
38
|
end
|
@@ -67,7 +67,7 @@ module Actions
|
|
67
67
|
def trigger_proxy_task
|
68
68
|
suspend do |_suspended_action|
|
69
69
|
remote_task = prepare_remote_task
|
70
|
-
remote_task.
|
70
|
+
ForemanTasks::RemoteTask.batch_trigger(remote_task.operation, [remote_task])
|
71
71
|
output[:proxy_task_id] = remote_task.remote_task_id
|
72
72
|
end
|
73
73
|
end
|
@@ -193,11 +193,6 @@ module Actions
|
|
193
193
|
:proxy_batch_triggering => Setting['foreman_tasks_proxy_batch_trigger'] || false }
|
194
194
|
end
|
195
195
|
|
196
|
-
def with_batch_triggering?(proxy_version)
|
197
|
-
((proxy_version[:major] == 1 && proxy_version[:minor] > 20) || proxy_version[:major] > 1) &&
|
198
|
-
input.fetch(:connection_options, {}).fetch(:proxy_batch_triggering, false)
|
199
|
-
end
|
200
|
-
|
201
196
|
def clean_remote_task(*_args)
|
202
197
|
remote_task.destroy! if remote_task
|
203
198
|
end
|
@@ -222,11 +217,6 @@ module Actions
|
|
222
217
|
.try(:fetch, 'output', {}) || {}
|
223
218
|
end
|
224
219
|
|
225
|
-
def proxy_version(proxy)
|
226
|
-
match = proxy.statuses[:version].version['version'].match(/(\d+)\.(\d+)\.(\d+)/)
|
227
|
-
{ :major => match[1].to_i, :minor => match[2].to_i, :patch => match[3].to_i }
|
228
|
-
end
|
229
|
-
|
230
220
|
def failed_proxy_tasks
|
231
221
|
metadata[:failed_proxy_tasks] ||= []
|
232
222
|
end
|
@@ -273,7 +263,7 @@ module Actions
|
|
273
263
|
end
|
274
264
|
|
275
265
|
def proxy_task_id
|
276
|
-
output[:proxy_task_id]
|
266
|
+
output[:proxy_task_id] || remote_task.try(:remote_task_id) || @execution_plan_id
|
277
267
|
end
|
278
268
|
end
|
279
269
|
end
|
@@ -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({}) }
|
@@ -16,7 +16,7 @@ module ForemanTasks
|
|
16
16
|
# Triggers a task on the proxy "the old way"
|
17
17
|
def trigger(proxy_action_name, input)
|
18
18
|
response = begin
|
19
|
-
proxy.
|
19
|
+
proxy.launch_tasks('single', :action_class => proxy_action_name, :action_input => input)
|
20
20
|
rescue RestClient::Exception => e
|
21
21
|
logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
22
22
|
{}
|
@@ -31,37 +31,28 @@ module ForemanTasks
|
|
31
31
|
acc.merge(remote_task.execution_plan_id => { :action_input => remote_task.proxy_input,
|
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 do |remote_task|
|
36
|
+
remote_task.update_from_batch_trigger results.fetch(remote_task.execution_plan_id, {}),
|
37
|
+
results.fetch('parent', {})
|
38
|
+
end
|
35
39
|
end
|
36
40
|
remote_tasks
|
37
41
|
end
|
38
42
|
|
39
|
-
|
40
|
-
# if it fails
|
41
|
-
def self.safe_batch_trigger(operation, remote_tasks, input_hash)
|
42
|
-
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
43
|
-
remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
|
44
|
-
rescue RestClient::NotFound
|
45
|
-
fallback_batch_trigger remote_tasks, input_hash
|
46
|
-
end
|
47
|
-
|
48
|
-
# Trigger the tasks one-by-one using the old API
|
49
|
-
def self.fallback_batch_trigger(remote_tasks, input_hash)
|
50
|
-
remote_tasks.each do |remote_task|
|
51
|
-
task_data = input_hash[remote_task.execution_plan_id]
|
52
|
-
remote_task.trigger(task_data[:action_class], task_data[:action_input])
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def update_from_batch_trigger(data)
|
43
|
+
def update_from_batch_trigger(data, parent = {})
|
57
44
|
if data['result'] == 'success'
|
58
45
|
self.remote_task_id = data['task_id']
|
59
46
|
self.state = 'triggered'
|
47
|
+
elsif !parent.empty?
|
48
|
+
self.parent_task_id = parent['task_id']
|
49
|
+
self.state = 'parent-triggered'
|
60
50
|
else
|
61
51
|
# Tell the action the task on the smart proxy stopped
|
62
52
|
ForemanTasks.dynflow.world.event execution_plan_id,
|
63
53
|
step_id,
|
64
|
-
::Actions::ProxyAction::ProxyActionStopped.new
|
54
|
+
::Actions::ProxyAction::ProxyActionStopped.new,
|
55
|
+
optional: true
|
65
56
|
end
|
66
57
|
save!
|
67
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?
|
@@ -40,4 +40,8 @@
|
|
40
40
|
<th><%= N_("Purpose") %></th>
|
41
41
|
<td><%= recurring_logic.purpose %></td>
|
42
42
|
</tr>
|
43
|
+
<tr>
|
44
|
+
<th><%= N_("Task count") %></th>
|
45
|
+
<td><%= link_to(task_group.tasks.count, foreman_tasks_tasks_url(:search => "task_group.id = #{task_group.id}")) %></td>
|
46
|
+
</tr>
|
43
47
|
</table>
|
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
|
@@ -21,6 +21,21 @@ function build_rake() {
|
|
21
21
|
echo
|
22
22
|
}
|
23
23
|
|
24
|
+
function incorrect_usage() {
|
25
|
+
echo "$1" >&2
|
26
|
+
echo
|
27
|
+
usage
|
28
|
+
|
29
|
+
exit 1
|
30
|
+
}
|
31
|
+
|
32
|
+
function validate_options!() {
|
33
|
+
if [ -z "$TASK_SEARCH" ]; then
|
34
|
+
[ -n "$AFTER" ] && incorrect_usage "Error: -a|--after cannot be used without -s|--search"
|
35
|
+
[ -n "$STATES" ] && incorrect_usage "Error: -S|--states cannot be used without -s|--search"
|
36
|
+
fi
|
37
|
+
}
|
38
|
+
|
24
39
|
function usage() {
|
25
40
|
cat << EOF
|
26
41
|
Usage: $PROGNAME [script_options...] [options...]
|
@@ -43,8 +58,8 @@ EOF
|
|
43
58
|
echo Cleanup options:
|
44
59
|
cat <<EOF | column -s\& -t
|
45
60
|
-B|--batch-size BATCH_SIZE & process tasks in batches of BATCH_SIZE, 1000 by default
|
46
|
-
-S|--states STATES & operate on tasks in STATES, comma separated list of states, set to all to operate on tasks in any state
|
47
|
-
-a|--after AGE & operate on tasks older than AGE. Expected format is a number followed by the time unit (s,h,m,y), such as '10d' for 10 days
|
61
|
+
-S|--states STATES & operate on tasks in STATES, comma separated list of states, set to all to operate on tasks in any state. Has to be used together with -s|--search
|
62
|
+
-a|--after AGE & operate on tasks older than AGE. Expected format is a number followed by the time unit (s,h,m,y), such as '10d' for 10 days. Has to be used together with -s|--search
|
48
63
|
-b|--backup & backup deleted tasks
|
49
64
|
-n|--noop & do a dry run, print what would be done
|
50
65
|
-s|--search QUERY & use QUERY in scoped search format to match tasks to delete
|
@@ -119,6 +134,8 @@ while true; do
|
|
119
134
|
shift
|
120
135
|
done
|
121
136
|
|
137
|
+
validate_options!
|
138
|
+
|
122
139
|
if [ "$EXECUTE" -eq 1 ]; then
|
123
140
|
build_rake | sh
|
124
141
|
else
|
@@ -14,7 +14,7 @@ function die() {
|
|
14
14
|
function build_rake() {
|
15
15
|
echo -n "$RAKE_COMMAND "
|
16
16
|
echo -n 'foreman_tasks:export_tasks '
|
17
|
-
for env in TASK_SEARCH TASK_FILE TASK_FORMAT TASK_DAYS; do
|
17
|
+
for env in TASK_SEARCH TASK_FILE TASK_FORMAT TASK_DAYS SKIP_FAILED; do
|
18
18
|
local value="${!env}"
|
19
19
|
[ -n "${value}" ] && echo -n "${env}=$(printf '%q' "$value") "
|
20
20
|
done
|
@@ -46,11 +46,12 @@ EOF
|
|
46
46
|
-f|--format FORMAT & export tasks in FORMAT, one of html, html-dir, csv
|
47
47
|
-o|--output FILE & export tasks into FILE, a random file will be used if not provided
|
48
48
|
-s|--search QUERY & use QUERY in scoped search format to match tasks to export
|
49
|
+
-S|--skip-failed & skip tasks that fail to export
|
49
50
|
EOF
|
50
51
|
}
|
51
52
|
|
52
|
-
SHORTOPTS="d:Ehs:o:f:"
|
53
|
-
LONGOPTS="days:,execute,help,search:,output:,format
|
53
|
+
SHORTOPTS="d:Ehs:o:f:S"
|
54
|
+
LONGOPTS="days:,execute,help,search:,output:,format:,skip-failed"
|
54
55
|
|
55
56
|
ARGS=$(getopt -s bash \
|
56
57
|
--options $SHORTOPTS \
|
@@ -96,6 +97,9 @@ while true; do
|
|
96
97
|
-E|--execute)
|
97
98
|
EXECUTE=1
|
98
99
|
;;
|
100
|
+
-S|--skip-failed)
|
101
|
+
SKIP_FAILED=1
|
102
|
+
;;
|
99
103
|
\?)
|
100
104
|
die 1 "Invalid option: -$OPTARG"
|
101
105
|
;;
|
data/foreman-tasks.gemspec
CHANGED
@@ -20,15 +20,13 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
|
|
20
20
|
DESC
|
21
21
|
|
22
22
|
s.files = `git ls-files`.split("\n").reject do |file|
|
23
|
-
file.end_with?("test.rake")
|
24
|
-
file.start_with?('lib/foreman_tasks_core') ||
|
25
|
-
file == 'foreman-tasks-core.gemspec'
|
23
|
+
file.end_with?("test.rake")
|
26
24
|
end
|
27
25
|
|
28
26
|
s.test_files = `git ls-files test`.split("\n")
|
29
27
|
s.extra_rdoc_files = Dir['README*', 'LICENSE']
|
30
28
|
|
31
|
-
s.add_dependency "dynflow", '>= 1.
|
29
|
+
s.add_dependency "dynflow", '>= 1.6.0'
|
32
30
|
s.add_dependency "get_process_mem" # for memory polling
|
33
31
|
s.add_dependency "parse-cron", '~> 0.1.4'
|
34
32
|
s.add_dependency "sinatra" # for Dynflow web console
|