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
|