puppetdb_foreman 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +674 -0
- data/README.md +82 -0
- data/Rakefile +47 -0
- data/app/controllers/api/v2/puppetdb_nodes_controller.rb +63 -0
- data/app/controllers/puppetdb_foreman/nodes_controller.rb +39 -0
- data/app/controllers/puppetdb_foreman/puppetdb_controller.rb +17 -21
- data/app/models/concerns/orchestration/puppetdb.rb +27 -0
- data/app/models/puppetdb_foreman/host_extensions.rb +14 -66
- data/app/models/setting/puppetdb.rb +27 -36
- data/app/services/puppetdb.rb +31 -0
- data/app/services/puppetdb_client/base.rb +109 -0
- data/app/services/puppetdb_client/v1.rb +25 -0
- data/app/services/puppetdb_client/v3.rb +41 -0
- data/app/services/puppetdb_host.rb +47 -0
- data/app/views/api/v2/puppetdb_nodes/import.json.rabl +3 -0
- data/app/views/api/v2/puppetdb_nodes/index.json.rabl +5 -0
- data/app/views/api/v2/puppetdb_nodes/unknown.json.rabl +3 -0
- data/app/views/puppetdb_foreman/nodes/index.html.erb +82 -0
- data/config/routes.rb +29 -1
- data/lib/puppetdb_foreman/engine.rb +33 -7
- data/lib/puppetdb_foreman/version.rb +1 -1
- data/lib/tasks/puppetdb_foreman_tasks.rake +35 -0
- data/test/controllers/api/v2/puppetdb_nodes_controller_test.rb +68 -0
- data/test/controllers/nodes_controller_test.rb +56 -0
- data/test/models/host_test.rb +47 -0
- data/test/static_fixtures/facts.json +513 -0
- data/test/static_fixtures/query_nodes.json +61 -0
- data/test/test_plugin_helper.rb +27 -0
- data/test/unit/puppetdb_host_test.rb +43 -0
- data/test/unit/puppetdb_test.rb +86 -0
- metadata +94 -6
@@ -0,0 +1,31 @@
|
|
1
|
+
module Puppetdb
|
2
|
+
def self.client
|
3
|
+
options = {
|
4
|
+
:uri => uri,
|
5
|
+
:ssl_ca_file => Setting[:puppetdb_ssl_ca_file],
|
6
|
+
:ssl_certificate_file => Setting[:puppetdb_ssl_certificate],
|
7
|
+
:ssl_private_key_file => Setting[:puppetdb_ssl_private_key]
|
8
|
+
}
|
9
|
+
if uri.path.start_with?('/pdb')
|
10
|
+
PuppetdbClient::V3.new(options)
|
11
|
+
else
|
12
|
+
PuppetdbClient::V1.new(options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.uri
|
17
|
+
URI.parse(Setting[:puppetdb_address])
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.enabled?
|
21
|
+
[true, 'true'].include? Setting[:puppetdb_enabled]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.configured?
|
25
|
+
Setting[:puppetdb_address].present?
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.ready?
|
29
|
+
enabled? && configured?
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module PuppetdbClient
|
2
|
+
class Base
|
3
|
+
delegate :logger, :to => :Rails
|
4
|
+
attr_reader :uri, :ssl_ca_file, :ssl_certificate_file, :ssl_private_key_file
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
@uri = opts.fetch(:uri)
|
8
|
+
@ssl_ca_file = opts.fetch(:ssl_ca_file)
|
9
|
+
@ssl_certificate_file = opts.fetch(:ssl_certificate_file)
|
10
|
+
@ssl_private_key_file = opts.fetch(:ssl_private_key_file)
|
11
|
+
end
|
12
|
+
|
13
|
+
def deactivate_node(nodename)
|
14
|
+
body = parse(post(command_url, deactivate_node_payload(nodename)))
|
15
|
+
uuid = body['uuid']
|
16
|
+
logger.info "Submitted deactivate_node job to PuppetDB with UUID: #{uuid}"
|
17
|
+
uuid
|
18
|
+
end
|
19
|
+
|
20
|
+
def query_nodes
|
21
|
+
nodes = parse(get(nodes_url))
|
22
|
+
nodes.map { |node| node['name'] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def facts(nodename)
|
26
|
+
parse(get("#{facts_url}?#{URI.escape("query=[\"=\", \"certname\", \"#{nodename}\"]")}"))
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def connection(url)
|
32
|
+
RestClient::Resource.new(
|
33
|
+
request_url(url),
|
34
|
+
request_options
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def request_url(url)
|
39
|
+
URI.join(baseurl, url).to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def baseurl
|
43
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(endpoint)
|
47
|
+
logger.debug "PuppetdbClient: GET request to #{endpoint}"
|
48
|
+
connection(endpoint).get.body
|
49
|
+
end
|
50
|
+
|
51
|
+
def post(endpoint, payload)
|
52
|
+
logger.debug "PuppetdbClient: POST request to #{endpoint} with payload: #{payload}"
|
53
|
+
connection(endpoint).post(payload, post_options).body
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse(body)
|
57
|
+
JSON.parse(body)
|
58
|
+
end
|
59
|
+
|
60
|
+
def post_options
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_options
|
65
|
+
{
|
66
|
+
headers: request_headers
|
67
|
+
}.merge(auth_options).merge(ssl_options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def request_headers
|
71
|
+
{
|
72
|
+
'Accept' => 'application/json'
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def ssl_options
|
77
|
+
if ssl_ca_file
|
78
|
+
{
|
79
|
+
ssl_ca_file: ssl_ca_file,
|
80
|
+
verify_ssl: true
|
81
|
+
}
|
82
|
+
else
|
83
|
+
{
|
84
|
+
verify_ssl: false
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def auth_options
|
90
|
+
return {} unless certificate_request?
|
91
|
+
{
|
92
|
+
ssl_client_cert: ssl_certificate,
|
93
|
+
ssl_client_key: ssl_private_key
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def certificate_request?
|
98
|
+
ssl_certificate_file.present? && ssl_private_key_file.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
def ssl_certificate
|
102
|
+
OpenSSL::X509::Certificate.new(File.read(ssl_certificate_file))
|
103
|
+
end
|
104
|
+
|
105
|
+
def ssl_private_key
|
106
|
+
OpenSSL::PKey::RSA.new(File.read(ssl_private_key_file), nil)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PuppetdbClient
|
2
|
+
class V1 < Base
|
3
|
+
# The payload is expected to be the certname of a node as a serialized JSON string.
|
4
|
+
def deactivate_node_payload(nodename)
|
5
|
+
payload = {
|
6
|
+
'command' => 'deactivate node',
|
7
|
+
'version' => 1,
|
8
|
+
'payload' => nodename.to_json
|
9
|
+
}.to_json
|
10
|
+
'payload=' + payload
|
11
|
+
end
|
12
|
+
|
13
|
+
def command_url
|
14
|
+
'/v3/commands'
|
15
|
+
end
|
16
|
+
|
17
|
+
def nodes_url
|
18
|
+
'/v3/nodes'
|
19
|
+
end
|
20
|
+
|
21
|
+
def facts_url
|
22
|
+
'/v3/facts'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module PuppetdbClient
|
2
|
+
class V3 < Base
|
3
|
+
# The payload is formatted as a JSON map.
|
4
|
+
# certname: The name of the node for which the catalog was compiled.
|
5
|
+
# producer_timestamp: The time of command submission.
|
6
|
+
def deactivate_node_payload(nodename)
|
7
|
+
{
|
8
|
+
'command' => 'deactivate node',
|
9
|
+
'version' => 3,
|
10
|
+
'payload' => {
|
11
|
+
'certname' => nodename,
|
12
|
+
'producer_timestamp' => producer_timestamp
|
13
|
+
}
|
14
|
+
}.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def command_url
|
18
|
+
'/pdb/cmd/v1'
|
19
|
+
end
|
20
|
+
|
21
|
+
def nodes_url
|
22
|
+
'/pdb/query/v4/nodes'
|
23
|
+
end
|
24
|
+
|
25
|
+
def facts_url
|
26
|
+
'/pdb/query/v4/facts'
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def post_options
|
32
|
+
{
|
33
|
+
content_type: :json
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def producer_timestamp
|
38
|
+
Time.now.iso8601.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class PuppetdbHost
|
2
|
+
attr_accessor :certname, :environment, :facts
|
3
|
+
|
4
|
+
def initialize(opts = {})
|
5
|
+
self.facts = HashWithIndifferentAccess.new
|
6
|
+
parse_facts(opts.fetch(:facts))
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_host
|
10
|
+
host = Host::Managed.import_host(facts[:fqdn], 'puppet', certname)
|
11
|
+
host.import_facts(facts)
|
12
|
+
host.update_attribute(:environment => environment) if environment
|
13
|
+
host
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse_facts(pdb_facts)
|
19
|
+
pdb_facts.each do |fact|
|
20
|
+
name = fact['name']
|
21
|
+
value = parse_fact_value(fact['value'])
|
22
|
+
facts[name] = value
|
23
|
+
self.certname = fact['certname']
|
24
|
+
self.environment = fact['environment']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_fact_value(value)
|
29
|
+
result = JSON.parse(value)
|
30
|
+
return result.to_s unless result.is_a?(Hash)
|
31
|
+
deep_stringify_values(result)
|
32
|
+
rescue JSON::ParserError
|
33
|
+
value.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def deep_stringify_values(obj)
|
37
|
+
result = {}
|
38
|
+
obj.each do |key, value|
|
39
|
+
result[key] = if value.is_a?(Hash)
|
40
|
+
deep_stringify_values(value)
|
41
|
+
else
|
42
|
+
value.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
result
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
<% title _('PuppetDB Nodes') %>
|
2
|
+
|
3
|
+
<div class="col-md-12">
|
4
|
+
<div class="row">
|
5
|
+
<div class="container-fluid container-cards-pf">
|
6
|
+
<div class="row row-cards-pf">
|
7
|
+
<div class="col-xs-6 col-sm-4 col-md-4">
|
8
|
+
<div class="card-pf card-pf-accented card-pf-aggregate-status">
|
9
|
+
<h2 class="card-pf-title">
|
10
|
+
<span class="fa fa-database"></span><span class="card-pf-aggregate-status-count"><%= @puppetdb_hosts.count %></span> <%= _('Hosts in PuppetDB') %>
|
11
|
+
</h2>
|
12
|
+
<div class="card-pf-body">
|
13
|
+
<p class="card-pf-aggregate-status-notifications">
|
14
|
+
<span class="card-pf-aggregate-status-notification"><span class="pficon pficon-ok"></span></span>
|
15
|
+
</p>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
<div class="col-xs-6 col-sm-4 col-md-4">
|
20
|
+
<div class="card-pf card-pf-accented card-pf-aggregate-status">
|
21
|
+
<h2 class="card-pf-title">
|
22
|
+
<span class="pficon pficon-server"></span><span class="card-pf-aggregate-status-count"><%= @foreman_hosts.count %></span> <%= _('Hosts in Foreman') %>
|
23
|
+
</h2>
|
24
|
+
<div class="card-pf-body">
|
25
|
+
<p class="card-pf-aggregate-status-notifications">
|
26
|
+
<span class="card-pf-aggregate-status-notification"><span class="pficon pficon-ok"></span></span>
|
27
|
+
</p>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
<div class="row">
|
34
|
+
<div class="col-md-7">
|
35
|
+
<div class="panel panel-default">
|
36
|
+
<div class="panel-heading">
|
37
|
+
<h3 class="panel-title"><%= _('PuppetDB Nodes without Host in Foreman') %></h3>
|
38
|
+
</div>
|
39
|
+
<div class="panel-body">
|
40
|
+
<table class="<%= table_css_classes 'table-fixed' %>">
|
41
|
+
<% if @unknown_hosts.any? %>
|
42
|
+
<thead>
|
43
|
+
<tr>
|
44
|
+
<th class="col-md-5"><%= _('Name') %></th>
|
45
|
+
<th class="col-md-2"><%= _('Actions') %></th>
|
46
|
+
</tr>
|
47
|
+
</thead>
|
48
|
+
<tbody>
|
49
|
+
<% @unknown_hosts.each do |node| %>
|
50
|
+
<tr>
|
51
|
+
<td class="ellipsis"><%= node %></td>
|
52
|
+
<td>
|
53
|
+
<%= action_buttons(
|
54
|
+
(display_link_if_authorized(_("Import"), hash_for_import_puppetdb_foreman_node_path(:id => node), :method => :put)),
|
55
|
+
(display_delete_if_authorized(hash_for_puppetdb_foreman_node_path(:id => node), :class => 'delete', :text => _('Deactivate')))
|
56
|
+
) %>
|
57
|
+
</td>
|
58
|
+
</tr>
|
59
|
+
<% end %>
|
60
|
+
</tbody>
|
61
|
+
<% else %>
|
62
|
+
<tbody>
|
63
|
+
<tr>
|
64
|
+
<td class="blank-slate-pf" style="width: auto;">
|
65
|
+
<div class="blank-slate-pf-icon">
|
66
|
+
<%= icon_text("ok", "", :kind => "pficon") %>
|
67
|
+
</div>
|
68
|
+
<h1><%= _('PuppetDB in Sync') %></h1>
|
69
|
+
<p>
|
70
|
+
<%= _("For every node in PuppetDB a host in Foreman could be found. Both systems are in sync.") %>
|
71
|
+
</p>
|
72
|
+
</td>
|
73
|
+
</tr>
|
74
|
+
</tbody>
|
75
|
+
<% end %>
|
76
|
+
</table>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
</div>
|
data/config/routes.rb
CHANGED
@@ -1,3 +1,31 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
get ':puppetdb(/*puppetdb_url)', :to => 'puppetdb_foreman/puppetdb#index', :puppetdb => /d3\.v2|charts|v3|puppetdb|metrics|\/pdb\/meta\/v1\/version\/latest|pdb\/meta\/v1\/version/, :as =>
|
2
|
+
get ':puppetdb(/*puppetdb_url)', :to => 'puppetdb_foreman/puppetdb#index', :puppetdb => /d3\.v2|charts|v3|puppetdb|metrics|\/pdb\/meta\/v1\/version\/latest|pdb\/meta\/v1\/version|pdb\/dashboard\/data/, :as => 'puppetdb'
|
3
|
+
|
4
|
+
namespace :puppetdb_foreman do
|
5
|
+
constraints(:id => %r{[^\/]+}) do
|
6
|
+
resources :nodes, :only => [:index, :destroy] do
|
7
|
+
member do
|
8
|
+
put 'import'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :api, :defaults => { :format => 'json' } do
|
15
|
+
scope '(:apiv)', :module => :v2,
|
16
|
+
:defaults => { :apiv => 'v2' },
|
17
|
+
:apiv => /v1|v2/,
|
18
|
+
:constraints => ApiConstraints.new(:version => 2) do
|
19
|
+
constraints(:id => /[^\/]+/) do
|
20
|
+
resources :puppetdb_nodes, :only => [:index, :destroy] do
|
21
|
+
collection do
|
22
|
+
get 'unknown'
|
23
|
+
end
|
24
|
+
member do
|
25
|
+
put 'import'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
3
31
|
end
|
@@ -1,26 +1,52 @@
|
|
1
1
|
module PuppetdbForeman
|
2
2
|
class Engine < ::Rails::Engine
|
3
|
+
engine_name 'puppetdb_foreman'
|
3
4
|
|
4
|
-
initializer 'puppetdb_foreman.load_default_settings', :before => :load_config_initializers do |
|
5
|
-
require_dependency File.expand_path(
|
5
|
+
initializer 'puppetdb_foreman.load_default_settings', :before => :load_config_initializers do |_app|
|
6
|
+
require_dependency File.expand_path('../../../app/models/setting/puppetdb.rb', __FILE__) if begin
|
7
|
+
Setting.table_exists?
|
8
|
+
rescue
|
9
|
+
(false)
|
10
|
+
end
|
6
11
|
end
|
7
12
|
|
8
|
-
initializer 'puppetdb_foreman.register_plugin', :before => :finisher_hook do |
|
13
|
+
initializer 'puppetdb_foreman.register_plugin', :before => :finisher_hook do |_app|
|
9
14
|
Foreman::Plugin.register :puppetdb_foreman do
|
10
15
|
requires_foreman '>= 1.11'
|
16
|
+
|
17
|
+
apipie_documented_controllers ["#{PuppetdbForeman::Engine.root}/app/controllers/api/v2/*.rb"]
|
18
|
+
|
11
19
|
security_block :puppetdb_foreman do
|
12
|
-
permission :view_puppetdb_dashboard,
|
20
|
+
permission :view_puppetdb_dashboard, :'puppetdb_foreman/puppetdb' => [:index]
|
21
|
+
permission :view_puppetdb_nodes, :'puppetdb_foreman/nodes' => [:index],
|
22
|
+
:'api/v2/puppetdb_nodes' => [:index, :unknown]
|
23
|
+
permission :destroy_puppetdb_nodes, :'puppetdb_foreman/nodes' => [:destroy],
|
24
|
+
:'api/v2/puppetdb_nodes' => [:destroy]
|
25
|
+
permission :import_puppetdb_nodes, :'puppetdb_foreman/nodes' => [:import],
|
26
|
+
:'api/v2/puppetdb_nodes' => [:import]
|
13
27
|
end
|
28
|
+
|
14
29
|
role 'PuppetDB Dashboard', [:view_puppetdb_dashboard]
|
30
|
+
role 'PuppetDB Node Viewer', [:view_puppetdb_nodes]
|
31
|
+
role 'PuppetDB Node Manager', [:view_puppetdb_nodes, :destroy_puppetdb_nodes, :import_puppetdb_nodes]
|
32
|
+
|
15
33
|
menu :top_menu, :puppetdb, :caption => N_('PuppetDB Dashboard'),
|
16
|
-
:url_hash => {:controller => 'puppetdb_foreman/puppetdb', :action => 'index', :puppetdb => 'puppetdb'},
|
34
|
+
:url_hash => { :controller => 'puppetdb_foreman/puppetdb', :action => 'index', :puppetdb => 'puppetdb' },
|
17
35
|
:parent => :monitor_menu,
|
18
|
-
:last => true
|
36
|
+
:last => :true
|
37
|
+
menu :top_menu, :nodes, :caption => N_('PuppetDB Nodes'),
|
38
|
+
:url_hash => { :controller => 'puppetdb_foreman/nodes', :action => 'index' },
|
39
|
+
:parent => :monitor_menu,
|
40
|
+
:after => :puppetdb
|
19
41
|
end
|
20
42
|
end
|
21
43
|
|
22
44
|
config.to_prepare do
|
23
|
-
|
45
|
+
begin
|
46
|
+
Host::Managed.send :include, PuppetdbForeman::HostExtensions
|
47
|
+
rescue => e
|
48
|
+
Rails.logger.warn "PuppetdbForeman: skipping engine hook (#{e})"
|
49
|
+
end
|
24
50
|
end
|
25
51
|
end
|
26
52
|
end
|