foreman_expire_hosts 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 }