Class: Puppet::Node::Facts::DiffPuppetdb

Inherits:
Indirector::REST
  • Object
show all
Includes:
Util::Puppetdb, Util::Puppetdb::CommandNames
Defined in:
lib/puppet/indirector/facts/diff_puppetdb.rb

Overview

A copy of the 2.x PuppetDB Facts terminus that performs find requests using the v4 API so that non-stringified and structured facts are returned. This is done because many comparisons executed by the future parser are type-sensitive. Without fully-typed facts, a variable containing a stringified fact (such as $::processorcount) will raise a fatal error when compared against an integer.

TODO: Remove once Puppet 4.0 or PuppetDB 3.0 are standard and backwards compatibility with PE 3.x isn’t required.

Instance Method Summary collapse

Instance Method Details

#find(request) ⇒ Object



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
# File 'lib/puppet/indirector/facts/diff_puppetdb.rb', line 53

def find(request)
  profile('facts#find', [:puppetdb, :facts, :find, request.key]) do
    begin
      url = Puppet::Util::Puppetdb.url_path("/v4/nodes/#{CGI.escape(request.key)}/facts")
      response = profile("Query for nodes facts: #{url}",
                         [:puppetdb, :facts, :find, :query_nodes, request.key]) do
        http_get(request, url, headers)
      end
      log_x_deprecation_header(response)

      if response.is_a? Net::HTTPSuccess
        profile("Parse fact query response (size: #{response.body.size})",
                [:puppetdb, :facts, :find, :parse_response, request.key]) do
          result = JSON.parse(response.body)
          # Note: the Inventory Service API appears to expect us to return nil here
          # if the node isn't found.  However, PuppetDB returns an empty array in
          # this case; for now we will just look for that condition and assume that
          # it means that the node wasn't found, so we will return nil.  In the
          # future we may want to improve the logic such that we can distinguish
          # between the "node not found" and the "no facts for this node" cases.
          if result.empty?
            return nil
          end
          facts = result.inject({}) do |a,h|
            a.merge(h['name'] => h['value'])
          end
          Puppet::Node::Facts.new(request.key, facts)
        end
      else
        # Newline characters cause an HTTP error, so strip them
        raise "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
      end
    rescue => e
      raise Puppet::Error, "Failed to find facts from PuppetDB at #{self.class.server}:#{self.class.port}: #{e}"
    end
  end
end

#get_trusted_info(node) ⇒ Object



21
22
23
24
25
26
# File 'lib/puppet/indirector/facts/diff_puppetdb.rb', line 21

def get_trusted_info(node)
  trusted = Puppet.lookup(:trusted_information) do
    Puppet::Context::TrustedInformation.local(node)
  end
  trusted.to_h
end

#headersObject



149
150
151
152
153
154
# File 'lib/puppet/indirector/facts/diff_puppetdb.rb', line 149

def headers
  {
    'Accept' => 'application/json',
    'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
  }
end

#save(request) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/puppet/indirector/facts/diff_puppetdb.rb', line 28

def save(request)
  profile('facts#save', [:puppetdb, :facts, :save, request.key]) do
    payload = profile('Encode facts command submission payload',
                      [:puppetdb, :facts, :encode]) do
      facts = request.instance.dup
      facts.values = facts.strip_internal.dup

      if ! Puppet::Util::Puppetdb.puppet3compat? || Puppet[:trusted_node_data]
        facts.values[:trusted] = get_trusted_info(request.node)
      end
      {
        'name' => facts.name,
        'values' => facts.values,
        # PDB-453: we call to_s to avoid a 'stack level too deep' error
        # when we attempt to use ActiveSupport 2.3.16 on RHEL 5 with
        # legacy storeconfigs.
        'environment' => request.options[:environment] || request.environment.to_s,
        'producer-timestamp' => request.options[:producer_timestamp] || Time.now.iso8601,
      }
    end

    submit_command(request.key, payload, CommandReplaceFacts, 3)
  end
end

#search(request) ⇒ Object

Search for nodes matching a set of fact constraints. The constraints are specified as a hash of the form:

‘=> value`

The only accepted ‘type` is ’facts’.

‘name` must be the fact name to query against.

‘operator` may be one of {eq, ne, lt, gt, le, ge, and will default to ’eq’ if unspecified.



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
# File 'lib/puppet/indirector/facts/diff_puppetdb.rb', line 102

def search(request)
  profile('facts#search', [:puppetdb, :facts, :search, request.key]) do
    return [] unless request.options
    operator_map = {
      'eq' => '=',
      'gt' => '>',
      'lt' => '<',
      'ge' => '>=',
      'le' => '<=',
    }
    filters = request.options.sort.map do |key,value|
      type, name, operator = key.to_s.split('.')
      operator ||= 'eq'
      raise Puppet::Error, "Fact search against keys of type '#{type}' is unsupported" unless type == 'facts'
      if operator == 'ne'
        ['not', ['=', ['fact', name], value]]
      else
        [operator_map[operator], ['fact', name], value]
      end
    end

    query = ['and'] + filters
    query_param = CGI.escape(query.to_json)

    begin
      url = Puppet::Util::Puppetdb.url_path("/v3/nodes?query=#{query_param}")
      response = profile("Fact query request: #{URI.unescape(url)}",
                         [:puppetdb, :facts, :search, :query_request, request.key]) do
        http_get(request, url, headers)
      end
      log_x_deprecation_header(response)

      if response.is_a? Net::HTTPSuccess
        profile("Parse fact query response (size: #{response.body.size})",
                [:puppetdb, :facts, :search, :parse_query_response, request.key,]) do
          JSON.parse(response.body).collect {|s| s['name']}
        end
      else
        # Newline characters cause an HTTP error, so strip them
        raise "[#{response.code} #{response.message}] #{response.body.gsub(/[\r\n]/, '')}"
      end
    rescue => e
      raise Puppet::Error, "Could not perform inventory search from PuppetDB at #{self.class.server}:#{self.class.port}: #{e}"
    end
  end
end