foreman_dlm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +84 -0
  4. data/Rakefile +47 -0
  5. data/app/controllers/api/v2/dlmlocks_controller.rb +157 -0
  6. data/app/controllers/concerns/foreman/controller/parameters/dlmlocks.rb +15 -0
  7. data/app/controllers/concerns/foreman_dlm/find_host_by_client_cert.rb +63 -0
  8. data/app/controllers/concerns/foreman_dlm/find_host_by_ip.rb +54 -0
  9. data/app/controllers/dlmlocks_controller.rb +13 -0
  10. data/app/helpers/foreman_dlm/dlmlock_helper.rb +15 -0
  11. data/app/models/concerns/foreman_dlm/host_extensions.rb +14 -0
  12. data/app/models/concerns/foreman_dlm/host_monitoring_extensions.rb +36 -0
  13. data/app/models/dlmlock/update.rb +5 -0
  14. data/app/models/dlmlock.rb +79 -0
  15. data/app/views/api/v2/dlmlocks/acquire.json.rabl +3 -0
  16. data/app/views/api/v2/dlmlocks/base.json.rabl +3 -0
  17. data/app/views/api/v2/dlmlocks/create.json.rabl +3 -0
  18. data/app/views/api/v2/dlmlocks/index.json.rabl +3 -0
  19. data/app/views/api/v2/dlmlocks/main.json.rabl +7 -0
  20. data/app/views/api/v2/dlmlocks/release.json.rabl +3 -0
  21. data/app/views/api/v2/dlmlocks/show.json.rabl +8 -0
  22. data/app/views/api/v2/dlmlocks/update.json.rabl +3 -0
  23. data/app/views/api/v2/errors/precondition_failed.json.rabl +5 -0
  24. data/app/views/dlmlocks/_details.html.erb +35 -0
  25. data/app/views/dlmlocks/_list.html.erb +45 -0
  26. data/app/views/dlmlocks/index.html.erb +2 -0
  27. data/app/views/dlmlocks/show.html.erb +7 -0
  28. data/app/views/dlmlocks/welcome.html.erb +14 -0
  29. data/config/routes.rb +25 -0
  30. data/db/migrate/20170824084100_add_dlmlock.foreman_dlm.rb +12 -0
  31. data/lib/foreman_dlm/engine.rb +76 -0
  32. data/lib/foreman_dlm/version.rb +3 -0
  33. data/lib/foreman_dlm.rb +4 -0
  34. data/lib/tasks/foreman_dlm_tasks.rake +37 -0
  35. data/locale/Makefile +60 -0
  36. data/locale/en/foreman_dlm.po +19 -0
  37. data/locale/foreman_dlm.pot +19 -0
  38. data/locale/gemspec.rb +2 -0
  39. data/test/controllers/api/v2/dlmlocks_controller_test.rb +367 -0
  40. data/test/controllers/dlmlocks_test.rb +24 -0
  41. data/test/controllers/find_host_by_client_cert_test.rb +91 -0
  42. data/test/factories/dlmlock.rb +6 -0
  43. data/test/models/dlmlock_test.rb +201 -0
  44. data/test/models/host_monitoring_test.rb +42 -0
  45. data/test/test_plugin_helper.rb +9 -0
  46. metadata +124 -0
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Foreman Distributed Lock Manager
2
+
3
+ This is a plugin for Foreman that allows Foreman to act as a distributed lock manager.
4
+ 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
+ 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.
6
+
7
+ With this plugin servers can acquire a lock in Foreman to ensure only one server in a cluster installs updates at the same time. This can successfully prevent service disruptions.
8
+
9
+ ## Compatibility
10
+
11
+ | Foreman Version | Plugin Version |
12
+ | --------------- | -------------- |
13
+ | >= 1.15 | any |
14
+
15
+ ## Installation
16
+
17
+ See [Plugins install instructions](https://theforeman.org/plugins/)
18
+ for how to install Foreman plugins.
19
+ On Enterprise Linux, you need to install the package `tfm-rubygem-foreman_dlm`.
20
+
21
+ ## Usage
22
+
23
+ Servers are identified by their puppet certificates. Support for using subscription-manager certificates is planned, but currently not implemented.
24
+ Here you find a usage example using `curl` that shows how to acquire the `test` lock. The lock will be created if it does not exist.
25
+
26
+ ```
27
+ curl -D - -H 'Content-Type: application/json' --key $(puppet config print hostprivkey) --cert $(puppet config print hostcert) -X PUT https://foreman.example.com/api/dlmlocks/test/lock
28
+ ```
29
+
30
+ Use the HTTP method `GET` to show a lock, `PUT` to acquire a lock and `DELETE` to release a lock.
31
+ Foreman will respond with the HTTP status code `200 OK` if the action was successful and `412 Precondition Failed` if the lock could not be acquired or release. This may happen, if the lock is taken by another host.
32
+
33
+ To process the HTTP status code in a bash script, you can do something like this:
34
+ ```
35
+ 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
36
+ ```
37
+
38
+ ## Note about curl on macOS
39
+
40
+ macOS uses curl with a different ssl library which gets problematic testing the cert-signed requests.
41
+ The Error:
42
+ `WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure Transport. The private key must be in the Keychain.`
43
+
44
+ A fix is described here:
45
+ https://github.com/curl/curl/issues/283
46
+
47
+ ```
48
+ $ brew install curl --with-openssl
49
+ $ brew link curl --force
50
+ ```
51
+
52
+ After that `curl --version` changes from
53
+ ```
54
+ $ curl --version
55
+ curl 7.54.0 (x86_64-apple-darwin16.0) libcurl/7.54.0 SecureTransport zlib/1.2.8
56
+ ```
57
+ to
58
+ ```
59
+ $ curl --version
60
+ curl 7.56.1 (x86_64-apple-darwin16.7.0) libcurl/7.56.1 OpenSSL/1.0.2m zlib/1.2.8
61
+ ```
62
+
63
+
64
+ ## Contributing
65
+
66
+ Fork and send a Pull Request. Thanks!
67
+
68
+ ## Copyright
69
+
70
+ Copyright (c) 2018 dm-drogerie markt GmbH & Co. KG, https://dm.de
71
+
72
+ This program is free software: you can redistribute it and/or modify
73
+ it under the terms of the GNU General Public License as published by
74
+ the Free Software Foundation, either version 3 of the License, or
75
+ (at your option) any later version.
76
+
77
+ This program is distributed in the hope that it will be useful,
78
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
79
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
80
+ GNU General Public License for more details.
81
+
82
+ You should have received a copy of the GNU General Public License
83
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
84
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ForemanDlm'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ task default: :test
37
+
38
+ begin
39
+ require 'rubocop/rake_task'
40
+ RuboCop::RakeTask.new
41
+ rescue => _
42
+ puts 'Rubocop not loaded.'
43
+ end
44
+
45
+ task :default do
46
+ Rake::Task['rubocop'].execute
47
+ end
@@ -0,0 +1,157 @@
1
+ module Api
2
+ module V2
3
+ class DlmlocksController < V2::BaseController
4
+ include Api::Version2
5
+ include Foreman::Controller::Parameters::Dlmlocks
6
+ include ::ForemanDlm::FindHostByClientCert
7
+
8
+ wrap_parameters Dlmlock, :include => dlmlocks_params_filter.accessible_attributes(parameter_filter_context)
9
+
10
+ authorize_host_by_client_cert [:show, :release, :acquire]
11
+
12
+ before_action :find_resource, :only => [:show, :update, :destroy]
13
+ before_action :find_resource_or_create, :only => [:release, :acquire]
14
+ before_action :find_host, :only => [:release, :acquire]
15
+ before_action :setup_search_options, :only => [:index]
16
+
17
+ def_param_group :dlmlock do
18
+ param :dlmlock, Hash, :required => true, :action_aware => true do
19
+ param :name, String, :required => true, :desc => N_('Name')
20
+ param :type, ['Dlmlock:Update'], :required => true, :desc => N_('Type, e.g. Dlmlock:Update')
21
+ param :enabled, :bool, :desc => N_('Enable the lock')
22
+ end
23
+ end
24
+
25
+ api :GET, '/dlmlocks/', N_('List all DLM locks')
26
+ param_group :search_and_pagination, ::Api::V2::BaseController
27
+
28
+ def index
29
+ @dlmlocks = resource_scope_for_index
30
+ @total = resource_scope_for_index.count
31
+ end
32
+
33
+ api :GET, '/dlmlocks/:id/', N_('Show a DLM lock')
34
+ api :GET, '/dlmlocks/:id/lock', N_('Show a DLM lock')
35
+ error 404, 'Lock could not be found.'
36
+ param :id, String, :required => true, :desc => N_('Id or name of the DLM lock')
37
+
38
+ def show; end
39
+
40
+ api :POST, '/dlmlocks', N_('Create a DLM lock')
41
+ param_group :dlmlock, :as => :create
42
+
43
+ def create
44
+ @dlmlock = Dlmlock.new(dlmlocks_params)
45
+ process_response @dlmlock.save
46
+ end
47
+
48
+ api :PUT, "/dlmlocks/:id/", N_("Update a DLM lock")
49
+ param :id, String, :required => true, :desc => N_('Id or name of the DLM lock')
50
+ param_group :dlmlock
51
+
52
+ def update
53
+ process_response @dlmlock.update_attributes(dlmlocks_params)
54
+ end
55
+
56
+ api :DELETE, '/dlmlocks/:id/', N_('Delete a DLM lock')
57
+ param :id, String, :required => true, :desc => N_('Id or name of the DLM lock')
58
+
59
+ def destroy
60
+ process_response @dlmlock.destroy
61
+ end
62
+
63
+ api :PUT, '/dlmlocks/:id/lock', N_('Acquire a DLM lock')
64
+ param :id, String, :required => true, :desc => N_('Id or name of the DLM lock')
65
+ error 200, 'Lock acquired successfully.'
66
+ error 412, 'Lock could not be acquired.'
67
+ description <<-EOS
68
+ == Acquire a lock
69
+ This action acquires a lock.
70
+ It fails, if the lock is currently taken by another host.
71
+
72
+ == Authentication & Host Identification
73
+ The host is authenticated via a client certificate and identified via the CN of that certificate.
74
+ EOS
75
+
76
+ def acquire
77
+ process_lock_response @dlmlock.acquire!(@host)
78
+ end
79
+
80
+ api :DELETE, '/dlmlocks/:id/lock', N_('Release a DLM lock')
81
+ param :id, String, :required => true, :desc => N_('Id or name of the DLM lock')
82
+ error 200, 'Lock released successfully.'
83
+ error 412, 'Lock could not be released.'
84
+
85
+ description <<-EOS
86
+ == Release a lock
87
+ This action releases a lock.
88
+ It fails, if the lock is currently taken by another host.
89
+
90
+ == Authentication & Host Identification
91
+ The host is authenticated via a client certificate and identified via the CN of that certificate.
92
+ EOS
93
+
94
+ def release
95
+ process_lock_response @dlmlock.release!(@host)
96
+ end
97
+
98
+ private
99
+
100
+ def resource_finder(scope, id)
101
+ super
102
+ rescue ActiveRecord::RecordNotFound
103
+ result = scope.find_by(:name => id)
104
+ raise ActiveRecord::RecordNotFound unless result
105
+ result
106
+ end
107
+
108
+ def find_resource_or_create
109
+ find_resource
110
+ rescue ActiveRecord::RecordNotFound
111
+ @dlmlock = Dlmlock.create(:name => params[:id], :type => 'Dlmlock::Update')
112
+ end
113
+
114
+ def action_permission
115
+ case params[:action]
116
+ when 'release', 'acquire'
117
+ :edit
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ def find_host
124
+ @host = detected_host
125
+ unless @host
126
+ logger.info 'Denying access because no host could be detected.'
127
+ if User.current
128
+ render_error 'access_denied', :status => :forbidden, :locals => { :details => 'You need to authenticate with a valid client cert. The DN has to match a known host.' }
129
+ else
130
+ render_error 'unauthorized', :status => :unauthorized, :locals => { :user_login => get_client_cert_hostname }
131
+ end
132
+ end
133
+ true
134
+ end
135
+
136
+ def process_lock_response(condition, response = nil)
137
+ if condition
138
+ process_success response
139
+ else
140
+ process_lock_resource_error
141
+ end
142
+ end
143
+
144
+ def process_lock_resource_error(options = {})
145
+ resource = options[:resource] || get_resource(options[:message])
146
+
147
+ if resource.respond_to?(:permission_failed?) && resource.permission_failed?
148
+ deny_access
149
+ else
150
+ render_error 'precondition_failed', :status => :precondition_failed, :locals => {
151
+ :message => 'Precondition failed. Lock is in invalid state for this operation.',
152
+ }
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,15 @@
1
+ module Foreman::Controller::Parameters::Dlmlocks
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def dlmlocks_params_filter
6
+ Foreman::ParameterFilter.new(::SshKey).tap do |filter|
7
+ filter.permit :name, :type, :host_id, :enabled
8
+ end
9
+ end
10
+ end
11
+
12
+ def dlmlocks_params
13
+ self.class.dlmlocks_params_filter.filter_params(params, parameter_filter_context)
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ module ForemanDlm
2
+ module FindHostByClientCert
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def authorize_host_by_client_cert(actions, options = {})
7
+ skip_before_action :require_login, :only => actions, :raise => false
8
+ skip_before_action :authorize, :only => actions
9
+ skip_before_action :verify_authenticity_token, :only => actions
10
+ skip_before_action :set_taxonomy, :only => actions, :raise => false
11
+ skip_before_action :session_expiry, :update_activity_time, :only => actions
12
+ before_action(:only => actions) { require_client_cert_or_login }
13
+ attr_reader :detected_host
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Permits Hosts authorized by their client cert
20
+ # or a user with permission
21
+ def require_client_cert_or_login
22
+ @detected_host = find_host_by_client_cert
23
+
24
+ if detected_host
25
+ set_admin_user
26
+ return true
27
+ end
28
+
29
+ require_login
30
+ unless User.current
31
+ render_error 'unauthorized', :status => :unauthorized unless performed? && api_request?
32
+ return false
33
+ end
34
+ authorize
35
+ end
36
+
37
+ def find_host_by_client_cert
38
+ hostname = get_client_cert_hostname
39
+
40
+ return unless hostname
41
+
42
+ host ||= Host::Base.find_by_certname(hostname) ||
43
+ Host::Base.find_by_name(hostname)
44
+ logger.info { "Found Host #{host} by client cert #{hostname}" } if host
45
+ host
46
+ end
47
+
48
+ def get_client_cert_hostname
49
+ verify = request.env[Setting[:ssl_client_verify_env]]
50
+ unless verify == 'SUCCESS'
51
+ logger.info { "Client certificate is invalid: #{verify}" }
52
+ return
53
+ end
54
+
55
+ dn = request.env[Setting[:ssl_client_dn_env]]
56
+ return unless (dn && dn =~ /CN=([^\s\/,]+)/i)
57
+
58
+ hostname = $1.downcase
59
+ logger.debug "Extracted hostname '#{hostname}' from client certificate."
60
+ hostname
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,54 @@
1
+ module ForemanDlm
2
+ module FindHostByIp
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def authorize_host_by_ip(actions, options = {})
7
+ skip_before_action :require_login, :only => actions, :raise => false
8
+ skip_before_action :authorize, :only => actions
9
+ skip_before_action :verify_authenticity_token, :only => actions
10
+ skip_before_action :set_taxonomy, :only => actions, :raise => false
11
+ skip_before_action :session_expiry, :update_activity_time, :only => actions
12
+ before_action(:only => actions) { require_ip_auth_or_login }
13
+ attr_reader :detected_host
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Permits Hosts with an IP or a user with permission
20
+ def require_ip_auth_or_login
21
+ @detected_host = find_host_by_ip
22
+
23
+ if detected_host
24
+ set_admin_user
25
+ return true
26
+ end
27
+
28
+ require_login
29
+ unless User.current
30
+ render_error 'access_denied', :status => :forbidden unless performed? && api_request?
31
+ return false
32
+ end
33
+ authorize
34
+ end
35
+
36
+ def find_host_by_ip
37
+ # try to find host based on our client ip address
38
+ ip = ip_from_request_env
39
+
40
+ # in case we got back multiple ips (see #1619)
41
+ ip = ip.split(',').first
42
+
43
+ # host is readonly because of association so we reload it if we find it
44
+ host = Host.joins(:provision_interface).where(:nics => { :ip => ip }).first
45
+ host = host ? Host.find(host.id) : nil
46
+ logger.info { "Found Host #{host} by request IP #{ip}" } if host
47
+ host
48
+ end
49
+
50
+ def ip_from_request_env
51
+ request.env['REMOTE_ADDR']
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ class DlmlocksController < ::ApplicationController
2
+ include Foreman::Controller::AutoCompleteSearch
3
+
4
+ before_action :setup_search_options, :only => :index
5
+ before_action :find_resource, :only => [:show]
6
+
7
+ def index
8
+ @dlmlocks = resource_base_search_and_page(:host)
9
+ end
10
+
11
+ def show
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module ForemanDlm
2
+ module DlmlockHelper
3
+ def dlmlock_status_icon_class(lock)
4
+ return 'ban' if lock.disabled?
5
+ return 'lock' if lock.taken?
6
+ 'unlock'
7
+ end
8
+
9
+ def dlmlock_status_icon_color_class(lock)
10
+ return 'text-danger' if lock.disabled?
11
+ return 'text-success' if lock.taken?
12
+ 'text-info'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module ForemanDlm
2
+ module HostExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :dlmlocks,
7
+ foreign_key: 'host_id',
8
+ dependent: :nullify
9
+
10
+ define_model_callbacks :lock, :only => :after
11
+ define_model_callbacks :unlock, :only => :after
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ module ForemanDlm
2
+ module HostMonitoringExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_lock :add_lock_monitoring_downtime
7
+ after_unlock :remove_lock_monitoring_downtime
8
+ end
9
+
10
+ def add_lock_monitoring_downtime
11
+ return unless monitored?
12
+ logger.info "Setting Monitoring downtime for #{self}"
13
+ monitoring.set_downtime_host(self, lock_monitoring_downtime_options)
14
+ true
15
+ rescue ProxyAPI::ProxyException => e
16
+ Foreman::Logging.exception("Unable to set monitoring downtime for #{e}", e)
17
+ end
18
+
19
+ def remove_lock_monitoring_downtime
20
+ return unless monitored?
21
+ logger.info "Deleting Monitoring downtime for #{self}"
22
+ monitoring.del_downtime_host(self, lock_monitoring_downtime_options)
23
+ true
24
+ rescue ProxyAPI::ProxyException => e
25
+ Foreman::Logging.exception("Unable to remove monitoring downtime for #{e}", e)
26
+ end
27
+
28
+ def lock_monitoring_downtime_options
29
+ {
30
+ comment: _('Host acquired lock.'),
31
+ start_time: Time.current.to_i,
32
+ end_time: Time.current.advance(:minutes => 180).to_i
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ class Dlmlock::Update < Dlmlock
2
+ def humanized_type
3
+ _('Update Lock')
4
+ end
5
+ end
@@ -0,0 +1,79 @@
1
+ class Dlmlock < ActiveRecord::Base
2
+ include Authorizable
3
+
4
+ def self.humanize_class_name
5
+ N_('Distributed Lock')
6
+ end
7
+
8
+ belongs_to_host
9
+ audited
10
+
11
+ validates :name, presence: true, uniqueness: true
12
+
13
+ scoped_search :on => :name, :complete_value => true, :default_order => true
14
+ scoped_search :relation => :host, :on => :name, :complete_value => true, :rename => :host
15
+ scoped_search :on => :type, :complete_value => true, :default_order => true
16
+ scoped_search :on => :enabled, :complete_value => { :true => true, :false => false }, :only_explicit => true
17
+
18
+ attr_accessor :old
19
+
20
+ def acquire!(host)
21
+ atomic_update(nil, host)
22
+ end
23
+
24
+ def release!(host)
25
+ atomic_update(host, nil)
26
+ end
27
+
28
+ def locked_by?(host)
29
+ self.host == host
30
+ end
31
+ alias_method :acquired_by?, :locked_by?
32
+
33
+ def disabled?
34
+ !enabled?
35
+ end
36
+
37
+ def locked?
38
+ host.present?
39
+ end
40
+ alias_method :taken?, :locked?
41
+
42
+ def humanized_type
43
+ _('Generic Lock')
44
+ end
45
+
46
+ private
47
+
48
+ def atomic_update(old_host, new_host)
49
+ changes = {
50
+ host_id: new_host.try(:id)
51
+ }
52
+ self.old = dup
53
+ num_updated = self.class.where(
54
+ id: id,
55
+ host_id: [new_host.try(:id), old_host.try(:id)],
56
+ enabled: true
57
+ ).update_all(changes.merge(updated_at: DateTime.now))
58
+ if num_updated > 0
59
+ reload
60
+ process_host_change(old_host, new_host, changes)
61
+ return self
62
+ end
63
+ false
64
+ end
65
+
66
+ def process_host_change(old_host, new_host, changes)
67
+ return if host.try(:id) == old.host.try(:id)
68
+ write_audit(action: 'update', audited_changes: changes)
69
+ run_callback(old_host, :unlock) if old.host
70
+ run_callback(new_host, :lock) if host
71
+ end
72
+
73
+ def run_callback(h, callback)
74
+ h.run_callbacks callback do
75
+ logger.debug { "custom hook after_#{callback} on #{h} will be executed if defined." }
76
+ true
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ object @dlmlock
2
+
3
+ extends 'api/v2/dlmlocks/main'
@@ -0,0 +1,3 @@
1
+ object @dlmlock
2
+
3
+ attributes :id, :host_id, :name, :enabled, :type
@@ -0,0 +1,3 @@
1
+ object @dlmlock
2
+
3
+ extends 'api/v2/dlmlocks/show'
@@ -0,0 +1,3 @@
1
+ collection @dlmlocks
2
+
3
+ extends 'api/v2/dlmlocks/main'
@@ -0,0 +1,7 @@
1
+ object @dlmlock
2
+
3
+ extends 'api/v2/dlmlocks/base'
4
+
5
+ attributes :created_at, :updated_at
6
+
7
+ child(:host) { attributes :name }
@@ -0,0 +1,3 @@
1
+ object @dlmlock
2
+
3
+ extends 'api/v2/dlmlocks/main'
@@ -0,0 +1,8 @@
1
+ object @dlmlock
2
+
3
+ extends 'api/v2/dlmlocks/main'
4
+
5
+ child :host do
6
+ node(:name) { |host| host.name }
7
+ node(:self) { |host| host == @detected_host } if @detected_host
8
+ end
@@ -0,0 +1,3 @@
1
+ object @dlmlock
2
+
3
+ extends 'api/v2/dlmlocks/show'
@@ -0,0 +1,5 @@
1
+ object resource = controller.get_resource
2
+
3
+ attributes :id
4
+
5
+ node(:message) { locals[:message] || 'Precondition failed.' }
@@ -0,0 +1,35 @@
1
+ <table class="<%= table_css_classes('table-fixed') %>">
2
+ <thead>
3
+ <tr>
4
+ <th><%= _("Owner") %></th>
5
+ <th><%= _("Initiator") %></th>
6
+ <th><%= _("Timestamp") %></th>
7
+ <th><%= _("Enabled") %></th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <% @dlmlock.audits.includes(:user).reorder(created_at: :desc).each do |audit| %>
12
+ <% revision = audit.revision %>
13
+ <tr>
14
+ <td>
15
+ <% if revision.host.present? %>
16
+ <%= link_to_if_authorized(revision.host.name, hash_for_host_path(:id => revision.host)) %>
17
+ <% end %>
18
+ </td>
19
+ <td>
20
+ <% if audit.user.hidden? %>
21
+ <em><%= audit.user.name %></em>
22
+ <% else %>
23
+ <%= link_to_if_authorized(audit.user.name, hash_for_edit_user_path(audit.user)) %>
24
+ <% end %>
25
+ </td>
26
+ <td>
27
+ <%= audit.created_at %>
28
+ </td>
29
+ <td>
30
+ <%= revision.enabled? %>
31
+ </td>
32
+ </tr>
33
+ <% end %>
34
+ </tbody>
35
+ </table>