foreman_dlm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +84 -0
- data/Rakefile +47 -0
- data/app/controllers/api/v2/dlmlocks_controller.rb +157 -0
- data/app/controllers/concerns/foreman/controller/parameters/dlmlocks.rb +15 -0
- data/app/controllers/concerns/foreman_dlm/find_host_by_client_cert.rb +63 -0
- data/app/controllers/concerns/foreman_dlm/find_host_by_ip.rb +54 -0
- data/app/controllers/dlmlocks_controller.rb +13 -0
- data/app/helpers/foreman_dlm/dlmlock_helper.rb +15 -0
- data/app/models/concerns/foreman_dlm/host_extensions.rb +14 -0
- data/app/models/concerns/foreman_dlm/host_monitoring_extensions.rb +36 -0
- data/app/models/dlmlock/update.rb +5 -0
- data/app/models/dlmlock.rb +79 -0
- data/app/views/api/v2/dlmlocks/acquire.json.rabl +3 -0
- data/app/views/api/v2/dlmlocks/base.json.rabl +3 -0
- data/app/views/api/v2/dlmlocks/create.json.rabl +3 -0
- data/app/views/api/v2/dlmlocks/index.json.rabl +3 -0
- data/app/views/api/v2/dlmlocks/main.json.rabl +7 -0
- data/app/views/api/v2/dlmlocks/release.json.rabl +3 -0
- data/app/views/api/v2/dlmlocks/show.json.rabl +8 -0
- data/app/views/api/v2/dlmlocks/update.json.rabl +3 -0
- data/app/views/api/v2/errors/precondition_failed.json.rabl +5 -0
- data/app/views/dlmlocks/_details.html.erb +35 -0
- data/app/views/dlmlocks/_list.html.erb +45 -0
- data/app/views/dlmlocks/index.html.erb +2 -0
- data/app/views/dlmlocks/show.html.erb +7 -0
- data/app/views/dlmlocks/welcome.html.erb +14 -0
- data/config/routes.rb +25 -0
- data/db/migrate/20170824084100_add_dlmlock.foreman_dlm.rb +12 -0
- data/lib/foreman_dlm/engine.rb +76 -0
- data/lib/foreman_dlm/version.rb +3 -0
- data/lib/foreman_dlm.rb +4 -0
- data/lib/tasks/foreman_dlm_tasks.rake +37 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_dlm.po +19 -0
- data/locale/foreman_dlm.pot +19 -0
- data/locale/gemspec.rb +2 -0
- data/test/controllers/api/v2/dlmlocks_controller_test.rb +367 -0
- data/test/controllers/dlmlocks_test.rb +24 -0
- data/test/controllers/find_host_by_client_cert_test.rb +91 -0
- data/test/factories/dlmlock.rb +6 -0
- data/test/models/dlmlock_test.rb +201 -0
- data/test/models/host_monitoring_test.rb +42 -0
- data/test/test_plugin_helper.rb +9 -0
- 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,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,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>
|