foreman_expire_hosts 3.0.0 → 4.0.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +28 -2
  3. data/.rubocop_todo.yml +10 -47
  4. data/README.md +1 -0
  5. data/app/controllers/concerns/foreman_expire_hosts/host_controller_extensions.rb +11 -13
  6. data/app/helpers/concerns/foreman_expire_hosts/audits_helper_extensions.rb +2 -2
  7. data/app/helpers/concerns/foreman_expire_hosts/hosts_helper_extensions.rb +8 -11
  8. data/app/mailers/expire_hosts_mailer.rb +72 -20
  9. data/app/models/concerns/foreman_expire_hosts/host_ext.rb +3 -3
  10. data/app/models/host_status/expiration_status.rb +1 -1
  11. data/app/models/setting/expire_hosts.rb +2 -2
  12. data/app/services/foreman_expire_hosts/action/base.rb +57 -0
  13. data/app/services/foreman_expire_hosts/action/delete_expired_hosts.rb +23 -0
  14. data/app/services/foreman_expire_hosts/action/stop_expired_hosts.rb +37 -0
  15. data/app/services/foreman_expire_hosts/expiry_edit_authorizer.rb +21 -0
  16. data/app/services/foreman_expire_hosts/notification/base.rb +71 -0
  17. data/app/services/foreman_expire_hosts/notification/deleted_hosts.rb +15 -0
  18. data/app/services/foreman_expire_hosts/notification/expiry_warning.rb +26 -0
  19. data/app/services/foreman_expire_hosts/notification/failed_deleted_hosts.rb +15 -0
  20. data/app/services/foreman_expire_hosts/notification/failed_stopped_hosts.rb +15 -0
  21. data/app/services/foreman_expire_hosts/notification/stopped_hosts.rb +26 -0
  22. data/app/services/foreman_expire_hosts/safe_destroy.rb +23 -0
  23. data/app/services/foreman_expire_hosts/ui_notifications/hosts/base.rb +68 -0
  24. data/app/services/foreman_expire_hosts/ui_notifications/hosts/expiry_warning.rb +28 -0
  25. data/app/services/foreman_expire_hosts/ui_notifications/hosts/stopped_host.rb +11 -0
  26. data/app/views/expire_hosts_mailer/expiry_warning_notification.html.erb +4 -1
  27. data/app/views/expire_hosts_mailer/stopped_hosts_notification.html.erb +4 -1
  28. data/db/seeds.d/80_expire_hosts_ui_notification.rb +30 -0
  29. data/foreman_expire_hosts.gemspec +2 -2
  30. data/lib/expire_hosts_notifications.rb +7 -104
  31. data/lib/foreman_expire_hosts/engine.rb +14 -8
  32. data/lib/foreman_expire_hosts/version.rb +1 -1
  33. data/lib/tasks/expired_hosts.rake +7 -5
  34. data/test/factories/foreman_expire_hosts_factories.rb +1 -1
  35. data/test/functional/concerns/hosts_controller_extensions_test.rb +8 -6
  36. data/test/lib/expire_hosts_notifications_test.rb +128 -32
  37. data/test/test_plugin_helper.rb +8 -3
  38. data/test/unit/concerns/host_extensions_test.rb +17 -17
  39. data/test/unit/expire_hosts_mailer_test.rb +47 -27
  40. data/test/unit/expiry_edit_authorizer_test.rb +55 -0
  41. data/test/unit/host_status/expiration_status_test.rb +1 -1
  42. data/test/unit/safe_destroy_test.rb +26 -0
  43. metadata +30 -11
@@ -0,0 +1,23 @@
1
+ module ForemanExpireHosts
2
+ module Action
3
+ class DeleteExpiredHosts < Base
4
+ private
5
+
6
+ def selector
7
+ Host.expired_past_grace_period
8
+ end
9
+
10
+ def action(host)
11
+ SafeDestroy.new(host).destroy!
12
+ end
13
+
14
+ def success_notification
15
+ ForemanExpireHosts::Notification::DeletedHosts
16
+ end
17
+
18
+ def failure_notification
19
+ ForemanExpireHosts::Notification::FailedDeletedHosts
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module ForemanExpireHosts
2
+ module Action
3
+ class StopExpiredHosts < Base
4
+ private
5
+
6
+ def selector
7
+ Host.expired
8
+ end
9
+
10
+ def action(host)
11
+ return true unless host.supports_power_and_running?
12
+ logger.info "Powering down expired host in grace period #{host}"
13
+ host.power.stop
14
+ rescue StandardError
15
+ false
16
+ end
17
+
18
+ def success_notification
19
+ ForemanExpireHosts::Notification::StoppedHosts
20
+ end
21
+
22
+ def failure_notification
23
+ ForemanExpireHosts::Notification::FailedStoppedHosts
24
+ end
25
+
26
+ def success_notification_options
27
+ super.merge(
28
+ :delete_date => (Date.today + days_to_delete_after_expired)
29
+ )
30
+ end
31
+
32
+ def days_to_delete_after_expired
33
+ Setting[:days_to_delete_after_host_expiration].to_i
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module ForemanExpireHosts
2
+ class ExpiryEditAuthorizer
3
+ attr_accessor :user, :hosts
4
+
5
+ def initialize(opts = {})
6
+ self.user = opts.fetch(:user)
7
+ self.hosts = opts.fetch(:hosts)
8
+ end
9
+
10
+ def authorized?
11
+ hosts.each do |host|
12
+ next unless user.can?(:edit_hosts, host)
13
+ return true if user.can?(:edit_host_expiry, host)
14
+ return true if Setting[:can_owner_modify_host_expiry_date] &&
15
+ ((host.owner_type == 'User' && host.owner == user) ||
16
+ (host.owner_type == 'Usergroup' && host.owner.all_users.include?(user)))
17
+ end
18
+ false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ module ForemanExpireHosts
2
+ module Notification
3
+ class Base
4
+ attr_accessor :all_hosts, :global_recipients
5
+
6
+ def initialize(opts)
7
+ @all_hosts = opts.fetch(:hosts)
8
+ @global_recipients = [opts[:to]].flatten.compact
9
+ end
10
+
11
+ def deliver
12
+ deliver_mail_notifications if respond_to?(:build_mail_notification, true)
13
+ deliver_ui_notifications if respond_to?(:build_ui_notification, true)
14
+ end
15
+
16
+ private
17
+
18
+ def deliver_mail_notifications
19
+ hosts_by_recipient(all_hosts).each do |recipient, hosts|
20
+ deliver_mail_notification(recipient, hosts)
21
+ end
22
+ deliver_mail_notification(additional_recipients, all_hosts) if additional_recipients.present?
23
+ true
24
+ end
25
+
26
+ def deliver_mail_notification(recipient, hosts)
27
+ build_mail_notification(recipient, hosts).deliver_now
28
+ rescue SocketError, Net::SMTPError => error
29
+ message = _('Failed to deliver %{notification_name} for Hosts %{hosts}') % {
30
+ :notification_name => humanized_name,
31
+ :hosts => hosts.map(&:name).to_sentence
32
+ }
33
+ Foreman::Logging.exception(message, error)
34
+ end
35
+
36
+ def deliver_ui_notifications
37
+ all_hosts.each do |host|
38
+ build_ui_notification(host).deliver!
39
+ end
40
+ end
41
+
42
+ delegate :logger, :to => :Rails
43
+
44
+ def humanized_name
45
+ _('Notification')
46
+ end
47
+
48
+ def hosts_by_recipient(hosts)
49
+ hosts.each_with_object({}) do |host, hash|
50
+ recipients = recipients_for_host(host)
51
+ recipients.each do |recipient|
52
+ hash[recipient] ||= []
53
+ hash[recipient] << host
54
+ end
55
+ end
56
+ end
57
+
58
+ def recipients_for_host(host)
59
+ return global_recipients if global_recipients.present?
60
+ return [User.anonymous_admin] if host.owner.blank?
61
+ return [host.owner] if host.owner_type == 'User'
62
+ host.owner.all_users
63
+ end
64
+
65
+ def additional_recipients
66
+ return [] if Setting[:host_expiry_email_recipients].nil?
67
+ Setting[:host_expiry_email_recipients].split(',').compact.map(&:strip)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,15 @@
1
+ module ForemanExpireHosts
2
+ module Notification
3
+ class DeletedHosts < Base
4
+ private
5
+
6
+ def humanized_name
7
+ _('Deleted Hosts Notification')
8
+ end
9
+
10
+ def build_mail_notification(recipient, hosts)
11
+ ExpireHostsMailer.deleted_hosts_notification(recipient, hosts)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ module ForemanExpireHosts
2
+ module Notification
3
+ class ExpiryWarning < Base
4
+ attr_accessor :expiry_date
5
+
6
+ def initialize(opts)
7
+ super
8
+ @expiry_date = opts.fetch(:expiry_date)
9
+ end
10
+
11
+ private
12
+
13
+ def humanized_name
14
+ _('Host Expiry Warning Notification')
15
+ end
16
+
17
+ def build_ui_notification(host)
18
+ ForemanExpireHosts::UINotifications::Hosts::ExpiryWarning.new(host)
19
+ end
20
+
21
+ def build_mail_notification(recipient, hosts)
22
+ ExpireHostsMailer.expiry_warning_notification(recipient, expiry_date, hosts)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module ForemanExpireHosts
2
+ module Notification
3
+ class FailedDeletedHosts < Base
4
+ private
5
+
6
+ def humanized_name
7
+ _('Failed Deleted Hosts Notification')
8
+ end
9
+
10
+ def build_mail_notification(recipient, hosts)
11
+ ExpireHostsMailer.failed_to_delete_hosts_notification(recipient, hosts)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ForemanExpireHosts
2
+ module Notification
3
+ class FailedStoppedHosts < Base
4
+ private
5
+
6
+ def humanized_name
7
+ _('Failed Stopped Hosts Notification')
8
+ end
9
+
10
+ def build_mail_notification(recipient, hosts)
11
+ ExpireHostsMailer.failed_to_stop_hosts_notification(recipient, hosts)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ module ForemanExpireHosts
2
+ module Notification
3
+ class StoppedHosts < Base
4
+ attr_accessor :delete_date
5
+
6
+ def initialize(opts)
7
+ super
8
+ @delete_date = opts.fetch(:delete_date)
9
+ end
10
+
11
+ private
12
+
13
+ def humanized_name
14
+ _('Stopped Hosts Notification')
15
+ end
16
+
17
+ def build_ui_notification(host)
18
+ ForemanExpireHosts::UINotifications::Hosts::StoppedHost.new(host)
19
+ end
20
+
21
+ def build_mail_notification(recipient, hosts)
22
+ ExpireHostsMailer.stopped_hosts_notification(recipient, delete_date, hosts)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ module ForemanExpireHosts
2
+ class SafeDestroy
3
+ # See http://projects.theforeman.org/issues/14702 for reasoning.
4
+ attr_accessor :subject
5
+
6
+ def initialize(subject)
7
+ self.subject = subject
8
+ end
9
+
10
+ def destroy!
11
+ subject.destroy!
12
+ rescue ActiveRecord::RecordNotDestroyed => invalid
13
+ message = _('Failed to delete %{class_name} %{subject}: %{message} - Errors: %{errors}') % {
14
+ :class_name => subject.class.name,
15
+ :subject => subject,
16
+ :message => invalid.message,
17
+ :errors => invalid.record.errors.full_messages.to_sentence
18
+ }
19
+ Foreman::Logging.exception(message, invalid)
20
+ false
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ module ForemanExpireHosts
2
+ module UINotifications
3
+ module Hosts
4
+ class Base < ::UINotifications::Hosts::Base
5
+ private
6
+
7
+ def create
8
+ return add_notification unless find_notification
9
+ redeliver! if redeliver?
10
+ find_notification
11
+ end
12
+
13
+ def add_notification
14
+ ::Notification.create!(
15
+ initiator: initiator,
16
+ subject: subject,
17
+ message: parsed_message,
18
+ audience: audience,
19
+ notification_blueprint: blueprint
20
+ )
21
+ end
22
+
23
+ def message
24
+ blueprint.message
25
+ end
26
+
27
+ def parsed_message
28
+ ::UINotifications::StringParser.new(
29
+ message,
30
+ message_variables
31
+ ).to_s
32
+ end
33
+
34
+ def message_variables
35
+ {
36
+ subject: subject,
37
+ initator: initiator
38
+ }
39
+ end
40
+
41
+ def update_notification
42
+ find_notification
43
+ .update_attributes(expired_at: blueprint.expired_at, :message => parsed_message)
44
+ end
45
+
46
+ def redeliver!
47
+ recipients = find_notification.notification_recipients
48
+ recipients.update_all(seen: false) # rubocop:disable Rails/SkipsModelValidations
49
+ recipients.pluck(:user_id).each do |user_id|
50
+ ::UINotifications::CacheHandler.new(user_id).clear
51
+ end
52
+ end
53
+
54
+ def redeliver?
55
+ false
56
+ end
57
+
58
+ def find_notification
59
+ blueprint.notifications.find_by(subject: subject)
60
+ end
61
+
62
+ def blueprint
63
+ @blueprint ||= NotificationBlueprint.find_by(name: blueprint_name)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,28 @@
1
+ module ForemanExpireHosts
2
+ module UINotifications
3
+ module Hosts
4
+ class ExpiryWarning < Base
5
+ include ActionView::Helpers::DateHelper
6
+
7
+ def blueprint_name
8
+ 'expire_hosts_expiry_warning'
9
+ end
10
+
11
+ private
12
+
13
+ # Nag the user about expiring hosts
14
+ def redeliver?
15
+ true
16
+ end
17
+
18
+ def message
19
+ N_('%{subject} will expire in %{relative_expiry_time}.')
20
+ end
21
+
22
+ def message_variables
23
+ super.merge(:relative_expiry_time => time_ago_in_words(subject.expired_on))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module ForemanExpireHosts
2
+ module UINotifications
3
+ module Hosts
4
+ class StoppedHost < Base
5
+ def blueprint_name
6
+ 'expire_hosts_stopped_host'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,7 @@
1
- <%= _('The following hosts will be expired on %s. Please change expiry date if you want to keep these hosts alive.') % @expiry_date %>
1
+ <%= _('The following hosts will be expired on %s.') % @expiry_date %>
2
+ <% if @authorized_for_expiry_date_change %>
3
+ <%= _('Please change their expiry date if you want to keep these hosts alive.') %>
4
+ <% end %>
2
5
  <br/><br/>
3
6
 
4
7
  <%= render 'hosts_table', :hosts => @hosts, :link_to_hosts => true %>
@@ -1,4 +1,7 @@
1
- <%= _('The following hosts have been expired in Foreman and will be stopped for now. These hosts will be destroyed on %s. Please change their expiry date and power them on if you want to keep the hosts.') % l(@delete_date) %>
1
+ <%= _('The following hosts have been expired in Foreman and will be stopped for now. These hosts will be destroyed on %s.') % l(@delete_date) %>
2
+ <% if @authorized_for_expiry_date_change %>
3
+ <%= _('Please change their expiry date and power them on if you want to keep the hosts.') %>
4
+ <% end %>
2
5
  <br/><br/>
3
6
 
4
7
  <%= render 'hosts_table', :hosts => @hosts, :link_to_hosts => true %>
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ group: _('Hosts'),
4
+ name: 'expire_hosts_expiry_warning',
5
+ message: _('%{subject} will expire soon.'),
6
+ level: 'info',
7
+ actions:
8
+ {
9
+ links:
10
+ [
11
+ path_method: :host_path,
12
+ title: _('Details')
13
+ ]
14
+ }
15
+ },
16
+ {
17
+ group: _('Hosts'),
18
+ name: 'expire_hosts_stopped_host',
19
+ message: _('%{subject} was stopped because it expired.'),
20
+ level: 'info',
21
+ actions:
22
+ {
23
+ links:
24
+ [
25
+ path_method: :host_path,
26
+ title: _('Details')
27
+ ]
28
+ }
29
+ }
30
+ ].each { |blueprint| UINotifications::Seed.new(blueprint).configure }