class OctocatalogDiff::PuppetDB
A standard way to connect to PuppetDB
from the various scripts in this repository.
Constants
- DEFAULT_HTTPS_PORT
- DEFAULT_HTTP_PORT
Attributes
Allow connections to be read (used in tests for now)
Public Class Methods
Constructor - will construct connection parameters from a variety of sources, including arguments and environment variables. Supported environment variables:
PUPPETDB_URL PUPPETDB_HOST [+ PUPPETDB_PORT] [+ PUPPETDB_SSL]
Order of precedence:
1. :puppetdb_url argument (String or Array<String>) 2. :puppetdb_host argument [+ :puppetdb_port] [+ :puppetdb_ssl] 3. ENV['PUPPETDB_URL'] 4. ENV['PUPPETDB_HOST'] [+ ENV['PUPPETDB_PORT']], [+ ENV['PUPPETDB_SSL']]
When it finds one of these, it stops and does not process any others.
When :puppetdb_url is an array, all given URLs are tried, in random order, until a connection succeeds. If a connection succeeds, any errors from previously failed connections are suppressed.
Supported arguments: @param :puppetdb_url [String or Array<String>] PuppetDB
URL(s) to try in random order @param :puppetdb_host [String] PuppetDB
hostname, when constructing a URL @param :puppetdb_port [Integer] Port number, defaults to 8080 (non-SSL) or 8081 (SSL) @param :puppetdb_ssl [Boolean] defaults to true, because you should use SSL @param :puppetdb_ssl_ca [String] Path to file containing CA certificate @param :puppetdb_ssl_verify [Boolean] Override the CA verification setting guessed from parameters @param :puppetdb_ssl_client_pem [String] PEM-encoded client key and certificate @param :puppetdb_ssl_client_p12 [String] pkcs12-encoded client key and certificate @param :puppetdb_ssl_client_password [String] Path to file containing password for SSL client key (any format) @param :puppetdb_ssl_client_auth [Boolean] Override the client-auth that is guessed from parameters @param :puppetdb_token [String] PE RBAC token to authenticate to PuppetDB
API
@param :timeout [Integer] Connection timeout for PuppetDB
(default=10)
# File lib/octocatalog-diff/puppetdb.rb, line 47 def initialize(options = {}) @connections = if options.key?(:puppetdb_url) urls = options[:puppetdb_url].is_a?(Array) ? options[:puppetdb_url] : [options[:puppetdb_url]] urls.map { |url| parse_url(url) } elsif options.key?(:puppetdb_host) is_ssl = options.fetch(:puppetdb_ssl, true) default_port = is_ssl ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT port = options.fetch(:puppetdb_port, default_port).to_i [{ ssl: is_ssl, host: options[:puppetdb_host], port: port }] elsif ENV['PUPPETDB_URL'] && !ENV['PUPPETDB_URL'].empty? [parse_url(ENV['PUPPETDB_URL'])] elsif ENV['PUPPETDB_HOST'] && !ENV['PUPPETDB_HOST'].empty? # Because environment variables are strings... # This will get the env var and see if it equals 'true'; the result # of this == comparison is the true/false boolean we need. is_ssl = ENV.fetch('PUPPETDB_SSL', 'true') == 'true' default_port = is_ssl ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT port = ENV.fetch('PUPPETDB_PORT', default_port).to_i [{ ssl: is_ssl, host: ENV['PUPPETDB_HOST'], port: port }] else [] end @timeout = options.fetch(:timeout, 10) @options = options end
Public Instance Methods
Wrapper around the httparty call in the private _get method. Returns the parsed result of getting the provided URL and returns a friendlier error message if there are network connection problems to PuppetDB
. @param path [String] Path portion of the URL @return [Object] Parsed reply from PuppetDB
as an object
# File lib/octocatalog-diff/puppetdb.rb, line 80 def get(path) _get(path) rescue Net::OpenTimeout, Errno::ECONNREFUSED => exc raise OctocatalogDiff::Errors::PuppetDBConnectionError, "#{exc.class} connecting to PuppetDB (need VPN on?): #{exc.message}" end
Private Instance Methods
HTTP(S) Query - will attempt to retrieve URL from each connection @param path [String] Path portion of the URL @return [String] Parsed response
# File lib/octocatalog-diff/puppetdb.rb, line 91 def _get(path) # You need at least one connection or else this can't do anything raise ArgumentError, 'No PuppetDB connections configured' if @connections.empty? # Keep track of the latest exception seen exc = nil # Try each connection in random order. This will return the first successful # response, and try the next connection if there's an error. Once it's out of # connections to try it will raise the last exception encountered. @connections.shuffle.each do |connection| complete_url = [ connection[:ssl] ? 'https://' : 'http://', connection[:host], ':', connection[:port], path ].join('') begin headers = { 'Accept' => 'application/json' } headers['X-Authentication'] = @options[:puppetdb_token] if @options[:puppetdb_token] more_options = { headers: headers, timeout: @timeout } if connection[:username] || connection[:password] more_options[:basic_auth] = { username: connection[:username], password: connection[:password] } end response = OctocatalogDiff::Util::HTTParty.get(complete_url, @options.merge(more_options), 'puppetdb') # Handle all non-200's from PuppetDB unless response[:code] == 200 raise OctocatalogDiff::Errors::PuppetDBNodeNotFoundError, "404 - #{response[:error]}" if response[:code] == 404 raise OctocatalogDiff::Errors::PuppetDBGenericError, "#{response[:code]} - #{response[:error]}" end # PuppetDB can return 'Not Found' as a string with a 200 response code raise NotFoundError, '404 - Not Found' if response[:body] == 'Not Found' # PuppetDB can also return an error message in a 200; we'll call this a 500 if response.key?(:error) raise OctocatalogDiff::Errors::PuppetDBGenericError, "500 - #{response[:error]}" end # If we get here without raising an error, it will fall out of the begin/rescue # with 'result' non-nil, and 'result' will then get returned. raise "Unparseable response from puppetdb: '#{response.inspect}'" unless response[:parsed] result = response[:parsed] rescue => exc # Set response to nil so the loop repeats itself if there are retries left. # Also sets 'exc' to the most recent exception, in case all retries are # exhausted and this exception has to be raised. result = nil end # If the previous query didn't error, return result return result unless result.nil? end # At this point no query has succeeded, so raise the last error encountered. raise exc end
Parse a URL to determine hostname, port number, and whether or not SSL is used. @param url [String] URL to parse @return [Hash] { ssl: true/false, host: <String>, port: <Integer> }
# File lib/octocatalog-diff/puppetdb.rb, line 156 def parse_url(url) uri = URI(url) if URI.split(url)[3].nil? uri.port = uri.scheme == 'https' ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT end raise ArgumentError, "URL #{url} has invalid scheme" unless uri.scheme =~ /^https?$/ parsed_url = { ssl: uri.scheme == 'https', host: uri.host, port: uri.port } if uri.user || uri.password parsed_url[:username] = uri.user parsed_url[:password] = uri.password end parsed_url rescue URI::InvalidURIError => exc raise exc.class, "Invalid URL: #{url} (#{exc.message})" end