foreman-tasks 0.6.13 → 0.6.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Njc1OWI4YzE5OWI1ZmNhMmI3YjIwMzc3NDRjZjVjMTViMzNjZjNhOQ==
5
+ data.tar.gz: !binary |-
6
+ ZTMzODQyZWM1MDljMmNmZDcxMGM0OTc5MDExOTVjOGE3YWNhMmMyNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ N2M3NTQzNzFiMGVlN2NiZDE5NTU2YmM1ZmYzMWU2YWQwYWRmYmFmNzUxZDMz
10
+ MDkzYzc3NmFkMjYwZGE3MjgzMTI4ZWZlMjM1NzU4NjkzY2Q4ODU3NTAxNDM5
11
+ MTZmY2Q4YzU3MWM5MjNmNGU3Mzg5NmI5NzdhZTgwOWQzNzk0NWQ=
12
+ data.tar.gz: !binary |-
13
+ YmUzZTYyNjUyOTFkZjFiZWVlZDJmNGFmMjEyNTYwYmJmM2IwMGViNGMzMWQ4
14
+ Y2VkMzM2YzdhNTg2YmYzNTk3NDNhODg1Yzk2ODU2ZmY5NDE2MjU2NjU3YjE1
15
+ YzUyYmU1OTZhZGYzYmJiZThmMzAwOWQ2NzUzMjEwYmQwNDNlYTI=
@@ -28,6 +28,11 @@ module ForemanTasks
28
28
 
29
29
  before_filter :find_resource, :only => [:show]
30
30
 
31
+ api :GET, "/tasks/summary", "Show task summary"
32
+ def summary
33
+ render :json => ForemanTasks::Task::Summarizer.new.summarize_by_status
34
+ end
35
+
31
36
  api :GET, "/tasks/:id", "Show task details"
32
37
  param :id, :identifier, desc: "UUID of the task"
33
38
  def show
@@ -76,6 +81,83 @@ module ForemanTasks
76
81
  render :json => ret
77
82
  end
78
83
 
84
+ api :POST, '/tasks/bulk_resume', N_('Resume all paused error tasks')
85
+ param :search, String, :desc => N_('Resume tasks matching search string')
86
+ param :task_ids, Array, :desc => N_('Resume specific tasks by id')
87
+ def bulk_resume
88
+ scope = resource_scope
89
+ scope = scope.search_for(params[:search]) if params[:search]
90
+ scope = scope.select('DISTINCT foreman_tasks_tasks.*')
91
+ if params[:search].nil? && params[:task_ids].nil?
92
+ scope = scope.where(:state => :paused)
93
+ scope = scope.where(:result => :error)
94
+ end
95
+ scope = scope.where(:id => params[:task_ids]) if params[:task_ids]
96
+
97
+ resumed = []
98
+ failed = []
99
+ skipped = []
100
+ scope.each do |task|
101
+ if task.resumable?
102
+ begin
103
+ ForemanTasks.dynflow.world.execute(task.execution_plan.id)
104
+ resumed << task_hash(task)
105
+ rescue RuntimeError => e
106
+ failed << task_hash(task)
107
+ end
108
+ else
109
+ skipped << task_hash(task)
110
+ end
111
+ end
112
+
113
+ render :json => {
114
+ total: resumed.length + failed.length + skipped.length,
115
+ resumed: resumed,
116
+ failed: failed,
117
+ skipped: skipped
118
+ }
119
+ end
120
+
121
+ api :GET, '/tasks', N_("List tasks")
122
+ param :search, String, :desc => N_("Search string")
123
+ param :page, :number, :desc => N_("Page number, starting at 1")
124
+ param :per_page, :number, :desc => N_("Number of results per page to return")
125
+ param :order, String, :desc => N_("Sort field and order, eg. 'name DESC'")
126
+ param :sort, Hash, :desc => N_("Hash version of 'order' param") do
127
+ param :by, String, :desc => N_("Field to sort the results on")
128
+ param :order, String, :desc => N_("How to order the sorted results (e.g. ASC for ascending)")
129
+ end
130
+ def index
131
+ scope =resource_scope.search_for(params[:search]).select('DISTINCT foreman_tasks_tasks.*')
132
+ total = scope.count
133
+
134
+ ordering_params = {
135
+ sort_by: params[:sort_by] || 'started_at',
136
+ sort_order: params[:sort_order] || 'DESC'
137
+ }
138
+ scope = ordering_scope(scope, ordering_params)
139
+
140
+
141
+ pagination_params = {
142
+ page: params[:page] || 1,
143
+ per_page: params[:per_page] || 20
144
+ }
145
+ scope = pagination_scope(scope, pagination_params)
146
+ results = scope.map { |task| task_hash(task) }
147
+
148
+ render :json => {
149
+ total: total,
150
+ subtotal: results.count,
151
+ page: pagination_params[:page],
152
+ per_page: pagination_params[:per_page],
153
+ sort: {
154
+ by: ordering_params[:sort_by],
155
+ order: ordering_params[:sort_order]
156
+ },
157
+ results: results
158
+ }
159
+ end
160
+
79
161
  private
80
162
 
81
163
  def search_tasks(search_params)
@@ -127,7 +209,8 @@ module ForemanTasks
127
209
  end
128
210
 
129
211
  def action_types_scope(scope, search_params)
130
- if action_types = search_params[:action_types]
212
+ action_types = search_params[:action_types]
213
+ if action_types
131
214
  scope.for_action_types(action_types)
132
215
  else
133
216
  scope
@@ -137,21 +220,23 @@ module ForemanTasks
137
220
  def pagination_scope(scope, search_params)
138
221
  page = search_params[:page] || 1
139
222
  per_page = search_params[:per_page] || 10
140
- scope = scope.limit(per_page).offset((page - 1) * per_page)
223
+ scope = scope.limit(per_page).offset((page.to_i - 1) * per_page.to_i)
141
224
  end
142
225
 
143
- def ordering_scope(scope, search_params)
144
- scope.order('started_at DESC')
226
+ def ordering_scope(scope, ordering_params)
227
+ sort_by = ordering_params[:sort_by] || 'started_at'
228
+ sort_order = ordering_params[:sort_order] || 'DESC'
229
+ scope.order("#{sort_by} #{sort_order}")
145
230
  end
146
231
 
147
232
  def task_hash(task)
148
- return @tasks[task.id] if @tasks[task.id]
233
+ return @tasks[task.id] if @tasks && @tasks[task.id]
149
234
  task_hash = Rabl.render(
150
235
  task, 'show',
151
236
  view_path: "#{ForemanTasks::Engine.root}/app/views/foreman_tasks/api/tasks",
152
237
  format: :hash,
153
238
  scope: self)
154
- @tasks[task.id] = task_hash
239
+ @tasks[task.id] = task_hash if @tasks
155
240
  return task_hash
156
241
  end
157
242
 
@@ -165,6 +250,8 @@ module ForemanTasks
165
250
  case params[:action]
166
251
  when 'bulk_search'
167
252
  :view
253
+ when 'bulk_resume'
254
+ :edit
168
255
  else
169
256
  super
170
257
  end
@@ -2,6 +2,8 @@ module ForemanTasks
2
2
  class TasksController < ::ApplicationController
3
3
  include Foreman::Controller::AutoCompleteSearch
4
4
 
5
+ before_filter :restrict_dangerous_actions, :only => [:unlock, :force_unlock]
6
+
5
7
  def show
6
8
  @task = find_resource
7
9
  end
@@ -62,6 +64,10 @@ module ForemanTasks
62
64
 
63
65
  private
64
66
 
67
+ def restrict_dangerous_actions
68
+ render_403 unless Setting['dynflow_allow_dangerous_actions']
69
+ end
70
+
65
71
  def controller_permission
66
72
  'foreman_tasks'
67
73
  end
@@ -16,6 +16,10 @@ module Actions
16
16
  :args => args)
17
17
  end
18
18
 
19
+ def run(event = nil)
20
+ super unless event == Dynflow::Action::Skip
21
+ end
22
+
19
23
  def humanized_name
20
24
  if task.sub_tasks.first
21
25
  task.sub_tasks.first.humanized[:action]
@@ -17,7 +17,9 @@ module ForemanTasks
17
17
  end
18
18
  end
19
19
  self.label ||= main_action.class.name
20
+ changes = self.changes
20
21
  self.save!
22
+ return changes
21
23
  end
22
24
 
23
25
  def resumable?
@@ -80,5 +82,21 @@ module ForemanTasks
80
82
  end
81
83
  end
82
84
  end
85
+
86
+ def self.consistency_check
87
+ fixed_count = 0
88
+ self.running.each do |task|
89
+ begin
90
+ changes = task.update_from_dynflow(task.execution_plan.to_hash)
91
+ unless changes.empty?
92
+ fixed_count += 1
93
+ Rails.logger.warn("Task %s updated at consistency check: %s" % [task.id, changes.inspect])
94
+ end
95
+ rescue => e
96
+ Rails.logger.warn("Failed at consistency check for task %s: %s\n %s" % [task.id, e.message, e.backtrace.join("\n")])
97
+ end
98
+ end
99
+ return fixed_count
100
+ end
83
101
  end
84
102
  end
@@ -0,0 +1,17 @@
1
+ module ForemanTasks
2
+ class Task::StatusExplicator
3
+ ANY = 1
4
+ ERRONEOUS_STATUSES = [
5
+ { :state => 'paused', :result => ANY },
6
+ { :state => ANY, :result => 'error'},
7
+ { :state => ANY, :result => 'warning'}
8
+ ]
9
+ def is_erroneous(task)
10
+ remainder = ERRONEOUS_STATUSES.select do |status|
11
+ (status[:state] == ANY || status[:state] == task.state) &&
12
+ (status[:result] == ANY || status[:result] == task.result)
13
+ end
14
+ !remainder.empty?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module ForemanTasks
2
+ class Task::Summarizer
3
+ def summarize_by_status(since=nil)
4
+ result = ::ForemanTasks::Task.select('count(state), state, result').group(:state, :result).order(:state)
5
+ result.where('started_at > ?', since) if since
6
+ result
7
+ end
8
+
9
+ def latest_tasks_in_errors_warning(limit=5)
10
+ ::ForemanTasks::Task.where('result in (?)', ['error', 'warning']).order('started_at DESC').limit(limit)
11
+ end
12
+ end
13
+ end
@@ -6,6 +6,7 @@ class Setting::ForemanTasks < Setting
6
6
 
7
7
  self.transaction do
8
8
  [
9
+ self.set('dynflow_allow_dangerous_actions', N_("Allow unlocking actions which can have dangerous consequences."), false),
9
10
  self.set('dynflow_enable_console', N_("Enable the dynflow console (/foreman_tasks/dynflow) for debugging"), true),
10
11
  self.set('dynflow_console_require_auth', N_("Require user to be authenticated as user with admin rights when accessing dynflow console"), true)
11
12
  ].each { |s| self.create! s.update(:category => "Setting::ForemanTasks")}
@@ -9,18 +9,19 @@
9
9
  resume_foreman_tasks_task_path(@task),
10
10
  class: ['btn', 'btn-sm', 'btn-primary', ('disabled' unless @task.resumable?)].compact,
11
11
  method: :post) %>
12
+ <% if Setting['dynflow_allow_dangerous_actions'] %>
13
+ <%= link_to(_('Unlock'),
14
+ '',
15
+ class: ['btn', 'btn-sm', 'btn-warning', 'reload-button-stop', ('disabled' unless @task.state == 'paused')].compact,
16
+ :'data-toggle' => "modal",
17
+ :'data-target' => "#unlock_modal") %>
12
18
 
13
- <%= link_to(_('Unlock'),
14
- '',
15
- class: ['btn', 'btn-sm', 'btn-warning', 'reload-button-stop', ('disabled' unless @task.state == 'paused')].compact,
16
- :'data-toggle' => "modal",
17
- :'data-target' => "#unlock_modal") %>
18
-
19
- <%= link_to(_('Force Unlock'),
20
- '',
21
- class: ['btn', 'btn-sm', 'btn-danger', 'reload-button-stop', ('disabled' if @task.state == 'stopped')].compact,
22
- :'data-toggle' => "modal",
23
- :'data-target' => "#force_unlock_modal") %>
19
+ <%= link_to(_('Force Unlock'),
20
+ '',
21
+ class: ['btn', 'btn-sm', 'btn-danger', 'reload-button-stop', ('disabled' if @task.state == 'stopped')].compact,
22
+ :'data-toggle' => "modal",
23
+ :'data-target' => "#force_unlock_modal") %>
24
+ <% end %>
24
25
  <% end %>
25
26
  </p>
26
27
 
@@ -35,11 +36,15 @@
35
36
  </h2>
36
37
  </div>
37
38
  <div class="modal-body">
38
- <%= _("This will unlock the resources that the task is running against. Please note that this might lead to inconsistent state and should be used with caution, after making sure that the task can't be resumed") %>
39
+ <%= _("This will unlock the resources that the task is running against. Please note that this might lead to inconsistent state and should be used with caution, after making sure that the task can't be resumed.") %>
40
+ <div>
41
+ <input class="disable-unlock" type="checkbox"/>
42
+ <%= _("I understand that this may cause harm and have working database backups of all backend services.") %>
43
+ </div>
39
44
  </div>
40
45
  <div class="modal-footer">
41
46
  <button type="button" class="btn btn-default" data-dismiss="modal"><%= _("Cancel") %></button>
42
- <%= link_to(_("Unlock"), unlock_foreman_tasks_task_path(@task), method: :post, class: 'btn btn-warning') %>
47
+ <%= link_to(_("Unlock"), unlock_foreman_tasks_task_path(@task), method: :post, class: 'btn btn-warning modal-submit disabled') %>
43
48
  </div>
44
49
  </div>
45
50
  </div>
@@ -57,10 +62,14 @@
57
62
  </div>
58
63
  <div class="modal-body">
59
64
  <%= _("Resources will be unlocked and will not prevent other tasks from being run. As the task might be still running, it should be avoided to use this unless you are really sure the task got stuck") %>
65
+ <div>
66
+ <input class="disable-unlock" type="checkbox"/>
67
+ <%= _("I understand that this may cause harm and have working database backups of all backend services.") %>
68
+ </div>
60
69
  </div>
61
70
  <div class="modal-footer">
62
71
  <button type="button" class="btn btn-default" data-dismiss="modal"><%= _("Cancel") %></button>
63
- <%= link_to(_("Force Unlock"), force_unlock_foreman_tasks_task_path(@task), method: :post, class: 'btn btn-danger') %>
72
+ <%= link_to(_("Force Unlock"), force_unlock_foreman_tasks_task_path(@task), method: :post, class: 'btn btn-danger modal-submit disabled') %>
64
73
  </div>
65
74
  </div>
66
75
  </div>
@@ -162,4 +171,4 @@
162
171
  <pre><%= @task.humanized[:errors].join("\n") %></pre>
163
172
  </span>
164
173
  </div>
165
- <% end %>
174
+ <% end %>
@@ -5,7 +5,7 @@
5
5
  <%= _("No running steps") %>
6
6
  <% else %>
7
7
  <% running_steps.each do |step| %>
8
- <% action = step.action(@task.execution_plan) %>
8
+ <% action = ForemanTasks.dynflow.world.persistence.load_action(step) %>
9
9
  <div class="alert alert-warning">
10
10
  <p>
11
11
  <% if @task.cancellable_action?(action) %>
@@ -20,6 +20,10 @@
20
20
  <span class="param-value">
21
21
  <pre><%= action.class %></pre>
22
22
  </span>
23
+ <p>
24
+ <span class="param-name"><%= _("State") %>:</span>
25
+ <span class="param-value"><%= action.humanized_state %></span>
26
+ </p>
23
27
  <span class="param-name"><%= _("Input") %>:</span>
24
28
  <span class="param-value">
25
29
  <pre><%= action.input.pretty_inspect %></pre>
@@ -0,0 +1,15 @@
1
+ <h4><%= link_to _("Latest Warning/Error Tasks "), foreman_tasks_tasks_path(:order=>'started_at DESC') %></h4>
2
+ <table class="table table-striped">
3
+ <tr>
4
+ <th><%= _("Name") %></th>
5
+ <th><%= _("State") %></th>
6
+ <th><%= _("Result") %></th>
7
+ </tr>
8
+ <% ForemanTasks::Task::Summarizer.new.latest_tasks_in_errors_warning.each do |task| %>
9
+ <tr class="<%= ForemanTasks::Task::StatusExplicator.new.is_erroneous(task) ? 'text-danger' : '' %>">
10
+ <td><%= link_to task.humanized[:action], defined?(main_app) ? main_app.foreman_tasks_task_path(task.id) : foreman_tasks_task_path(task.id) %></td>
11
+ <td><%= task.state %></td>
12
+ <td><%= task.result %></td>
13
+ </tr>
14
+ <% end %>
15
+ </table>
@@ -0,0 +1,15 @@
1
+ <h4 class="header"><%=_("Task Status")%></h4>
2
+ <table class="table table-striped">
3
+ <tr>
4
+ <th><%= _("State") %></th>
5
+ <th><%= _("Result") %></th>
6
+ <th><%= _("No. of Tasks") %></th>
7
+ </tr>
8
+ <% ForemanTasks::Task::Summarizer.new.summarize_by_status.each do |result| %>
9
+ <tr class="<%= ForemanTasks::Task::StatusExplicator.new.is_erroneous(result) ? 'text-danger' : '' %>">
10
+ <td><%= result.state %></td>
11
+ <td><%= result.result %></td>
12
+ <td><%= link_to result.count, main_app.foreman_tasks_tasks_path(:search => "state=#{result.state}&result=#{result.result}") %></td>
13
+ </tr>
14
+ <% end %>
15
+ </table>
@@ -41,6 +41,22 @@
41
41
  };
42
42
 
43
43
  $(document).ready(function () {
44
+ $('.modal-submit').click(function(e){
45
+ if($(this).hasClass('disabled')){
46
+ e.preventDefault();
47
+ }
48
+ });
49
+ $('.disable-unlock').click(function() {
50
+ var button = $(this).parents('.modal').find('.modal-submit');
51
+
52
+ if($(this).is(':checked')){
53
+ button.removeClass('disabled')
54
+ }
55
+ else{
56
+ button.addClass('disabled')
57
+ }
58
+ });
59
+
44
60
  $('.reload-button').click(function (event) {
45
61
  taskProgressReloader.toggle();
46
62
  event.preventDefault();
data/config/routes.rb CHANGED
@@ -14,8 +14,12 @@ Foreman::Application.routes.draw do
14
14
  end
15
15
 
16
16
  namespace :api do
17
- resources :tasks, :only => [:show] do
18
- post :bulk_search, :on => :collection
17
+ resources :tasks, :only => [:show, :index] do
18
+ collection do
19
+ post :bulk_search
20
+ post :bulk_resume
21
+ get :summary
22
+ end
19
23
  end
20
24
  end
21
25
 
data/lib/foreman_tasks.rb CHANGED
@@ -4,6 +4,7 @@ require 'foreman_tasks/engine'
4
4
  require 'foreman_tasks/dynflow'
5
5
  require 'foreman_tasks/triggers'
6
6
  require 'foreman_tasks/authorizer_ext'
7
+ require 'foreman_tasks/widget_manager'
7
8
 
8
9
  module ForemanTasks
9
10
  extend Algebrick::TypeCheck
@@ -49,6 +49,8 @@ module ForemanTasks
49
49
  # of executors
50
50
  world.consistency_check
51
51
  world.execute_planned_execution_plans
52
+
53
+ Task::DynflowTask.consistency_check
52
54
  end
53
55
  end
54
56
  end
@@ -17,8 +17,9 @@ module ForemanTasks
17
17
 
18
18
  security_block :foreman_tasks do |map|
19
19
  permission :view_foreman_tasks, {:'foreman_tasks/tasks' => [:auto_complete_search, :sub_tasks, :index, :show],
20
- :'foreman_tasks/api/tasks' => [:bulk_search, :show] }, :resource_type => ForemanTasks::Task.name
21
- permission :edit_foreman_tasks, {:'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step]}, :resource_type => ForemanTasks::Task.name
20
+ :'foreman_tasks/api/tasks' => [:bulk_search, :show, :index, :summary] }, :resource_type => ForemanTasks::Task.name
21
+ permission :edit_foreman_tasks, {:'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step],
22
+ :'foreman_tasks/api/tasks' => [:bulk_resume]}, :resource_type => ForemanTasks::Task.name
22
23
  end
23
24
 
24
25
  role "Tasks Manager", [:view_foreman_tasks, :edit_foreman_tasks]
@@ -79,6 +80,10 @@ module ForemanTasks
79
80
  end
80
81
  end
81
82
 
83
+ initializer "foreman_tasks.initialize_dynflow", :after => 'foreman_tasks.initializer' do
84
+ ::ForemanTasks::WidgetManager.register_widgets
85
+ end
86
+
82
87
  config.to_prepare do
83
88
  ForemanTasks.dynflow.eager_load_actions!
84
89
 
@@ -87,7 +92,7 @@ module ForemanTasks
87
92
 
88
93
 
89
94
  rake_tasks do
90
- %w[dynflow.rake test.rake].each do |rake_file|
95
+ %w[dynflow.rake test.rake export_tasks.rake].each do |rake_file|
91
96
  full_path = File.expand_path("../tasks/#{rake_file}", __FILE__)
92
97
  load full_path if File.exists?(full_path)
93
98
  end
@@ -0,0 +1,247 @@
1
+ #
2
+ # export_tasks.rake is a debugging tool to extract tasks from the
3
+ # current foreman instance.
4
+ #
5
+ # Run "foreman-rake export_tasks" to export tasks whcih are not listed
6
+ # as successful.
7
+ # To export all tasks "foreman-rake export_tasks tasks=all"
8
+ # to specify the number of days of tasks to gather: days=60 (defaults to 60)
9
+
10
+ namespace :foreman_tasks do
11
+ desc 'Export Dynflow Tasks'
12
+
13
+ task :export_tasks => :environment do
14
+
15
+ class TaskRender
16
+ def initialize
17
+ @cache = {}
18
+ end
19
+
20
+ def h(foo)
21
+ foo
22
+ end
23
+
24
+ def url(foo)
25
+ foo
26
+ end
27
+
28
+ def render_task(task)
29
+ @plan = task.execution_plan
30
+ erb('show', {})
31
+ end
32
+
33
+ def world
34
+ ForemanTasks.dynflow.world
35
+ end
36
+
37
+ def template(filename)
38
+ File.join(Gem::Specification.find_by_name("dynflow").gem_dir, 'web', 'views', "#{filename}.erb")
39
+ end
40
+
41
+ def erb(file, options)
42
+ unless @cache[file]
43
+ @cache[file] = Tilt.new(template(file))
44
+ end
45
+ @cache[file].render(self, options[:locals])
46
+ end
47
+
48
+ def prettify_value(value)
49
+ YAML.dump(value)
50
+ end
51
+
52
+ def prettyprint(value)
53
+ value = prettyprint_references(value)
54
+ if value
55
+ pretty_value = prettify_value(value)
56
+ <<-HTML
57
+ <pre class="prettyprint lang-yaml">#{h(pretty_value)}</pre>
58
+ HTML
59
+ else
60
+ ""
61
+ end
62
+ end
63
+
64
+ def prettyprint_references(value)
65
+ case value
66
+ when Hash
67
+ value.reduce({}) do |h, (key, val)|
68
+ h.update(key => prettyprint_references(val))
69
+ end
70
+ when Array
71
+ value.map { |val| prettyprint_references(val) }
72
+ when Dynflow::ExecutionPlan::OutputReference
73
+ value.inspect
74
+ else
75
+ value
76
+ end
77
+ end
78
+
79
+ def duration_to_s(duration)
80
+ h("%0.2fs" % duration)
81
+ end
82
+
83
+ def load_action(step)
84
+ world.persistence.load_action(step)
85
+ end
86
+
87
+ def step_error(step)
88
+ if step.error
89
+ ['<pre>',
90
+ "#{h(step.error.message)} (#{h(step.error.exception_class)})\n",
91
+ h(step.error.backtrace.join("\n")),
92
+ '</pre>'].join
93
+ end
94
+ end
95
+
96
+ def show_action_data(label, value)
97
+ value_html = prettyprint(value)
98
+ if !value_html.empty?
99
+ <<-HTML
100
+ <p>
101
+ <b>#{h(label)}</b>
102
+ #{value_html}
103
+ </p>
104
+ HTML
105
+ else
106
+ ""
107
+ end
108
+ end
109
+
110
+ def atom_css_classes(atom)
111
+ classes = ["atom"]
112
+ step = @plan.steps[atom.step_id]
113
+ case step.state
114
+ when :success
115
+ classes << "success"
116
+ when :error
117
+ classes << "error"
118
+ when :skipped, :skipping
119
+ classes << "skipped"
120
+ end
121
+ return classes.join(" ")
122
+ end
123
+
124
+ def flow_css_classes(flow, sub_flow = nil)
125
+ classes = []
126
+ case flow
127
+ when Dynflow::Flows::Sequence
128
+ classes << "sequence"
129
+ when Dynflow::Flows::Concurrence
130
+ classes << "concurrence"
131
+ when Dynflow::Flows::Atom
132
+ classes << atom_css_classes(flow)
133
+ else
134
+ raise "Unknown run plan #{run_plan.inspect}"
135
+ end
136
+ classes << atom_css_classes(sub_flow) if sub_flow.is_a? Dynflow::Flows::Atom
137
+ return classes.join(" ")
138
+ end
139
+
140
+ def step_css_class(step)
141
+ case step.state
142
+ when :success
143
+ "success"
144
+ when :error
145
+ "important"
146
+ end
147
+ end
148
+
149
+ def progress_width(step)
150
+ if step.state == :error
151
+ 100 # we want to show the red bar in full width
152
+ else
153
+ step.progress_done * 100
154
+ end
155
+ end
156
+
157
+ def step(step_id)
158
+ @plan.steps[step_id]
159
+ end
160
+
161
+
162
+ def updated_url(new_params)
163
+ url("?" + Rack::Utils.build_nested_query(params.merge(new_params.stringify_keys)))
164
+ end
165
+
166
+
167
+ end
168
+
169
+ class PageHelper
170
+ def self.pagify(template)
171
+ pre = <<-HTML
172
+ <html>
173
+ <head>
174
+ <title>Dynflow Console</title>
175
+ <script src="jquery.js"></script>
176
+ <link rel="stylesheet" type="text/css" href="bootstrap.css">
177
+ <link rel="stylesheet" type="text/css" href="application.css">
178
+ <script src="bootstrap.js"></script>
179
+ <script src="run_prettify.js"></script>
180
+ <script src="application.js"></script>
181
+ </head>
182
+ <body>
183
+ #{template}
184
+ <body>
185
+ </html>
186
+ HTML
187
+ end
188
+
189
+ def self.copy_assets(tmp_dir)
190
+ ['vendor/bootstrap/js/bootstrap.js',
191
+ 'vendor/google-code-prettify/run_prettify.js',
192
+ 'vendor/jquery/jquery.js',
193
+ 'vendor/jquery/jquery.js',
194
+ 'javascripts/application.js',
195
+ 'vendor/bootstrap/css/bootstrap.css',
196
+ 'stylesheets/application.css'].each do |file|
197
+
198
+ filename = File.join(Gem::Specification.find_by_name("dynflow").gem_dir, 'web', 'assets', file)
199
+ FileUtils.copy_file(filename, File.join(tmp_dir, File.basename(file)))
200
+ end
201
+ end
202
+
203
+ def self.generate_index(tasks)
204
+ html = "<div><table class=\"table\">"
205
+ tasks.order("started_at desc").all.each do |task|
206
+ html << "<tr><td><a href=\"#{task.id}.html\">#{task.label}</a></td><td>#{task.started_at}</td>\
207
+ <td>#{task.state}</td><td>#{task.result}</td></tr>"
208
+ end
209
+ html << "</table></div>"
210
+ end
211
+ end
212
+
213
+
214
+ if ENV['tasks'] == 'all'
215
+ tasks = ForemanTasks::Task
216
+ else
217
+ tasks = ForemanTasks::Task.where("result != 'success'")
218
+ end
219
+
220
+ days = ENV['days'].try(:to_i) || 60
221
+ tasks = tasks.where('started_at > ?', days.days.ago)
222
+ export_filename = ENV['export'] || "/tmp/task-export-#{DateTime.now.to_i}.tar.gz"
223
+
224
+ puts _("Gathering last #{days} days of tasks.")
225
+ Dir.mktmpdir('task-export') do |tmp_dir|
226
+ PageHelper.copy_assets(tmp_dir)
227
+
228
+
229
+ renderer = TaskRender.new
230
+ count = 1
231
+ total = tasks.count
232
+
233
+ tasks.all.each do |task|
234
+ File.open(File.join(tmp_dir, "#{task.id}.html"), 'w') {|file| file.write(PageHelper.pagify(renderer.render_task(task)))}
235
+ puts "#{count}/#{total}"
236
+ count += 1
237
+ end
238
+
239
+ File.open(File.join(tmp_dir, "index.html"), 'w') {|file| file.write(PageHelper.pagify(PageHelper.generate_index(tasks)))}
240
+
241
+ sh("tar cvzf #{export_filename} #{tmp_dir} > /dev/null")
242
+ end
243
+
244
+ puts "Created #{export_filename}"
245
+
246
+ end
247
+ end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = "0.6.13"
2
+ VERSION = "0.6.14"
3
3
  end
@@ -0,0 +1,16 @@
1
+ module ForemanTasks
2
+ class WidgetManager
3
+ DEFAULT_WIDGETS = [
4
+ {:template=>'foreman_tasks/tasks/dashboard/tasks_status', :sizex=>6, :sizey=>1, :name=> N_('Tasks Status table')},
5
+ {:template=>'foreman_tasks/tasks/dashboard/latest_tasks_in_error_warning', :sizex=>6, :sizey=>1,:name=> N_('Tasks in Error/Warning')}
6
+ ]
7
+
8
+ def self.register_widgets
9
+ if ForemanTasks.dynflow.required?
10
+ DEFAULT_WIDGETS.each do |widget|
11
+ Dashboard::Manager.register_default_widget(widget)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -23,10 +23,10 @@ FactoryGirl.define do
23
23
  parent_task_id nil
24
24
 
25
25
  after(:build) do |task|
26
- dynflow_task = ForemanTasks.dynflow.world.plan(Support::DummyDynflowAction)
26
+ execution_plan = ForemanTasks.dynflow.world.plan(Support::DummyDynflowAction)
27
27
  # remove the task created automatically by the persistence
28
- ForemanTasks::Task.where(:external_id => dynflow_task.id).delete_all
29
- task.external_id = dynflow_task.id
28
+ ForemanTasks::Task.where(:external_id => execution_plan.id).delete_all
29
+ task.update_from_dynflow(execution_plan.to_hash)
30
30
  end
31
31
 
32
32
  trait :user_create_task do
@@ -36,6 +36,12 @@ FactoryGirl.define do
36
36
  trait :product_create_task do
37
37
  label "Actions::Katello::Product::Create"
38
38
  end
39
+
40
+ trait :inconsistent_dynflow_task do
41
+ after(:build) do |task|
42
+ task.update_attributes!(:state => 'running')
43
+ end
44
+ end
39
45
  end
40
46
  end
41
47
  end
@@ -34,4 +34,21 @@ class TasksTest < ActiveSupport::TestCase
34
34
  assert auth.can?(permission.name.to_sym, task)
35
35
  end
36
36
  end
37
+
38
+ describe 'consistency check' do
39
+
40
+ let(:consistent_task) { FactoryGirl.create(:dynflow_task) }
41
+ let(:inconsistent_task) { FactoryGirl.create(:dynflow_task, :inconsistent_dynflow_task) }
42
+
43
+ it 'ensures the tasks marked as running are really running in Dynflow' do
44
+ consistent_task.state.must_equal 'planned'
45
+ inconsistent_task.state.must_equal 'running'
46
+
47
+ fixed_count = ForemanTasks::Task::DynflowTask.consistency_check
48
+
49
+ fixed_count.must_equal 1
50
+ consistent_task.reload.state.must_equal 'planned'
51
+ inconsistent_task.reload.state.must_equal 'planned'
52
+ end
53
+ end
37
54
  end
metadata CHANGED
@@ -1,36 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.13
5
- prerelease:
4
+ version: 0.6.14
6
5
  platform: ruby
7
6
  authors:
8
7
  - Ivan Nečas
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-03-17 00:00:00.000000000 Z
11
+ date: 2015-06-30 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: dynflow
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ~>
20
18
  - !ruby/object:Gem::Version
21
- version: 0.7.7
19
+ version: 0.7.9
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ~>
28
25
  - !ruby/object:Gem::Version
29
- version: 0.7.7
26
+ version: 0.7.9
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: sequel
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ! '>='
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ! '>='
44
39
  - !ruby/object:Gem::Version
@@ -46,7 +41,6 @@ dependencies:
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: sinatra
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ! '>='
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ! '>='
60
53
  - !ruby/object:Gem::Version
@@ -62,7 +55,6 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: daemons
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
59
  - - ! '>='
68
60
  - !ruby/object:Gem::Version
@@ -70,7 +62,6 @@ dependencies:
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
66
  - - ! '>='
76
67
  - !ruby/object:Gem::Version
@@ -94,105 +85,110 @@ executables: []
94
85
  extensions: []
95
86
  extra_rdoc_files: []
96
87
  files:
97
- - app/helpers/foreman_tasks/tasks_helper.rb
98
- - app/models/foreman_tasks/task/dynflow_task.rb
99
- - app/models/foreman_tasks/concerns/host_action_subject.rb
100
- - app/models/foreman_tasks/concerns/action_triggering.rb
101
- - app/models/foreman_tasks/concerns/architecture_action_subject.rb
102
- - app/models/foreman_tasks/concerns/action_subject.rb
103
- - app/models/foreman_tasks/lock.rb
104
- - app/models/foreman_tasks/task.rb
105
- - app/models/setting/foreman_tasks.rb
88
+ - LICENSE
89
+ - README.md
106
90
  - app/assets/stylesheets/foreman_tasks/application.css.scss
107
- - app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb
108
91
  - app/controllers/foreman_tasks/api/tasks_controller.rb
92
+ - app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb
109
93
  - app/controllers/foreman_tasks/tasks_controller.rb
110
- - app/lib/actions/helpers/args_serialization.rb
111
- - app/lib/actions/helpers/lock.rb
112
- - app/lib/actions/helpers/humanizer.rb
94
+ - app/helpers/foreman_tasks/tasks_helper.rb
95
+ - app/lib/actions/action_with_sub_plans.rb
96
+ - app/lib/actions/base.rb
113
97
  - app/lib/actions/bulk_action.rb
98
+ - app/lib/actions/entry_action.rb
114
99
  - app/lib/actions/foreman/architecture/create.rb
115
- - app/lib/actions/foreman/architecture/update.rb
116
100
  - app/lib/actions/foreman/architecture/destroy.rb
101
+ - app/lib/actions/foreman/architecture/update.rb
117
102
  - app/lib/actions/foreman/host/import_facts.rb
118
- - app/lib/actions/entry_action.rb
119
- - app/lib/actions/action_with_sub_plans.rb
120
- - app/lib/actions/base.rb
103
+ - app/lib/actions/helpers/args_serialization.rb
104
+ - app/lib/actions/helpers/humanizer.rb
105
+ - app/lib/actions/helpers/lock.rb
121
106
  - app/lib/actions/middleware/keep_current_user.rb
107
+ - app/models/foreman_tasks/concerns/action_subject.rb
108
+ - app/models/foreman_tasks/concerns/action_triggering.rb
109
+ - app/models/foreman_tasks/concerns/architecture_action_subject.rb
110
+ - app/models/foreman_tasks/concerns/host_action_subject.rb
111
+ - app/models/foreman_tasks/lock.rb
112
+ - app/models/foreman_tasks/task.rb
113
+ - app/models/foreman_tasks/task/dynflow_task.rb
114
+ - app/models/foreman_tasks/task/status_explicator.rb
115
+ - app/models/foreman_tasks/task/summarizer.rb
116
+ - app/models/setting/foreman_tasks.rb
117
+ - app/views/foreman_tasks/api/tasks/show.json.rabl
122
118
  - app/views/foreman_tasks/tasks/_details.html.erb
123
- - app/views/foreman_tasks/tasks/index.html.erb
124
- - app/views/foreman_tasks/tasks/_running_steps.html.erb
125
119
  - app/views/foreman_tasks/tasks/_errors.html.erb
120
+ - app/views/foreman_tasks/tasks/_locks.html.erb
126
121
  - app/views/foreman_tasks/tasks/_raw.html.erb
122
+ - app/views/foreman_tasks/tasks/_running_steps.html.erb
123
+ - app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb
124
+ - app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb
125
+ - app/views/foreman_tasks/tasks/index.html.erb
127
126
  - app/views/foreman_tasks/tasks/show.html.erb
128
- - app/views/foreman_tasks/tasks/_locks.html.erb
129
- - app/views/foreman_tasks/api/tasks/show.json.rabl
130
127
  - bin/dynflow-executor
131
128
  - bin/foreman-tasks
132
129
  - config/routes.rb
133
- - db/seeds.d/20-foreman_tasks_permissions.rb
134
130
  - db/migrate/20131205204140_create_foreman_tasks.rb
131
+ - db/migrate/20131209122644_create_foreman_tasks_locks.rb
135
132
  - db/migrate/20140324104010_remove_foreman_tasks_progress.rb
136
133
  - db/migrate/20140813215942_add_parent_task_id.rb
137
- - db/migrate/20131209122644_create_foreman_tasks_locks.rb
134
+ - db/seeds.d/20-foreman_tasks_permissions.rb
135
+ - deploy/foreman-tasks.init
138
136
  - deploy/foreman-tasks.service
139
137
  - deploy/foreman-tasks.sysconfig
140
- - deploy/foreman-tasks.init
141
- - lib/tasks/gettext.rake
142
138
  - lib/foreman-tasks.rb
143
- - lib/foreman_tasks/tasks/dynflow.rake
144
- - lib/foreman_tasks/version.rb
145
- - lib/foreman_tasks/triggers.rb
139
+ - lib/foreman_tasks.rb
140
+ - lib/foreman_tasks/authorizer_ext.rb
146
141
  - lib/foreman_tasks/dynflow.rb
147
- - lib/foreman_tasks/dynflow/console_authorizer.rb
148
142
  - lib/foreman_tasks/dynflow/configuration.rb
149
- - lib/foreman_tasks/dynflow/persistence.rb
143
+ - lib/foreman_tasks/dynflow/console_authorizer.rb
150
144
  - lib/foreman_tasks/dynflow/daemon.rb
151
- - lib/foreman_tasks/authorizer_ext.rb
145
+ - lib/foreman_tasks/dynflow/persistence.rb
152
146
  - lib/foreman_tasks/engine.rb
153
147
  - lib/foreman_tasks/task_error.rb
154
- - lib/foreman_tasks.rb
155
- - LICENSE
156
- - README.md
157
- - test/helpers/foreman_tasks/tasks_helper_test.rb
158
- - test/support/dummy_dynflow_action.rb
159
- - test/foreman_tasks_test_helper.rb
148
+ - lib/foreman_tasks/tasks/dynflow.rake
149
+ - lib/foreman_tasks/tasks/export_tasks.rake
150
+ - lib/foreman_tasks/triggers.rb
151
+ - lib/foreman_tasks/version.rb
152
+ - lib/foreman_tasks/widget_manager.rb
153
+ - lib/tasks/gettext.rake
160
154
  - test/controllers/api/tasks_controller_test.rb
161
155
  - test/factories/task_factory.rb
162
- - test/unit/dynflow_console_authorizer_test.rb
156
+ - test/foreman_tasks_test_helper.rb
157
+ - test/helpers/foreman_tasks/tasks_helper_test.rb
158
+ - test/support/dummy_dynflow_action.rb
163
159
  - test/unit/actions/action_with_sub_plans_test.rb
160
+ - test/unit/dynflow_console_authorizer_test.rb
164
161
  - test/unit/task_test.rb
165
162
  homepage: https://github.com/theforeman/foreman-tasks
166
163
  licenses: []
164
+ metadata: {}
167
165
  post_install_message:
168
166
  rdoc_options: []
169
167
  require_paths:
170
168
  - lib
171
169
  required_ruby_version: !ruby/object:Gem::Requirement
172
- none: false
173
170
  requirements:
174
171
  - - ! '>='
175
172
  - !ruby/object:Gem::Version
176
173
  version: '0'
177
174
  required_rubygems_version: !ruby/object:Gem::Requirement
178
- none: false
179
175
  requirements:
180
176
  - - ! '>='
181
177
  - !ruby/object:Gem::Version
182
178
  version: '0'
183
179
  requirements: []
184
180
  rubyforge_project:
185
- rubygems_version: 1.8.23
181
+ rubygems_version: 2.4.8
186
182
  signing_key:
187
- specification_version: 3
183
+ specification_version: 4
188
184
  summary: Foreman plugin for showing tasks information for resoruces and users
189
185
  test_files:
190
- - test/helpers/foreman_tasks/tasks_helper_test.rb
191
186
  - test/support/dummy_dynflow_action.rb
187
+ - test/factories/task_factory.rb
188
+ - test/helpers/foreman_tasks/tasks_helper_test.rb
192
189
  - test/foreman_tasks_test_helper.rb
193
190
  - test/controllers/api/tasks_controller_test.rb
194
- - test/factories/task_factory.rb
195
191
  - test/unit/dynflow_console_authorizer_test.rb
196
- - test/unit/actions/action_with_sub_plans_test.rb
197
192
  - test/unit/task_test.rb
193
+ - test/unit/actions/action_with_sub_plans_test.rb
198
194
  has_rdoc: