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.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # puppetdb\_foreman
2
+
3
+ [![Code Climate](https://codeclimate.com/github/theforeman/puppetdb_foreman/badges/gpa.svg)](https://codeclimate.com/github/theforeman/puppetdb_foreman)
4
+ [![Gem Version](https://badge.fury.io/rb/puppetdb_foreman.svg)](http://badge.fury.io/rb/puppetdb_foreman)
5
+ [![Dependency Status](https://gemnasium.com/theforeman/puppetdb_foreman.svg)](https://gemnasium.com/theforeman/puppetdb_foreman)
6
+ [![Issue stats](http://issuestats.com/github/theforeman/puppetdb_foreman/badge/pr?style=flat)](http://issuestats.com/github/theforeman/puppetdb_foreman)
7
+
8
+ This is a [Foreman](http://theforeman.org) plugin to interact with [PuppetDB](https://docs.puppetlabs.com/puppetdb/index.html).
9
+
10
+ It does the following:
11
+
12
+ * Disables hosts on PuppetDB after they are deleted in Foreman.
13
+ * Proxies the PuppetDB Performance Dashboard for access through Foreman.
14
+ * Compares nodes in PuppetDB to Hosts in Foreman.
15
+ * Creates Hosts in Foreman from PuppetDB facts.
16
+
17
+ Feel free to raise issues, ask for features, anything, in the github repository or #theforeman IRC channel in Freenode.
18
+
19
+ # Installation:
20
+
21
+ **From packages**
22
+
23
+ Set up the appropriate repository by following [these instructions](https://theforeman.org/plugins/)
24
+
25
+ *RPM* users can install the `tfm-rubygem-puppetdb_foreman` (el7) or `rubygem-puppetdb_foreman` (f24) packages.
26
+
27
+ *deb* users can install the `ruby-puppetdb-foreman` package.
28
+
29
+ **From Rubygems**
30
+
31
+ Add to bundler.d/Gemfile.local.rb as:
32
+
33
+ gem 'puppetdb_foreman'
34
+
35
+ then update & restart Foreman:
36
+
37
+ bundle update
38
+
39
+ service restart foreman or equivalent
40
+
41
+
42
+ **Versioning**
43
+
44
+ puppetdb_foreman uses [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html)
45
+
46
+ # Usage:
47
+
48
+ Go to Administer > Settings > PuppetDB and set `puppetdb_address` with your PuppetDB address, `puppetdb_dashboard_address` (optional) to proxy the dashboard, `puppetdb_enabled` to either true or false if you want to enable or disable PuppetDB integration. Obviously you will need a PuppetDB instance at the address you provide.
49
+
50
+ Alternatively you can put your settings in `config/settings.yaml`. Please keep in mind these will be overwritten if changed in the application, and if the application is rebooted, YAML rules will overwrite again your manually changed settings. Hence passing your settings in this format is discouraged, but allowed.
51
+
52
+ for PuppetDB 4
53
+ ```yaml
54
+ :puppetdb:
55
+ :enabled: true
56
+ :address: 'https://puppetdb:8081/pdb/cmd/v1'
57
+ :dashboard_address: 'http://puppetdb:8080/pdb/dashboard'
58
+ :puppetdb_ssl_ca_file: '/etc/puppetlabs/puppet/ssl/certs/ca.pem'
59
+ :puppetdb_ssl_certificate: '/etc/puppetlabs/puppet/ssl/certs/FQDN.pem'
60
+ :puppetdb_ssl_private_key: '/etc/puppetlabs/puppet/ssl/private_keys/FQDN.pem'
61
+ ```
62
+
63
+ You can find the dashboard under Monitor > PuppetDB dashboard. Only administrators and users with a role `view_puppetdb_dashboard` will be able to access it. Aside from convenience, the PuppetDB dashboard cannot be served over HTTPS, you can restrict your dashboard requests to only Foreman boxes and serve it securely to your users through HTTPS in Foreman.
64
+
65
+ ![Screenshot](http://i.imgur.com/5d80CtZ.png)
66
+
67
+
68
+ # Copyright:
69
+ Copyright 2013 CERN, Switzerland and various authors
70
+
71
+ This program is free software: you can redistribute it and/or modify
72
+ it under the terms of the GNU General Public License as published by
73
+ the Free Software Foundation, either version 3 of the License, or
74
+ (at your option) any later version.
75
+
76
+ This program is distributed in the hope that it will be useful,
77
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
78
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
79
+ GNU General Public License for more details.
80
+
81
+ You should have received a copy of the GNU General Public License
82
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
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 = 'PuppetdbForeman'
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,63 @@
1
+ module Api
2
+ module V2
3
+ class PuppetdbNodesController < V2::BaseController
4
+ include Api::Version2
5
+ before_action :find_node, :only => [:destroy, :import]
6
+
7
+ layout 'api/v2/layouts/index_layout', :only => [:index, :unknown]
8
+
9
+ api :GET, '/puppetdb_nodes/', N_('List all PuppetDB nodes')
10
+ def index
11
+ @nodes = Puppetdb.client.query_nodes
12
+ end
13
+
14
+ api :GET, '/puppetdb_nodes/unknown/', N_('List all PuppetDB nodes without a Host in Foreman')
15
+ def unknown
16
+ @foreman_hosts = Host.unscoped.pluck(:name)
17
+ @puppetdb_hosts = Puppetdb.client.query_nodes
18
+ @nodes = @puppetdb_hosts - @foreman_hosts
19
+ end
20
+
21
+ api :DELETE, '/puppetdb_nodes/:id/', N_('Deactivate a node in PuppetDB')
22
+ param :id, String, :required => true
23
+
24
+ def destroy
25
+ uuid = Puppetdb.client.deactivate_node(@node)
26
+ response = { :job => { :uuid => uuid } }
27
+ process_success response
28
+ end
29
+
30
+ api :PUT, '/puppetdb_nodes/:id/import/', N_('Import PuppetDB Node to Foreman Host')
31
+ param :id, :identifier, :required => true
32
+
33
+ def import
34
+ facts = Puppetdb.client.facts(@node)
35
+ @host = PuppetdbHost.new(:facts => facts).to_host
36
+ end
37
+
38
+ # Overrides because PuppetDB is not backed by ActiveRecord
39
+ def resource_name
40
+ 'node'
41
+ end
42
+
43
+ def resource_scope
44
+ @nodes
45
+ end
46
+
47
+ private
48
+
49
+ def find_node
50
+ @node = params[:id]
51
+ end
52
+
53
+ def action_permission
54
+ case params[:action]
55
+ when 'unknown'
56
+ 'index'
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ module PuppetdbForeman
2
+ class NodesController < ApplicationController
3
+ include ::Foreman::Controller::ActionPermissionDsl
4
+
5
+ before_action :find_node, :only => [:destroy, :import]
6
+ define_action_permission ['import'], :import
7
+
8
+ def controller_permission
9
+ 'puppetdb_nodes'
10
+ end
11
+
12
+ def index
13
+ @foreman_hosts = Host.unscoped.pluck(:name)
14
+ @puppetdb_hosts = Puppetdb.client.query_nodes
15
+ @unknown_hosts = @puppetdb_hosts - @foreman_hosts
16
+ end
17
+
18
+ def destroy
19
+ Puppetdb.client.deactivate_node(@node)
20
+ process_success :success_msg => _('Deactivated node %s in PuppetDB') % @node, :success_redirect => puppetdb_foreman_nodes_path
21
+ rescue => e
22
+ process_error(:redirect => puppetdb_foreman_nodes_path, :error_msg => _('Failed to deactivate node in PuppetDB: %s') % e.message)
23
+ end
24
+
25
+ def import
26
+ facts = Puppetdb.client.facts(@node)
27
+ host = PuppetdbHost.new(:facts => facts).to_host
28
+ process_success :success_msg => _('Imported host %s from PuppetDB') % @node, :success_redirect => host_path(:id => host)
29
+ rescue => e
30
+ process_error(:redirect => puppetdb_foreman_nodes_path, :error_msg => _('Failed to import host from PuppetDB: %s') % e.message)
31
+ end
32
+
33
+ private
34
+
35
+ def find_node
36
+ @node = params[:id]
37
+ end
38
+ end
39
+ end
@@ -1,34 +1,30 @@
1
1
  module PuppetdbForeman
2
2
  class PuppetdbController < ApplicationController
3
-
4
3
  protect_from_forgery :except => :index
5
4
 
6
5
  def index
7
- begin
8
- uri = URI.parse(Setting[:puppetdb_dashboard_address])
9
- puppetdb_url, layout = case params[:puppetdb]
10
- when 'd3.v2', 'charts' then ["#{uri.path}#{request.original_fullpath}", false]
11
- when 'v3', 'metrics', 'pdb/meta/v1/version', 'pdb/meta/v1/version/latest' then [request.original_fullpath, false]
12
- else ["#{uri.path}/index.html", true]
13
- end
14
- result = Net::HTTP.get_response(uri.host, puppetdb_url, uri.port)
15
- render :text => result.body, :layout => layout
16
- rescue SocketError => error
17
- @proxy_error = "Problem connecting to host #{uri.host} on port #{uri.port}"
18
- render :action => :error, :layout => true
19
- rescue Errno::ECONNREFUSED => error
20
- @proxy_error = "#{uri.host} refused our connection"
21
- render :action => :error, :layout => true
22
- rescue EOFError => error
23
- @proxy_error = "Don't use ssl (https)"
24
- render :action => :error, :layout => true
25
- end
6
+ uri = URI.parse(Setting[:puppetdb_dashboard_address])
7
+ puppetdb_url, layout = case params[:puppetdb]
8
+ when 'd3.v2', 'charts' then ["#{uri.path}#{request.original_fullpath}", false]
9
+ when 'v3', 'metrics', 'pdb/meta/v1/version', 'pdb/meta/v1/version/latest', 'pdb/dashboard/data' then [request.original_fullpath, false]
10
+ else ["#{uri.path}/index.html", true]
11
+ end
12
+ result = Net::HTTP.get_response(uri.host, puppetdb_url, uri.port)
13
+ render :text => result.body, :layout => layout
14
+ rescue SocketError
15
+ @proxy_error = "Problem connecting to host #{uri.host} on port #{uri.port}"
16
+ render :action => :error, :layout => true
17
+ rescue Errno::ECONNREFUSED
18
+ @proxy_error = "#{uri.host} refused our connection"
19
+ render :action => :error, :layout => true
20
+ rescue EOFError
21
+ @proxy_error = "Don't use ssl (https)"
22
+ render :action => :error, :layout => true
26
23
  end
27
24
 
28
25
  # Override from application controller to fix issue
29
26
  def api_request?
30
27
  request.format && (request.format.json? || request.format.yaml?)
31
28
  end
32
-
33
29
  end
34
30
  end
@@ -0,0 +1,27 @@
1
+ module Orchestration
2
+ module Puppetdb
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_destroy :queue_puppetdb_destroy
7
+ end
8
+
9
+ protected
10
+
11
+ def queue_puppetdb_destroy
12
+ return unless ::Puppetdb.ready? && errors.empty?
13
+ queue.create(:name => _('Deactivating node %s in PuppetDB') % self, :priority => 60,
14
+ :action => [self, :delPuppetdb])
15
+ end
16
+
17
+ def delPuppetdb
18
+ Rails.logger.info "Deactivating node in PuppetDB: #{name}"
19
+ ::Puppetdb.client.deactivate_node(name)
20
+ rescue => e
21
+ failure _("Failed to deactiate node %{name} in PuppetDB: %{message}\n ") %
22
+ { :name => name, :message => e.message }, e
23
+ end
24
+
25
+ def setPuppetdb; end
26
+ end
27
+ end
@@ -2,77 +2,25 @@ module PuppetdbForeman
2
2
  module HostExtensions
3
3
  extend ActiveSupport::Concern
4
4
  included do
5
- before_destroy :deactivate_host
6
- after_build :deactivate_host
7
-
8
- def deactivate_host
9
- logger.debug "Deactivating host #{name} in Puppetdb"
10
- return false unless puppetdb_configured?
11
-
12
- if puppetdb_enabled?
13
- begin
14
- uri = URI.parse(Setting[:puppetdb_address])
15
- req = Net::HTTP::Post.new(uri.path)
16
- req['Accept'] = 'application/json'
17
- req.content_type = 'application/json'
18
-
19
- res = Net::HTTP.new(uri.host, uri.port)
20
- res.use_ssl = uri.scheme == 'https'
21
- if res.use_ssl?
22
- if Setting[:puppetdb_ssl_ca_file]
23
- res.ca_file = Setting[:puppetdb_ssl_ca_file]
24
- res.verify_mode = OpenSSL::SSL::VERIFY_PEER
25
- else
26
- res.verify_mode = OpenSSL::SSL::VERIFY_NONE
27
- end
28
- if Setting[:puppetdb_ssl_certificate] &&
29
- Setting[:puppetdb_ssl_private_key]
30
- res.cert = OpenSSL::X509::Certificate.new(
31
- File.read(Setting[:puppetdb_ssl_certificate]))
32
- res.key = OpenSSL::PKey::RSA.new(
33
- File.read(Setting[:puppetdb_ssl_private_key]), nil)
34
- end
35
-
36
- end
5
+ include Orchestration::Puppetdb
37
6
 
38
- if uri.path.start_with?("/pdb")
39
- logger.debug "Using PuppetDB API v3"
40
- req.body = {
41
- "command" => "deactivate node",
42
- "version" => 3,
43
- "payload" => {
44
- "certname" => name,
45
- "producer_timestamp" => "#{Time.now.iso8601}"
46
- }
47
- }.to_json
48
- else
49
- logger.debug "Using PuppetDB API v1"
50
- req.body = {
51
- "command" => "deactivate node",
52
- "version" => 1,
53
- "payload" => name
54
- }.to_json
55
- end
56
- res.start { |http| http.request(req) }
57
-
58
- rescue => e
59
- errors.add(:base, _("Could not deactivate host on PuppetDB: #{e}"))
60
- end
61
- errors.empty?
62
- end
63
- end
7
+ after_build :deactivate_host
8
+ end
64
9
 
65
- private
10
+ def deactivate_host
11
+ logger.debug "Deactivating host #{name} in PuppetDB"
12
+ return true unless Puppetdb.enabled?
66
13
 
67
- def puppetdb_configured?
68
- if puppetdb_enabled? && Setting[:puppetdb_address].blank?
69
- errors.add(:base, _("PuppetDB plugin is enabled but not configured. Please configure it before trying to delete a host."))
70
- end
71
- errors.empty?
14
+ unless Puppetdb.configured?
15
+ errors.add(:base,
16
+ _('PuppetDB plugin is enabled but not configured. Please configure it before trying to delete a host.'))
72
17
  end
73
18
 
74
- def puppetdb_enabled?
75
- [true, 'true'].include? Setting[:puppetdb_enabled]
19
+ begin
20
+ Puppetdb.client.deactivate_node(name)
21
+ rescue => e
22
+ errors.add(:base, _("Could not deactivate host on PuppetDB: #{e}"))
23
+ return false
76
24
  end
77
25
  end
78
26
  end
@@ -1,12 +1,15 @@
1
1
  class Setting::Puppetdb < ::Setting
2
2
  BLANK_ATTRS << 'puppetdb_address'
3
+ BLANK_ATTRS << 'puppetdb_ssl_ca_file'
4
+ BLANK_ATTRS << 'puppetdb_ssl_certificate'
5
+ BLANK_ATTRS << 'puppetdb_ssl_private_key'
3
6
 
4
- def self.load_defaults
7
+ def self.default_settings
5
8
  if SETTINGS[:puppetdb].present?
6
9
  default_enabled = SETTINGS[:puppetdb][:enabled]
7
10
  default_address = SETTINGS[:puppetdb][:address]
8
11
  default_dashboard_address = SETTINGS[:puppetdb][:dashboard_address]
9
- default_ssl_ca_file= SETTINGS[:puppetdb][:ssl_ca_file]
12
+ default_ssl_ca_file = SETTINGS[:puppetdb][:ssl_ca_file]
10
13
  default_ssl_certificate = SETTINGS[:puppetdb][:ssl_certificate]
11
14
  default_ssl_private_key = SETTINGS[:puppetdb][:ssl_private_key]
12
15
  end
@@ -14,44 +17,32 @@ class Setting::Puppetdb < ::Setting
14
17
  default_enabled = false if default_enabled.nil?
15
18
  default_address ||= 'https://puppetdb:8081/pdb/cmd/v1'
16
19
  default_dashboard_address ||= 'http://puppetdb:8080/pdb/dashboard'
17
- default_ssl_ca_file ||= "#{SETTINGS[:ssl_ca_file]}"
18
- default_ssl_certificate ||= "#{SETTINGS[:ssl_certificate]}"
19
- default_ssl_private_key ||= "#{SETTINGS[:ssl_priv_key]}"
20
-
21
- Setting.transaction do
22
- [
23
- self.set('puppetdb_enabled', _("Integration with PuppetDB, enabled will deactivate a host in PuppetDB when it's deleted in Foreman"), default_enabled)
24
- ].compact.each { |s| self.create s.update(:category => 'Setting::Puppetdb')}
25
- end
26
-
27
- Setting.transaction do
28
- [
29
- self.set('puppetdb_address', _('Foreman will send PuppetDB requests to this address'), default_address)
30
- ].compact.each { |s| self.create s.update(:category => 'Setting::Puppetdb')}
31
- end
20
+ default_ssl_ca_file ||= (SETTINGS[:ssl_ca_file]).to_s
21
+ default_ssl_certificate ||= (SETTINGS[:ssl_certificate]).to_s
22
+ default_ssl_private_key ||= (SETTINGS[:ssl_priv_key]).to_s
23
+
24
+ [
25
+ set('puppetdb_enabled', _("Integration with PuppetDB, enabled will deactivate a host in PuppetDB when it's deleted in Foreman"), default_enabled),
26
+ set('puppetdb_address', _('Foreman will send PuppetDB requests to this address'), default_address),
27
+ set('puppetdb_dashboard_address', _('Foreman will proxy PuppetDB Performance Dashboard requests to this address'), default_dashboard_address),
28
+ set('puppetdb_ssl_ca_file', _('Foreman will send PuppetDB requests with this CA file'), default_ssl_ca_file),
29
+ set('puppetdb_ssl_certificate', _('Foreman will send PuppetDB requests with this certificate file'), default_ssl_certificate),
30
+ set('puppetdb_ssl_private_key', _('Foreman will send PuppetDB requests with this key file'), default_ssl_private_key)
31
+ ]
32
+ end
32
33
 
33
- Setting.transaction do
34
- [
35
- self.set('puppetdb_dashboard_address', _('Foreman will proxy PuppetDB Performance Dashboard requests to this address'), default_dashboard_address)
36
- ].compact.each { |s| self.create s.update(:category => 'Setting::Puppetdb')}
37
- end
34
+ def self.load_defaults
35
+ # Check the table exists
36
+ return unless super
38
37
 
39
- Setting.transaction do
40
- [
41
- self.set('puppetdb_ssl_ca_file', _('Foreman will send PuppetDB requests with this CA file'), default_ssl_ca_file)
42
- ].compact.each { |s| self.create s.update(:category => 'Setting::Puppetdb')}
38
+ transaction do
39
+ default_settings.each { |s| create! s.update(:category => 'Setting::Puppetdb') }
43
40
  end
44
41
 
45
- Setting.transaction do
46
- [
47
- self.set('puppetdb_ssl_certificate', _('Foreman will send PuppetDB requests with this certificate file'), default_ssl_certificate)
48
- ].compact.each { |s| self.create s.update(:category => 'Setting::Puppetdb')}
49
- end
42
+ true
43
+ end
50
44
 
51
- Setting.transaction do
52
- [
53
- self.set('puppetdb_ssl_private_key', _('Foreman will send PuppetDB requests with this key file'), default_ssl_private_key)
54
- ].compact.each { |s| self.create s.update(:category => 'Setting::Puppetdb')}
55
- end
45
+ def self.humanized_category
46
+ N_('PuppetDB')
56
47
  end
57
48
  end