foreman-tasks 0.6.12 → 0.6.13
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.
- data/app/helpers/foreman_tasks/tasks_helper.rb +1 -1
- data/app/lib/actions/action_with_sub_plans.rb +30 -0
- data/app/lib/actions/bulk_action.rb +11 -116
- data/app/models/foreman_tasks/lock.rb +2 -1
- data/app/models/foreman_tasks/task.rb +8 -0
- data/app/models/foreman_tasks/task/dynflow_task.rb +11 -6
- data/app/views/foreman_tasks/tasks/_details.html.erb +14 -0
- data/app/views/foreman_tasks/tasks/index.html.erb +1 -4
- data/lib/foreman_tasks/engine.rb +4 -2
- data/lib/foreman_tasks/version.rb +1 -1
- data/test/helpers/foreman_tasks/tasks_helper_test.rb +4 -8
- data/test/unit/actions/action_with_sub_plans_test.rb +58 -0
- metadata +7 -5
- data/lib/foreman_tasks/tasks/test.rake +0 -22
@@ -0,0 +1,30 @@
|
|
1
|
+
module Actions
|
2
|
+
|
3
|
+
class Actions::ActionWithSubPlans < Actions::EntryAction
|
4
|
+
|
5
|
+
middleware.use Actions::Middleware::KeepCurrentUser
|
6
|
+
|
7
|
+
include Dynflow::Action::WithSubPlans
|
8
|
+
|
9
|
+
def plan(*args)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def humanized_output
|
14
|
+
return unless counts_set?
|
15
|
+
_("%{total} tasks, %{success} success, %{failed} fail") %
|
16
|
+
{ total: output[:total_count],
|
17
|
+
success: output[:success_count],
|
18
|
+
failed: output[:failed_count] }
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_progress
|
22
|
+
if counts_set? && output[:total_count] > 0
|
23
|
+
(output[:success_count] + output[:failed_count]).to_f / output[:total_count]
|
24
|
+
else
|
25
|
+
0.1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -1,14 +1,6 @@
|
|
1
1
|
module Actions
|
2
2
|
|
3
|
-
class BulkAction < Actions::
|
4
|
-
|
5
|
-
middleware.use Actions::Middleware::KeepCurrentUser
|
6
|
-
|
7
|
-
SubPlanFinished = Algebrick.type do
|
8
|
-
fields! :execution_plan_id => String,
|
9
|
-
:success => type { variants TrueClass, FalseClass }
|
10
|
-
end
|
11
|
-
|
3
|
+
class BulkAction < Actions::ActionWithSubPlans
|
12
4
|
# == Parameters:
|
13
5
|
# actions_class::
|
14
6
|
# Class of action to trigger on targets
|
@@ -25,7 +17,15 @@ module Actions
|
|
25
17
|
end
|
26
18
|
|
27
19
|
def humanized_name
|
28
|
-
|
20
|
+
if task.sub_tasks.first
|
21
|
+
task.sub_tasks.first.humanized[:action]
|
22
|
+
else
|
23
|
+
_("Bulk action")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def rescue_strategy
|
28
|
+
Dynflow::Action::Rescue::Skip
|
29
29
|
end
|
30
30
|
|
31
31
|
def humanized_input
|
@@ -36,32 +36,6 @@ module Actions
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def humanized_output
|
40
|
-
return unless counts_set?
|
41
|
-
_("%{total} tasks, %{success} success, %{failed} fail") %
|
42
|
-
{ total: output[:total_count],
|
43
|
-
success: output[:success_count],
|
44
|
-
failed: output[:failed_count] }
|
45
|
-
end
|
46
|
-
|
47
|
-
def run(event = nil)
|
48
|
-
case(event)
|
49
|
-
when nil
|
50
|
-
if output[:total_count]
|
51
|
-
resume
|
52
|
-
else
|
53
|
-
initiate_sub_plans
|
54
|
-
end
|
55
|
-
when SubPlanFinished
|
56
|
-
mark_as_done(event.execution_plan_id, event.success)
|
57
|
-
if done?
|
58
|
-
check_for_errors!
|
59
|
-
else
|
60
|
-
suspend
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
39
|
# @api override when the logic for the initiation of the subtasks
|
66
40
|
# is different from the default one
|
67
41
|
def create_sub_plans
|
@@ -70,85 +44,10 @@ module Actions
|
|
70
44
|
targets = target_class.where(:id => input[:target_ids])
|
71
45
|
|
72
46
|
targets.map do |target|
|
73
|
-
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def initiate_sub_plans
|
78
|
-
output.update(total_count: 0,
|
79
|
-
failed_count: 0,
|
80
|
-
success_count: 0)
|
81
|
-
|
82
|
-
planned, failed = create_sub_plans.partition(&:planned?)
|
83
|
-
|
84
|
-
sub_plan_ids = ((planned + failed).map(&:execution_plan_id))
|
85
|
-
set_parent_task_id(sub_plan_ids)
|
86
|
-
|
87
|
-
output[:total_count] = sub_plan_ids.size
|
88
|
-
output[:failed_count] = failed.size
|
89
|
-
|
90
|
-
if planned.any?
|
91
|
-
wait_for_sub_plans(planned)
|
92
|
-
else
|
93
|
-
check_for_errors!
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def resume
|
98
|
-
if task.sub_tasks.active.any?
|
99
|
-
fail _("Some sub tasks are still not finished")
|
47
|
+
trigger(action_class, target, *input[:args])
|
100
48
|
end
|
101
49
|
end
|
102
50
|
|
103
|
-
def rescue_strategy
|
104
|
-
Dynflow::Action::Rescue::Skip
|
105
|
-
end
|
106
|
-
|
107
|
-
def wait_for_sub_plans(plans)
|
108
|
-
suspend do |suspended_action|
|
109
|
-
plans.each do |plan|
|
110
|
-
plan.finished.do_then do |value|
|
111
|
-
suspended_action << SubPlanFinished[plan.execution_plan_id,
|
112
|
-
value.result == :success]
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def mark_as_done(plan_id, success)
|
119
|
-
if success
|
120
|
-
output[:success_count] += 1
|
121
|
-
else
|
122
|
-
output[:failed_count] += 1
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def done?
|
127
|
-
if counts_set?
|
128
|
-
output[:total_count] - output[:success_count] - output[:failed_count] <= 0
|
129
|
-
else
|
130
|
-
false
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def run_progress
|
135
|
-
if counts_set?
|
136
|
-
(output[:success_count] + output[:failed_count]).to_f / output[:total_count]
|
137
|
-
else
|
138
|
-
0.1
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def counts_set?
|
143
|
-
output[:total_count] && output[:success_count] && output[:failed_count]
|
144
|
-
end
|
145
|
-
|
146
|
-
def set_parent_task_id(sub_plan_ids)
|
147
|
-
ForemanTasks::Task::DynflowTask.
|
148
|
-
where(external_id: sub_plan_ids).
|
149
|
-
update_all(parent_task_id: task.id)
|
150
|
-
end
|
151
|
-
|
152
51
|
def check_targets!(targets)
|
153
52
|
if targets.empty?
|
154
53
|
fail _("Empty bulk action")
|
@@ -158,9 +57,5 @@ module Actions
|
|
158
57
|
end
|
159
58
|
end
|
160
59
|
|
161
|
-
def check_for_errors!
|
162
|
-
fail _("A sub task failed") if output[:failed_count] > 0
|
163
|
-
end
|
164
|
-
|
165
60
|
end
|
166
61
|
end
|
@@ -57,7 +57,8 @@ module ForemanTasks
|
|
57
57
|
|
58
58
|
# returns a scope of the locks colliding with this one
|
59
59
|
def colliding_locks
|
60
|
-
|
60
|
+
task_ids = task.self_and_parents.map(&:id)
|
61
|
+
colliding_locks_scope = Lock.active.where(Lock.arel_table[:task_id].not_in(task_ids))
|
61
62
|
colliding_locks_scope = colliding_locks_scope.where(name: name,
|
62
63
|
resource_id: resource_id,
|
63
64
|
resource_type: resource_type)
|
@@ -77,6 +77,14 @@ module ForemanTasks
|
|
77
77
|
self.state == 'paused'
|
78
78
|
end
|
79
79
|
|
80
|
+
def self_and_parents
|
81
|
+
[self].tap do |ret|
|
82
|
+
if parent_task
|
83
|
+
ret.concat(parent_task.self_and_parents)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
80
88
|
def self.search_by_generic_resource(key, operator, value)
|
81
89
|
key = "resource_type" if key.blank?
|
82
90
|
key_name = self.connection.quote_column_name(key.sub(/^.*\./,''))
|
@@ -6,12 +6,17 @@ module ForemanTasks
|
|
6
6
|
scope :for_action, ->(action_class) { where(label: action_class.name) }
|
7
7
|
|
8
8
|
def update_from_dynflow(data)
|
9
|
-
self.external_id
|
10
|
-
self.started_at
|
11
|
-
self.ended_at
|
12
|
-
self.state
|
13
|
-
self.result
|
14
|
-
self.
|
9
|
+
self.external_id = data[:id]
|
10
|
+
self.started_at = data[:started_at]
|
11
|
+
self.ended_at = data[:ended_at]
|
12
|
+
self.state = data[:state].to_s
|
13
|
+
self.result = data[:result].to_s
|
14
|
+
self.parent_task_id ||= begin
|
15
|
+
if main_action.caller_execution_plan_id
|
16
|
+
DynflowTask.find_by_external_id!(main_action.caller_execution_plan_id).id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
self.label ||= main_action.class.name
|
15
20
|
self.save!
|
16
21
|
end
|
17
22
|
|
@@ -124,6 +124,8 @@
|
|
124
124
|
'progress-bar-success'
|
125
125
|
when 'error'
|
126
126
|
'progress-bar-danger'
|
127
|
+
when 'warning'
|
128
|
+
'progress-bar-warning'
|
127
129
|
else
|
128
130
|
nil
|
129
131
|
end
|
@@ -143,9 +145,21 @@
|
|
143
145
|
<%= progress.round %>%
|
144
146
|
</div>
|
145
147
|
</div>
|
148
|
+
|
149
|
+
<% unless @task.humanized[:output].blank? %>
|
146
150
|
<div>
|
147
151
|
<span class="param-name"><%= _("Output") %>:</span>
|
148
152
|
<span class="param-value">
|
149
153
|
<pre><%= @task.humanized[:output] %></pre>
|
150
154
|
</span>
|
151
155
|
</div>
|
156
|
+
<% end %>
|
157
|
+
|
158
|
+
<% unless @task.humanized[:errors].blank? %>
|
159
|
+
<div>
|
160
|
+
<span class="param-name"><%= _("Errors") %>:</span>
|
161
|
+
<span class="param-value">
|
162
|
+
<pre><%= @task.humanized[:errors].join("\n") %></pre>
|
163
|
+
</span>
|
164
|
+
</div>
|
165
|
+
<% end %>
|
@@ -2,9 +2,6 @@
|
|
2
2
|
<% title_actions help_path%>
|
3
3
|
|
4
4
|
<style>
|
5
|
-
.task-id {
|
6
|
-
white-space: nowrap;
|
7
|
-
}
|
8
5
|
.param-name {
|
9
6
|
display: inline-block;
|
10
7
|
width: 10em;
|
@@ -37,7 +34,7 @@ $(document).on('click', ".table-two-pane td.two-pane-link", function(e) {
|
|
37
34
|
<% for task in @tasks %>
|
38
35
|
<tr>
|
39
36
|
<td class="task-id two-pane-link">
|
40
|
-
<%= link_to_if_authorized(format_task_input(task, true),
|
37
|
+
<%= link_to_if_authorized(trunc_with_tooltip(format_task_input(task, true), 80),
|
41
38
|
hash_for_foreman_tasks_task_path(:id => task)) %>
|
42
39
|
</td>
|
43
40
|
<td><%= task.state %></td>
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -87,8 +87,10 @@ module ForemanTasks
|
|
87
87
|
|
88
88
|
|
89
89
|
rake_tasks do
|
90
|
-
|
91
|
-
|
90
|
+
%w[dynflow.rake test.rake].each do |rake_file|
|
91
|
+
full_path = File.expand_path("../tasks/#{rake_file}", __FILE__)
|
92
|
+
load full_path if File.exists?(full_path)
|
93
|
+
end
|
92
94
|
end
|
93
95
|
end
|
94
96
|
|
@@ -12,10 +12,8 @@ module ForemanTasks
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'formats the task input properly' do
|
15
|
-
|
16
|
-
format_task_input(@task)
|
17
|
-
expects(:h).with("Create user 'Anonymous Admin'")
|
18
|
-
format_task_input(@task, true)
|
15
|
+
format_task_input(@task).must_equal("user 'Anonymous Admin'")
|
16
|
+
format_task_input(@task, true).must_equal("Create user 'Anonymous Admin'")
|
19
17
|
end
|
20
18
|
|
21
19
|
end
|
@@ -38,10 +36,8 @@ module ForemanTasks
|
|
38
36
|
|
39
37
|
it 'formats the task input properly' do
|
40
38
|
response = "product 'product-2'; organization 'test-0'"
|
41
|
-
|
42
|
-
format_task_input(@task)
|
43
|
-
expects(:h).with("Create #{response}")
|
44
|
-
format_task_input(@task, true)
|
39
|
+
format_task_input(@task).must_equal(response)
|
40
|
+
format_task_input(@task, true).must_equal("Create #{response}")
|
45
41
|
end
|
46
42
|
end
|
47
43
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "foreman_tasks_test_helper"
|
2
|
+
|
3
|
+
module ForemanTasks
|
4
|
+
class ActionWithSubPlansTest < ActiveSupport::TestCase
|
5
|
+
self.use_transactional_fixtures = false
|
6
|
+
|
7
|
+
before do
|
8
|
+
User.current = User.where(:login => 'apiadmin').first
|
9
|
+
end
|
10
|
+
|
11
|
+
# to be able to use the locking
|
12
|
+
class ::User < User.parent
|
13
|
+
include ForemanTasks::Concerns::ActionSubject
|
14
|
+
end
|
15
|
+
|
16
|
+
class ParentAction < Actions::ActionWithSubPlans
|
17
|
+
def plan(user)
|
18
|
+
action_subject(user)
|
19
|
+
plan_self(user_id: user.id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_sub_plans
|
23
|
+
user = User.find(input[:user_id])
|
24
|
+
trigger(ChildAction, user)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ChildAction < Actions::EntryAction
|
29
|
+
def plan(user)
|
30
|
+
action_subject(user)
|
31
|
+
plan_self(user_id: user.id)
|
32
|
+
end
|
33
|
+
def run
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe Actions::ActionWithSubPlans do
|
38
|
+
let(:task) do
|
39
|
+
user = FactoryGirl.create(:user)
|
40
|
+
triggered = ForemanTasks.trigger(ParentAction, user)
|
41
|
+
raise triggered.error if triggered.respond_to?(:error)
|
42
|
+
triggered.finished.wait(2)
|
43
|
+
ForemanTasks::Task.find_by_external_id(triggered.id)
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "the sub-plan stores the information about its parent" do
|
47
|
+
task.sub_tasks.size.must_equal 1
|
48
|
+
task.sub_tasks.first.label.must_equal ChildAction.name
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "the locks of the sub-plan don't colide with the locks of its parent" do
|
52
|
+
child_task = task.sub_tasks.first
|
53
|
+
assert(child_task.locks.any? { |lock| lock.name == 'write' }, "it's locks don't conflict with parent's")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.13
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dynflow
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.7.
|
21
|
+
version: 0.7.7
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.7.
|
29
|
+
version: 0.7.7
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: sequel
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,6 +116,7 @@ files:
|
|
116
116
|
- app/lib/actions/foreman/architecture/destroy.rb
|
117
117
|
- app/lib/actions/foreman/host/import_facts.rb
|
118
118
|
- app/lib/actions/entry_action.rb
|
119
|
+
- app/lib/actions/action_with_sub_plans.rb
|
119
120
|
- app/lib/actions/base.rb
|
120
121
|
- app/lib/actions/middleware/keep_current_user.rb
|
121
122
|
- app/views/foreman_tasks/tasks/_details.html.erb
|
@@ -140,7 +141,6 @@ files:
|
|
140
141
|
- lib/tasks/gettext.rake
|
141
142
|
- lib/foreman-tasks.rb
|
142
143
|
- lib/foreman_tasks/tasks/dynflow.rake
|
143
|
-
- lib/foreman_tasks/tasks/test.rake
|
144
144
|
- lib/foreman_tasks/version.rb
|
145
145
|
- lib/foreman_tasks/triggers.rb
|
146
146
|
- lib/foreman_tasks/dynflow.rb
|
@@ -160,6 +160,7 @@ files:
|
|
160
160
|
- test/controllers/api/tasks_controller_test.rb
|
161
161
|
- test/factories/task_factory.rb
|
162
162
|
- test/unit/dynflow_console_authorizer_test.rb
|
163
|
+
- test/unit/actions/action_with_sub_plans_test.rb
|
163
164
|
- test/unit/task_test.rb
|
164
165
|
homepage: https://github.com/theforeman/foreman-tasks
|
165
166
|
licenses: []
|
@@ -192,5 +193,6 @@ test_files:
|
|
192
193
|
- test/controllers/api/tasks_controller_test.rb
|
193
194
|
- test/factories/task_factory.rb
|
194
195
|
- test/unit/dynflow_console_authorizer_test.rb
|
196
|
+
- test/unit/actions/action_with_sub_plans_test.rb
|
195
197
|
- test/unit/task_test.rb
|
196
198
|
has_rdoc:
|
@@ -1,22 +0,0 @@
|
|
1
|
-
namespace :test do
|
2
|
-
task :foreman_tasks => 'db:test:prepare' do
|
3
|
-
test_task = Rake::TestTask.new('foreman_tasks_test_task') do |t|
|
4
|
-
t.libs << ["test", "#{ForemanTasks::Engine.root}/test"]
|
5
|
-
t.test_files = ["#{ForemanTasks::Engine.root}/test/**/*_test.rb"]
|
6
|
-
t.verbose = true
|
7
|
-
end
|
8
|
-
|
9
|
-
Rake::Task[test_task.name].invoke
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
Rake::Task[:test].enhance do
|
14
|
-
Rake::Task['test:foreman_tasks'].invoke
|
15
|
-
end
|
16
|
-
|
17
|
-
load 'tasks/jenkins.rake'
|
18
|
-
if Rake::Task.task_defined?(:'jenkins:unit')
|
19
|
-
Rake::Task["jenkins:unit"].enhance do
|
20
|
-
Rake::Task['test:foreman_tasks'].invoke
|
21
|
-
end
|
22
|
-
end
|