foreman-tasks 3.0.5 → 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_tests.yml +5 -3
  3. data/app/controllers/concerns/foreman_tasks/find_tasks_common.rb +14 -0
  4. data/app/controllers/foreman_tasks/api/tasks_controller.rb +4 -6
  5. data/app/controllers/foreman_tasks/tasks_controller.rb +3 -11
  6. data/app/lib/actions/entry_action.rb +8 -4
  7. data/app/lib/actions/helpers/lock.rb +11 -5
  8. data/app/lib/actions/middleware/keep_current_request_id.rb +4 -1
  9. data/app/lib/actions/middleware/keep_current_user.rb +11 -1
  10. data/app/lib/actions/middleware/load_setting_values.rb +35 -0
  11. data/app/lib/actions/observable_action.rb +80 -0
  12. data/app/lib/actions/proxy_action.rb +2 -4
  13. data/app/models/foreman_tasks/concerns/action_subject.rb +0 -6
  14. data/app/models/foreman_tasks/link.rb +60 -0
  15. data/app/models/foreman_tasks/lock.rb +30 -128
  16. data/app/models/foreman_tasks/task.rb +20 -7
  17. data/app/models/foreman_tasks/task/search.rb +6 -6
  18. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  19. data/app/views/foreman_tasks/api/tasks/details.json.rabl +5 -3
  20. data/app/views/foreman_tasks/tasks/_lock_card.html.erb +10 -0
  21. data/db/migrate/20180927120509_add_user_id.foreman_tasks.rb +4 -2
  22. data/db/migrate/20181206123910_create_foreman_tasks_links.foreman_tasks.rb +26 -0
  23. data/db/migrate/20181206124952_migrate_non_exclusive_locks_to_links.foreman_tasks.rb +14 -0
  24. data/db/migrate/20181206131436_drop_old_locks.foreman_tasks.rb +20 -0
  25. data/db/migrate/20181206131627_make_locks_exclusive.foreman_tasks.rb +25 -0
  26. data/lib/foreman_tasks/cleaner.rb +10 -0
  27. data/lib/foreman_tasks/engine.rb +6 -2
  28. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  29. data/lib/foreman_tasks/version.rb +1 -1
  30. data/locale/action_names.rb +3 -2
  31. data/locale/en/foreman_tasks.po +60 -27
  32. data/locale/foreman_tasks.pot +180 -132
  33. data/locale/fr/foreman_tasks.po +61 -28
  34. data/locale/ja/foreman_tasks.po +61 -28
  35. data/locale/zh_CN/foreman_tasks.po +61 -28
  36. data/package.json +6 -6
  37. data/test/controllers/api/tasks_controller_test.rb +16 -1
  38. data/test/controllers/tasks_controller_test.rb +3 -3
  39. data/test/core/unit/runner_test.rb +4 -17
  40. data/test/factories/task_factory.rb +31 -4
  41. data/test/unit/actions/action_with_sub_plans_test.rb +5 -2
  42. data/test/unit/actions/proxy_action_test.rb +4 -1
  43. data/test/unit/cleaner_test.rb +4 -4
  44. data/test/unit/locking_test.rb +85 -0
  45. data/test/unit/task_test.rb +15 -15
  46. data/test/unit/triggering_test.rb +2 -2
  47. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +2 -2
  48. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +3 -2
  49. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +4 -4
  50. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +2 -0
  51. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +4 -1
  52. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +5 -1
  53. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +3 -0
  54. data/webpack/ForemanTasks/Components/TaskDetails/index.js +2 -0
  55. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +4 -3
  56. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -2
  57. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionNameCellFormatter.test.js.snap +2 -3
  58. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionNameCellFormatter.js +2 -3
  59. metadata +14 -4
  60. data/test/unit/lock_test.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e389326a8086026cc0359932b9d3a3c0c13621f9f0cb60e682d785ecbacddcc
4
- data.tar.gz: '0880da8a4cd833615fe5aa9b79aab13a5dcf1df0f84aea891a2490c6f7a342ba'
3
+ metadata.gz: 676dae1bc60b1633da496445b980e4b5fb491931bb227a70f2dcec4c44a9ae9e
4
+ data.tar.gz: 98d4f9798338d20bf7885a9e11b40d492eeeca4d30e2a7f84c4734e4f83e30d7
5
5
  SHA512:
6
- metadata.gz: 15589c91365b2f7f0f14b38d000575048c2c9e316430eb1564357fd7aee02b9af275155e7021cc69bffc55e8eb46d83445554a46f615d703d6e5da52912a4a94
7
- data.tar.gz: b5083cad3c0268b6c461574f7f9c86887bb395a85ec6cd41dd15d2816c07961bee099fc5e4d926029c1e57802c41852a4768c5c68674d26a8822e900ef879b59
6
+ metadata.gz: 8482b8a8e5ce147f2bd81d1eea004ba9507c95ecf5d83d0ebd50b7c74cc467a561f035d0049f0d1b44c3d63a29c35f3e55792705d37dfc0b4431e02d2370732a
7
+ data.tar.gz: c317248a7e396b2d556f397ba1fd91d162b5d38b75a593a7f8a28f555ef77ca15ec097f548162edd6c7a7198bfa7e84c6a902bbde6d7515c17b585f7632e9add
@@ -12,7 +12,7 @@ jobs:
12
12
  - name: Setup Ruby
13
13
  uses: ruby/setup-ruby@v1
14
14
  with:
15
- ruby-version: 2.6
15
+ ruby-version: 2.7
16
16
  - name: Setup
17
17
  run: |
18
18
  gem install bundler
@@ -31,7 +31,7 @@ jobs:
31
31
  fail-fast: false
32
32
  matrix:
33
33
  foreman-core-branch: [develop]
34
- ruby-version: [2.5, 2.6]
34
+ ruby-version: [2.5, 2.6, 2.7]
35
35
  node-version: [12]
36
36
  steps:
37
37
  - run: sudo apt-get update
@@ -60,6 +60,7 @@ jobs:
60
60
  - name: Setup Bundler
61
61
  run: |
62
62
  echo "gem 'foreman-tasks', path: './foreman-tasks'" > bundler.d/foreman-tasks.local.rb
63
+ echo "gem 'sqlite3'" >> bundler.d/foreman-tasks.local.rb
63
64
  gem install bundler
64
65
  bundle config set without journald development console libvirt
65
66
  bundle config set path vendor/bundle
@@ -70,5 +71,6 @@ jobs:
70
71
  bundle exec rake db:migrate
71
72
  - name: Run plugin tests
72
73
  run: |
73
- bundle exec rake test:foreman-tasks
74
+ bundle exec rake test:foreman_tasks
75
+ bundle exec rake test:foreman_tasks_core
74
76
  bundle exec rake test TEST="test/unit/foreman/access_permissions_test.rb"
@@ -0,0 +1,14 @@
1
+ module ForemanTasks
2
+ module FindTasksCommon
3
+ def search_query
4
+ [current_taxonomy_search, params[:search]].select(&:present?).join(' AND ')
5
+ end
6
+
7
+ def current_taxonomy_search
8
+ conditions = []
9
+ conditions << "organization_id = #{Organization.current.id}" if Organization.current
10
+ conditions << "location_id = #{Location.current.id}" if Location.current
11
+ conditions.empty? ? '' : "(#{conditions.join(' AND ')})"
12
+ end
13
+ end
14
+ end
@@ -1,6 +1,7 @@
1
1
  module ForemanTasks
2
2
  module Api
3
3
  class TasksController < ::Api::V2::BaseController
4
+ include ForemanTasks::FindTasksCommon
4
5
  include ::Foreman::Controller::SmartProxyAuth
5
6
  add_smart_proxy_filters :callback, :features => 'Dynflow'
6
7
 
@@ -51,7 +52,7 @@ module ForemanTasks
51
52
  In case :type = 'resource', what resource id we're searching the tasks for
52
53
  DESC
53
54
  param :action_types, [String], :desc => <<-DESC
54
- Return just tasks of given action type, e.g. ["Actions::Katello::Repository::Synchronize"]
55
+ Return just tasks of given action type, e.g. `["Actions::Katello::Repository::Synchronize"]`
55
56
  DESC
56
57
  param :active_only, :bool
57
58
  param :page, String
@@ -195,7 +196,7 @@ module ForemanTasks
195
196
  end
196
197
 
197
198
  def search_options
198
- [params[:search], {}]
199
+ [search_query, {}]
199
200
  end
200
201
 
201
202
  def_param_group :callback_target do
@@ -253,10 +254,7 @@ module ForemanTasks
253
254
  if search_params[:user_id].blank?
254
255
  raise BadRequest, _('User search_params requires user_id to be specified')
255
256
  end
256
- scope.joins(:locks).where(foreman_tasks_locks:
257
- { name: ::ForemanTasks::Lock::OWNER_LOCK_NAME,
258
- resource_type: 'User',
259
- resource_id: search_params[:user_id] })
257
+ scope.search_for("user_id = #{search_params[:user_id]}")
260
258
  when 'resource'
261
259
  if search_params[:resource_type].blank? || search_params[:resource_id].blank?
262
260
  raise BadRequest,
@@ -2,6 +2,7 @@ module ForemanTasks
2
2
  class TasksController < ::ApplicationController
3
3
  include Foreman::Controller::AutoCompleteSearch
4
4
  include Foreman::Controller::CsvResponder
5
+ include ForemanTasks::FindTasksCommon
5
6
 
6
7
  def show
7
8
  @task = resource_base.find(params[:id])
@@ -126,19 +127,10 @@ module ForemanTasks
126
127
  end
127
128
 
128
129
  def filter(scope, paginate: true)
129
- search = current_taxonomy_search
130
- search = [search, params[:search]].select(&:present?).join(' AND ')
131
130
  scope = DashboardTableFilter.new(scope, params).scope
132
- scope = scope.search_for(search, :order => params[:order])
133
- scope = scope.paginate(:page => params[:page], :per_page => params[:per_page]) if paginate
131
+ scope = scope.search_for(search_query, order: params[:order])
132
+ scope = scope.paginate(page: params[:page], per_page: params[:per_page]) if paginate
134
133
  scope.distinct
135
134
  end
136
-
137
- def current_taxonomy_search
138
- conditions = []
139
- conditions << "organization_id = #{Organization.current.id}" if Organization.current
140
- conditions << "location_id = #{Location.current.id}" if Location.current
141
- conditions.empty? ? '' : "(#{conditions.join(' AND ')})"
142
- end
143
135
  end
144
136
  end
@@ -3,6 +3,8 @@ module Actions
3
3
  include Helpers::ArgsSerialization
4
4
  include Helpers::Lock
5
5
 
6
+ execution_plan_hooks.use :drop_all_locks!, :on => :stopped
7
+
6
8
  # what locks to use on the resource? All by default, can be overriden.
7
9
  # It might one or more locks available for the resource. This following
8
10
  # special values are supported as well:
@@ -34,12 +36,10 @@ module Actions
34
36
  input.update serialize_args(resource, *resource.all_related_resources, *additional_args)
35
37
 
36
38
  if resource.is_a? ActiveRecord::Base
37
- if resource_locks == :exclusive
38
- exclusive_lock!(resource)
39
- elsif resource_locks == :link
39
+ if resource_locks == :link
40
40
  link!(resource)
41
41
  else
42
- lock!(resource, resource_locks)
42
+ exclusive_lock!(resource)
43
43
  end
44
44
  end
45
45
  end
@@ -63,5 +63,9 @@ module Actions
63
63
  def self.serializer_class
64
64
  Serializers::ActiveRecordSerializer
65
65
  end
66
+
67
+ def drop_all_locks!(_execution_plan)
68
+ ForemanTasks::Lock.where(:task_id => task.id).destroy_all
69
+ end
66
70
  end
67
71
  end
@@ -4,19 +4,25 @@ module Actions
4
4
  # @see Lock.exclusive!
5
5
  def exclusive_lock!(resource)
6
6
  phase! Dynflow::Action::Plan
7
- ::ForemanTasks::Lock.exclusive!(resource, task.id)
7
+ parent_lock = ::ForemanTasks::Lock.for_resource(resource).where(:task_id => task.self_and_parents.map(&:id)).first
8
+ if parent_lock
9
+ ForemanTasks::Link.link_resource_and_related!(resource, task)
10
+ parent_lock
11
+ else
12
+ ::ForemanTasks::Lock.exclusive!(resource, task)
13
+ end
8
14
  end
9
15
 
10
16
  # @see Lock.lock!
11
- def lock!(resource, *lock_names)
12
- phase! Dynflow::Action::Plan
13
- ::ForemanTasks::Lock.lock!(resource, task.id, *lock_names.flatten)
17
+ def lock!(resource, *_lock_names)
18
+ Foreman::Deprecation.deprecation_warning('2.4', 'locking in foreman-tasks was reworked, please use a combination of exclusive_lock! and link! instead.')
19
+ exclusive_lock!(resource)
14
20
  end
15
21
 
16
22
  # @see Lock.link!
17
23
  def link!(resource)
18
24
  phase! Dynflow::Action::Plan
19
- ::ForemanTasks::Lock.link!(resource, task.id)
25
+ ::ForemanTasks::Link.link!(resource, task)
20
26
  end
21
27
  end
22
28
  end
@@ -21,7 +21,10 @@ module Actions
21
21
 
22
22
  # Run all execution plan lifecycle hooks as the original request_id
23
23
  def hook(*args)
24
- restore_current_request_id { pass(*args) }
24
+ store_current_request_id if !action.input.key?(:current_request_id) && ::Logging.mdc['request']
25
+ restore_current_request_id do
26
+ pass(*args)
27
+ end
25
28
  end
26
29
 
27
30
  private
@@ -16,9 +16,19 @@ module Actions
16
16
  end
17
17
 
18
18
  def finalize
19
+ current_id = User.current.try(:id)
20
+ saved_id = action.input[:current_user_id]
21
+ if User.current && saved_id && current_id != saved_id
22
+ Foreman::Deprecation.deprecation_warning('2.5', 'relying on per-step setting of current user in finalize phase')
23
+ end
24
+
19
25
  restore_curent_user { pass }
20
26
  end
21
27
 
28
+ def finalize_phase(execution_plan, *args)
29
+ restore_curent_user(execution_plan.entry_action) { pass(execution_plan, *args) }
30
+ end
31
+
22
32
  # Run all execution plan lifecycle hooks as the original user
23
33
  def hook(*args)
24
34
  restore_curent_user { pass(*args) }
@@ -38,7 +48,7 @@ module Actions
38
48
  action.input[:current_user_id] = User.current.try(:id)
39
49
  end
40
50
 
41
- def restore_curent_user
51
+ def restore_curent_user(action = self.action)
42
52
  old_user = User.current
43
53
  User.current = User.unscoped.find(action.input[:current_user_id]) if action.input[:current_user_id].present?
44
54
  yield
@@ -0,0 +1,35 @@
1
+ module Actions
2
+ module Middleware
3
+ class LoadSettingValues < ::Dynflow::Middleware
4
+ # ::Actions::Middleware::LoadSettingValues
5
+ #
6
+ # A middleware to ensure we load current setting values
7
+
8
+ def delay(*args)
9
+ reload_setting_values
10
+ pass(*args)
11
+ end
12
+
13
+ def plan(*args)
14
+ reload_setting_values
15
+ pass(*args)
16
+ end
17
+
18
+ def run(*args)
19
+ reload_setting_values
20
+ pass(*args)
21
+ end
22
+
23
+ def finalize(*args)
24
+ reload_setting_values
25
+ pass(*args)
26
+ end
27
+
28
+ private
29
+
30
+ def reload_setting_values
31
+ ::Foreman.settings.load_values
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,80 @@
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 ObservableAction
26
+ module ClassMethods
27
+ def event_name_suffix(hook)
28
+ case hook
29
+ when :success
30
+ 'succeeded'
31
+ when :failure
32
+ 'failed'
33
+ else
34
+ hook
35
+ end
36
+ end
37
+
38
+ def event_names
39
+ [event_name_base + '_' + event_name_suffix(:success)]
40
+ end
41
+
42
+ def namespaced_event_names
43
+ event_names.map { |e| ::Foreman::Observable.event_name_for(e) }
44
+ end
45
+
46
+ def event_name_base
47
+ to_s.underscore.tr('/', '.')
48
+ end
49
+ end
50
+
51
+ def self.included(base)
52
+ base.extend ClassMethods
53
+ base.include ::Foreman::Observable
54
+ base.execution_plan_hooks.use :emit_event, :on => :success
55
+ end
56
+
57
+ def emit_event(execution_plan, hook = :success)
58
+ return unless root_action?
59
+
60
+ trigger_hook "#{self.class.event_name_base}_#{self.class.event_name_suffix(hook)}",
61
+ payload: event_payload(execution_plan)
62
+ end
63
+
64
+ def event_payload(_execution_plan)
65
+ { object: self }
66
+ end
67
+
68
+ extend ApipieDSL::Module
69
+
70
+ apipie :class, "An common ancestor action for observable actions" do
71
+ name 'Actions::ObservableAction'
72
+ refs 'Actions::ObservableAction'
73
+ sections only: %w[all webhooks]
74
+ property :task, object_of: 'Task', desc: 'Returns the task to which this action belongs'
75
+ end
76
+ class Jail < Safemode::Jail
77
+ allow :task
78
+ end
79
+ end
80
+ end
@@ -218,10 +218,8 @@ module Actions
218
218
  end
219
219
 
220
220
  def get_proxy_data(response)
221
- proxy_data = response['actions'].detect do |action|
222
- action['class'] == proxy_action_name || action.fetch('input', {})['proxy_operation_name'] == proxy_operation_name
223
- end
224
- proxy_data.fetch('output', {})
221
+ response['actions'].detect { |action| action.fetch('input', {})['task_id'] == task.id }
222
+ .try(:fetch, 'output', {}) || {}
225
223
  end
226
224
 
227
225
  def proxy_version(proxy)
@@ -3,12 +3,6 @@ module ForemanTasks
3
3
  module ActionSubject
4
4
  extend ActiveSupport::Concern
5
5
 
6
- module ClassMethods
7
- def available_locks
8
- [:read, :write]
9
- end
10
- end
11
-
12
6
  def action_input_key
13
7
  self.class.name.demodulize.underscore
14
8
  end
@@ -0,0 +1,60 @@
1
+ module ForemanTasks
2
+ class Link < ApplicationRecord
3
+ belongs_to :task
4
+
5
+ belongs_to :resource, polymorphic: true
6
+
7
+ scope :for_resource, ->(resource) { where(:resource => resource) }
8
+
9
+ validates :task_id, :resource_id, :resource_type, presence: true
10
+
11
+ class << self
12
+ # Assigns the resource to the task to easily track the task in context of
13
+ # the resource. This doesn't prevent other actions to lock the resource
14
+ # and should be used only for actions that tolerate other actions to be
15
+ # performed on the resource. Usually, this shouldn't needed to be done
16
+ # through the action directly, because the lock should assign it's parent
17
+ # objects to the action recursively (using +related_resources+ method in model
18
+ # objects)
19
+ def link!(resource, task)
20
+ link = build(resource, task)
21
+ link.save!
22
+ link
23
+ end
24
+
25
+ # Records the information about the user that triggered the task
26
+ def owner!(user, task)
27
+ link!(user, task)
28
+ end
29
+
30
+ def link_resource_and_related!(resource, task)
31
+ link!(resource, task)
32
+ build_related_links(resource, task).each(&:save!)
33
+ end
34
+
35
+ def build_related_links(resource, task)
36
+ related_resources(resource).map do |related_resource|
37
+ build(related_resource, task)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def build(resource, task)
44
+ find_or_initialize_by(task_id: task.id,
45
+ resource_type: resource.class.name,
46
+ resource_id: resource.id)
47
+ end
48
+
49
+ # recursively search for related resources of the resource (using
50
+ # the +related_resources+ method, avoiding the cycles
51
+ def related_resources(resource)
52
+ if resource.respond_to?(:all_related_resources)
53
+ resource.all_related_resources
54
+ else
55
+ []
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end