File: facts.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (164 lines) | stat: -rw-r--r-- 4,003 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
require 'time'

require_relative '../../puppet/node'
require_relative '../../puppet/indirector'
require_relative '../../puppet/util/psych_support'


# Manage a given node's facts.  This either accepts facts and stores them, or
# returns facts for a given node.
class Puppet::Node::Facts
  include Puppet::Util::PsychSupport

  # Set up indirection, so that nodes can be looked for in
  # the node sources.
  extend Puppet::Indirector

  # We want to expire any cached nodes if the facts are saved.
  module NodeExpirer
    def save(instance, key = nil, options={})
      Puppet::Node.indirection.expire(instance.name, options)
      super
    end
  end

  indirects :facts, :terminus_setting => :facts_terminus, :extend => NodeExpirer

  attr_accessor :name, :values, :timestamp

  def add_local_facts
    values["clientcert"] = Puppet.settings[:certname]
    values["clientversion"] = Puppet.version.to_s
    values["clientnoop"] = Puppet.settings[:noop]
  end

  def initialize(name, values = {})
    @name = name
    @values = values

    add_timestamp
  end

  def initialize_from_hash(data)
    @name = data['name']
    @values = data['values']
    # Timestamp will be here in YAML, e.g. when reading old reports
    timestamp = @values.delete('_timestamp')
    # Timestamp will be here in JSON
    timestamp ||= data['timestamp']

    if timestamp.is_a? String
      @timestamp = Time.parse(timestamp)
    else
      @timestamp = timestamp
    end

    self.expiration = data['expiration']
    if expiration.is_a? String
      self.expiration = Time.parse(expiration)
    end
  end

  # Add extra values, such as facts given to lookup on the command line. The
  # extra values will override existing values.
  # @param extra_values [Hash{String=>Object}] the values to add
  # @api private
  def add_extra_values(extra_values)
    @values.merge!(extra_values)
    nil
  end

  # Sanitize fact values by converting everything not a string, Boolean
  # numeric, array or hash into strings.
  def sanitize
    values.each do |fact, value|
      values[fact] = sanitize_fact value
    end
  end

  def ==(other)
    return false unless self.name == other.name
    values == other.values
  end

  def self.from_data_hash(data)
    new_facts = allocate
    new_facts.initialize_from_hash(data)
    new_facts
  end

  def to_data_hash
    result = {
      'name' => name,
      'values' => values
    }

    if @timestamp
      if @timestamp.is_a? Time
        result['timestamp'] = @timestamp.iso8601(9)
      else
        result['timestamp'] = @timestamp
      end
    end

    if expiration
      if expiration.is_a? Time
        result['expiration'] = expiration.iso8601(9)
      else
        result['expiration'] = expiration
      end
    end

    result
  end

  def add_timestamp
    @timestamp = Time.now
  end

  def to_yaml
    facts_to_display = Psych.parse_stream(YAML.dump(self))
    quote_special_strings(facts_to_display)
  end

  private

  def quote_special_strings(fact_hash)
    fact_hash.grep(Psych::Nodes::Scalar).each do |node|
      next unless node.value =~ /:/

      node.plain  = false
      node.quoted = true
      node.style  = Psych::Nodes::Scalar::DOUBLE_QUOTED
    end

    fact_hash.yaml
  end

  def sanitize_fact(fact)
    if fact.is_a? Hash then
      ret = {}
      fact.each_pair { |k,v| ret[sanitize_fact k]=sanitize_fact v }
      ret
    elsif fact.is_a? Array then
      fact.collect { |i| sanitize_fact i }
    elsif fact.is_a? Numeric \
      or fact.is_a? TrueClass \
      or fact.is_a? FalseClass \
      or fact.is_a? String
      fact
    else
      result = fact.to_s
      # The result may be ascii-8bit encoded without being a binary (low level object.inspect returns ascii-8bit string)
      if result.encoding == Encoding::ASCII_8BIT
        begin
          result = result.encode(Encoding::UTF_8)
        rescue
          # return the ascii-8bit - it will be taken as a binary
          result
        end
      end
      result
    end
  end
end