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.
@@ -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,3 @@
1
+ object @host
2
+
3
+ extends "api/v2/hosts/show"
@@ -0,0 +1,5 @@
1
+ collection @nodes
2
+
3
+ node :name do |node|
4
+ node
5
+ end
@@ -0,0 +1,3 @@
1
+ collection @nodes
2
+
3
+ extends "api/v2/puppetdb_nodes/index"
@@ -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 => "puppetdb"
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 |app|
5
- require_dependency File.expand_path("../../../app/models/setting/puppetdb.rb", __FILE__) if (Setting.table_exists? rescue(false))
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 |app|
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, {:'puppetdb_foreman/puppetdb' => [:index]}
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
- Host::Managed.send :include, PuppetdbForeman::HostExtensions
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