foreman-tasks 0.6.12 → 0.6.13
Sign up to get free protection for your applications and to get access to all the features.
- 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
|