foreman-tasks 5.2.3 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_tasks/tasks_controller.rb +18 -18
  3. data/app/lib/actions/base.rb +1 -0
  4. data/app/lib/actions/foreman/host/import_facts.rb +1 -1
  5. data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +18 -11
  6. data/app/lib/actions/proxy_action.rb +1 -1
  7. data/app/lib/actions/task_synchronization.rb +65 -0
  8. data/app/lib/actions/trigger_proxy_batch.rb +2 -3
  9. data/app/models/foreman_tasks/recurring_logic.rb +1 -1
  10. data/app/models/foreman_tasks/remote_task.rb +13 -7
  11. data/app/models/foreman_tasks/task/dynflow_task.rb +20 -21
  12. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  13. data/config/routes.rb +1 -1
  14. data/db/migrate/20210708123832_add_parent_task_id_to_remote_tasks.foreman_tasks.rb +5 -0
  15. data/db/migrate/20211123170430_tasks_settings_to_dsl_category.rb +5 -0
  16. data/extra/foreman-tasks-cleanup.sh +2 -19
  17. data/foreman-tasks.gemspec +1 -1
  18. data/lib/foreman_tasks/dynflow/configuration.rb +0 -5
  19. data/lib/foreman_tasks/engine.rb +56 -9
  20. data/lib/foreman_tasks/tasks/export_tasks.rake +19 -27
  21. data/lib/foreman_tasks/test_helpers.rb +1 -1
  22. data/lib/foreman_tasks/version.rb +1 -1
  23. data/test/factories/task_factory.rb +1 -1
  24. data/test/lib/actions/middleware/keep_current_taxonomies_test.rb +9 -1
  25. data/test/support/dummy_dynflow_action.rb +1 -1
  26. data/test/unit/actions/trigger_proxy_batch_test.rb +0 -1
  27. data/test/unit/remote_task_test.rb +0 -26
  28. data/test/unit/troubleshooting_help_generator_test.rb +0 -1
  29. data/test/unit/ui_notifications_test.rb +0 -1
  30. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +1 -1
  31. data/webpack/ForemanTasks/Components/TaskActions/index.js +1 -1
  32. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +28 -14
  33. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +50 -42
  34. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +1 -1
  35. data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +1 -1
  36. data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js} +0 -0
  37. metadata +9 -8
  38. data/app/models/setting/foreman_tasks.rb +0 -29
  39. data/lib/foreman_tasks/dynflow/persistence.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f1c154c83d919ac42b7d12edd03cff55e67f11a1367a67250a9a090759cd099
4
- data.tar.gz: 519d85e8e975aab38b2e2314eb5761cb2bb3fe2d07b291822f3c667e6c0feab4
3
+ metadata.gz: 6f5ae3bb36274977790149d11f722477d6db0254b251ffa1dc79ec850a206183
4
+ data.tar.gz: bfd76d0fcc43a78e34c1831a392042e5770bd09276d95d126043f3a14db174b7
5
5
  SHA512:
6
- metadata.gz: 28a96b1b6c28bb5697607d69950167100815c667ecbebbc3f9f446f7e44899e7459e79a4f8efe8f53d798ee7ece6ebaad6d0becd610b11ab499cd32a4e2b5b53
7
- data.tar.gz: 16c30b57fc2b2f82be9baf9d2dcf4c1b9f7a47df3013393d274fb6268ea04b6a066d3464bdeb1d911b998d7d5e68faa19ea9551193a25a07fb1cc12ddb45ee66
6
+ metadata.gz: b2bde1cb649304ba13019b544c8db8f7fa72b2e0539d47b2db07d6e945364d08b4e5fa0b100b701aa7f080e8be308ad7a6af404eaee2865e7b13fd423f9a9300
7
+ data.tar.gz: 00a77aaf192bbfc12a70dc034aa75bfd728c18956b1b9377bbef3d7fce7491c68cfa520d9fd04de4789c721fcc2d2b2b605b63a9165601092841e44184219dfc
@@ -4,8 +4,6 @@ module ForemanTasks
4
4
  include Foreman::Controller::CsvResponder
5
5
  include ForemanTasks::FindTasksCommon
6
6
 
7
- before_action :find_dynflow_task, only: [:unlock, :force_unlock, :cancel, :cancel_step, :resume]
8
-
9
7
  def show
10
8
  @task = resource_base.find(params[:id])
11
9
  render :layout => !request.xhr?
@@ -33,7 +31,8 @@ module ForemanTasks
33
31
  end
34
32
 
35
33
  def cancel_step
36
- result = ForemanTasks.dynflow.world.event(@dynflow_task.external_id, params[:step_id].to_i, ::Dynflow::Action::Cancellable::Cancel).wait
34
+ task = find_dynflow_task
35
+ result = ForemanTasks.dynflow.world.event(task.external_id, params[:step_id].to_i, ::Dynflow::Action::Cancellable::Cancel).wait
37
36
  if result.rejected?
38
37
  render json: { error: result.reason }, status: :bad_request
39
38
  else
@@ -42,7 +41,8 @@ module ForemanTasks
42
41
  end
43
42
 
44
43
  def cancel
45
- if @dynflow_task.cancel
44
+ task = find_dynflow_task
45
+ if task.cancel
46
46
  render json: { statusText: 'OK' }
47
47
  else
48
48
  render json: {}, status: :bad_request
@@ -50,17 +50,19 @@ module ForemanTasks
50
50
  end
51
51
 
52
52
  def abort
53
- if @dynflow_task.abort
53
+ task = find_dynflow_task
54
+ if task.abort
54
55
  flash[:info] = _('Trying to abort the task')
55
56
  else
56
57
  flash[:warning] = _('The task cannot be aborted at the moment.')
57
58
  end
58
- redirect_back(:fallback_location => foreman_tasks_task_path(@dynflow_task))
59
+ redirect_back(:fallback_location => foreman_tasks_task_path(task))
59
60
  end
60
61
 
61
62
  def resume
62
- if @dynflow_task.resumable?
63
- ForemanTasks.dynflow.world.execute(@dynflow_task.execution_plan.id)
63
+ task = find_dynflow_task
64
+ if task.resumable?
65
+ ForemanTasks.dynflow.world.execute(task.execution_plan.id)
64
66
  render json: { statusText: 'OK' }
65
67
  else
66
68
  render json: {}, status: :bad_request
@@ -68,8 +70,10 @@ module ForemanTasks
68
70
  end
69
71
 
70
72
  def unlock
71
- if @dynflow_task.paused?
72
- unlock_task(@dynflow_task)
73
+ task = find_dynflow_task
74
+ if task.paused?
75
+ task.state = :stopped
76
+ task.save!
73
77
  render json: { statusText: 'OK' }
74
78
  else
75
79
  render json: {}, status: :bad_request
@@ -77,7 +81,9 @@ module ForemanTasks
77
81
  end
78
82
 
79
83
  def force_unlock
80
- unlock_task(@dynflow_task)
84
+ task = find_dynflow_task
85
+ task.state = :stopped
86
+ task.save!
81
87
  render json: { statusText: 'OK' }
82
88
  end
83
89
 
@@ -92,12 +98,6 @@ module ForemanTasks
92
98
 
93
99
  private
94
100
 
95
- def unlock_task(task)
96
- task.state = :stopped
97
- task.locks.destroy_all
98
- task.save!
99
- end
100
-
101
101
  def respond_with_tasks(scope)
102
102
  @tasks = filter(scope, paginate: false).with_duration
103
103
  csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :duration, :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'Duration', 'User'])
@@ -123,7 +123,7 @@ module ForemanTasks
123
123
  end
124
124
 
125
125
  def find_dynflow_task
126
- @dynflow_task = resource_scope.where(:type => 'ForemanTasks::Task::DynflowTask').find(params[:id])
126
+ resource_scope.where(:type => 'ForemanTasks::Task::DynflowTask').find(params[:id])
127
127
  end
128
128
 
129
129
  def filter(scope, paginate: true)
@@ -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
@@ -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)
@@ -263,7 +263,7 @@ module Actions
263
263
  end
264
264
 
265
265
  def proxy_task_id
266
- output[:proxy_task_id] ||= remote_task.try(:remote_task_id)
266
+ output[:proxy_task_id] || remote_task.try(:remote_task_id) || @execution_plan_id
267
267
  end
268
268
  end
269
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,11 +37,10 @@ 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
- action_logger.warn "Could not trigger task on the smart proxy"
44
- action_logger.warn e
43
+ action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
45
44
  batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
46
45
  output[:failed_count] += batch.size
47
46
  end
@@ -90,7 +90,7 @@ module ForemanTasks
90
90
  def next_occurrence_time(time = Time.zone.now)
91
91
  @parser ||= CronParser.new(cron_line, Time.zone)
92
92
  # @parser.next(start_time) is not inclusive of the start_time hence stepping back one run to include checking start_time for the first run.
93
- before_next = @parser.next(@parser.last(time.in_time_zone))
93
+ before_next = @parser.next(@parser.last(time))
94
94
  return before_next if before_next >= time && tasks.count == 0
95
95
  @parser.next(time)
96
96
  end
@@ -18,8 +18,7 @@ module ForemanTasks
18
18
  response = begin
19
19
  proxy.launch_tasks('single', :action_class => proxy_action_name, :action_input => input)
20
20
  rescue RestClient::Exception => e
21
- logger.warn "Could not trigger task on the smart proxy"
22
- logger.warn e
21
+ logger.warn "Could not trigger task on the smart proxy: #{e.message}"
23
22
  {}
24
23
  end
25
24
  update_from_batch_trigger(response)
@@ -27,26 +26,33 @@ module ForemanTasks
27
26
  end
28
27
 
29
28
  def self.batch_trigger(operation, remote_tasks)
30
- remote_tasks.group_by(&:proxy_url).each_value do |group|
29
+ remote_tasks.group_by(&:proxy_url).values.map do |group|
31
30
  input_hash = group.reduce({}) do |acc, remote_task|
32
31
  acc.merge(remote_task.execution_plan_id => { :action_input => remote_task.proxy_input,
33
32
  :action_class => remote_task.proxy_action_name })
34
33
  end
35
- results = group.first.proxy.launch_tasks(operation, input_hash)
36
- group.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
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
37
39
  end
38
40
  remote_tasks
39
41
  end
40
42
 
41
- def update_from_batch_trigger(data)
43
+ def update_from_batch_trigger(data, parent = {})
42
44
  if data['result'] == 'success'
43
45
  self.remote_task_id = data['task_id']
44
46
  self.state = 'triggered'
47
+ elsif !parent.empty?
48
+ self.parent_task_id = parent['task_id']
49
+ self.state = 'parent-triggered'
45
50
  else
46
51
  # Tell the action the task on the smart proxy stopped
47
52
  ForemanTasks.dynflow.world.event execution_plan_id,
48
53
  step_id,
49
- ::Actions::ProxyAction::ProxyActionStopped.new
54
+ ::Actions::ProxyAction::ProxyActionStopped.new,
55
+ optional: true
50
56
  end
51
57
  save!
52
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
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,21 +21,6 @@ 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
-
39
24
  function usage() {
40
25
  cat << EOF
41
26
  Usage: $PROGNAME [script_options...] [options...]
@@ -58,8 +43,8 @@ EOF
58
43
  echo Cleanup options:
59
44
  cat <<EOF | column -s\& -t
60
45
  -B|--batch-size BATCH_SIZE & process tasks in batches of BATCH_SIZE, 1000 by default
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
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
63
48
  -b|--backup & backup deleted tasks
64
49
  -n|--noop & do a dry run, print what would be done
65
50
  -s|--search QUERY & use QUERY in scoped search format to match tasks to delete
@@ -134,8 +119,6 @@ while true; do
134
119
  shift
135
120
  done
136
121
 
137
- validate_options!
138
-
139
122
  if [ "$EXECUTE" -eq 1 ]; then
140
123
  build_rake | sh
141
124
  else
@@ -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.2.3'
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
@@ -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 '>= 2.6.0'
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
@@ -241,42 +241,35 @@ namespace :foreman_tasks do
241
241
  end
242
242
  end
243
243
 
244
- def csv_export(export_filename, id_scope, task_scope)
244
+ def csv_export(export_filename, tasks)
245
245
  CSV.open(export_filename, 'wb') do |csv|
246
246
  csv << %w[id state type label result parent_task_id started_at ended_at duration]
247
- id_scope.pluck(:id).each_slice(1000).each do |ids|
248
- task_scope.where(id: ids).each do |task|
249
- with_error_handling(task) do
250
- csv << [task.id, task.state, task.type, task.label, task.result,
251
- task.parent_task_id, task.started_at, task.ended_at, task.duration]
252
- end
247
+ tasks.find_each do |task|
248
+ with_error_handling(task) do
249
+ csv << [task.id, task.state, task.type, task.label, task.result,
250
+ task.parent_task_id, task.started_at, task.ended_at, task.duration]
253
251
  end
254
252
  end
255
253
  end
256
254
  end
257
255
 
258
- def html_export(workdir, id_scope, task_scope)
256
+ def html_export(workdir, tasks)
259
257
  PageHelper.copy_assets(workdir)
260
258
 
261
- ids = id_scope.pluck(:id)
262
259
  renderer = TaskRender.new
263
- count = 0
264
- total = ids.count
260
+ total = tasks.count(:all)
265
261
  index = File.open(File.join(workdir, 'index.html'), 'w')
266
262
 
267
263
  File.open(File.join(workdir, 'index.html'), 'w') do |index|
268
264
  PageHelper.pagify(index) do |io|
269
265
  PageHelper.generate_with_index(io) do |index|
270
- ids.each_slice(1000).each do |ids|
271
- task_scope.where(id: ids).each do |task|
272
- content = with_error_handling(task) { renderer.render_task(task) }
273
- if content
274
- File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, content) }
275
- with_error_handling(task, _('task index entry')) { PageHelper.generate_index_entry(index, task) }
276
- end
277
- count += 1
278
- puts "#{count}/#{total}"
266
+ tasks.find_each.each_with_index do |task, count|
267
+ content = with_error_handling(task) { renderer.render_task(task) }
268
+ if content
269
+ File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, content) }
270
+ with_error_handling(task, _('task index entry')) { PageHelper.generate_index_entry(index, task) }
279
271
  end
272
+ puts "#{count + 1}/#{total}"
280
273
  end
281
274
  end
282
275
  end
@@ -307,7 +300,7 @@ namespace :foreman_tasks do
307
300
  end
308
301
  end
309
302
 
310
- SKIP_ERRORS = ['true', '1', 'y', 'yes'].include? (ENV['SKIP_FAILED'] || '').downcase
303
+ SKIP_ERRORS = ['true', '1', 'y', 'yes'].include? ENV['SKIP_FAILED'].downcase
311
304
 
312
305
  filter = if ENV['TASK_SEARCH'].nil? && ENV['TASK_DAYS'].nil?
313
306
  "started_at > \"#{7.days.ago.to_s(:db)}\" || " \
@@ -324,22 +317,21 @@ namespace :foreman_tasks do
324
317
  format = ENV['TASK_FORMAT'] || 'html'
325
318
  export_filename = ENV['TASK_FILE'] || generate_filename(format)
326
319
 
327
- task_scope = ForemanTasks::Task.search_for(filter).with_duration.order(:started_at => :desc)
328
- id_scope = task_scope.group(:id, :started_at)
320
+ tasks = ForemanTasks::Task.search_for(filter).order(:started_at => :desc).with_duration.distinct
329
321
 
330
322
  puts _("Exporting all tasks matching filter #{filter}")
331
- puts _("Gathering #{id_scope.count(:all).count} tasks.")
323
+ puts _("Gathering #{tasks.count(:all)} tasks.")
332
324
  case format
333
325
  when 'html'
334
326
  Dir.mktmpdir('task-export') do |tmp_dir|
335
- html_export(tmp_dir, id_scope, task_scope)
327
+ html_export(tmp_dir, tasks)
336
328
  system("tar", "czf", export_filename, tmp_dir)
337
329
  end
338
330
  when 'html-dir'
339
331
  FileUtils.mkdir_p(export_filename)
340
- html_export(export_filename, id_scope, task_scope)
332
+ html_export(export_filename, tasks)
341
333
  when 'csv'
342
- csv_export(export_filename, id_scope, task_scope)
334
+ csv_export(export_filename, tasks)
343
335
  else
344
336
  raise "Unkonwn export format '#{format}'"
345
337
  end
@@ -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
- ::ForemanTasks::Dynflow::Persistence.new('adapter' => 'sqlite', 'database' => ':memory:')
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)
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '5.2.3'.freeze
2
+ VERSION = '5.3.0'.freeze
3
3
  end
@@ -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.to_hash)
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' }
@@ -1,5 +1,5 @@
1
1
  module Support
2
- class DummyDynflowAction < Dynflow::Action
2
+ class DummyDynflowAction < Actions::EntryAction
3
3
  end
4
4
 
5
5
  class DummyPauseAction < Actions::EntryAction
@@ -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
 
@@ -28,32 +28,6 @@ module ForemanTasks
28
28
  _(remote_task.remote_task_id).must_equal((remote_task.id + 5).to_s)
29
29
  end
30
30
  end
31
-
32
- it 'honors the batches with multiple proxies' do
33
- remote_task = remote_tasks.last
34
- remote_task.proxy_url = 'something else'
35
-
36
- results = remote_tasks.reduce({}) do |acc, cur|
37
- acc.merge(cur.execution_plan_id.to_s => { 'task_id' => cur.id + 5, 'result' => 'success' })
38
- end
39
- other_results = { remote_task.execution_plan_id => results.delete(remote_task.execution_plan_id) }
40
-
41
- fake_proxy = mock
42
- fake_proxy.expects(:launch_tasks).returns(results)
43
-
44
- another_fake_proxy = mock
45
- another_fake_proxy.expects(:launch_tasks).returns(other_results)
46
-
47
- remote_tasks.first.expects(:proxy).returns(fake_proxy)
48
- remote_tasks.last.expects(:proxy).returns(another_fake_proxy)
49
-
50
- RemoteTask.batch_trigger('a_operation', remote_tasks)
51
- remote_tasks.each do |remote_task|
52
- remote_task.reload
53
- _(remote_task.state).must_equal 'triggered'
54
- _(remote_task.remote_task_id).must_equal((remote_task.id + 5).to_s)
55
- end
56
- end
57
31
  end
58
32
  end
59
33
  end
@@ -21,7 +21,6 @@ module ForemanTasks
21
21
  end
22
22
 
23
23
  before do
24
- Setting::ForemanTasks.load_defaults
25
24
  ::ForemanTasks::Task.delete_all
26
25
  @task = trigger_task
27
26
  Setting[:foreman_tasks_troubleshooting_url] = sample_troubleshooting_url
@@ -12,7 +12,6 @@ module ForemanTasks
12
12
  end
13
13
 
14
14
  before do
15
- Setting::ForemanTasks.load_defaults
16
15
  ::ForemanTasks::Task.delete_all
17
16
  load File.join(ForemanTasks::Engine.root, 'db', 'seeds.d', '30-notification_blueprints.rb')
18
17
  @admin_user = FactoryBot.create(:user, :admin)
@@ -1,5 +1,5 @@
1
1
  import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
- import { addToast } from 'foremanReact/redux/actions/toasts';
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/redux/actions/toasts';
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
- <Card className="card-pf-aggregate-status" accented>
18
- <Card.Title>
19
- <span
20
- className={`fa ${
21
- lock.exclusive ? 'fa-lock' : 'fa-unlock-alt'
22
- }`}
23
- />
24
- {lock.resource_type}
25
- </Card.Title>
26
- <Card.Body>
27
- {`id:${lock.resource_id}`}
28
- <br />
29
- </Card.Body>
30
- </Card>
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
- <Card
29
- accented={true}
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
- <CardTitle
37
- className=""
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
- <span
40
- className="fa fa-unlock-alt"
41
- />
42
- User
43
- </CardTitle>
44
- <CardBody
45
- className=""
46
- >
47
- id:4
48
- <br />
49
- </CardBody>
50
- </Card>
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
- <Card
61
- accented={true}
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
- <CardTitle
69
- className=""
70
- >
71
- <span
72
- className="fa fa-unlock-alt"
73
- />
74
- User
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
- id:2
80
- <br />
81
- </CardBody>
82
- </Card>
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 { post, get } from 'foremanReact/redux/API';
2
- import { addToast } from 'foremanReact/redux/actions/toasts';
2
+ import { addToast } from 'foremanReact/components/ToastsList';
3
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
4
  import {
5
5
  withInterval,
@@ -1,5 +1,5 @@
1
1
  import { API } from 'foremanReact/redux/API';
2
- import { addToast } from 'foremanReact/redux/actions/toasts';
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,
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.2.3
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: 2022-04-21 00:00:00.000000000 Z
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.2.3
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.2.3
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
@@ -607,7 +608,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
607
608
  - !ruby/object:Gem::Version
608
609
  version: '0'
609
610
  requirements: []
610
- rubygems_version: 3.1.4
611
+ rubygems_version: 3.1.2
611
612
  signing_key:
612
613
  specification_version: 4
613
614
  summary: Foreman plugin for showing tasks information for resources and users
@@ -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