Class: OctocatalogDiff::Facts::PuppetDB

Inherits:
Object
  • Object
show all
Defined in:
lib/octocatalog-diff/facts/puppetdb.rb

Overview

Deal with facts in PuppetDB

Constant Summary collapse

PUPPETDB_QUERY_FACTS_URL =

Supporting multiple versions of the PuppetDB API.

{
  '3' => '/v3/nodes/<NODE>/facts',
  '4' => '/pdb/query/v4/nodes/<NODE>/facts'
}.freeze

Class Method Summary collapse

Class Method Details

.fact_retriever(options = {}, node) ⇒ Hash

Retrieve facts from PuppetDB for a specified node.

Parameters:

  • :puppetdb_url (String|Array)

    > URL to PuppetDB

  • :retry (Integer)

    > Retry after timeout (default 0 retries, can be more)

  • node (String)

    Node name. (REQUIRED for PuppetDB fact source)

Returns:

  • (Hash)

    Facts

Raises:

  • (ArgumentError)


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
# File 'lib/octocatalog-diff/facts/puppetdb.rb', line 23

def self.fact_retriever(options = {}, node)
  # Set up some variables from options
  raise ArgumentError, 'puppetdb_url is required' unless options[:puppetdb_url].is_a?(String)
  raise ArgumentError, 'node must be a non-empty string' unless node.is_a?(String) && node != ''
  puppetdb_api_version = options.fetch(:puppetdb_api_version, 4)
  uri = PUPPETDB_QUERY_FACTS_URL.fetch(puppetdb_api_version.to_s).gsub('<NODE>', node)
  retries = options.fetch(:retry, 0).to_i

  # Construct puppetdb object and options
  opts = options.merge(timeout: 5)
  puppetdb = OctocatalogDiff::PuppetDB.new(opts)

  # Use OctocatalogDiff::PuppetDB to pull facts
  exception_class = nil
  exception_message = nil
  obj_to_return = nil
  packages = nil
  (retries + 1).times do
    begin
      result = puppetdb.get(uri)
      facts = {}
      result.map { |x| facts[x['name']] = x['value'] }
      if facts.empty?
        message = "Unable to retrieve facts for node #{node} from PuppetDB (empty or nil)!"
        raise OctocatalogDiff::Errors::FactRetrievalError, message
      end

      # Create a structure compatible with YAML fact files.
      obj_to_return = { 'name' => node, 'values' => {} }
      facts.each { |k, v| obj_to_return['values'][k.sub(/^::/, '')] = v }
      break # Not return, to avoid LocalJumpError in Ruby 2.2
    rescue OctocatalogDiff::Errors::PuppetDBConnectionError => exc
      exception_class = OctocatalogDiff::Errors::FactSourceError
      exception_message = "Fact retrieval failed (#{exc.class}) (#{exc.message})"
    rescue OctocatalogDiff::Errors::PuppetDBNodeNotFoundError => exc
      exception_class = OctocatalogDiff::Errors::FactRetrievalError
      exception_message = "Node #{node} not found in PuppetDB (#{exc.message})"
    rescue OctocatalogDiff::Errors::PuppetDBGenericError => exc
      exception_class = OctocatalogDiff::Errors::FactRetrievalError
      exception_message = "Fact retrieval failed for node #{node} from PuppetDB (#{exc.message})"
    end
  end

  raise exception_class, exception_message if obj_to_return.nil?

  return obj_to_return if puppetdb_api_version < 4 || (!options[:puppetdb_package_inventory])

  (retries + 1).times do
    begin
      result = puppetdb.get("/pdb/query/v4/package-inventory/#{node}")
      packages = {}
      result.each do |pkg|
        key = "#{pkg['package_name']}+#{pkg['provider']}"
        # Need to handle the situation where a package has multiple versions installed.
        # The _puppet_inventory_1 hash lists them separated by "; ".
        if packages.key?(key)
          packages[key]['version'] += "; #{pkg['version']}"
        else
          packages[key] = pkg
        end
      end
      break
    rescue OctocatalogDiff::Errors::PuppetDBConnectionError => exc
      exception_class = OctocatalogDiff::Errors::FactSourceError
      exception_message = "Package inventory retrieval failed (#{exc.class}) (#{exc.message})"
    # This is not expected to occur, but we'll leave it just in case. A query to package-inventory
    # for a non-existant node returns a 200 OK with an empty list of packages:
    rescue OctocatalogDiff::Errors::PuppetDBNodeNotFoundError
      packages = {}
    rescue OctocatalogDiff::Errors::PuppetDBGenericError => exc
      exception_class = OctocatalogDiff::Errors::FactRetrievalError
      exception_message = "Package inventory retrieval failed for node #{node} from PuppetDB (#{exc.message})"
    end
  end

  raise exception_class, exception_message if packages.nil?

  unless packages.empty?
    obj_to_return['values']['_puppet_inventory_1'] = {
      'packages' => packages.values.map { |pkg| [pkg['package_name'], pkg['version'], pkg['provider']] }
    }
  end

  obj_to_return
end