foreman_dlm 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -1
  3. data/app/controllers/api/v2/dlmlock_events_controller.rb +41 -0
  4. data/app/controllers/api/v2/dlmlocks_controller.rb +10 -4
  5. data/app/controllers/concerns/foreman_dlm/update_checkin_time.rb +21 -0
  6. data/app/controllers/foreman_dlm/application_controller.rb +19 -0
  7. data/app/controllers/foreman_dlm/dlmlocks_controller.rb +73 -0
  8. data/app/helpers/foreman_dlm/dlmlock_helper.rb +31 -0
  9. data/app/jobs/foreman_dlm/refresh_dlmlock_status.rb +17 -0
  10. data/app/models/concerns/foreman_dlm/dlm_facet_host_extensions.rb +13 -0
  11. data/app/models/concerns/foreman_dlm/expirable.rb +35 -0
  12. data/app/models/concerns/foreman_dlm/host_extensions.rb +11 -0
  13. data/app/models/concerns/foreman_dlm/user_extensions.rb +13 -0
  14. data/app/models/foreman_dlm/dlm_facet.rb +7 -0
  15. data/app/models/foreman_dlm/dlmlock.rb +136 -0
  16. data/app/models/foreman_dlm/dlmlock/update.rb +9 -0
  17. data/app/models/foreman_dlm/dlmlock_event.rb +23 -0
  18. data/app/models/host_status/dlmlock_status.rb +44 -0
  19. data/app/models/settings/dlm.rb +20 -0
  20. data/app/views/api/v2/dlmlock_events/index.json.rabl +2 -0
  21. data/app/views/foreman_dlm/api/v2/dlm_facets/base.json.rabl +1 -0
  22. data/app/views/foreman_dlm/api/v2/dlm_facets/base_with_root.json.rabl +3 -0
  23. data/app/views/foreman_dlm/api/v2/dlm_facets/show.json.rabl +3 -0
  24. data/app/views/foreman_dlm/dlmlocks/_details.html.erb +42 -0
  25. data/app/views/{dlmlocks → foreman_dlm/dlmlocks}/_list.html.erb +3 -1
  26. data/app/views/{dlmlocks → foreman_dlm/dlmlocks}/index.html.erb +0 -0
  27. data/app/views/foreman_dlm/dlmlocks/show.html.erb +10 -0
  28. data/app/views/{dlmlocks → foreman_dlm/dlmlocks}/welcome.html.erb +0 -0
  29. data/app/views/hosts/_dlmlocks_tab.html.erb +39 -0
  30. data/config/routes.rb +10 -3
  31. data/contrib/systemd/foreman-dlm-expire-events.service +10 -0
  32. data/contrib/systemd/foreman-dlm-expire-events.timer +8 -0
  33. data/db/migrate/20180627150003_rename_dlmlock_sti_models.rb +9 -0
  34. data/db/migrate/20180704162345_add_dlmlock_events.rb +11 -0
  35. data/db/migrate/20180711090022_add_hosts_fk_to_dlmlocks.rb +5 -0
  36. data/db/migrate/20180711111903_create_dlm_facets.foreman_dlm.rb +10 -0
  37. data/db/migrate/20180713113208_update_permissions_for_scoped_models.rb +13 -0
  38. data/lib/foreman_dlm/engine.rb +43 -7
  39. data/lib/foreman_dlm/version.rb +1 -1
  40. data/lib/tasks/dlmlock_events.rake +19 -0
  41. data/test/controllers/api/v2/dlmlocks_controller_test.rb +32 -11
  42. data/test/controllers/api/v2/dlmlocks_dlmlock_events_controller_test.rb +81 -0
  43. data/test/controllers/api/v2/hosts_controller_test.rb +28 -0
  44. data/test/controllers/foreman_dlm/dlmlocks_test.rb +55 -0
  45. data/test/controllers/hosts_controller_test.rb +12 -0
  46. data/test/factories/dlm_facets.rb +6 -0
  47. data/test/factories/dlmlock.rb +6 -2
  48. data/test/factories/dlmlock_events.rb +13 -0
  49. data/test/factories/host.rb +7 -0
  50. data/test/jobs/refresh_dlmlock_status_test.rb +10 -0
  51. data/test/models/foreman_dlm/dlm_facet_test.rb +13 -0
  52. data/test/models/foreman_dlm/dlmlock_event_test.rb +19 -0
  53. data/test/models/foreman_dlm/dlmlock_test.rb +299 -0
  54. data/test/models/host_managed_test.rb +23 -0
  55. data/test/models/host_status/dlmlock_status_test.rb +49 -0
  56. data/test/models/user_test.rb +5 -0
  57. metadata +63 -15
  58. data/app/controllers/dlmlocks_controller.rb +0 -12
  59. data/app/models/dlmlock.rb +0 -79
  60. data/app/models/dlmlock/update.rb +0 -7
  61. data/app/views/dlmlocks/_details.html.erb +0 -35
  62. data/app/views/dlmlocks/show.html.erb +0 -7
  63. data/test/controllers/dlmlocks_test.rb +0 -24
  64. data/test/models/dlmlock_test.rb +0 -201
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0b972d0b154b81592615546f153d32893af3647364ea66f1e0ab5826884f02e
4
- data.tar.gz: 38ee0b23847863b293adaed2e15c9dcdb6883eec9b512d3c308ae0f7dee88615
3
+ metadata.gz: 5be1f946127eae4cab43fdd6ec4337fc1b3980ed40de368aa895c89f0714c459
4
+ data.tar.gz: 3f9df625f01d5c1bc64f36628388a5de2e1a38f84aac3f00be0a36c9b33a75f3
5
5
  SHA512:
6
- metadata.gz: 15549c3c5366a7217fb5ed65f87528f711527698e2a6ecdcefe7330a9e3a02d34a2f4c04fb5400632ab4a79ffab503d75dfbdc9d364d351b8d68e1e443c6dc81
7
- data.tar.gz: 5f5036aa09ed8ff7e72313352c1bc83a3e8eba76448524305cb4bfda5a49c05c119a080b0168a16a296bbe9ddc1d49b9397c08b0e124ca397ad3ecf4a79fe9b1
6
+ metadata.gz: 752c99cf8b373c6aec85803c3bd8c16104bdb50cee490138da55dcf1e924174f27121308f3a319b9632c97843b2e8f69277fc6900f881a8159b2679d6bdacbbf
7
+ data.tar.gz: 9b4d6b7903914234db467d42d44e33dd3d78126a3219a15b221e5a9080e84881e249b64f6fd506440fa3eb509d00e12eb3d5526445ab8bf455a0e7a5d57042e6
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Foreman Distributed Lock Manager
2
2
 
3
+ [<img src="https://opensourcelogos.aws.dmtech.cloud/dmTECH_opensource_logo%401x.svg" height="21" width="130">](https://www.dmtech.de/)
4
+
3
5
  This is a plugin for Foreman that allows Foreman to act as a distributed lock manager.
4
6
  Updates are key to security, but updates of an operating system are hard to apply and existing tools are hard to manage at scale. This might lead to a large drift between important security updates becoming available and all your hosts being successfully patched. Security experts recommend to install updates as soon as they come available. The ability to easily update software is the most effective way to improve server security. Automation is key to ensure this goal is reached.
5
7
  This plug-in aims to provide painless updates of the operating system. This keeps your company more secure and frees up resources of your operations team for more important tasks.
@@ -36,6 +38,12 @@ To process the HTTP status code in a bash script, you can do something like this
36
38
  curl --write-out %{http_code} -H 'Content-Type: application/json' -sS -o /dev/null -X PUT --key $(puppet config print hostprivkey) --cert $(puppet config print hostcert) https://foreman.example.com/api/dlmlocks/test/lock
37
39
  ```
38
40
 
41
+ ## Client setup
42
+
43
+ The Foreman plugin itself just provides a central lock manager. To setup automatic updates, you need to run a script on your clients that tries to acquire a lock in Foreman and takes care of the actual patching process.
44
+ The `contrib/client` directory in this repo contains a basic script and systemd units that should allow you to get started.
45
+ A [client counterpart](https://github.com/schlitzered/foreman_dlm_updater) written in Python has been developed by the community to make it easier to use this Foreman plug-in.
46
+
39
47
  ## Note about curl on macOS
40
48
 
41
49
  macOS uses curl with a different ssl library which gets problematic testing the cert-signed requests.
@@ -68,7 +76,7 @@ Fork and send a Pull Request. Thanks!
68
76
 
69
77
  ## Copyright
70
78
 
71
- Copyright (c) 2018 dm-drogerie markt GmbH & Co. KG, https://dm.de
79
+ Copyright (c) 2018 dmTECH GmbH, [dmtech.de](https://www.dmtech.de/)
72
80
 
73
81
  This program is free software: you can redistribute it and/or modify
74
82
  it under the terms of the GNU General Public License as published by
@@ -0,0 +1,41 @@
1
+ module Api
2
+ module V2
3
+ class DlmlockEventsController < V2::BaseController
4
+ include Api::Version2
5
+
6
+ before_action :find_required_nested_object
7
+
8
+ api :GET, '/dlmlocks/:dlmlock_id/dlmlock_events', N_('List all events for a given DLM lock')
9
+ param :dlmlock_id, String, :desc => N_('ID of DLM lock')
10
+
11
+ def index
12
+ @events = resource_scope_for_index
13
+ end
14
+
15
+ def resource_class
16
+ ForemanDlm::DlmlockEvent
17
+ end
18
+
19
+ private
20
+
21
+ def action_permission
22
+ case params[:action]
23
+ when 'index'
24
+ :view
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def allowed_nested_id
31
+ ['dlmlock_id']
32
+ end
33
+
34
+ def resource_class_for(resource)
35
+ return ForemanDlm::Dlmlock if resource == 'dlmlock'
36
+ return ForemanDlm::DlmlockEvent if resource == 'dlmlock_event'
37
+ super
38
+ end
39
+ end
40
+ end
41
+ end
@@ -4,10 +4,12 @@ module Api
4
4
  include Api::Version2
5
5
  include Foreman::Controller::Parameters::Dlmlocks
6
6
  include ::ForemanDlm::FindHostByClientCert
7
+ include ::ForemanDlm::UpdateCheckinTime
7
8
 
8
- wrap_parameters Dlmlock, :include => dlmlocks_params_filter.accessible_attributes(parameter_filter_context)
9
+ wrap_parameters ForemanDlm::Dlmlock, :include => dlmlocks_params_filter.accessible_attributes(parameter_filter_context)
9
10
 
10
11
  authorize_host_by_client_cert [:show, :release, :acquire]
12
+ update_host_checkin_time [:show, :release, :acquire]
11
13
 
12
14
  before_action :find_resource, :only => [:show, :update, :destroy]
13
15
  before_action :find_resource_or_create, :only => [:release, :acquire]
@@ -17,7 +19,7 @@ module Api
17
19
  def_param_group :dlmlock do
18
20
  param :dlmlock, Hash, :required => true, :action_aware => true do
19
21
  param :name, String, :required => true, :desc => N_('Name')
20
- param :type, ['Dlmlock:Update'], :required => true, :desc => N_('Type, e.g. Dlmlock:Update')
22
+ param :type, ['ForemanDlm::Dlmlock:Update'], :required => true, :desc => N_('Type, e.g. ForemanDlm::Dlmlock:Update')
21
23
  param :enabled, :bool, :desc => N_('Enable the lock')
22
24
  end
23
25
  end
@@ -41,7 +43,7 @@ module Api
41
43
  param_group :dlmlock, :as => :create
42
44
 
43
45
  def create
44
- @dlmlock = Dlmlock.new(dlmlocks_params)
46
+ @dlmlock = ForemanDlm::Dlmlock.new(dlmlocks_params)
45
47
  process_response @dlmlock.save
46
48
  end
47
49
 
@@ -95,6 +97,10 @@ module Api
95
97
  process_lock_response @dlmlock.release!(@host)
96
98
  end
97
99
 
100
+ def resource_class
101
+ ForemanDlm::Dlmlock
102
+ end
103
+
98
104
  private
99
105
 
100
106
  def resource_finder(scope, id)
@@ -108,7 +114,7 @@ module Api
108
114
  def find_resource_or_create
109
115
  find_resource
110
116
  rescue ActiveRecord::RecordNotFound
111
- @dlmlock = Dlmlock.create(:name => params[:id], :type => 'Dlmlock::Update')
117
+ @dlmlock = ForemanDlm::Dlmlock.create(:name => params[:id], :type => 'ForemanDlm::Dlmlock::Update')
112
118
  end
113
119
 
114
120
  def action_permission
@@ -0,0 +1,21 @@
1
+ module ForemanDlm
2
+ module UpdateCheckinTime
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def update_host_checkin_time(actions)
7
+ before_action(:only => actions) { update_detected_host_checkin_time }
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ # Updates the last_checkin timestamp of a user
14
+ def update_detected_host_checkin_time
15
+ return unless @detected_host
16
+ facet = @detected_host.dlm_facet || @detected_host.build_dlm_facet
17
+ facet.save unless facet.persisted?
18
+ facet.touch(:last_checkin_at)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module ForemanDlm
2
+ class ApplicationController < ::ApplicationController
3
+ def resource_class
4
+ self.class.to_s.sub(/Controller$/, '').singularize.constantize
5
+ end
6
+
7
+ def resource_name(resource = resource_class)
8
+ resource.name.split('::').last.downcase.singularize
9
+ end
10
+
11
+ def controller_name
12
+ "foreman_dlm_#{super}"
13
+ end
14
+
15
+ def controller_permission
16
+ super.sub(/^foreman_dlm_/, '')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,73 @@
1
+ module ForemanDlm
2
+ class DlmlocksController < ::ForemanDlm::ApplicationController
3
+ include ::Foreman::Controller::AutoCompleteSearch
4
+
5
+ before_action :setup_search_options, :only => :index
6
+ before_action :find_resource, :only => [:show, :destroy, :release, :disable, :enable]
7
+
8
+ def index
9
+ @dlmlocks = resource_base_search_and_page(:host)
10
+ end
11
+
12
+ def show; end
13
+
14
+ def destroy
15
+ if @dlmlock.destroy
16
+ process_success(
17
+ :success_msg => _('Successfully deleted lock.'),
18
+ :success_redirect => foreman_dlm_dlmlocks_path
19
+ )
20
+ else
21
+ process_error
22
+ end
23
+ end
24
+
25
+ def release
26
+ if @dlmlock.update(host: nil)
27
+ process_success(
28
+ :success_msg => _('Successfully released lock.'),
29
+ :success_redirect => foreman_dlm_dlmlocks_path
30
+ )
31
+ else
32
+ process_error
33
+ end
34
+ end
35
+
36
+ def disable
37
+ if @dlmlock.disable!
38
+ process_success(
39
+ :success_msg => _('Successfully disabled lock.'),
40
+ :success_redirect => foreman_dlm_dlmlocks_path
41
+ )
42
+ else
43
+ process_error
44
+ end
45
+ end
46
+
47
+ def enable
48
+ if @dlmlock.enable!
49
+ process_success(
50
+ :success_msg => _('Successfully enabled lock.'),
51
+ :success_redirect => foreman_dlm_dlmlocks_path
52
+ )
53
+ else
54
+ process_error
55
+ end
56
+ end
57
+
58
+ def model_of_controller
59
+ ForemanDlm::Dlmlock
60
+ end
61
+
62
+ private
63
+
64
+ def action_permission
65
+ case params[:action]
66
+ when 'release', 'disable', 'enable'
67
+ :edit
68
+ else
69
+ super
70
+ end
71
+ end
72
+ end
73
+ end
@@ -11,5 +11,36 @@ module ForemanDlm
11
11
  return 'text-success' if lock.taken?
12
12
  'text-info'
13
13
  end
14
+
15
+ def dlmlock_actions(lock, authorizer)
16
+ actions = []
17
+
18
+ if lock.enabled?
19
+ actions << display_link_if_authorized(
20
+ _('Disable'),
21
+ hash_for_disable_foreman_dlm_dlmlock_path(:id => lock.to_param).merge(auth_object: lock, authorizer: authorizer),
22
+ method: :put
23
+ )
24
+ end
25
+
26
+ if lock.disabled?
27
+ actions << display_link_if_authorized(
28
+ _('Enable'),
29
+ hash_for_enable_foreman_dlm_dlmlock_path(:id => lock.to_param).merge(auth_object: lock, authorizer: authorizer),
30
+ method: :put
31
+ )
32
+ end
33
+
34
+ if lock.taken?
35
+ actions << display_link_if_authorized(
36
+ _('Release'),
37
+ hash_for_release_foreman_dlm_dlmlock_path(:id => lock.to_param).merge(auth_object: lock, authorizer: authorizer),
38
+ method: :put
39
+ )
40
+ end
41
+
42
+ actions << display_delete_if_authorized(hash_for_foreman_dlm_dlmlock_path(:id => lock.to_param).merge(auth_object: lock, authorizer: authorizer), class: 'delete')
43
+ actions
44
+ end
14
45
  end
15
46
  end
@@ -0,0 +1,17 @@
1
+ module ForemanDlm
2
+ class RefreshDlmlockStatus < ApplicationJob
3
+ queue_as :refresh_dlmlock_status_queue
4
+
5
+ def perform(host_ids)
6
+ Host::Managed.where(id: host_ids).each(&:refresh_dlmlock_status)
7
+ end
8
+
9
+ rescue_from(StandardError) do |error|
10
+ Foreman::Logging.exception('Failed to refresh Distributed Lock status', error, logger: 'background')
11
+ end
12
+
13
+ def humanized_name
14
+ _('Refresh Distributed Lock status')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module ForemanDlm
2
+ module DlmFacetHostExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_one :dlm_facet, class_name: '::ForemanDlm::DlmFacet', foreign_key: :host_id, inverse_of: :host, dependent: :destroy
7
+
8
+ accepts_nested_attributes_for :dlm_facet, update_only: true, reject_if: ->(attrs) { attrs.values.compact.empty? }
9
+
10
+ scoped_search on: :last_checkin_at, relation: :dlm_facet, rename: :last_dlm_checkin_at, complete_value: true, only_explicit: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ module ForemanDlm
2
+ module Expirable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def expire(created_before:, batch_size:, sleep_time:)
7
+ created_before ||= 1.week
8
+ batch_size ||= 1000
9
+ sleep_time ||= 0.2
10
+
11
+ total_count = 0
12
+ event_ids = []
13
+
14
+ logger.info "Starting #{to_s.underscore.humanize.pluralize} expiration before #{created_before.ago} in batches of #{batch_size}"
15
+
16
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
17
+ loop do
18
+ event_ids = where(arel_table[:created_at].lt(created_before.ago)).reorder('').limit(batch_size).pluck(:id)
19
+
20
+ count = where(id: event_ids).reorder('').delete_all
21
+
22
+ total_count += count
23
+
24
+ break if event_ids.blank?
25
+ sleep sleep_time
26
+ end
27
+ duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) / 60).to_i
28
+
29
+ logger.info "Total #{to_s.underscore.humanize.pluralize} expired: #{total_count}, duration: #{duration} min(s)"
30
+
31
+ total_count
32
+ end
33
+ end
34
+ end
35
+ end
@@ -4,12 +4,23 @@ module ForemanDlm
4
4
 
5
5
  included do
6
6
  has_many :dlmlocks,
7
+ class_name: 'ForemanDlm::Dlmlock',
7
8
  foreign_key: 'host_id',
8
9
  dependent: :nullify,
9
10
  inverse_of: :host
10
11
 
12
+ has_many :dlmlock_events,
13
+ class_name: 'ForemanDlm::DlmlockEvent',
14
+ foreign_key: 'host_id',
15
+ dependent: :destroy,
16
+ inverse_of: :host
17
+
11
18
  define_model_callbacks :lock, :only => :after
12
19
  define_model_callbacks :unlock, :only => :after
13
20
  end
21
+
22
+ def refresh_dlmlock_status
23
+ refresh_statuses([HostStatus::DlmlockStatus])
24
+ end
14
25
  end
15
26
  end
@@ -0,0 +1,13 @@
1
+ module ForemanDlm
2
+ module UserExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :dlmlock_events,
7
+ class_name: 'ForemanDlm::DlmlockEvent',
8
+ foreign_key: 'user_id',
9
+ dependent: :nullify,
10
+ inverse_of: :user
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module ForemanDlm
2
+ class DlmFacet < ApplicationRecord
3
+ include Facets::Base
4
+
5
+ validates :host, presence: true, allow_blank: false
6
+ end
7
+ end
@@ -0,0 +1,136 @@
1
+ module ForemanDlm
2
+ class Dlmlock < ApplicationRecord
3
+ include Authorizable
4
+
5
+ def self.humanize_class_name
6
+ N_('Distributed Lock')
7
+ end
8
+
9
+ def self.dlm_stale_time
10
+ (Setting::General[:dlm_stale_time] || 4).hours
11
+ end
12
+
13
+ belongs_to_host
14
+
15
+ has_many :dlmlock_events,
16
+ class_name: '::ForemanDlm::DlmlockEvent',
17
+ foreign_key: 'dlmlock_id',
18
+ dependent: :destroy,
19
+ inverse_of: :dlmlock
20
+
21
+ validates :name, presence: true, uniqueness: true
22
+
23
+ after_save :log_enable_or_disable_event, if: -> { saved_change_to_enabled? }
24
+ after_save :log_release_and_acquire_events, if: -> { saved_change_to_host_id? }
25
+
26
+ def log_enable_or_disable_event
27
+ event_type = enabled ? :enable : :disable
28
+ log_event(host, event_type)
29
+ end
30
+
31
+ def log_release_and_acquire_events
32
+ old_host_id = saved_changes[:host_id].first
33
+ old_host = Host.find_by(id: old_host_id) if old_host_id
34
+ log_event(old_host, :release) if old_host
35
+ log_event(host, :acquire) if host
36
+ end
37
+
38
+ scope :locked, -> { where.not(host_id: nil) }
39
+ scope :stale, -> { locked.where('updated_at < ?', Time.now.utc - dlm_stale_time) }
40
+
41
+ scoped_search :on => :name, :complete_value => true, :default_order => true
42
+ scoped_search :relation => :host, :on => :name, :complete_value => true, :rename => :host
43
+ scoped_search :on => :type, :complete_value => true, :default_order => true
44
+ scoped_search :on => :enabled, :complete_value => { :true => true, :false => false }, :only_explicit => true
45
+
46
+ attr_accessor :old
47
+
48
+ def acquire!(host)
49
+ result = atomic_update(nil, host)
50
+ ForemanDlm::RefreshDlmlockStatus.set(wait: self.class.dlm_stale_time).perform_later([host.id]) if result
51
+ result
52
+ end
53
+
54
+ def release!(host)
55
+ atomic_update(host, nil)
56
+ end
57
+
58
+ def enable!
59
+ update(enabled: true)
60
+ end
61
+
62
+ def disable!
63
+ update(enabled: false)
64
+ end
65
+
66
+ def locked_by?(host)
67
+ self.host == host
68
+ end
69
+ alias acquired_by? locked_by?
70
+
71
+ def disabled?
72
+ !enabled?
73
+ end
74
+
75
+ def locked?
76
+ host.present?
77
+ end
78
+ alias taken? locked?
79
+
80
+ def humanized_type
81
+ _('Generic Lock')
82
+ end
83
+
84
+ private
85
+
86
+ def atomic_update(old_host, new_host)
87
+ changes = { host_id: new_host.try(:id) }
88
+ self.old = dup
89
+
90
+ query = {
91
+ id: id,
92
+ host_id: [new_host.try(:id), old_host.try(:id)],
93
+ enabled: true
94
+ }
95
+
96
+ updated = self.class.where(query).update(changes.merge(updated_at: Time.now.utc))
97
+
98
+ unless updated.count.zero?
99
+ reload
100
+ process_host_change(old_host, new_host)
101
+ [old_host, new_host].compact.each(&:refresh_dlmlock_status)
102
+ return true
103
+ end
104
+
105
+ log_event(host, :fail)
106
+
107
+ false
108
+ end
109
+
110
+ def process_host_change(old_host, new_host)
111
+ return if host.try(:id) == old.host.try(:id)
112
+
113
+ run_callback(old_host, :unlock) if old.host
114
+
115
+ return unless host
116
+
117
+ run_callback(new_host, :lock)
118
+ end
119
+
120
+ def log_event(host, event_type)
121
+ DlmlockEvent.create!(
122
+ dlmlock: self,
123
+ event_type: event_type,
124
+ host: host,
125
+ user: User.current
126
+ )
127
+ end
128
+
129
+ def run_callback(h, callback)
130
+ h.run_callbacks callback do
131
+ logger.debug { "custom hook after_#{callback} on #{h} will be executed if defined." }
132
+ true
133
+ end
134
+ end
135
+ end
136
+ end