foreman-tasks 3.0.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_tests.yml +5 -3
  3. data/app/controllers/foreman_tasks/api/tasks_controller.rb +19 -7
  4. data/app/lib/actions/entry_action.rb +8 -4
  5. data/app/lib/actions/helpers/lock.rb +11 -5
  6. data/app/lib/actions/middleware/keep_current_request_id.rb +4 -1
  7. data/app/lib/actions/middleware/keep_current_user.rb +11 -1
  8. data/app/lib/actions/observable_action.rb +80 -0
  9. data/app/lib/actions/proxy_action.rb +2 -4
  10. data/app/models/foreman_tasks/concerns/action_subject.rb +0 -6
  11. data/app/models/foreman_tasks/link.rb +60 -0
  12. data/app/models/foreman_tasks/lock.rb +30 -128
  13. data/app/models/foreman_tasks/recurring_logic.rb +3 -3
  14. data/app/models/foreman_tasks/task.rb +20 -7
  15. data/app/models/foreman_tasks/task/search.rb +7 -6
  16. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  17. data/app/views/foreman_tasks/api/tasks/details.json.rabl +5 -3
  18. data/app/views/foreman_tasks/tasks/_lock_card.html.erb +10 -0
  19. data/app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb +1 -1
  20. data/app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb +1 -1
  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 +5 -2
  28. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  29. data/lib/foreman_tasks/version.rb +1 -1
  30. data/package.json +6 -6
  31. data/test/controllers/api/tasks_controller_test.rb +1 -1
  32. data/test/controllers/tasks_controller_test.rb +3 -3
  33. data/test/core/unit/runner_test.rb +4 -17
  34. data/test/factories/task_factory.rb +31 -4
  35. data/test/unit/actions/action_with_sub_plans_test.rb +5 -2
  36. data/test/unit/actions/proxy_action_test.rb +1 -1
  37. data/test/unit/cleaner_test.rb +4 -4
  38. data/test/unit/locking_test.rb +85 -0
  39. data/test/unit/recurring_logic_test.rb +6 -0
  40. data/test/unit/task_test.rb +20 -10
  41. data/test/unit/triggering_test.rb +2 -2
  42. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +2 -2
  43. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +3 -2
  44. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +4 -4
  45. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +2 -0
  46. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +4 -1
  47. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +5 -1
  48. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +3 -0
  49. data/webpack/ForemanTasks/Components/TaskDetails/index.js +2 -0
  50. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +0 -8
  51. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +13 -3
  52. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +4 -3
  53. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -2
  54. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionNameCellFormatter.test.js.snap +1 -0
  55. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionNameCellFormatter.js +5 -1
  56. metadata +12 -4
  57. data/test/unit/lock_test.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c51f734a6c321d30bd6c3aa93499d9924b93435cadfd48bf129452758ed88338
4
- data.tar.gz: ffd67ecf17550277bf7bcbc86f8fb98a1ac566c1d386350ff4ed1aa97ffad9e3
3
+ metadata.gz: 99321b6a250c3166881128f05a88feb20cf24cc14e6ccbf813e5cc1853f4447a
4
+ data.tar.gz: 99ce5e8f81c1dbbdf41a6980b0ff4cb98529928dfd0769676821e6c71f8e2302
5
5
  SHA512:
6
- metadata.gz: 68b31fa072fbf39a1bd93184dfd6a0588270f897ba4a546d79a4f25be2e5815473eedf9a2975eb57c5344a024f7581e8427f39219af00cf6478ce0697b9fb403
7
- data.tar.gz: b531f627380fc77b93c930b6b22051b60e4adad8861cc137cbedfee30237e05fa70a1226b1651b4e7268bd618816a3256ba8b2993a27b5bf04771c3f73c776da
6
+ metadata.gz: 7284f7dd3ff25480f57f697ebe57cbff5f6b865afd1e6792c710ea880bc23f0a6f261333c727a7917be9a30c9d1de54d7e4bfeebc9bc43635244fdd1ca8f5d79
7
+ data.tar.gz: 6580dd94504e0d9c5cf8f2d070bb42da3782feadbdba59ee9f1275e526b5b53b1cb34106163807f99eac09b59ba8991ea2f51bb224184007f7aee4bf30808553
@@ -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"
@@ -75,11 +75,16 @@ module ForemanTasks
75
75
  end
76
76
 
77
77
  api :POST, '/tasks/bulk_resume', N_('Resume all paused error tasks')
78
+ desc <<~DOC
79
+ Resumes all selected resumable tasks. If neither a search query nor an
80
+ explicit list of task IDs is provided, it tries to resume all tasks in
81
+ paused state with result error.
82
+ DOC
78
83
  param :search, String, :desc => N_('Resume tasks matching search string')
79
84
  param :task_ids, Array, :desc => N_('Resume specific tasks by ID')
80
85
  def bulk_resume
81
86
  if params[:search].nil? && params[:task_ids].nil?
82
- raise BadRequest, _('Please provide at least one of search or task_ids parameters in the request')
87
+ params[:search] = 'state = paused and result = error'
83
88
  end
84
89
  resumed = []
85
90
  failed = []
@@ -109,9 +114,14 @@ module ForemanTasks
109
114
  }
110
115
  end
111
116
 
112
- api :POST, '/tasks/bulk_cancel', N_('Cancel all cancellable tasks')
117
+ api :POST, '/tasks/bulk_cancel', N_('Cancel selected cancellable tasks')
118
+ desc <<~DOC
119
+ Cancels all selected cancellable tasks. Requires a search query or an
120
+ explicit list of task IDs to be provided.
121
+ DOC
113
122
  param :search, String, :desc => N_('Cancel tasks matching search string')
114
123
  param :task_ids, Array, :desc => N_('Cancel specific tasks by ID')
124
+ error :bad_request, 'Returned if neither search nor task_ids parameter is provided.'
115
125
  def bulk_cancel
116
126
  if params[:search].nil? && params[:task_ids].nil?
117
127
  raise BadRequest, _('Please provide at least one of search or task_ids parameters in the request')
@@ -130,9 +140,14 @@ module ForemanTasks
130
140
  }
131
141
  end
132
142
 
133
- api :POST, '/tasks/bulk_stop', N_('Stop all stoppable tasks')
143
+ api :POST, '/tasks/bulk_stop', N_('Stop selected stoppable tasks')
144
+ desc <<~DOC
145
+ Stops all selected tasks which are not already stopped. Requires a
146
+ search query or an explicit list of task IDs to be provided.
147
+ DOC
134
148
  param :search, String, :desc => N_('Stop tasks matching search string')
135
149
  param :task_ids, Array, :desc => N_('Stop specific tasks by ID')
150
+ error :bad_request, 'Returned if neither search nor task_ids parameter is provided.'
136
151
  def bulk_stop
137
152
  if params[:search].nil? && params[:task_ids].nil?
138
153
  raise BadRequest, _('Please provide at least one of search or task_ids parameters in the request')
@@ -238,10 +253,7 @@ module ForemanTasks
238
253
  if search_params[:user_id].blank?
239
254
  raise BadRequest, _('User search_params requires user_id to be specified')
240
255
  end
241
- scope.joins(:locks).where(foreman_tasks_locks:
242
- { name: ::ForemanTasks::Lock::OWNER_LOCK_NAME,
243
- resource_type: 'User',
244
- resource_id: search_params[:user_id] })
256
+ scope.search_for("user_id = #{search_params[:user_id]}")
245
257
  when 'resource'
246
258
  if search_params[:resource_type].blank? || search_params[:resource_id].blank?
247
259
  raise BadRequest,
@@ -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,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
@@ -1,14 +1,5 @@
1
1
  module ForemanTasks
2
2
  class Lock < ApplicationRecord
3
- LINK_LOCK_NAME = :link_resource
4
- OWNER_LOCK_NAME = :task_owner
5
-
6
- # not really intended to be created in database, but it's used for
7
- # explicitly stating that the all the locks for resource should be used
8
- ALL_LOCK_NAME = :all
9
-
10
- RESERVED_LOCK_NAMES = [LINK_LOCK_NAME, OWNER_LOCK_NAME, ALL_LOCK_NAME].freeze
11
-
12
3
  class LockConflict < StandardError
13
4
  attr_reader :required_lock, :conflicting_locks
14
5
  def initialize(required_lock, conflicting_locks)
@@ -16,7 +7,6 @@ module ForemanTasks
16
7
  | #{_('Required lock is already taken by other running tasks.')}
17
8
  | #{_('Please inspect their state, fix their errors and resume them.')}
18
9
  |
19
- | #{_('Required lock: %s') % required_lock.name}
20
10
  | #{_('Conflicts with tasks:')}
21
11
  HEADER
22
12
  url_helpers = Rails.application.routes.url_helpers
@@ -36,30 +26,28 @@ module ForemanTasks
36
26
 
37
27
  belongs_to :resource, polymorphic: true
38
28
 
39
- scope :active, -> { joins(:task).where('foreman_tasks_tasks.state != ?', :stopped) }
40
-
41
- validates :task_id, :name, :resource_id, :resource_type, presence: true
29
+ validates :task_id, :resource_id, :resource_type, presence: true
42
30
 
43
31
  validate do
44
- raise LockConflict.new(self, colliding_locks) unless available?
32
+ if (locks = colliding_locks).any?
33
+ raise LockConflict.new(self, locks)
34
+ end
45
35
  end
46
36
 
47
- # returns true if it's possible to aquire this kind of lock
48
- def available?
49
- !colliding_locks.exists?
50
- end
37
+ scope :for_resource, ->(resource) { where(:resource => resource) }
51
38
 
52
39
  # returns a scope of the locks colliding with this one
53
40
  def colliding_locks
54
41
  task_ids = task.self_and_parents.map(&:id)
55
- colliding_locks_scope = Lock.active.where(Lock.arel_table[:task_id].not_in(task_ids))
56
- colliding_locks_scope = colliding_locks_scope.where(name: name,
57
- resource_id: resource_id,
58
- resource_type: resource_type)
59
- unless exclusive?
60
- colliding_locks_scope = colliding_locks_scope.where(:exclusive => true)
61
- end
62
- colliding_locks_scope
42
+ colliding_locks_scope = Lock.where(Lock.arel_table[:task_id].not_in(task_ids))
43
+ colliding_locks_scope.where(resource_id: resource_id,
44
+ resource_type: resource_type)
45
+ end
46
+
47
+ def save!
48
+ super
49
+ rescue ActiveRecord::RecordNotUnique
50
+ raise LockConflict.new(self, colliding_locks)
63
51
  end
64
52
 
65
53
  class << self
@@ -67,116 +55,30 @@ module ForemanTasks
67
55
  # No other task related to the resource is not allowed (even not-locking ones)
68
56
  # A typical usecase is resource deletion, where it's good idea to make sure
69
57
  # nothing else related to the resource is really running.
70
- def exclusive!(resource, uuid)
71
- build_exclusive_locks(resource, uuid).each(&:save!)
72
- end
73
-
74
- def exclusive?(resource)
75
- build_exclusive_locks(resource).all?(&:available?)
76
- end
77
-
78
- # Locks the resource so that no other task can lock it while running.
79
- # Other not-locking tasks are tolerated.
80
- #
81
- # The lock names allow to specify what locks should be activated. It has to
82
- # be a subset of names defined in model's class available_locks method
83
- #
84
- # When no lock name is specified, the resource is locked against all the available
85
- # locks.
86
- #
87
- # It also looks at +related_resources+ method of the resource to calcuate all
88
- # the related resources (recursively) and links the task to them as well.
89
- def lock!(resource, uuid, *lock_names)
90
- build_locks(resource, lock_names, uuid).each(&:save!)
91
- end
92
-
93
- def lockable?(resource, uuid, *lock_names)
94
- build_locks(resource, lock_names, uuid).all?(&:available?)
58
+ # It also creates a Link between the task and the resource and between the task
59
+ # and all related resources.
60
+ def exclusive!(resource, task)
61
+ lock = build(resource, task)
62
+ lock.save!
63
+ ForemanTasks::Link.link_resource_and_related!(resource, task)
64
+ lock
95
65
  end
96
66
 
97
- def locked?(resource, uuid, *lock_names)
98
- !lockable?(resource, uuid, *lock_names)
67
+ # See #exclusive!
68
+ def lock!(resource, task, *_lock_names)
69
+ exclusive!(resource, task)
99
70
  end
100
71
 
101
- def colliding_locks(resource, uuid, *lock_names)
102
- build_locks(resource, lock_names, uuid)
103
- .inject([]) { |collisions, lock| collisions.concat lock.colliding_locks.to_a }
104
- end
105
-
106
- # Assigns the resource to the task to easily track the task in context of
107
- # the resource. This doesn't prevent other actions to lock the resource
108
- # and should be used only for actions that tolerate other actions to be
109
- # performed on the resource. Usually, this shouldn't needed to be done
110
- # through the action directly, because the lock should assign it's parrent
111
- # objects to the action srecursively (using +related_resources+ method in model
112
- # objects)
113
- def link!(resource, uuid)
114
- build_link(resource, uuid).save!
115
- end
116
-
117
- def link?(resource, uuid)
118
- build_link(resource, uuid).available?
72
+ def colliding_locks(resource, task, *_lock_names)
73
+ build(resource, task).colliding_locks.to_a
119
74
  end
120
75
 
121
76
  private
122
77
 
123
- def all_lock_names(resource, include_links = false)
124
- lock_names = []
125
- if resource.class.respond_to?(:available_locks) &&
126
- resource.class.available_locks.any?
127
- lock_names.concat(resource.class.available_locks)
128
- else
129
- raise "The resource #{resource.class.name} doesn't define any available lock"
130
- end
131
- if lock_names.any? { |lock_name| RESERVED_LOCK_NAMES.include?(lock_name) }
132
- raise "Lock name #{lock_name} is reserved"
133
- end
134
- lock_names.concat([LINK_LOCK_NAME, OWNER_LOCK_NAME]) if include_links
135
- lock_names
136
- end
137
-
138
- def build_exclusive_locks(resource, uuid = nil)
139
- build_locks(resource, all_lock_names(resource, true), uuid)
140
- end
141
-
142
- def build_locks(resource, lock_names, uuid = nil)
143
- locks = []
144
- if lock_names.empty? || lock_names == [:all]
145
- lock_names = all_lock_names(resource)
146
- end
147
- lock_names.map do |lock_name|
148
- locks << build(uuid, resource, lock_name, true)
149
- end
150
- locks.concat(build_links(resource, uuid))
151
- locks
152
- end
153
-
154
- def build_links(resource, uuid = nil)
155
- related_resources(resource).map do |related_resource|
156
- build_link(related_resource, uuid)
157
- end
158
- end
159
-
160
- def build_link(resource, uuid = nil)
161
- build(uuid, resource, LINK_LOCK_NAME, false)
162
- end
163
-
164
- def build(uuid, resource, lock_name, exclusive)
165
- new(task_id: uuid,
166
- name: lock_name,
167
- resource_type: resource.class.name,
168
- resource_id: resource.id,
169
- exclusive: !!exclusive)
170
- end
171
-
172
- # recursively search for related resources of the resource (using
173
- # the +related_resources+ method, avoiding the cycles
174
- def related_resources(resource)
175
- if resource.respond_to?(:all_related_resources)
176
- resource.all_related_resources
177
- else
178
- []
179
- end
78
+ def build(resource, task)
79
+ find_or_initialize_by(task_id: task.id,
80
+ resource_type: resource.class.name,
81
+ resource_id: resource.id)
180
82
  end
181
83
  end
182
84
  end