File: node.rb

package info (click to toggle)
puppet-agent 8.10.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,404 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (256 lines) | stat: -rw-r--r-- 8,473 bytes parent folder | download | duplicates (2)
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# frozen_string_literal: true

require_relative '../puppet/indirector'

# A class for managing nodes, including their facts and environment.
class Puppet::Node
  require_relative 'node/facts'
  require_relative 'node/environment'

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

  # Asymmetric serialization/deserialization required in this class via to/from datahash
  include Puppet::Util::PsychSupport

  # Use the node source as the indirection terminus.
  indirects :node, :terminus_setting => :node_terminus, :doc => "Where to find node information.
    A node is composed of its name, its facts, and its environment."

  attr_accessor :name, :classes, :source, :ipaddress, :parameters, :environment_name
  attr_reader :time, :facts, :trusted_data

  attr_reader :server_facts

  ENVIRONMENT = 'environment'

  def initialize_from_hash(data)
    @name       = data['name']       || (raise ArgumentError, _("No name provided in serialized data"))
    @classes    = data['classes']    || []
    @parameters = data['parameters'] || {}
    env_name = data['environment'] || @parameters[ENVIRONMENT]
    unless env_name.nil?
      @parameters[ENVIRONMENT] = env_name
      @environment_name = env_name.intern
    end
  end

  def self.from_data_hash(data)
    node = new(name)
    node.initialize_from_hash(data)
    node
  end

  def to_data_hash
    result = {
      'name' => name,
      'environment' => environment.name.to_s,
    }
    result['classes'] = classes unless classes.empty?
    serialized_params = serializable_parameters
    result['parameters'] = serialized_params unless serialized_params.empty?
    result
  end

  def serializable_parameters
    new_params = parameters.dup
    new_params.delete(ENVIRONMENT)
    new_params
  end

  def environment
    unless @environment
      env = parameters[ENVIRONMENT]
      if env
        self.environment = env
      elsif environment_name
        self.environment = environment_name
      else
        # This should not be :current_environment, this is the default
        # for a node when it has not specified its environment
        # it will be used to establish what the current environment is.
        #
        self.environment = Puppet.lookup(:environments).get!(Puppet[:environment])
      end

    end
    @environment
  end

  def environment=(env)
    if env.is_a?(String) or env.is_a?(Symbol)
      @environment = Puppet.lookup(:environments).get!(env)
    else
      @environment = env
    end

    # Keep environment_name attribute and parameter in sync if they have been set
    unless @environment.nil?
      # always set the environment parameter. It becomes top scope $environment for a manifest during catalog compilation.
      @parameters[ENVIRONMENT] = @environment.name.to_s
      self.environment_name = @environment.name
    end
  end

  def has_environment_instance?
    !@environment.nil?
  end

  def initialize(name, options = {})
    raise ArgumentError, _("Node names cannot be nil") unless name

    @name = name

    classes = options[:classes]
    if classes
      if classes.is_a?(String)
        @classes = [classes]
      else
        @classes = classes
      end
    else
      @classes = []
    end

    @parameters = options[:parameters] || {}

    @facts = options[:facts]

    @server_facts = {}

    env = options[:environment]
    if env
      self.environment = env
    end

    @time = Time.now
  end

  # Merge the node facts with parameters from the node source.
  # @api public
  # @param facts [optional, Puppet::Node::Facts] facts to merge into node parameters.
  #   Will query Facts indirection if not supplied.
  # @raise [Puppet::Error] Raise on failure to retrieve facts if not supplied
  # @return [nil]
  def fact_merge(facts = nil)
    begin
      @facts = facts.nil? ? Puppet::Node::Facts.indirection.find(name, :environment => environment) : facts
    rescue => detail
      error = Puppet::Error.new(_("Could not retrieve facts for %{name}: %{detail}") % { name: name, detail: detail }, detail)
      error.set_backtrace(detail.backtrace)
      raise error
    end

    unless @facts.nil?
      @facts.sanitize
      # facts should never modify the environment parameter
      orig_param_env = @parameters[ENVIRONMENT]
      merge(@facts.values)
      @parameters[ENVIRONMENT] = orig_param_env
    end
  end

  # Merge any random parameters into our parameter list.
  def merge(params)
    params.each do |name, value|
      if @parameters.include?(name)
        Puppet::Util::Warnings.warnonce(_("The node parameter '%{param_name}' for node '%{node_name}' was already set to '%{value}'. It could not be set to '%{desired_value}'") % { param_name: name, node_name: @name, value: @parameters[name], desired_value: value })
      else
        @parameters[name] = value
      end
    end
  end

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

  def add_server_facts(facts)
    # Append the current environment to the list of server facts
    @server_facts = facts.merge({ "environment" => environment.name.to_s })

    # Merge the server facts into the parameters for the node
    merge(facts)
  end

  # Calculate the list of names we might use for looking
  # up our node.  This is only used for AST nodes.
  def names
    @names ||= [name]
  end

  def split_name(name)
    list = name.split(".")
    tmp = []
    list.each_with_index do |_short, i|
      tmp << list[0..i].join(".")
    end
    tmp.reverse
  end

  # Ensures the data is frozen
  #
  def trusted_data=(data)
    Puppet.warning(_("Trusted node data modified for node %{name}") % { name: name }) unless @trusted_data.nil?
    @trusted_data = data.freeze
  end

  # Resurrects and sanitizes trusted information in the node by modifying it and setting
  # the trusted_data in the node from parameters.
  # This modifies the node
  #
  def sanitize
    # Resurrect "trusted information" that comes from node/fact terminus.
    # The current way this is done in puppet db (currently the only one)
    # is to store the node parameter 'trusted' as a hash of the trusted information.
    #
    # Thus here there are two main cases:
    # 1. This terminus was used in a real agent call (only meaningful if someone curls the request as it would
    #  fail since the result is a hash of two catalogs).
    # 2  It is a command line call with a given node that use a terminus that:
    # 2.1 does not include a 'trusted' fact - use local from node trusted information
    # 2.2 has a 'trusted' fact - this in turn could be
    # 2.2.1 puppet db having stored trusted node data as a fact (not a great design)
    # 2.2.2 some other terminus having stored a fact called "trusted" (most likely that would have failed earlier, but could
    #       be spoofed).
    #
    # For the reasons above, the resurrection of trusted node data with authenticated => true is only performed
    # if user is running as root, else it is resurrected as unauthenticated.
    #
    trusted_param = @parameters['trusted']
    if trusted_param
      # Blows up if it is a parameter as it will be set as $trusted by the compiler as if it was a variable
      @parameters.delete('trusted')
      unless trusted_param.is_a?(Hash) && %w[authenticated certname extensions].all? { |key| trusted_param.has_key?(key) }
        # trusted is some kind of garbage, do not resurrect
        trusted_param = nil
      end
    else
      # trusted may be Boolean false if set as a fact by someone
      trusted_param = nil
    end

    # The options for node.trusted_data in priority order are:
    # 1) node came with trusted_data so use that
    # 2) else if there is :trusted_information in the puppet context
    # 3) else if the node provided a 'trusted' parameter (parsed out above)
    # 4) last, fallback to local node trusted information
    #
    # Note that trusted_data should be a hash, but (2) and (4) are not
    # hashes, so we to_h at the end
    unless trusted_data
      trusted = Puppet.lookup(:trusted_information) do
        trusted_param || Puppet::Context::TrustedInformation.local(self)
      end

      self.trusted_data = trusted.to_h
    end
  end
end