foreman-tasks 3.0.2 → 4.0.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_tasks/api/tasks_controller.rb +19 -7
  3. data/app/lib/actions/entry_action.rb +8 -4
  4. data/app/lib/actions/helpers/lock.rb +11 -5
  5. data/app/lib/actions/middleware/keep_current_request_id.rb +4 -1
  6. data/app/lib/actions/middleware/keep_current_user.rb +11 -1
  7. data/app/lib/actions/observable_action.rb +80 -0
  8. data/app/models/foreman_tasks/concerns/action_subject.rb +0 -6
  9. data/app/models/foreman_tasks/link.rb +60 -0
  10. data/app/models/foreman_tasks/lock.rb +30 -128
  11. data/app/models/foreman_tasks/recurring_logic.rb +3 -3
  12. data/app/models/foreman_tasks/task.rb +20 -7
  13. data/app/models/foreman_tasks/task/search.rb +7 -6
  14. data/app/views/foreman_tasks/api/locks/show.json.rabl +4 -0
  15. data/app/views/foreman_tasks/api/tasks/details.json.rabl +5 -3
  16. data/app/views/foreman_tasks/tasks/_lock_card.html.erb +10 -0
  17. data/app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb +1 -1
  18. data/app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb +1 -1
  19. data/db/migrate/20180927120509_add_user_id.foreman_tasks.rb +4 -2
  20. data/db/migrate/20181206123910_create_foreman_tasks_links.foreman_tasks.rb +26 -0
  21. data/db/migrate/20181206124952_migrate_non_exclusive_locks_to_links.foreman_tasks.rb +14 -0
  22. data/db/migrate/20181206131436_drop_old_locks.foreman_tasks.rb +20 -0
  23. data/db/migrate/20181206131627_make_locks_exclusive.foreman_tasks.rb +25 -0
  24. data/lib/foreman_tasks/cleaner.rb +10 -0
  25. data/lib/foreman_tasks/engine.rb +5 -2
  26. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  27. data/lib/foreman_tasks/version.rb +1 -1
  28. data/package.json +6 -6
  29. data/test/controllers/api/tasks_controller_test.rb +1 -1
  30. data/test/controllers/tasks_controller_test.rb +3 -3
  31. data/test/unit/actions/action_with_sub_plans_test.rb +5 -2
  32. data/test/unit/cleaner_test.rb +4 -4
  33. data/test/unit/locking_test.rb +85 -0
  34. data/test/unit/recurring_logic_test.rb +6 -0
  35. data/test/unit/task_test.rb +10 -0
  36. data/webpack/ForemanTasks/Components/TaskDetails/Components/Locks.js +2 -2
  37. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +3 -2
  38. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Locks.test.js.snap +4 -4
  39. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +2 -0
  40. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.js +4 -1
  41. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetails.scss +5 -1
  42. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +3 -0
  43. data/webpack/ForemanTasks/Components/TaskDetails/index.js +2 -0
  44. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +0 -8
  45. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +13 -3
  46. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +4 -3
  47. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +2 -2
  48. data/webpack/ForemanTasks/Components/TasksTable/formatters/__test__/__snapshots__/actionNameCellFormatter.test.js.snap +1 -0
  49. data/webpack/ForemanTasks/Components/TasksTable/formatters/actionNameCellFormatter.js +5 -1
  50. metadata +12 -4
  51. 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: 9bb66102c3571249534dc929d2f1515666d393d4d9f7987c098e529849bfeca7
4
+ data.tar.gz: 01c53850b401aaf7c50f4807a5947956bdfe0fa99582c778bd615fba922286f9
5
5
  SHA512:
6
- metadata.gz: 68b31fa072fbf39a1bd93184dfd6a0588270f897ba4a546d79a4f25be2e5815473eedf9a2975eb57c5344a024f7581e8427f39219af00cf6478ce0697b9fb403
7
- data.tar.gz: b531f627380fc77b93c930b6b22051b60e4adad8861cc137cbedfee30237e05fa70a1226b1651b4e7268bd618816a3256ba8b2993a27b5bf04771c3f73c776da
6
+ metadata.gz: bc272bf14fd55504672179272d3b26123b0d3c68cc6ddd97a8181bf9cfc1dd257e809501b9cb4d94cfe5730e8098c2fa0232fe26b35d227921520a66d976ae39
7
+ data.tar.gz: 4b264080240f04e2544af9d026026df2c70f126f4168cef1f74a21fd0d810c21b7696f8fc7520c107d4402ea5bfad0762c75c93d6679c841e406e4d0898c23bd
@@ -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
@@ -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