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.
@@ -10,7 +10,7 @@ module ForemanTasks
10
10
  part.to_s
11
11
  end
12
12
  end.join('; ')
13
- h(parts.join(" "))
13
+ parts.join(" ")
14
14
  end
15
15
  end
16
16
  end
@@ -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::EntryAction
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
- _("Bulk action")
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
- ForemanTasks.trigger(action_class, target, *input[:args])
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
- colliding_locks_scope = Lock.active.where(Lock.arel_table[:task_id].not_eq(task_id))
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 = 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.label ||= main_action.class.name
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>
@@ -87,8 +87,10 @@ module ForemanTasks
87
87
 
88
88
 
89
89
  rake_tasks do
90
- load File.expand_path('../tasks/dynflow.rake', __FILE__)
91
- load File.expand_path('../tasks/test.rake', __FILE__)
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
 
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = "0.6.12"
2
+ VERSION = "0.6.13"
3
3
  end
@@ -12,10 +12,8 @@ module ForemanTasks
12
12
  end
13
13
 
14
14
  it 'formats the task input properly' do
15
- expects(:h).with("user 'Anonymous Admin'")
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
- expects(:h).with(response)
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.12
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-01-30 00:00:00.000000000 Z
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.2
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.2
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