diff --git a/README.md b/README.md index 16235b2..8f4c9df 100644 --- a/README.md +++ b/README.md @@ -276,6 +276,13 @@ If true, the module will overwrite the puppet master's routes file to configure If true, the module will manage the puppet master's storeconfig settings (defaults to true). +####`manage_config` +If true, the module will store values from puppetdb_server and puppetdb_port parameters in the puppetdb configuration file. +If false, an existing puppetdb configuration file will be used to retrieve server and port values. + +####`strict_validation` +If true, the module will fail if puppetdb is not reachable, otherwise it will preconfigure puppetdb without checking. + ####`puppet_confdir` Puppet's config directory (defaults to `/etc/puppet`). diff --git a/lib/puppet/provider/puppetdb_conn_validator/puppet_https.rb b/lib/puppet/provider/puppetdb_conn_validator/puppet_https.rb index 1cb788f..7b8d297 100644 --- a/lib/puppet/provider/puppetdb_conn_validator/puppet_https.rb +++ b/lib/puppet/provider/puppetdb_conn_validator/puppet_https.rb @@ -1,36 +1,17 @@ -require 'puppet/network/http_pool' +# See: #10295 for more details. +# +# This is a workaround for bug: #4248 whereby ruby files outside of the normal +# provider/type path do not load until pluginsync has occured on the puppetmaster +# +# In this case I'm trying the relative path first, then falling back to normal +# mechanisms. This should be fixed in future versions of puppet but it looks +# like we'll need to maintain this for some time perhaps. +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"..","..","..")) +require 'puppet/util/puppetdb_validator' # This file contains a provider for the resource type `puppetdb_conn_validator`, # which validates the puppetdb connection by attempting an https connection. -# Utility method; attempts to make an https connection to the puppetdb server. -# This is abstracted out into a method so that it can be called multiple times -# for retry attempts. -# -# @return true if the connection is successful, false otherwise. -def attempt_connection - begin - host = resource[:puppetdb_server] - port = resource[:puppetdb_port] - - # All that we care about is that we are able to connect successfully via - # https, so here we're simpling hitting a somewhat arbitrary low-impact URL - # on the puppetdb server. - path = "/metrics/mbean/java.lang:type=Memory" - headers = {"Accept" => "application/json"} - conn = Puppet::Network::HttpPool.http_instance(host, port, true) - response = conn.get(path, headers) - unless response.kind_of?(Net::HTTPSuccess) - Puppet.err "Unable to connect to puppetdb server (#{host}:#{port}): [#{response.code}] #{response.msg}" - return false - end - return true - rescue Errno::ECONNREFUSED => e - Puppet.notice "Unable to connect to puppetdb server (#{host}:#{port}): #{e.inspect} " - return false - end -end - Puppet::Type.type(:puppetdb_conn_validator).provide(:puppet_https) do desc "A provider for the resource type `puppetdb_conn_validator`, which validates the puppetdb connection by attempting an https @@ -41,7 +22,7 @@ Puppet::Type.type(:puppetdb_conn_validator).provide(:puppet_https) do start_time = Time.now timeout = resource[:timeout] - success = attempt_connection + success = validator.attempt_connection unless success while (Time.now - start_time) < timeout # It can take several seconds for the puppetdb server to start up; @@ -50,7 +31,7 @@ Puppet::Type.type(:puppetdb_conn_validator).provide(:puppet_https) do # seconds until the configurable timeout has expired. Puppet.notice("Failed to connect to puppetdb; sleeping 2 seconds before retry") sleep 2 - success = attempt_connection + success = validator.attempt_connection end end @@ -65,8 +46,13 @@ Puppet::Type.type(:puppetdb_conn_validator).provide(:puppet_https) do # If `#create` is called, that means that `#exists?` returned false, which # means that the connection could not be established... so we need to # cause a failure here. - raise Puppet::Error, "Unable to connect to puppetdb server! (#{resource[:puppetdb_server]}:#{resource[:puppetdb_port]})" + raise Puppet::Error, "Unable to connect to puppetdb server! (#{@validator.puppetdb_server}:#{@validator.puppetdb_port})" end + # @api private + def validator + @validator ||= Puppet::Util::PuppetdbValidator.new(resource[:puppetdb_server], resource[:puppetdb_port]) + end end + diff --git a/lib/puppet/util/puppetdb_validator.rb b/lib/puppet/util/puppetdb_validator.rb new file mode 100644 index 0000000..97aeaa0 --- /dev/null +++ b/lib/puppet/util/puppetdb_validator.rb @@ -0,0 +1,39 @@ +require 'puppet/network/http_pool' + +module Puppet + module Util + class PuppetdbValidator + attr_reader :puppetdb_server + attr_reader :puppetdb_port + + def initialize(puppetdb_server, puppetdb_port) + @puppetdb_server = puppetdb_server + @puppetdb_port = puppetdb_port + end + + # Utility method; attempts to make an https connection to the puppetdb server. + # This is abstracted out into a method so that it can be called multiple times + # for retry attempts. + # + # @return true if the connection is successful, false otherwise. + def attempt_connection + # All that we care about is that we are able to connect successfully via + # https, so here we're simpling hitting a somewhat arbitrary low-impact URL + # on the puppetdb server. + path = "/metrics/mbean/java.lang:type=Memory" + headers = {"Accept" => "application/json"} + conn = Puppet::Network::HttpPool.http_instance(@puppetdb_server, @puppetdb_port, true) + response = conn.get(path, headers) + unless response.kind_of?(Net::HTTPSuccess) + Puppet.notice "Unable to connect to puppetdb server (#{@puppetdb_server}:#{@puppetdb_port}): [#{response.code}] #{response.msg}" + return false + end + return true + rescue Exception => e + Puppet.notice "Unable to connect to puppetdb server (#{@puppetdb_server}:#{@puppetdb_port}): #{e.message}" + return false + end + end + end +end + diff --git a/manifests/init.pp b/manifests/init.pp index 2295725..5c78ebd 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -140,7 +140,7 @@ class puppetdb( validate_re ($report_ttl_real, ['^(\d)+[s,m,d]$'], "report_ttl is <${report_ttl}> which does not match the regex validation") if ($manage_redhat_firewall != undef) { - notify {'Deprecation notice: `$manage_redhat_firewall` has been deprecated in `puppetdb` class and will be removed in a future versions. Use $open_ssl_listen_port and $open_postgres_port instead.':} + notify {'Deprecation notice: `$manage_redhat_firewall` has been deprecated in `puppetdb` class and will be removed in a future version. Use $open_ssl_listen_port and $open_postgres_port instead.':} } class { 'puppetdb::server': diff --git a/manifests/master/config.pp b/manifests/master/config.pp index 754d232..c4220e6 100644 --- a/manifests/master/config.pp +++ b/manifests/master/config.pp @@ -17,6 +17,12 @@ # file to configure it to use puppetdb (defaults to true) # ['manage_storeconfigs'] - If true, the module will manage the puppet master's # storeconfig settings (defaults to true) +# ['manage_config'] - If true, the module will store values from puppetdb_server +# and puppetdb_port parameters in the puppetdb configuration file. +# If false, an existing puppetdb configuration file will be used +# to retrieve server and port values. +# ['strict_validation'] - If true, the module will fail if puppetdb is not reachable, +# otherwise it will preconfigure puppetdb without checking. # ['puppet_confdir'] - Puppet's config directory; defaults to /etc/puppet # ['puppet_conf'] - Puppet's config file; defaults to /etc/puppet/puppet.conf # ['puppetdb_version'] - The version of the `puppetdb` package that should @@ -51,6 +57,8 @@ class puppetdb::master::config( $puppetdb_port = 8081, $manage_routes = true, $manage_storeconfigs = true, + $manage_config = true, + $strict_validation = true, $puppet_confdir = $puppetdb::params::puppet_confdir, $puppet_conf = $puppetdb::params::puppet_conf, $puppetdb_version = $puppetdb::params::puppetdb_version, @@ -64,27 +72,29 @@ class puppetdb::master::config( ensure => $puppetdb_version, } - # Validate the puppetdb connection. If we can't connect to puppetdb then we - # *must* not perform the other configuration steps, or else - puppetdb_conn_validator { 'puppetdb_conn': - puppetdb_server => $puppetdb_server, - puppetdb_port => $puppetdb_port, - timeout => $puppetdb_startup_timeout, - require => Package[$terminus_package], - } + if ($strict_validation) { + # Validate the puppetdb connection. If we can't connect to puppetdb then we + # *must* not perform the other configuration steps, or else + puppetdb_conn_validator { 'puppetdb_conn': + puppetdb_server => $manage_config ? { true => $puppetdb_server, default => undef }, + puppetdb_port => $manage_config ? { true => $puppetdb_port, default => undef }, + timeout => $puppetdb_startup_timeout, + require => Package[$terminus_package], + } - # This is a bit of puppet chicanery that allows us to create a - # conditional dependency. Basically, we're saying that "if the PuppetDB - # service is being managed in this same catalog, it needs to come before - # this validator." - Service<|title == 'puppetdb'|> -> Puppetdb_conn_validator['puppetdb_conn'] + # This is a bit of puppet chicanery that allows us to create a + # conditional dependency. Basically, we're saying that "if the PuppetDB + # service is being managed in this same catalog, it needs to come before + # this validator." + Service<|title == $puppetdb::params::puppetdb_service|> -> Puppetdb_conn_validator['puppetdb_conn'] + } # Conditionally manage the `routes.yaml` file. Restart the puppet service # if changes are made. if ($manage_routes) { class { 'puppetdb::master::routes': puppet_confdir => $puppet_confdir, - require => Puppetdb_conn_validator['puppetdb_conn'], + require => $strict_validation ? { true => Puppetdb_conn_validator['puppetdb_conn'], default => Package[$terminus_package] }, } } @@ -93,18 +103,20 @@ class puppetdb::master::config( # it polls it automatically. if ($manage_storeconfigs) { class { 'puppetdb::master::storeconfigs': - puppet_conf => $puppet_conf, - require => Puppetdb_conn_validator['puppetdb_conn'], - } + puppet_conf => $puppet_conf, + require => $strict_validation ? { true => Puppetdb_conn_validator['puppetdb_conn'], default => Package[$terminus_package] }, + } } - # Manage the `puppetdb.conf` file. Restart the puppet service if changes - # are made. - class { 'puppetdb::master::puppetdb_conf': - server => $puppetdb_server, - port => $puppetdb_port, - puppet_confdir => $puppet_confdir, - require => Puppetdb_conn_validator['puppetdb_conn'], + if ($manage_config) { + # Manage the `puppetdb.conf` file. Restart the puppet service if changes + # are made. + class { 'puppetdb::master::puppetdb_conf': + server => $puppetdb_server, + port => $puppetdb_port, + puppet_confdir => $puppet_confdir, + require => $strict_validation ? { true => Puppetdb_conn_validator['puppetdb_conn'], default => Package[$terminus_package] }, + } } if ($restart_puppet) { @@ -116,8 +128,14 @@ class puppetdb::master::config( } } - Class['puppetdb::master::puppetdb_conf'] ~> Service[$puppet_service_name] - Class['puppetdb::master::routes'] ~> Service[$puppet_service_name] + if ($manage_config) { + Class['puppetdb::master::puppetdb_conf'] ~> Service[$puppet_service_name] + } + + if ($manage_routes) { + Class['puppetdb::master::routes'] ~> Service[$puppet_service_name] + } + } } diff --git a/spec/unit/util/puppetdb_validator_spec.rb b/spec/unit/util/puppetdb_validator_spec.rb new file mode 100644 index 0000000..2c07f98 --- /dev/null +++ b/spec/unit/util/puppetdb_validator_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' +require 'puppet/util/puppetdb_validator' + +describe 'Puppet::Util::PuppetdbValidator' do + + before do + response_ok = stub() + response_ok.stubs(:kind_of?).with(Net::HTTPSuccess).returns(true) + response_not_found = stub() + response_not_found.stubs(:kind_of?).with(Net::HTTPSuccess).returns(false) + response_not_found.stubs(:code).returns(404) + response_not_found.stubs(:msg).returns('Not found') + + conn_ok = stub() + conn_ok.stubs(:get).with('/metrics/mbean/java.lang:type=Memory', {"Accept" => "application/json"}).returns(response_ok) + + conn_not_found = stub() + conn_not_found.stubs(:get).with('/metrics/mbean/java.lang:type=Memory', {"Accept" => "application/json"}).returns(response_not_found) + + Puppet::Network::HttpPool.stubs(:http_instance).raises('Unknown host') + Puppet::Network::HttpPool.stubs(:http_instance).with('mypuppetdb.com', 8080, true).raises('Connection refused') + Puppet::Network::HttpPool.stubs(:http_instance).with('mypuppetdb.com', 8081, true).returns(conn_ok) + Puppet::Network::HttpPool.stubs(:http_instance).with('wrongserver.com', 8081, true).returns(conn_not_found) + end + + it 'returns true if connection succeeds' do + validator = Puppet::Util::PuppetdbValidator.new('mypuppetdb.com', 8081) + validator.attempt_connection.should be_true + end + + it 'returns false and issues an appropriate notice if connection is refused' do + puppetdb_server = 'mypuppetdb.com' + puppetdb_port = 8080 + validator = Puppet::Util::PuppetdbValidator.new(puppetdb_server, puppetdb_port) + Puppet.expects(:notice).with("Unable to connect to puppetdb server (#{puppetdb_server}:#{puppetdb_port}): Connection refused") + #Puppet.expects(:notice).with("Unable to connect to puppetdb server (#{puppetdb_server}:#{puppetdb_port}): [404] Not found") + validator.attempt_connection.should be_false + end + + it 'returns false and issues an appropriate notice if connection succeeds but puppetdb is not available' do + puppetdb_server = 'wrongserver.com' + puppetdb_port = 8081 + validator = Puppet::Util::PuppetdbValidator.new(puppetdb_server, puppetdb_port) + Puppet.expects(:notice).with("Unable to connect to puppetdb server (#{puppetdb_server}:#{puppetdb_port}): [404] Not found") + validator.attempt_connection.should be_false + end + + + it 'returns false and issues an appropriate notice if host:port is unreachable or does not exist' do + puppetdb_server = 'non-existing.com' + puppetdb_port = nil + validator = Puppet::Util::PuppetdbValidator.new(puppetdb_server, puppetdb_port) + Puppet.expects(:notice).with("Unable to connect to puppetdb server (#{puppetdb_server}:#{puppetdb_port}): Unknown host") + validator.attempt_connection.should be_false + end +end