foreman-tasks 8.0.2 → 8.1.0

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.
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