class Catalog::Compiler
Attributes
Public Class Methods
# File lib/puppet/indirector/catalog/compiler.rb 85 def initialize 86 Puppet::Util::Profiler.profile(_("Setup server facts for compiling"), [:compiler, :init_server_facts]) do 87 set_server_facts 88 end 89 end
Public Instance Methods
@param request [Puppet::Indirector::Request] an indirection request
(possibly) containing facts
@return [Puppet::Node::Facts] facts object corresponding to facts in request
# File lib/puppet/indirector/catalog/compiler.rb 22 def extract_facts_from_request(request) 23 text_facts = request.options[:facts] 24 return unless text_facts 25 format = request.options[:facts_format] 26 unless format 27 raise ArgumentError, _("Facts but no fact format provided for %{request}") % { request: request.key } 28 end 29 30 Puppet::Util::Profiler.profile(_("Found facts"), [:compiler, :find_facts]) do 31 facts = text_facts.is_a?(Puppet::Node::Facts) ? text_facts : 32 convert_wire_facts(text_facts, format) 33 34 unless facts.name == request.key 35 raise Puppet::Error, _("Catalog for %{request} was requested with fact definition for the wrong node (%{fact_name}).") % { request: request.key.inspect, fact_name: facts.name.inspect } 36 end 37 return facts 38 end 39 end
filter-out a catalog to remove exported resources
# File lib/puppet/indirector/catalog/compiler.rb 80 def filter(catalog) 81 return catalog.filter { |r| r.virtual? } if catalog.respond_to?(:filter) 82 catalog 83 end
Compile a node's catalog.
# File lib/puppet/indirector/catalog/compiler.rb 48 def find(request) 49 facts = extract_facts_from_request(request) 50 51 save_facts_from_request(facts, request) if !facts.nil? 52 53 node = node_from_request(facts, request) 54 node.trusted_data = Puppet.lookup(:trusted_information) { Puppet::Context::TrustedInformation.local(node) }.to_h 55 56 if node.environment 57 # If the requested environment doesn't match the server specified environment, 58 # as determined by the node terminus, and the request wants us to check for an 59 # environment mismatch, then return an empty catalog with the server-specified 60 # enviroment. 61 if request.remote? && request.options[:check_environment] && node.environment != request.environment 62 return Puppet::Resource::Catalog.new(node.name, node.environment) 63 end 64 65 node.environment.with_text_domain do 66 envs = Puppet.lookup(:environments) 67 envs.guard(node.environment.name) 68 begin 69 compile(node, request.options) 70 ensure 71 envs.unguard(node.environment.name) 72 end 73 end 74 else 75 compile(node, request.options) 76 end 77 end
Is our compiler part of a network, or are we just local?
# File lib/puppet/indirector/catalog/compiler.rb 92 def networked? 93 Puppet.run_mode.server? 94 end
# File lib/puppet/indirector/catalog/compiler.rb 96 def require_environment? 97 false 98 end
# File lib/puppet/indirector/catalog/compiler.rb 41 def save_facts_from_request(facts, request) 42 Puppet::Node::Facts.indirection.save(facts, nil, 43 :environment => request.environment, 44 :transaction_uuid => request.options[:transaction_uuid]) 45 end
Private Instance Methods
Add any extra data necessary to the node.
# File lib/puppet/indirector/catalog/compiler.rb 119 def add_node_data(node) 120 # Merge in our server-side facts, so they can be used during compilation. 121 node.add_server_facts(@server_facts) 122 end
Determine which checksum to use; if agent_checksum_type is not nil, use the first entry in it that is also in known_checksum_types. If no match is found, return nil.
# File lib/puppet/indirector/catalog/compiler.rb 127 def common_checksum_type(agent_checksum_type) 128 if agent_checksum_type 129 agent_checksum_types = agent_checksum_type.split('.').map {|type| type.to_sym} 130 checksum_type = agent_checksum_types.drop_while do |type| 131 not known_checksum_types.include? type 132 end.first 133 end 134 checksum_type 135 end
Compile the actual catalog.
# File lib/puppet/indirector/catalog/compiler.rb 296 def compile(node, options) 297 if node.environment && node.environment.static_catalogs? && options[:static_catalog] && options[:code_id] 298 # Check for errors before compiling the catalog 299 checksum_type = common_checksum_type(options[:checksum_type]) 300 raise Puppet::Error, _("Unable to find a common checksum type between agent '%{agent_type}' and master '%{master_type}'.") % { agent_type: options[:checksum_type], master_type: known_checksum_types } unless checksum_type 301 end 302 303 escaped_node_name = node.name.gsub(/%/, '%%') 304 if checksum_type 305 if node.environment 306 escaped_node_environment = node.environment.to_s.gsub(/%/, '%%') 307 benchmark_str = _("Compiled static catalog for %{node} in environment %{environment} in %%{seconds} seconds") % { node: escaped_node_name, environment: escaped_node_environment } 308 profile_str = _("Compiled static catalog for %{node} in environment %{environment}") % { node: node.name, environment: node.environment } 309 else 310 benchmark_str = _("Compiled static catalog for %{node} in %%{seconds} seconds") % { node: escaped_node_name } 311 profile_str = _("Compiled static catalog for %{node}") % { node: node.name } 312 end 313 else 314 if node.environment 315 escaped_node_environment = node.environment.to_s.gsub(/%/, '%%') 316 benchmark_str = _("Compiled catalog for %{node} in environment %{environment} in %%{seconds} seconds") % { node: escaped_node_name, environment: escaped_node_environment } 317 profile_str = _("Compiled catalog for %{node} in environment %{environment}") % { node: node.name, environment: node.environment } 318 else 319 benchmark_str = _("Compiled catalog for %{node} in %%{seconds} seconds") % { node: escaped_node_name } 320 profile_str = _("Compiled catalog for %{node}") % { node: node.name } 321 end 322 end 323 config = nil 324 325 benchmark(:notice, benchmark_str) do 326 compile_type = checksum_type ? :static_compile : :compile 327 Puppet::Util::Profiler.profile(profile_str, [:compiler, compile_type, node.environment, node.name]) do 328 begin 329 config = Puppet::Parser::Compiler.compile(node, options[:code_id]) 330 rescue Puppet::Error => detail 331 Puppet.err(detail.to_s) if networked? 332 raise 333 ensure 334 Puppet::Type.clear_misses unless Puppet[:always_retry_plugins] 335 end 336 337 if checksum_type && config.is_a?(model) 338 escaped_node_name = node.name.gsub(/%/, '%%') 339 if node.environment 340 escaped_node_environment = node.environment.to_s.gsub(/%/, '%%') 341 #TRANSLATORS Inlined refers to adding additional metadata 342 benchmark_str = _("Inlined resource metadata into static catalog for %{node} in environment %{environment} in %%{seconds} seconds") % { node: escaped_node_name, environment: escaped_node_environment } 343 #TRANSLATORS Inlined refers to adding additional metadata 344 profile_str = _("Inlined resource metadata into static catalog for %{node} in environment %{environment}") % { node: node.name, environment: node.environment } 345 else 346 #TRANSLATORS Inlined refers to adding additional metadata 347 benchmark_str = _("Inlined resource metadata into static catalog for %{node} in %%{seconds} seconds") % { node: escaped_node_name } 348 #TRANSLATORS Inlined refers to adding additional metadata 349 profile_str = _("Inlined resource metadata into static catalog for %{node}") % { node: node.name } 350 end 351 benchmark(:notice, benchmark_str) do 352 Puppet::Util::Profiler.profile(profile_str, [:compiler, :static_compile_postprocessing, node.environment, node.name]) do 353 inline_metadata(config, checksum_type) 354 end 355 end 356 end 357 end 358 end 359 360 361 config 362 end
@param facts [String] facts in a wire format for decoding @param format [String] a content-type string @return [Puppet::Node::Facts] facts object deserialized from supplied string @api private
# File lib/puppet/indirector/catalog/compiler.rb 106 def convert_wire_facts(facts, format) 107 if format == 'pson' 108 # We unescape here because the corresponding code in Puppet::Configurer::FactHandler encodes with Puppet::Util.uri_query_encode 109 # PSON is deprecated, but continue to accept from older agents 110 return Puppet::Node::Facts.convert_from('pson', CGI.unescape(facts)) 111 elsif format == 'application/json' 112 return Puppet::Node::Facts.convert_from('json', CGI.unescape(facts)) 113 else 114 raise ArgumentError, _("Unsupported facts format") 115 end 116 end
Use indirection to find the node associated with a given request
# File lib/puppet/indirector/catalog/compiler.rb 365 def find_node(name, environment, transaction_uuid, configured_environment, facts) 366 Puppet::Util::Profiler.profile(_("Found node information"), [:compiler, :find_node]) do 367 node = nil 368 begin 369 node = Puppet::Node.indirection.find(name, :environment => environment, 370 :transaction_uuid => transaction_uuid, 371 :configured_environment => configured_environment, 372 :facts => facts) 373 rescue => detail 374 message = _("Failed when searching for node %{name}: %{detail}") % { name: name, detail: detail } 375 Puppet.log_exception(detail, message) 376 raise Puppet::Error, message, detail.backtrace 377 end 378 379 380 # Add any external data to the node. 381 if node 382 add_node_data(node) 383 end 384 node 385 end 386 end
# File lib/puppet/indirector/catalog/compiler.rb 137 def get_content_uri(metadata, source, environment_path) 138 # The static file content server doesn't know how to expand mountpoints, so 139 # we need to do that ourselves from the actual system path of the source file. 140 # This does that, while preserving any user-specified server or port. 141 source_path = Pathname.new(metadata.full_path) 142 path = source_path.relative_path_from(environment_path).to_s 143 source_as_uri = URI.parse(Puppet::Util.uri_encode(source)) 144 server = source_as_uri.host 145 port = ":#{source_as_uri.port}" if source_as_uri.port 146 return "puppet://#{server}#{port}/#{path}" 147 end
Inline file metadata for static catalogs Initially restricted to files sourced from codedir via puppet:/// uri.
# File lib/puppet/indirector/catalog/compiler.rb 194 def inline_metadata(catalog, checksum_type) 195 environment_path = Pathname.new File.join(Puppet[:environmentpath], catalog.environment) 196 environment_path = Puppet::Environments::Directories.real_path(environment_path) 197 list_of_resources = catalog.resources.find_all { |res| res.type == "File" } 198 199 # TODO: get property/parameter defaults if entries are nil in the resource 200 # For now they're hard-coded to match the File type. 201 202 list_of_resources.each do |resource| 203 sources = [resource[:source]].flatten.compact 204 next unless inlineable?(resource, sources) 205 206 # both need to handle multiple sources 207 if resource[:recurse] == true || resource[:recurse] == 'true' || resource[:recurse] == 'remote' 208 # Construct a hash mapping sources to arrays (list of files found recursively) of metadata 209 options = { 210 :environment => catalog.environment_instance, 211 :links => resource[:links] ? resource[:links].to_sym : :manage, 212 :checksum_type => resource[:checksum] ? resource[:checksum].to_sym : checksum_type.to_sym, 213 :source_permissions => resource[:source_permissions] ? resource[:source_permissions].to_sym : :ignore, 214 :recurse => true, 215 :recurselimit => resource[:recurselimit], 216 :max_files => resource[:max_files], 217 :ignore => resource[:ignore], 218 } 219 220 sources_in_environment = true 221 222 source_to_metadatas = {} 223 sources.each do |source| 224 source = Puppet::Type.type(:file).attrclass(:source).normalize(source) 225 226 list_of_data = Puppet::FileServing::Metadata.indirection.search(source, options) 227 if list_of_data 228 basedir_meta = list_of_data.find {|meta| meta.relative_path == '.'} 229 devfail "FileServing::Metadata search should always return the root search path" if basedir_meta.nil? 230 231 if ! inlineable_metadata?(basedir_meta, source, environment_path) 232 # If any source is not in the environment path, skip inlining this resource. 233 log_file_outside_environment 234 sources_in_environment = false 235 break 236 end 237 238 base_content_uri = get_content_uri(basedir_meta, source, environment_path) 239 list_of_data.each do |metadata| 240 if metadata.relative_path == '.' 241 metadata.content_uri = base_content_uri 242 else 243 metadata.content_uri = "#{base_content_uri}/#{metadata.relative_path}" 244 end 245 end 246 247 source_to_metadatas[source] = list_of_data 248 # Optimize for returning less data if sourceselect is first 249 if resource[:sourceselect] == 'first' || resource[:sourceselect].nil? 250 break 251 end 252 end 253 end 254 255 if sources_in_environment && !source_to_metadatas.empty? 256 log_metadata_inlining 257 catalog.recursive_metadata[resource.title] = source_to_metadatas 258 end 259 else 260 options = { 261 :environment => catalog.environment_instance, 262 :links => resource[:links] ? resource[:links].to_sym : :manage, 263 :checksum_type => resource[:checksum] ? resource[:checksum].to_sym : checksum_type.to_sym, 264 :source_permissions => resource[:source_permissions] ? resource[:source_permissions].to_sym : :ignore 265 } 266 267 metadata = nil 268 sources.each do |source| 269 source = Puppet::Type.type(:file).attrclass(:source).normalize(source) 270 271 data = Puppet::FileServing::Metadata.indirection.find(source, options) 272 if data 273 metadata = data 274 metadata.source = source 275 break 276 end 277 end 278 279 raise _("Could not get metadata for %{resource}") % { resource: resource[:source] } unless metadata 280 281 if inlineable_metadata?(metadata, metadata.source, environment_path) 282 metadata.content_uri = get_content_uri(metadata, metadata.source, environment_path) 283 log_metadata_inlining 284 285 # If the file is in the environment directory, we can safely inline 286 catalog.metadata[resource.title] = metadata 287 else 288 # Log a profiler event that we skipped this file because it is not in an environment. 289 log_file_outside_environment 290 end 291 end 292 end 293 end
Helper method to decide if a file resource's metadata can be inlined. Also used to profile/log reasons for not inlining.
# File lib/puppet/indirector/catalog/compiler.rb 151 def inlineable?(resource, sources) 152 case 153 when resource[:ensure] == 'absent' 154 #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) 155 return Puppet::Util::Profiler.profile(_("Not inlining absent resource"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :absent]) { false } 156 when sources.empty? 157 #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) 158 return Puppet::Util::Profiler.profile(_("Not inlining resource without sources"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :no_sources]) { false } 159 when (not (sources.all? {|source| source =~ /^puppet:/})) 160 #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) 161 return Puppet::Util::Profiler.profile(_("Not inlining unsupported source scheme"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :unsupported_scheme]) { false } 162 else 163 return true 164 end 165 end
Return true if metadata is inlineable, meaning the request's source is for the 'modules' mount and the resolved path is of the form:
$codedir/environments/$environment/*/*/files/**
# File lib/puppet/indirector/catalog/compiler.rb 170 def inlineable_metadata?(metadata, source, environment_path) 171 source_as_uri = URI.parse(Puppet::Util.uri_encode(source)) 172 173 location = Puppet::Module::FILETYPES['files'] 174 175 !!(source_as_uri.path =~ /^\/modules\// && 176 metadata.full_path =~ /#{environment_path}\/[^\/]+\/[^\/]+\/#{location}\/.+/) 177 end
Helper method to log file resources that could not be inlined because they fall outside of an environment.
# File lib/puppet/indirector/catalog/compiler.rb 181 def log_file_outside_environment 182 #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) 183 Puppet::Util::Profiler.profile(_("Not inlining file outside environment"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :file_outside_environment]) { true } 184 end
Helper method to log file resources that were successfully inlined.
# File lib/puppet/indirector/catalog/compiler.rb 187 def log_metadata_inlining 188 #TRANSLATORS Inlining refers to adding additional metadata 189 Puppet::Util::Profiler.profile(_("Inlining file metadata"), [:compiler, :static_compile_inlining, :inlined_file_metadata]) { true } 190 end
Extract the node from the request, or use the request to find the node.
# File lib/puppet/indirector/catalog/compiler.rb 390 def node_from_request(facts, request) 391 node = request.options[:use_node] 392 if node 393 if request.remote? 394 raise Puppet::Error, _("Invalid option use_node for a remote request") 395 else 396 return node 397 end 398 end 399 400 # We rely on our authorization system to determine whether the connected 401 # node is allowed to compile the catalog's node referenced by key. 402 # By default the REST authorization system makes sure only the connected node 403 # can compile his catalog. 404 # This allows for instance monitoring systems or puppet-load to check several 405 # node's catalog with only one certificate and a modification to auth.conf 406 # If no key is provided we can only compile the currently connected node. 407 name = request.key || request.node 408 node = find_node(name, request.environment, request.options[:transaction_uuid], request.options[:configured_environment], facts) 409 if node 410 return node 411 end 412 413 raise ArgumentError, _("Could not find node '%{name}'; cannot compile") % { name: name } 414 end
Initialize our server fact hash; we add these to each client, and they won't change while we're running, so it's safe to cache the values.
See also set_server_facts in Puppet::Server::Compiler in puppetserver.
# File lib/puppet/indirector/catalog/compiler.rb 420 def set_server_facts 421 @server_facts = {} 422 423 # Add our server Puppet Enterprise version, if available. 424 pe_version_file = '/opt/puppetlabs/server/pe_version' 425 if File.readable?(pe_version_file) and !File.zero?(pe_version_file) 426 @server_facts['pe_serverversion'] = File.read(pe_version_file).chomp 427 end 428 429 # Add our server version to the fact list 430 @server_facts["serverversion"] = Puppet.version.to_s 431 432 # And then add the server name and IP 433 {"servername" => "networking.fqdn", 434 "serverip" => "networking.ip", 435 "serverip6" => "networking.ip6" 436 }.each do |var, fact| 437 value = Puppet.runtime[:facter].value(fact) 438 if !value.nil? 439 @server_facts[var] = value 440 end 441 end 442 443 if @server_facts["servername"].nil? 444 host = Puppet.runtime[:facter].value('networking.hostname') 445 if host.nil? 446 Puppet.warning _("Could not retrieve fact servername") 447 elsif domain = Puppet.runtime[:facter].value('networking.domain') #rubocop:disable Lint/AssignmentInCondition 448 @server_facts["servername"] = [host, domain].join(".") 449 else 450 @server_facts["servername"] = host 451 end 452 end 453 454 if @server_facts["serverip"].nil? && @server_facts["serverip6"].nil? 455 Puppet.warning _("Could not retrieve either serverip or serverip6 fact") 456 end 457 end