foreman-tasks 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/lib/actions/base.rb +1 -0
  3. data/app/lib/actions/foreman/host/import_facts.rb +1 -1
  4. data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +18 -11
  5. data/app/lib/actions/proxy_action.rb +1 -1
  6. data/app/lib/actions/task_synchronization.rb +65 -0
  7. data/app/lib/actions/trigger_proxy_batch.rb +1 -1
  8. data/app/models/foreman_tasks/remote_task.rb +10 -3
  9. data/app/models/foreman_tasks/task/dynflow_task.rb +20 -21
  10. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  11. data/config/routes.rb +1 -1
  12. data/db/migrate/20210708123832_add_parent_task_id_to_remote_tasks.foreman_tasks.rb +5 -0
  13. data/db/migrate/20211123170430_tasks_settings_to_dsl_category.rb +5 -0
  14. data/foreman-tasks.gemspec +1 -1
  15. data/lib/foreman_tasks/dynflow/configuration.rb +0 -5
  16. data/lib/foreman_tasks/engine.rb +56 -9
  17. data/lib/foreman_tasks/test_helpers.rb +1 -1
  18. data/lib/foreman_tasks/version.rb +1 -1
  19. data/test/factories/task_factory.rb +1 -1
  20. data/test/lib/actions/middleware/keep_current_taxonomies_test.rb +9 -1
  21. data/test/support/dummy_dynflow_action.rb +1 -1
  22. data/test/unit/actions/trigger_proxy_batch_test.rb +0 -1
  23. data/test/unit/troubleshooting_help_generator_test.rb +0 -1
  24. data/test/unit/ui_notifications_test.rb +0 -1
  25. data/webpack/ForemanTasks/Components/TaskActions/TaskActionHelpers.js +1 -1
  26. data/webpack/ForemanTasks/Components/TaskActions/index.js +1 -1
  27. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +28 -14
  28. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +50 -42
  29. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +1 -1
  30. data/webpack/ForemanTasks/Components/TasksTable/TasksBulkActions.js +1 -1
  31. data/webpack/__mocks__/foremanReact/{redux/actions/toasts.js → components/ToastsList/index.js} +0 -0
  32. metadata +8 -7
  33. data/app/models/setting/foreman_tasks.rb +0 -29
  34. data/lib/foreman_tasks/dynflow/persistence.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa2189368cc093a6ca613ca1b91add99bc3689b2cb22cd0c04a3718821b5f27f
4
- data.tar.gz: f7e82294bbbb76fe6576b6c2de9e55c16342962f711a8e5c9070787be304a87c
3
+ metadata.gz: 6f5ae3bb36274977790149d11f722477d6db0254b251ffa1dc79ec850a206183
4
+ data.tar.gz: bfd76d0fcc43a78e34c1831a392042e5770bd09276d95d126043f3a14db174b7
5
5
  SHA512:
6
- metadata.gz: f20e5fba4dbc2401a4fed1f5eb68961ccc7095071fc309316ad14392b9209141e96aa2372eb7bb5db0e7f829ef08727ff2e232e53de86baa8385183ddb722252
7
- data.tar.gz: 534ac74d1c8010ac25330562a6bdb713beacc413d2c681ee72f865727741cfb17e04d9dda2c0ea8ab7bde2f7f2e08f54fa4266e25686ca9d1f38c5309b24b94c
6
+ metadata.gz: b2bde1cb649304ba13019b544c8db8f7fa72b2e0539d47b2db07d6e945364d08b4e5fa0b100b701aa7f080e8be308ad7a6af404eaee2865e7b13fd423f9a9300
7
+ data.tar.gz: 00a77aaf192bbfc12a70dc034aa75bfd728c18956b1b9377bbef3d7fce7491c68cfa520d9fd04de4789c721fcc2d2b2b605b63a9165601092841e44184219dfc
@@ -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,8 +37,8 @@ module Actions
37
37
  # Group the tasks by operation, in theory there should be only one operation
38
38
  batch.group_by(&:operation).each do |operation, group|
39
39
  ForemanTasks::RemoteTask.batch_trigger(operation, group)
40
+ output[:planned_count] += group.size
40
41
  end
41
- output[:planned_count] += batch.size
42
42
  rescue => e
43
43
  action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
44
44
  batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
@@ -32,20 +32,27 @@ module ForemanTasks
32
32
  :action_class => remote_task.proxy_action_name })
33
33
  end
34
34
  results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
35
- remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
35
+ remote_tasks.each do |remote_task|
36
+ remote_task.update_from_batch_trigger results.fetch(remote_task.execution_plan_id, {}),
37
+ results.fetch('parent', {})
38
+ end
36
39
  end
37
40
  remote_tasks
38
41
  end
39
42
 
40
- def update_from_batch_trigger(data)
43
+ def update_from_batch_trigger(data, parent = {})
41
44
  if data['result'] == 'success'
42
45
  self.remote_task_id = data['task_id']
43
46
  self.state = 'triggered'
47
+ elsif !parent.empty?
48
+ self.parent_task_id = parent['task_id']
49
+ self.state = 'parent-triggered'
44
50
  else
45
51
  # Tell the action the task on the smart proxy stopped
46
52
  ForemanTasks.dynflow.world.event execution_plan_id,
47
53
  step_id,
48
- ::Actions::ProxyAction::ProxyActionStopped.new
54
+ ::Actions::ProxyAction::ProxyActionStopped.new,
55
+ optional: true
49
56
  end
50
57
  save!
51
58
  end
@@ -5,15 +5,14 @@ module ForemanTasks
5
5
  scope :for_action, ->(action_class) { where(label: action_class.name) }
6
6
  after_validation :set_action_field
7
7
 
8
- def update_from_dynflow(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
@@ -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
@@ -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.0'.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
 
@@ -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.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-11 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
@@ -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