foreman-tasks 8.0.2 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 461b481178e5ad353de2b1476ffb71cc716fa516a451396e3ff9d40466a892b6
4
- data.tar.gz: 2c8a4689eed2d7b3e14369c92a252cb01267c0e4c4ff83343c20fd2335bdc335
3
+ metadata.gz: 8cbc85cc5038629927d1204242995f7a8ffa0074caaa56fcc7886f6f2093dff1
4
+ data.tar.gz: 4f848db967c1791278c0fc9cebe3ddaa3a7fd3500208995ae84289496710db4d
5
5
  SHA512:
6
- metadata.gz: a1f044567563379a76ad6a059d7e2357a4c7d1600f23a62a156abbbb1f0fc8b521719a75c4a52ffc4541933bff508c85b21d97ad4ee5026c93a90a4da15e85ac
7
- data.tar.gz: d0ef48cc17a0202e40c053b158a1ba89fedcc8f318eab700cb46c68fd412b25b3fbf9fb7d094bf1522ef371c99eefd54c776b9318156c6b2bae1ee50a035334b
6
+ metadata.gz: ba8c8768bdaef20f5750b1052e9d43b525fee4260c81c0e61e0847cab958fc534129065d24f556920f2f5270fd8aad7192f808e60ee5fd693eedb54b8afdb77c
7
+ data.tar.gz: 0e8628e1db3a0a7fb800ae92fc722d7c7af1feaf9cebf659117f08686f0dae70f376ffe7236a7a99b2c6d1e20ed47e40194f52d37a3b5b0a653a23879460738e
@@ -0,0 +1,45 @@
1
+ module Actions
2
+ class CheckLongRunningTasks < ::Actions::EntryAction
3
+ include Actions::RecurringAction
4
+
5
+ INTERVAL = 2.days
6
+ STATES = ['running', 'paused'].freeze
7
+
8
+ def plan
9
+ time = Time.now.utc
10
+ cutoff = time - INTERVAL
11
+ notification = ::ForemanTasks::TasksMailNotification.find_by(name: "long_running_tasks")
12
+ org_admin_role = Role.find_by(name: 'Organization admin')
13
+ users = User.left_joins(:roles)
14
+ .where(id: UserMailNotification.where(mail_notification_id: notification.id).select(:host_id))
15
+ .or(User.where(admin: true))
16
+ .or(User.where(id: UserRole.where(id: [org_admin_role.id] + org_admin_role.cloned_role_ids).select(:owner_id)))
17
+
18
+ query = "state ^ (#{STATES.join(', ')}) AND state_updated_at <= \"#{cutoff}\""
19
+ users.each do |user|
20
+ User.as(user) do
21
+ tasks = ForemanTasks::Task.authorized
22
+ .search_for(query)
23
+ .select(:id)
24
+ .pluck(:id)
25
+ plan_action(DeliverLongRunningTasksNotification,
26
+ OpenStruct.new(
27
+ user_id: User.current.id,
28
+ time: time,
29
+ interval: INTERVAL,
30
+ states: STATES,
31
+ task_uuids: tasks,
32
+ query: query,
33
+ # Non serializable fields
34
+ user: nil,
35
+ tasks: nil
36
+ ))
37
+ end
38
+ end
39
+ end
40
+
41
+ def humanized_name
42
+ _('Check for long running tasks')
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ module Actions
2
+ class DeliverLongRunningTasksNotification < EntryAction
3
+ def plan(report)
4
+ return if report.task_uuids.empty?
5
+
6
+ plan_self report: report
7
+ end
8
+
9
+ def run
10
+ report = OpenStruct.new(input[:report])
11
+ tasks = ForemanTasks::Task.where(id: report.task_uuids)
12
+ report.user = User.current
13
+ report.tasks = tasks
14
+ ::UINotifications::Tasks::TasksRunningLong.new.deliver!
15
+ TasksMailer.long_tasks(report).deliver_now
16
+ end
17
+
18
+ def humanized_name
19
+ _('Deliver notifications about long running tasks')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ class TasksMailer < ApplicationMailer
2
+ helper ApplicationHelper
3
+
4
+ def long_tasks(report, opts = {})
5
+ return if report.tasks.empty?
6
+
7
+ @report = report
8
+ @subject = opts[:subject]
9
+ @subject ||= _('Tasks pending since %s') % (@report.time - @report.interval)
10
+ mail(to: report.user.mail, subject: @subject)
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module ForemanTasks
2
+ class TasksMailNotification < MailNotification
3
+ ALL = N_("Subscribe")
4
+
5
+ def subscription_options
6
+ [ALL]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ module UINotifications
2
+ module Tasks
3
+ class TasksRunningLong < Tasks::Base
4
+ include Rails.application.routes.url_helpers
5
+
6
+ def deliver!
7
+ notification = ::Notification.new(
8
+ :audience => Notification::AUDIENCE_GLOBAL,
9
+ :notification_blueprint => blueprint,
10
+ :initiator => initiator,
11
+ :message => message,
12
+ :subject => nil,
13
+ :notification_recipients => [NotificationRecipient.create(:user => User.current)]
14
+ )
15
+ notification.actions['links'] ||= []
16
+ notification.actions['links'] << {
17
+ href: foreman_tasks_tasks_path(search: subject.query),
18
+ title: N_('Long running tasks'),
19
+ }
20
+ notification.save!
21
+ notification
22
+ end
23
+
24
+ def blueprint
25
+ @blueprint ||= NotificationBlueprint.unscoped.find_by(:name => 'tasks_running_long')
26
+ end
27
+
28
+ def message
29
+ _("%{count} tasks are in running or paused state for more than a day") % { count: subject.task_uuids.count }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ <p>
2
+ <%= _("Tasks lingering in states %{states} since %{time}") % {
3
+ time: @report.time - @report.interval,
4
+ states: @report.states.join(', ')
5
+ } %>
6
+ </p>
7
+
8
+ <div class="dashboard">
9
+ <table>
10
+ <tr>
11
+ <th>_("ID")</th>
12
+ <th>_("Action")</th>
13
+ <th>_("Label")</th>
14
+ <th>_("State")</th>
15
+ <th>_("State updated at")</th>
16
+ </tr>
17
+ <% @report.tasks.each do |task| %>
18
+ <tr>
19
+ <td><%= link_to task.id, foreman_tasks_task_url(task) %></td>
20
+ <td><%= task.action %></td>
21
+ <td><%= task.label %></td>
22
+ <td><%= task.state %></td>
23
+ <td><%= task.state_updated_at %></td>
24
+ </tr>
25
+ <% end %>
26
+ </table>
27
+ </div>
28
+
29
+ <%= link_to 'More details', foreman_tasks_tasks_url(search: @report.query) %>
@@ -0,0 +1,16 @@
1
+ <%= _("Tasks lingering in states %{states} since %{time}") % {
2
+ time: @report.time - @report.interval,
3
+ states: @report.states.join(', ')
4
+ } %>
5
+
6
+ More details: <%= foreman_tasks_tasks_url(search: @report.query) %>
7
+
8
+ <% @report.tasks.each do |task| %>
9
+ ID: <%= task.id %>
10
+ Action: <%= task.action %>
11
+ Label: <%= task.label %>
12
+ State: <%= task.state %>
13
+ State updated at: <%= task.state_updated_at %>
14
+ Details: <%= foreman_tasks_task_url(task) %>
15
+
16
+ <% end %>
@@ -49,6 +49,13 @@ blueprints = [
49
49
  level: 'info',
50
50
  message: "DYNAMIC",
51
51
  },
52
+
53
+ {
54
+ group: N_('Tasks'),
55
+ name: 'tasks_running_long',
56
+ message: 'DYNAMIC',
57
+ level: 'warning',
58
+ },
52
59
  ]
53
60
 
54
61
  blueprints.each { |blueprint| UINotifications::Seed.new(blueprint).configure }
@@ -0,0 +1,24 @@
1
+ N_('Long running tasks')
2
+
3
+ notifications = [
4
+ {
5
+ :name => 'long_running_tasks',
6
+ :description => N_('A notification when tasks run for suspiciously long time'),
7
+ :mailer => 'TasksMailer',
8
+ :method => 'long_tasks',
9
+ :subscription_type => 'alert',
10
+ },
11
+ ]
12
+
13
+ notifications.each do |notification|
14
+ if (mail = ForemanTasks::TasksMailNotification.find_by(name: notification[:name]))
15
+ mail.attributes = notification
16
+ mail.save! if mail.changed?
17
+ else
18
+ created_notification = ForemanTasks::TasksMailNotification.create(notification)
19
+ if created_notification.nil? || created_notification.errors.any?
20
+ raise ::Foreman::Exception.new(N_("Unable to create mail notification: %s"),
21
+ SeedHelper.format_errors(created_notification))
22
+ end
23
+ end
24
+ end
@@ -162,6 +162,7 @@ module ForemanTasks
162
162
  world.middleware.use Actions::Middleware::KeepCurrentTimezone
163
163
  world.middleware.use Actions::Middleware::KeepCurrentRequestID
164
164
  world.middleware.use ::Actions::Middleware::LoadSettingValues
165
+ ForemanTasks.register_scheduled_task(Actions::CheckLongRunningTasks, ENV['FOREMAN_TASKS_CHECK_LONG_RUNNING_TASKS_CRONLINE'] || '0 0 * * *')
165
166
  end
166
167
  ::ForemanTasks.dynflow.config.on_init(true) do
167
168
  ::ForemanTasks::Task::DynflowTask.consistency_check
@@ -186,7 +187,7 @@ module ForemanTasks
186
187
  end
187
188
 
188
189
  rake_tasks do
189
- %w[dynflow.rake test.rake export_tasks.rake cleanup.rake generate_task_actions.rake].each do |rake_file|
190
+ %w[dynflow.rake test.rake export_tasks.rake cleanup.rake generate_task_actions.rake reschedule_long_running_tasks_checker.rake].each do |rake_file|
190
191
  full_path = File.expand_path("../tasks/#{rake_file}", __FILE__)
191
192
  load full_path if File.exist?(full_path)
192
193
  end
@@ -0,0 +1,21 @@
1
+ namespace :foreman_tasks do
2
+ desc <<~DESC
3
+ Reschedules the long running task checker recurring logic to run at a different schedule. ENV variables:
4
+
5
+ * FOREMAN_TASKS_CHECK_LONG_RUNNING_TASKS_CRONLINE : A cron line describing the schedule, defaults to 0 0 * * *
6
+ DESC
7
+ task :reschedule_long_running_tasks_checker => ['environment', 'dynflow:client'] do
8
+ User.as_anonymous_admin do
9
+ task_class = Actions::CheckLongRunningTasks
10
+ cronline = ENV['FOREMAN_TASKS_CHECK_LONG_RUNNING_TASKS_CRONLINE'] || '0 0 * * *'
11
+ rl = ForemanTasks::RecurringLogic.joins(:tasks)
12
+ .where(state: 'active')
13
+ .merge(ForemanTasks::Task.where(label: task_class.name))
14
+ .first
15
+ if rl&.cron_line != cronline
16
+ rl.cancel
17
+ ForemanTasks.register_scheduled_task(task_class, cronline)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '8.0.2'.freeze
2
+ VERSION = '8.1.0'.freeze
3
3
  end
data/lib/foreman_tasks.rb CHANGED
@@ -61,4 +61,20 @@ module ForemanTasks
61
61
  result = dynflow.world.delay action, delay_options, *args
62
62
  ForemanTasks::Task::DynflowTask.where(:external_id => result.id).first!
63
63
  end
64
+
65
+ def self.register_scheduled_task(task_class, cronline)
66
+ ForemanTasks::RecurringLogic.transaction(isolation: :serializable) do
67
+ return if ForemanTasks::RecurringLogic.joins(:tasks)
68
+ .where(state: 'active')
69
+ .merge(ForemanTasks::Task.where(label: task_class.name))
70
+ .exists?
71
+
72
+ User.as_anonymous_admin do
73
+ recurring_logic = ForemanTasks::RecurringLogic.new_from_cronline(cronline)
74
+ recurring_logic.save!
75
+ recurring_logic.start(task_class)
76
+ end
77
+ end
78
+ rescue ActiveRecord::TransactionIsolationError # rubocop:disable Lint/SuppressedException
79
+ end
64
80
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.2
4
+ version: 8.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-19 00:00:00.000000000 Z
11
+ date: 2023-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -153,6 +153,8 @@ files:
153
153
  - app/lib/actions/action_with_sub_plans.rb
154
154
  - app/lib/actions/base.rb
155
155
  - app/lib/actions/bulk_action.rb
156
+ - app/lib/actions/check_long_running_tasks.rb
157
+ - app/lib/actions/deliver_long_running_tasks_notification.rb
156
158
  - app/lib/actions/entry_action.rb
157
159
  - app/lib/actions/foreman/host/import_facts.rb
158
160
  - app/lib/actions/foreman/puppetclass/import.rb
@@ -181,6 +183,7 @@ files:
181
183
  - app/lib/actions/trigger_proxy_batch.rb
182
184
  - app/lib/foreman_tasks/concerns/polling_action_extensions.rb
183
185
  - app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb
186
+ - app/mailers/tasks_mailer.rb
184
187
  - app/models/foreman_tasks/concerns/action_subject.rb
185
188
  - app/models/foreman_tasks/concerns/action_triggering.rb
186
189
  - app/models/foreman_tasks/concerns/host_action_subject.rb
@@ -199,6 +202,7 @@ files:
199
202
  - app/models/foreman_tasks/task_group.rb
200
203
  - app/models/foreman_tasks/task_group_member.rb
201
204
  - app/models/foreman_tasks/task_groups/recurring_logic_task_group.rb
205
+ - app/models/foreman_tasks/tasks_mail_notification.rb
202
206
  - app/models/foreman_tasks/triggering.rb
203
207
  - app/services/foreman_tasks/proxy_selector.rb
204
208
  - app/services/foreman_tasks/troubleshooting_help_generator.rb
@@ -208,6 +212,7 @@ files:
208
212
  - app/services/ui_notifications/tasks/task_bulk_stop.rb
209
213
  - app/services/ui_notifications/tasks/task_paused_admin.rb
210
214
  - app/services/ui_notifications/tasks/task_paused_owner.rb
215
+ - app/services/ui_notifications/tasks/tasks_running_long.rb
211
216
  - app/views/common/_trigger_form.html.erb
212
217
  - app/views/foreman_tasks/api/locks/show.json.rabl
213
218
  - app/views/foreman_tasks/api/recurring_logics/base.json.rabl
@@ -230,6 +235,8 @@ files:
230
235
  - app/views/foreman_tasks/tasks/dashboard/_latest_tasks_in_error_warning.html.erb
231
236
  - app/views/foreman_tasks/tasks/dashboard/_tasks_status.html.erb
232
237
  - app/views/foreman_tasks/tasks/show.html.erb
238
+ - app/views/tasks_mailer/long_tasks.html.erb
239
+ - app/views/tasks_mailer/long_tasks.text.erb
233
240
  - babel.config.js
234
241
  - bin/dynflow-executor
235
242
  - bin/foreman-tasks
@@ -268,6 +275,7 @@ files:
268
275
  - db/seeds.d/30-notification_blueprints.rb
269
276
  - db/seeds.d/60-dynflow_proxy_feature.rb
270
277
  - db/seeds.d/61-foreman_tasks_bookmarks.rb
278
+ - db/seeds.d/95-mail_notifications.rb
271
279
  - deploy/foreman-tasks.service
272
280
  - deploy/foreman-tasks.sysconfig
273
281
  - extra/dynflow-debug.sh
@@ -290,6 +298,7 @@ files:
290
298
  - lib/foreman_tasks/tasks/dynflow.rake
291
299
  - lib/foreman_tasks/tasks/export_tasks.rake
292
300
  - lib/foreman_tasks/tasks/generate_task_actions.rake
301
+ - lib/foreman_tasks/tasks/reschedule_long_running_tasks_checker.rake
293
302
  - lib/foreman_tasks/test_extensions.rb
294
303
  - lib/foreman_tasks/test_helpers.rb
295
304
  - lib/foreman_tasks/triggers.rb
@@ -633,7 +642,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
633
642
  - !ruby/object:Gem::Version
634
643
  version: '0'
635
644
  requirements: []
636
- rubygems_version: 3.4.12
645
+ rubygems_version: 3.4.13
637
646
  signing_key:
638
647
  specification_version: 4
639
648
  summary: Foreman plugin for showing tasks information for resources and users