puppetdb_foreman 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|