/usr/lib/ruby/vendor_ruby/octocatalog-diff/puppetdb.rb is in octocatalog-diff 1.5.3-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | # frozen_string_literal: true
require_relative 'errors'
require_relative 'util/httparty'
require 'uri'
module OctocatalogDiff
# A standard way to connect to PuppetDB from the various scripts in this repository.
class PuppetDB
DEFAULT_HTTPS_PORT = 8081
DEFAULT_HTTP_PORT = 8080
# Allow connections to be read (used in tests for now)
attr_reader :connections
# 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)
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
# 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
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
# HTTP(S) Query - will attempt to retrieve URL from each connection
# @param path [String] Path portion of the URL
# @return [String] Parsed response
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> }
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
end
end
|