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.
@@ -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