voxpupuli / onceover

Your gateway drug to automated infrastructure testing with Puppet
141 stars 45 forks source link

Factset instructions appear unsupported for Puppet 2021.4.x #321

Open chambersmp opened 2 years ago

chambersmp commented 2 years ago

Issue

The instructions to generate a node's factset on the Puppet primary server do not seem to work for Puppet Enterprise version 2021.4.x.

Provided Example: puppet facts --terminus puppetdb \<node certname> > fact_set_name.json

This command results in an empty factset containing only the node's certname as a key with null value.

Recommendation

  1. Add the following factset alternatives to README documentation:

Puppet Query CLI from PE Primary Server puppet query "inventory { certname = '\<targetnode>' }"

curl from PE Primary Server curl -X GET http://localhost:8080/pdb/query/v4/inventory -d 'query=["=", "certname", "\<targetnode>"]'

Authenticated PuppetDB Query from Remote Client curl -X GET https://\<pe-primary-fqdn>:8081/pdb/query/v4/inventory?query=["=", "certname", "\<targetnode>"] -H 'X-Authentication: \<Token>"

The resultant factset output requires a minor edit to change the 'facts' key to 'values' to allow onceover processing. The next recommendation suggests updating the onceover > control-repo class to accept the facts key as an alternative to avoid editing the output.

  1. Update the Control-Repo class definition for facts method to support the 'facts' key hash. Currently the control-repo class finds the facts hash at the root or nested the 'values' key of the factset json file. The output from the factset methods above will nest the facts under the 'facts' key at the parent level. To avoid editing the factset output each time it's generated, it would be ideal to update the self.facts() method definition to retrieve facts from 'values', 'facts' before defaulting to root level.

ref: onceover/lib/onceover/controlrepo.rb

#line 71 to 81
    def self.facts(filter = nil)
      # Would add a condition to look for the 'facts' key before defaulting to root. Atm only looks at 'values' key.
      @@existing_controlrepo.facts(filter, 'values'). 
    end

    def self.trusted_facts(filter = nil)
      @@existing_controlrepo.facts(filter, 'trusted')
    end

    def self.trusted_external_facts(filter = nil)
      @@existing_controlrepo.facts(filter, 'trusted_external')
    end

...
# Example: To obtain facts from 'facts' key hash in PuppetDB or Inventory API factset. 
# Reusing the 'values' key logic but searching for presence of 'facts' key in fact file before defaulting to root 
# 
#line 192 onwards
def facts(filter = nil, key = 'values')
      # Returns an array facts hashes
      all_facts = []
      logger.debug "Reading factsets"
      @facts_files.each do |file|
        facts_from_file = read_facts(file)
        # Facter 4 removed the top level key 'values' and, instead, puts facts
        # at the top level. The conditional below accounts for this.
        if (key.eql?('values') and facts_from_file.has_key?('values')) or !key.eql?('values')
          all_facts << facts_from_file[key]
        elsif (key.eql?('values') and facts_from_file.has_key?('facts')) # facts key present in puppetdb inventory factset
          all_facts << facts_from_file['facts']                                            # obtains from puppetdb factset 
        else
          all_facts << facts_from_file
        end
      end
      if filter
        # Allow us to pass a hash of facts to filter by
        raise "Filter param must be a hash" unless filter.is_a?(Hash)

        all_facts.keep_if do |hash|
          matches = []
          filter.each do |filter_fact,value|
            matches << keypair_is_in_hash(hash,filter_fact,value)
          end
          !matches.include? false
        end
      end
      return all_facts
    end

This should then support factsets like the ones below:

values hash { "certname": "\<example-certname.foo.bar>", "environment": "production", "values": { "agent_specified_environment": "production", "aio_agent_build": "6.25.1", "aio_agent_version": "6.25.1", "architecture": "x86_64", "augeas": { "version": "1.12.0" }, ... }

facts hash { "certname": "\<example-certname.foo.bar>", "environment": "production", "facts": { "agent_specified_environment": "production", "aio_agent_build": "6.25.1", "aio_agent_version": "6.25.1", "architecture": "x86_64", "augeas": { "version": "1.12.0" }, ... } root facts { "certname": "\<example-certname.foo.bar>", "environment": "production", "agent_specified_environment": "production", "aio_agent_build": "6.25.1", "aio_agent_version": "6.25.1", "architecture": "x86_64", "augeas": { "version": "1.12.0" ... }

marcospjunior commented 1 year ago

Try using the command puppet facts find --terminus puppetdb <certname>. If you are using one of the latest agents, since the release of version 7, the default action for puppet facts is "puppet facts show" which displays the current node's facts. That's the reason for the empty factsets when passing the certname as a parameter.