From b02fd609295c9b12a209ae7fe480afe5b26905d7 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Fri, 3 Jan 2020 18:45:52 -0800 Subject: [PATCH] Porting functions to the modern Puppet 4.x API --- .../functions/puppetdbquery/query_facts.rb | 68 +++++++++++ .../functions/puppetdbquery/query_nodes.rb | 80 +++++++++++++ .../puppetdbquery/query_resources.rb | 106 ++++++++++++++++++ .../puppetdbquery_query_facts_spec.rb | 41 +++++++ .../puppetdbquery_query_nodes_spec.rb | 41 +++++++ .../puppetdbquery_query_resources_spec.rb | 41 +++++++ 6 files changed, 377 insertions(+) create mode 100644 lib/puppet/functions/puppetdbquery/query_facts.rb create mode 100644 lib/puppet/functions/puppetdbquery/query_nodes.rb create mode 100644 lib/puppet/functions/puppetdbquery/query_resources.rb create mode 100644 spec/functions/puppetdbquery_query_facts_spec.rb create mode 100644 spec/functions/puppetdbquery_query_nodes_spec.rb create mode 100644 spec/functions/puppetdbquery_query_resources_spec.rb diff --git a/lib/puppet/functions/puppetdbquery/query_facts.rb b/lib/puppet/functions/puppetdbquery/query_facts.rb new file mode 100644 index 0000000..938c048 --- /dev/null +++ b/lib/puppet/functions/puppetdbquery/query_facts.rb @@ -0,0 +1,68 @@ +# This is an autogenerated function, ported from the original legacy version. +# It /should work/ as is, but will not have all the benefits of the modern +# function API. You should see the function docs to learn how to add function +# signatures for type safety and to document this function using puppet-strings. +# +# https://puppet.com/docs/puppet/latest/custom_functions_ruby.html +# +# ---- original file header ---- + +# ---- original file header ---- +# +# @summary +# +# accepts two arguments, a query used to discover nodes, and a list of facts +# that should be returned from those hosts. +# +# The query specified should conform to the following format: +# (Type[title] and fact_namefact_value) or ... +# Package[mysql-server] and cluster_id=my_first_cluster +# +# The facts list provided should be an array of fact names. +# +# The result is a hash that maps the name of the nodes to a hash of facts that +# contains the facts specified. +# +# +# +Puppet::Functions.create_function(:'puppetdbquery::query_facts') do + # @param args + # The original array of arguments. Port this to individually managed params + # to get the full benefit of the modern function API. + # + # @return [Data type] + # Describe what the function returns here + # + dispatch :default_impl do + # Call the method named 'default_impl' when this is matched + # Port this to match individual params for better type safety + repeated_param 'Any', :args + end + + + def default_impl(*args) + + query, facts = args + facts = facts.map { |fact| fact.match(/\./) ? fact.split('.') : fact } + facts_for_query = facts.map { |fact| fact.is_a?(Array) ? fact.first : fact } + + require 'puppet/util/puppetdb' + + # This is needed if the puppetdb library isn't pluginsynced to the master + $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..')) + begin + require 'puppetdb/connection' + ensure + $LOAD_PATH.shift + end + + PuppetDB::Connection.check_version + + uri = URI(Puppet::Util::Puppetdb.config.server_urls.first) + puppetdb = PuppetDB::Connection.new(uri.host, uri.port, uri.scheme == 'https') + parser = PuppetDB::Parser.new + query = parser.facts_query query, facts_for_query if query.is_a? String + parser.facts_hash(puppetdb.query(:facts, query, :extract => [:certname, :name, :value]), facts) + + end +end diff --git a/lib/puppet/functions/puppetdbquery/query_nodes.rb b/lib/puppet/functions/puppetdbquery/query_nodes.rb new file mode 100644 index 0000000..3a70b5e --- /dev/null +++ b/lib/puppet/functions/puppetdbquery/query_nodes.rb @@ -0,0 +1,80 @@ +# This is an autogenerated function, ported from the original legacy version. +# It /should work/ as is, but will not have all the benefits of the modern +# function API. You should see the function docs to learn how to add function +# signatures for type safety and to document this function using puppet-strings. +# +# https://puppet.com/docs/puppet/latest/custom_functions_ruby.html +# +# ---- original file header ---- + +# ---- original file header ---- +# +# @summary +# +# accepts two arguments, a query used to discover nodes, and an optional +# fact that should be returned. +# +# The query specified should conform to the following format: +# (Type[title] and fact_namefact_value) or ... +# Package["mysql-server"] and cluster_id=my_first_cluster +# +# The second argument should be single fact or series of keys joined on periods +# (this argument is optional) +# +# +# +Puppet::Functions.create_function(:'puppetdbquery::query_nodes') do + # @param args + # The original array of arguments. Port this to individually managed params + # to get the full benefit of the modern function API. + # + # @return [Data type] + # Describe what the function returns here + # + dispatch :default_impl do + # Call the method named 'default_impl' when this is matched + # Port this to match individual params for better type safety + repeated_param 'Any', :args + end + + + def default_impl(*args) + + query, fact = args + fact_for_query = if fact && fact.match(/\./) + fact.split('.').first + else + fact + end + + require 'puppet/util/puppetdb' + + # This is needed if the puppetdb library isn't pluginsynced to the master + $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..')) + begin + require 'puppetdb/connection' + ensure + $LOAD_PATH.shift + end + + PuppetDB::Connection.check_version + + uri = URI(Puppet::Util::Puppetdb.config.server_urls.first) + puppetdb = PuppetDB::Connection.new(uri.host, uri.port, uri.scheme == 'https') + parser = PuppetDB::Parser.new + if fact_for_query + query = parser.facts_query(query, [fact_for_query]) + response = puppetdb.query(:facts, query, :extract => :value) + + if fact.split('.').size > 1 + parser.extract_nested_fact(response, fact.split('.')[1..-1]) + else + response.collect { |f| f['value'] } + end + else + query = parser.parse(query, :nodes) if query.is_a? String + puppetdb.query(:nodes, query, :extract => :certname).collect { |n| n['certname'] } + end + + end +end diff --git a/lib/puppet/functions/puppetdbquery/query_resources.rb b/lib/puppet/functions/puppetdbquery/query_resources.rb new file mode 100644 index 0000000..095a6b2 --- /dev/null +++ b/lib/puppet/functions/puppetdbquery/query_resources.rb @@ -0,0 +1,106 @@ +# This is an autogenerated function, ported from the original legacy version. +# It /should work/ as is, but will not have all the benefits of the modern +# function API. You should see the function docs to learn how to add function +# signatures for type safety and to document this function using puppet-strings. +# +# https://puppet.com/docs/puppet/latest/custom_functions_ruby.html +# +# ---- original file header ---- + +# ---- original file header ---- +# +# @summary +# +# Accepts two or three arguments: a query used to discover nodes, a +# resource query for the resources that should be returned from +# those hosts, and optionally a boolean for whether or not to group the results by host. +# +# The result is a hash (by default) that maps the name of the nodes to a list of +# resource entries. This is a list because there's no single +# reliable key for resource operations that's of any use to the end user. +# +# If the third parameters is false the result will be a an array of all resources found. +# +# Examples: +# +# Returns the parameters and such for the ntp class for all CentOS nodes: +# +# query_resources('operatingsystem=CentOS', 'Class["ntp"]') +# +# Returns information on the apache user on all nodes that have apache installed on port 443: +# +# query_resources('Class["apache"]{ port = 443 }', 'User["apache"]') +# +# Returns the parameters and such for the apache class for all nodes: +# +# query_resources(false, 'Class["apache"]') +# +# Returns the parameters for the apache class for all nodes in a flat array: +# +# query_resources(false, 'Class["apache"]', false) +# +# +# +Puppet::Functions.create_function(:'puppetdbquery::query_resources') do + # @param args + # The original array of arguments. Port this to individually managed params + # to get the full benefit of the modern function API. + # + # @return [Data type] + # Describe what the function returns here + # + dispatch :default_impl do + # Call the method named 'default_impl' when this is matched + # Port this to match individual params for better type safety + repeated_param 'Any', :args + end + + + def default_impl(*args) + + nodequery, resquery, grouphosts = args + + require 'puppet/util/puppetdb' + # This is needed if the puppetdb library isn't pluginsynced to the master + $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..')) + begin + require 'puppetdb/connection' + ensure + $LOAD_PATH.shift + end + + PuppetDB::Connection.check_version + + uri = URI(Puppet::Util::Puppetdb.config.server_urls.first) + puppetdb = PuppetDB::Connection.new(uri.host, uri.port, uri.scheme == 'https') + parser = PuppetDB::Parser.new + nodequery = parser.parse nodequery, :facts if nodequery and nodequery.is_a? String + resquery = parser.parse resquery, :none if resquery and resquery.is_a? String + + # Construct query + if resquery && !resquery.empty? + if nodequery && !nodequery.empty? + q = ['and', resquery, nodequery] + else + q = resquery + end + else + fail "PuppetDB resources query error: at least one argument must be non empty; arguments were: nodequery: #{nodequery.inspect} and requery: #{resquery.inspect}" + end + + # Fetch the results + results = puppetdb.query(:resources, q) + + # If grouphosts is true create a nested hash with nodes and resources + if grouphosts + results.reduce({}) do |ret, resource| + ret[resource['certname']] = [] unless ret.key? resource['certname'] + ret[resource['certname']] << resource + ret + end + else + results + end + + end +end diff --git a/spec/functions/puppetdbquery_query_facts_spec.rb b/spec/functions/puppetdbquery_query_facts_spec.rb new file mode 100644 index 0000000..3e13ebf --- /dev/null +++ b/spec/functions/puppetdbquery_query_facts_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'puppetdbquery::query_facts' do + # without knowing details about the implementation, this is the only test + # case that we can autogenerate. You should add more examples below! + it { is_expected.not_to eq(nil) } + +################################# +# Below are some example test cases. You may uncomment and modify them to match +# your needs. Notice that they all expect the base error class of `StandardError`. +# This is because the autogenerated function uses an untyped array for parameters +# and relies on your implementation to do the validation. As you convert your +# function to proper dispatches and typed signatures, you should change the +# expected error of the argument validation examples to `ArgumentError`. +# +# Other error types you might encounter include +# +# * StandardError +# * ArgumentError +# * Puppet::ParseError +# +# Read more about writing function unit tests at https://rspec-puppet.com/documentation/functions/ +# +# it 'raises an error if called with no argument' do +# is_expected.to run.with_params.and_raise_error(StandardError) +# end +# +# it 'raises an error if there is more than 1 arguments' do +# is_expected.to run.with_params({ 'foo' => 1 }, 'bar' => 2).and_raise_error(StandardError) +# end +# +# it 'raises an error if argument is not the proper type' do +# is_expected.to run.with_params('foo').and_raise_error(StandardError) +# end +# +# it 'returns the proper output' do +# is_expected.to run.with_params(123).and_return('the expected output') +# end +################################# + +end diff --git a/spec/functions/puppetdbquery_query_nodes_spec.rb b/spec/functions/puppetdbquery_query_nodes_spec.rb new file mode 100644 index 0000000..6030cbb --- /dev/null +++ b/spec/functions/puppetdbquery_query_nodes_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'puppetdbquery::query_nodes' do + # without knowing details about the implementation, this is the only test + # case that we can autogenerate. You should add more examples below! + it { is_expected.not_to eq(nil) } + +################################# +# Below are some example test cases. You may uncomment and modify them to match +# your needs. Notice that they all expect the base error class of `StandardError`. +# This is because the autogenerated function uses an untyped array for parameters +# and relies on your implementation to do the validation. As you convert your +# function to proper dispatches and typed signatures, you should change the +# expected error of the argument validation examples to `ArgumentError`. +# +# Other error types you might encounter include +# +# * StandardError +# * ArgumentError +# * Puppet::ParseError +# +# Read more about writing function unit tests at https://rspec-puppet.com/documentation/functions/ +# +# it 'raises an error if called with no argument' do +# is_expected.to run.with_params.and_raise_error(StandardError) +# end +# +# it 'raises an error if there is more than 1 arguments' do +# is_expected.to run.with_params({ 'foo' => 1 }, 'bar' => 2).and_raise_error(StandardError) +# end +# +# it 'raises an error if argument is not the proper type' do +# is_expected.to run.with_params('foo').and_raise_error(StandardError) +# end +# +# it 'returns the proper output' do +# is_expected.to run.with_params(123).and_return('the expected output') +# end +################################# + +end diff --git a/spec/functions/puppetdbquery_query_resources_spec.rb b/spec/functions/puppetdbquery_query_resources_spec.rb new file mode 100644 index 0000000..7e367ff --- /dev/null +++ b/spec/functions/puppetdbquery_query_resources_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'puppetdbquery::query_resources' do + # without knowing details about the implementation, this is the only test + # case that we can autogenerate. You should add more examples below! + it { is_expected.not_to eq(nil) } + +################################# +# Below are some example test cases. You may uncomment and modify them to match +# your needs. Notice that they all expect the base error class of `StandardError`. +# This is because the autogenerated function uses an untyped array for parameters +# and relies on your implementation to do the validation. As you convert your +# function to proper dispatches and typed signatures, you should change the +# expected error of the argument validation examples to `ArgumentError`. +# +# Other error types you might encounter include +# +# * StandardError +# * ArgumentError +# * Puppet::ParseError +# +# Read more about writing function unit tests at https://rspec-puppet.com/documentation/functions/ +# +# it 'raises an error if called with no argument' do +# is_expected.to run.with_params.and_raise_error(StandardError) +# end +# +# it 'raises an error if there is more than 1 arguments' do +# is_expected.to run.with_params({ 'foo' => 1 }, 'bar' => 2).and_raise_error(StandardError) +# end +# +# it 'raises an error if argument is not the proper type' do +# is_expected.to run.with_params('foo').and_raise_error(StandardError) +# end +# +# it 'returns the proper output' do +# is_expected.to run.with_params(123).and_return('the expected output') +# end +################################# + +end