-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #238 from alexjfisher/remote_pql_query
Add `remote_pql_query` function
- Loading branch information
Showing
5 changed files
with
328 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
--- | ||
.travis.yml: | ||
secure: "IkrfAnec7ovZLMvhvXt8ZihyYdAJTC/nm7KDm4u2G/uD2NGaMdHNOAenkwIwC1vfCzHKcgC5u/lAYFrYvHpQpJW0kHLKnk1SpndfWX9kd5SlDDzEP5mJGjMZeTY6H9sV5fsB6Pt7l/sw5ACL/0bFDl0mYBnVhGv6UxZZ5xMQIUw=" | ||
Gemfile: | ||
optional: | ||
':test': | ||
- gem: 'puppetdb-ruby' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'tempfile' | ||
|
||
# Perform a PuppetDB query on an arbitrary PuppetDB server | ||
# | ||
# If you need to query a PuppetDB server that is not connected to your Puppet | ||
# Server (perhaps part of a separate Puppet installation that uses its own | ||
# PKI), then this function is for you! | ||
# | ||
# The `puppetdb-ruby` gem _must_ be installed in your puppetserver's ruby | ||
# environment before you can use this function! | ||
Puppet::Functions.create_function(:'extlib::remote_pql_query') do | ||
local_types do | ||
type 'HTTPUrl = Pattern[/(?i:\Ahttp:\/\/.*\z)/]' | ||
type 'HTTPSUrl = Pattern[/(?i:\Ahttps:\/\/.*\z)/]' | ||
end | ||
|
||
# @param query The PQL query to run | ||
# @param url The PuppetDB HTTPS URL (SSL with cert-based authentication) | ||
# @param key The client SSL key associated with the SSL client certificate | ||
# @param cert The client SSL cert to present to PuppetDB | ||
# @param cacert The CA certificate | ||
# @param options PuppetDB query options. (See https://www.puppet.com/docs/puppetdb/8/api/query/v4/paging) | ||
# @return Returns the PQL query response results | ||
dispatch :secure_remote_pql_query do | ||
param 'String[1]', :query | ||
param 'HTTPSUrl', :url | ||
param 'String[1]', :key | ||
param 'String[1]', :cert | ||
param 'String[1]', :cacert | ||
optional_param 'Hash', :options | ||
return_type 'Array' | ||
end | ||
|
||
# @param query The PQL query to run | ||
# @param url The PuppetDB HTTP URL (non SSL version) | ||
# @param options PuppetDB query options. (See https://www.puppet.com/docs/puppetdb/8/api/query/v4/paging) | ||
# @return Returns the PQL query response results | ||
# @example 'Collecting' exported resource defined type from a foreign PuppetDB | ||
# $pql_results = extlib::remote_pql_query( | ||
# "resources[title,parameters] { type = \"My_Module::My_type\" and nodes { deactivated is null } and exported = true and parameters.collect_on = \"${trusted['certname']}\" }", | ||
# 'http://puppetdb.example.com:8080', | ||
# ) | ||
# $pql_results.each |$result| { | ||
# my_module::my_type { $result['title']: | ||
# * => $result['parameters'] | ||
# } | ||
# } | ||
dispatch :insecure_remote_pql_query do | ||
param 'String[1]', :query | ||
param 'HTTPUrl', :url | ||
optional_param 'Hash', :options | ||
return_type 'Array' | ||
end | ||
|
||
def secure_remote_pql_query(query, url, key, cert, cacert, options = {}) | ||
keyfile = Tempfile.new('remote_pql_query_keyfile') | ||
certfile = Tempfile.new('remote_pql_query_certfile') | ||
cafile = Tempfile.new('remote_pql_query_cafile') | ||
|
||
begin | ||
keyfile.write(key) | ||
keyfile.close | ||
|
||
certfile.write(cert) | ||
certfile.close | ||
|
||
cafile.write(cacert) | ||
cafile.close | ||
|
||
client_options = { | ||
server: url, | ||
pem: { | ||
'key' => keyfile.path, | ||
'cert' => certfile.path, | ||
'ca_file' => cafile.path, | ||
} | ||
} | ||
|
||
remote_pql_query(query, options, client_options) | ||
ensure | ||
[keyfile, certfile, cafile].each(&:unlink) | ||
end | ||
end | ||
|
||
def insecure_remote_pql_query(query, url, options = {}) | ||
client_options = { server: url } | ||
|
||
remote_pql_query(query, options, client_options) | ||
end | ||
|
||
def remote_pql_query(query, query_options, client_options) | ||
require 'puppetdb' | ||
|
||
# If the dalen/puppetdbquery module is installed, then there'll be a clash | ||
# of libraries/namespaces and we need to manually require the files from | ||
# puppetdb-ruby... | ||
unless PuppetDB.constants.include?(:Client) | ||
require 'puppetdb/client' | ||
require 'puppetdb/query' | ||
require 'puppetdb/response' | ||
require 'puppetdb/error' | ||
require 'puppetdb/config' | ||
end | ||
|
||
client = PuppetDB::Client.new(client_options) | ||
|
||
begin | ||
response = client.request( | ||
'', # PQL | ||
query, | ||
query_options | ||
) | ||
|
||
response.data | ||
rescue PuppetDB::APIError => e | ||
raise Puppet::Error, "PuppetDB API Error: #{e.response.inspect}" | ||
rescue StandardError => e | ||
raise Puppet::Error, "Remote PQL query failed: #{e.message}" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
require 'puppetdb' | ||
|
||
describe 'extlib::remote_pql_query' do | ||
let(:mock_client) { instance_double(PuppetDB::Client) } | ||
let(:mock_response) { PuppetDB::Response.new(['test_result']) } | ||
|
||
before do | ||
allow(PuppetDB::Client).to receive(:new).and_return(mock_client) | ||
allow(mock_client).to receive(:request).and_return(mock_response) | ||
end | ||
|
||
context 'secure_remote_pql_query' do | ||
it 'returns the data array for valid HTTPS params' do | ||
is_expected.to run.with_params( | ||
'facts { name = "osfamily" }', # query | ||
'https://puppetdb.example.com', # URL (matches HTTPS dispatch) | ||
'client_key', # key | ||
'client_cert', # cert | ||
'ca_cert' # cacert | ||
).and_return(['test_result']) | ||
end | ||
|
||
it 'raises ArgumentError if given an HTTP URL in the secure dispatch' do | ||
is_expected.to run.with_params( | ||
'facts { name = "osfamily" }', | ||
'http://puppetdb.example.com', # Wrong for secure dispatch | ||
'client_key', | ||
'client_cert', | ||
'ca_cert' | ||
).and_raise_error( | ||
ArgumentError, %r{parameter 'url'}i | ||
) | ||
end | ||
end | ||
|
||
context 'insecure_remote_pql_query' do | ||
it 'returns the data array for valid HTTP params' do | ||
is_expected.to run.with_params( | ||
'facts { name = "osfamily" }', # query | ||
'http://puppetdb.example.com' # URL (matches HTTP dispatch) | ||
).and_return(['test_result']) | ||
end | ||
|
||
it 'raises ArgumentError if given an HTTPS URL in the insecure dispatch' do | ||
is_expected.to run.with_params( | ||
'facts { name = "osfamily" }', | ||
'https://puppetdb.example.com' # Wrong for insecure dispatch | ||
).and_raise_error( | ||
ArgumentError, %r{parameter 'url'}i | ||
) | ||
end | ||
end | ||
|
||
context 'with query options' do | ||
it 'passes options to the client.request call' do | ||
allow(mock_client).to receive(:request).with( | ||
'', | ||
'resources { type = "File" }', | ||
{ 'limit' => 5 } | ||
).and_return(mock_response) | ||
|
||
is_expected.to run.with_params( | ||
'resources { type = "File" }', | ||
'http://puppetdb.example.com', | ||
{ 'limit' => 5 } | ||
).and_return(['test_result']) | ||
|
||
expect(mock_client).to have_received(:request).with( | ||
'', | ||
'resources { type = "File" }', | ||
{ 'limit' => 5 } | ||
) | ||
end | ||
end | ||
|
||
context 'when PuppetDB::APIError is raised' do | ||
it 're-raises as a Puppet::Error' do | ||
allow(mock_client).to receive(:request).and_raise( | ||
PuppetDB::APIError.new( | ||
instance_double(PuppetDB::Response, inspect: 'some API error') | ||
) | ||
) | ||
|
||
is_expected.to run.with_params( | ||
'facts { name = "osfamily" }', | ||
'http://puppetdb.example.com' | ||
).and_raise_error(Puppet::Error, %r{PuppetDB API Error: some API error}) | ||
end | ||
end | ||
|
||
context 'when a generic error is raised' do | ||
it 're-raises as a Puppet::Error' do | ||
allow(mock_client).to receive(:request).and_raise(RuntimeError, 'boom') | ||
|
||
is_expected.to run.with_params( | ||
'facts { name = "osfamily" }', | ||
'http://puppetdb.example.com' | ||
).and_raise_error(Puppet::Error, %r{Remote PQL query failed: boom}) | ||
end | ||
end | ||
end |