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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -4
  3. data/.rubocop_todo.yml +0 -2
  4. data/README.md +8 -6
  5. data/app/graphql/mutations/recurring_logics/cancel.rb +27 -0
  6. data/app/graphql/types/recurring_logic.rb +2 -0
  7. data/app/lib/actions/base.rb +1 -0
  8. data/app/lib/actions/foreman/host/import_facts.rb +1 -1
  9. data/app/lib/actions/helpers/lifecycle_logging.rb +1 -1
  10. data/app/lib/actions/middleware/rails_executor_wrap.rb +2 -2
  11. data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +18 -11
  12. data/app/lib/actions/proxy_action.rb +4 -14
  13. data/app/lib/actions/task_synchronization.rb +65 -0
  14. data/app/lib/actions/trigger_proxy_batch.rb +1 -1
  15. data/app/models/foreman_tasks/lock.rb +1 -1
  16. data/app/models/foreman_tasks/remote_task.rb +12 -21
  17. data/app/models/foreman_tasks/task/dynflow_task.rb +20 -21
  18. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  19. data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +4 -0
  20. data/config/routes.rb +1 -1
  21. data/db/migrate/20210708123832_add_parent_task_id_to_remote_tasks.foreman_tasks.rb +5 -0
  22. data/db/migrate/20211123170430_tasks_settings_to_dsl_category.rb +5 -0
  23. data/extra/foreman-tasks-cleanup.sh +19 -2
  24. data/extra/foreman-tasks-export.sh +7 -3
  25. data/foreman-tasks.gemspec +2 -4
  26. data/lib/foreman_tasks/dynflow/configuration.rb +1 -6
  27. data/lib/foreman_tasks/dynflow.rb +1 -1
  28. data/lib/foreman_tasks/engine.rb +58 -9
  29. data/lib/foreman_tasks/tasks/export_tasks.rake +24 -4
  30. data/lib/foreman_tasks/test_helpers.rb +1 -1
  31. data/lib/foreman_tasks/version.rb +1 -1
  32. data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
  33. data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
  34. data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
  35. data/package.json +7 -9
  36. data/test/controllers/api/tasks_controller_test.rb +1 -2
  37. data/test/factories/recurring_logic_factory.rb +7 -1
  38. data/test/factories/task_factory.rb +1 -1
  39. data/test/graphql/mutations/recurring_logics/cancel_mutation_test.rb +66 -0
  40. data/test/lib/actions/middleware/keep_current_taxonomies_test.rb +9 -1
  41. data/test/support/dummy_dynflow_action.rb +1 -1
  42. data/test/support/dummy_proxy_action.rb +6 -0
  43. data/test/unit/actions/proxy_action_test.rb +20 -14
  44. data/test/unit/actions/trigger_proxy_batch_test.rb +0 -1
  45. data/test/unit/remote_task_test.rb +0 -8
  46. data/test/unit/troubleshooting_help_generator_test.rb +0 -1
  47. data/test/unit/ui_notifications_test.rb +0 -1
  48. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +1 -1
  49. data/webpack/ForemanTasks/Components/TaskActions/index.js +1 -1
  50. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +28 -14
  51. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +50 -42
  52. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +1 -1
  53. data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +1 -1
  54. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +2 -28
  55. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +5 -9
  56. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +1 -5
  57. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +2 -2
  58. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +1 -4
  59. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +1 -6
  60. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +1 -6
  61. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTable.test.js.snap +2 -22
  62. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -12
  63. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +1 -4
  64. data/webpack/ForemanTasks/Components/TasksTable/index.js +2 -2
  65. data/webpack/__mocks__/foremanReact/components/Pagination/index.js +2 -0
  66. data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js} +0 -0
  67. metadata +12 -8
  68. data/app/models/setting/foreman_tasks.rb +0 -29
  69. data/lib/foreman_tasks/dynflow/persistence.rb +0 -46
  70. 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: c191bf6093506d0c5346c71a026908b3c5c6b10fa9ceb27e5fdfbfa93a72c2c6
4
- data.tar.gz: 0fc1f15e955f6d13469aa28883db8447f8e3770d4751206faaec5c224d3ffcbc
3
+ metadata.gz: a31c8423a352c5eeb6ff93b23ce8ed3d868ee4bdb16d456233f2c3df475342cd
4
+ data.tar.gz: c181fdc4c13d8c3cfb7e8d8469c9188a8972b97ca223e68cb50d9a901e23d552
5
5
  SHA512:
6
- metadata.gz: d57a47e629be8d4bcfaba9bc853f7ec0853ea8f968884d4b589e98d74027a73bcef5ff45ff53f3e01f8794a28a01729bb4c811b004430296304a86b7936d4bd4
7
- data.tar.gz: e4e3b07b3e4f84e233534c40adadaa8c4032c33e5bd5a8623a8d54bcece9de022c1c4e2fd81ca0f3ed1c9e512df23dc14b5d2fe8be349de03df1a5b4a41c7235
6
+ metadata.gz: c7f2b880bb70b4fc2781ab9c5bff2c7bcd0434bc067669fae601f1815f6720e7dd771a170cdde8a4922565f1bd87c61d5b3dc1c0b10192bd895508d123aa84da
7
+ data.tar.gz: a4652958efa52fec83b6c0a8093914b1d22ad574bc4763cb66c3bc41e82bd67e5b1448be0fd79ae3ce64225cb44d6c25faeb324cd10a846dfbe77abee5d55b02
data/.rubocop.yml CHANGED
@@ -46,10 +46,6 @@ Style/SymbolArray:
46
46
  Style/FormatString:
47
47
  Enabled: false
48
48
 
49
- Rails/Present:
50
- Exclude:
51
- - lib/foreman_tasks_core/**/*
52
-
53
49
  Rails/FilePath:
54
50
  Enabled: false
55
51
 
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: [foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
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, the history of tasks has an auditing value, some kinds of
158
- tasks can grow up in number quite soon. Therefore there is a mechanism
159
- how to clean the tasks, using a rake command. When running without
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 witch to delete all the tasks (by default all tasks are not being deleted after some period)
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
@@ -3,6 +3,8 @@ module Types
3
3
  description 'A Recurring Logic'
4
4
  model_class ::ForemanTasks::RecurringLogic
5
5
 
6
+ include ::Types::Concerns::MetaField
7
+
6
8
  global_id_field :id
7
9
  field :cron_line, String
8
10
  field :end_time, GraphQL::Types::ISO8601DateTime
@@ -2,6 +2,7 @@ module Actions
2
2
  class Base < Dynflow::Action
3
3
  middleware.use ::Actions::Middleware::RailsExecutorWrap
4
4
  include Actions::Helpers::LifecycleLogging
5
+ include Actions::TaskSynchronization
5
6
 
6
7
  execution_plan_hooks.use :notify_paused, :on => [:paused]
7
8
 
@@ -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
- tasks = poll_proxy_tasks(url, tasks).flatten
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
- missing, present = tasks.partition { |task| task.result.nil? }
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.result['state'] }
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
- results = proxy.task_states(tasks.map(&:remote_task_id))
56
- tasks.map do |task|
57
- task.result = results[task.remote_task_id]
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, :proxy_version => proxy_version(proxy)))
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] && with_batch_triggering?(input[:proxy_version])
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.trigger(proxy_action_name, proxy_input)
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] ||= remote_task.try(:remote_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({}) }
@@ -9,7 +9,7 @@ module ForemanTasks
9
9
  |
10
10
  | #{_('Conflicts with tasks:')}
11
11
  HEADER
12
- url_helpers = Rails.application.routes.url_helpers
12
+ url_helpers = ::Rails.application.routes.url_helpers
13
13
  conflicting_tasks = conflicting_locks
14
14
  .map(&:task)
15
15
  .uniq
@@ -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.trigger_task(proxy_action_name, input).merge('result' => 'success')
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
- safe_batch_trigger(operation, group, input_hash)
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
- # Attempt to trigger the tasks using the new API and fall back to the old one
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(data)
9
- utc_zone = ActiveSupport::TimeZone.new('UTC')
10
- self.external_id = data[:id]
11
- self.result = map_result(data).to_s
12
- self.state = data[:state].to_s
13
- self.started_at = string_to_time(utc_zone, data[:started_at]) unless data[:started_at].nil?
14
- self.ended_at = string_to_time(utc_zone, data[:ended_at]) unless data[:ended_at].nil?
15
- self.start_at = string_to_time(utc_zone, data[:start_at]) if data[:start_at]
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.to_hash)
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(execution_plan_id, data)
240
- new(:external_id => execution_plan_id,
241
- :state => data[:state].to_s,
242
- :result => data[:result].to_s,
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(data)
262
- if state_result_transitioned?(%w[planned pending], %w[stopped error], data) ||
263
- (data[:result] == :error && cancelled?)
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
- data[:result]
265
+ plan.result
267
266
  end
268
267
  end
269
268
 
270
- def state_result_transitioned?(from, to, data)
269
+ def state_result_transitioned?(from, to, plan)
271
270
  oldstate, oldresult = from
272
271
  newstate, newresult = to
273
- state == oldstate && data[:state].to_s == newstate &&
274
- result == oldresult && data[:result].to_s == newresult
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?
@@ -2,3 +2,7 @@ object @lock
2
2
 
3
3
  attributes :name, :resource_type, :resource_id
4
4
  node(:exclusive) { !locals[:link] }
5
+ node(:link) do
6
+ method = "#{@object.resource_type.underscore.split('/').first}_path".to_sym
7
+ public_send(method, @object.resource_id) if defined?(method)
8
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ class AddParentTaskIdToRemoteTasks < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :foreman_tasks_remote_tasks, :parent_task_id, :string
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class TasksSettingsToDslCategory < ActiveRecord::Migration[6.0]
2
+ def up
3
+ Setting.where(category: 'Setting::ForemanTasks').update_all(category: 'Setting')
4
+ end
5
+ end
@@ -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
  ;;
@@ -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.2.3'
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