class Catalog::Compiler

Attributes

code[RW]

Public Class Methods

new() click to toggle source
   # 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

extract_facts_from_request(request) click to toggle source

@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(catalog) click to toggle source

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
find(request) click to toggle source

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
networked?() click to toggle source

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
require_environment?() click to toggle source
   # File lib/puppet/indirector/catalog/compiler.rb
96 def require_environment?
97   false
98 end
save_facts_from_request(facts, request) click to toggle source
   # 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_node_data(node) click to toggle source

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
common_checksum_type(agent_checksum_type) click to toggle source

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(node, options) click to toggle source

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
convert_wire_facts(facts, format) click to toggle source

@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
find_node(name, environment, transaction_uuid, configured_environment, facts) click to toggle source

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
get_content_uri(metadata, source, environment_path) click to toggle source
    # 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_metadata(catalog, checksum_type) click to toggle source

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
inlineable?(resource, sources) click to toggle source

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
inlineable_metadata?(metadata, source, environment_path) click to toggle source

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
log_file_outside_environment() click to toggle source

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
log_metadata_inlining() click to toggle source

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
node_from_request(facts, request) click to toggle source

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
set_server_facts() click to toggle source

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