class Puppet::Parser::Compiler

Maintain a graph of scopes, along with a bunch of data about the individual catalog we're compiling.

Abstract class for a catalog validator that can be registered with the compiler to run at a certain stage.

Constants

SETTINGS

Attributes

catalog[R]
code_id[RW]

The id of code input to the compiler. @api private

collections[R]
facts[R]
loaders[R]

Access to the configured loaders for 4x @return [Puppet::Pops::Loader::Loaders] the configured loaders @api private

node[R]
qualified_variables[R]
relationships[R]
resources[R]
topscope[R]

Public Class Methods

compile(node, code_id = nil) click to toggle source
   # File lib/puppet/parser/compiler.rb
22 def self.compile(node, code_id = nil)
23   node.environment.check_for_reparse
24 
25   errors = node.environment.validation_errors
26   if !errors.empty?
27     errors.each { |e| Puppet.err(e) } if errors.size > 1
28     errmsg = [
29       _("Compilation has been halted because: %{error}") % { error: errors.first },
30       _("For more information, see https://puppet.com/docs/puppet/latest/environments_about.html"),
31     ]
32     raise(Puppet::Error, errmsg.join(' '))
33   end
34 
35   new(node, :code_id => code_id).compile {|resulting_catalog| resulting_catalog.to_resource }
36 rescue Puppet::ParseErrorWithIssue => detail
37   detail.node = node.name
38   Puppet.log_exception(detail)
39   raise
40 rescue => detail
41   message = _("%{message} on node %{node}") % { message: detail, node: node.name }
42   Puppet.log_exception(detail, message)
43   raise Puppet::Error, message, detail.backtrace
44 end
new(node, code_id: nil) click to toggle source
    # File lib/puppet/parser/compiler.rb
274 def initialize(node, code_id: nil)
275   @node = sanitize_node(node)
276   @code_id = code_id
277   initvars
278   add_catalog_validators
279   # Resolutions of fully qualified variable names
280   @qualified_variables = {}
281 end

Public Instance Methods

add_catalog_validator(catalog_validators) click to toggle source

Add a catalog validator that will run at some stage to this compiler @param catalog_validators [Class<CatalogValidator>] The catalog validator class to add

    # File lib/puppet/parser/compiler.rb
105 def add_catalog_validator(catalog_validators)
106   @catalog_validators << catalog_validators
107   nil
108 end
add_catalog_validators() click to toggle source
    # File lib/puppet/parser/compiler.rb
110 def add_catalog_validators
111   add_catalog_validator(CatalogValidator::RelationshipValidator)
112 end
add_class(name) click to toggle source

Store the fact that we've evaluated a class

    # File lib/puppet/parser/compiler.rb
 99 def add_class(name)
100   @catalog.add_class(name) unless name == ""
101 end
add_override(override) click to toggle source

Store a resource override.

   # File lib/puppet/parser/compiler.rb
63 def add_override(override)
64   # If possible, merge the override in immediately.
65   resource = @catalog.resource(override.ref)
66   if resource
67     resource.merge(override)
68   else
69     # Otherwise, store the override for later; these
70     # get evaluated in Resource#finish.
71     @resource_overrides[override.ref] << override
72   end
73 end
add_resource(scope, resource) click to toggle source
   # File lib/puppet/parser/compiler.rb
75 def add_resource(scope, resource)
76   @resources << resource
77 
78   # Note that this will fail if the resource is not unique.
79   @catalog.add_resource(resource)
80 
81   if not resource.class? and resource[:stage]
82     #TRANSLATORS "stage" is a keyword in Puppet and should not be translated
83     raise ArgumentError, _("Only classes can set 'stage'; normal resources like %{resource} cannot change run stage") % { resource: resource }
84   end
85 
86   # Stages should not be inside of classes.  They are always a
87   # top-level container, regardless of where they appear in the
88   # manifest.
89   return if resource.stage?
90 
91   # This adds a resource to the class it lexically appears in in the
92   # manifest.
93   unless resource.class?
94     @catalog.add_edge(scope.resource, resource)
95   end
96 end
compile() { |catalog| ... } click to toggle source

Compiler our catalog. This mostly revolves around finding and evaluating classes. This is the main entry into our catalog.

    # File lib/puppet/parser/compiler.rb
123 def compile
124   Puppet.override( @context_overrides , _("For compiling %{node}") % { node: node.name }) do
125     @catalog.environment_instance = environment
126 
127     # Set the client's parameters into the top scope.
128     Puppet::Util::Profiler.profile(_("Compile: Set node parameters"), [:compiler, :set_node_params]) { set_node_parameters }
129 
130     Puppet::Util::Profiler.profile(_("Compile: Created settings scope"), [:compiler, :create_settings_scope]) { create_settings_scope }
131 
132     #TRANSLATORS "main" is a function name and should not be translated
133     Puppet::Util::Profiler.profile(_("Compile: Evaluated main"), [:compiler, :evaluate_main]) { evaluate_main }
134 
135     Puppet::Util::Profiler.profile(_("Compile: Evaluated AST node"), [:compiler, :evaluate_ast_node]) { evaluate_ast_node }
136 
137     Puppet::Util::Profiler.profile(_("Compile: Evaluated node classes"), [:compiler, :evaluate_node_classes]) { evaluate_node_classes }
138 
139     Puppet::Util::Profiler.profile(_("Compile: Evaluated generators"), [:compiler, :evaluate_generators]) { evaluate_generators }
140 
141     Puppet::Util::Profiler.profile(_("Compile: Validate Catalog pre-finish"), [:compiler, :validate_pre_finish]) do
142       validate_catalog(CatalogValidator::PRE_FINISH)
143     end
144 
145     Puppet::Util::Profiler.profile(_("Compile: Finished catalog"), [:compiler, :finish_catalog]) { finish }
146 
147     fail_on_unevaluated
148 
149     Puppet::Util::Profiler.profile(_("Compile: Validate Catalog final"), [:compiler, :validate_final]) do
150       validate_catalog(CatalogValidator::FINAL)
151     end
152 
153     if block_given?
154       yield @catalog
155     else
156       @catalog
157     end
158   end
159 end
context_overrides() click to toggle source

Constructs the overrides for the context

    # File lib/puppet/parser/compiler.rb
166 def context_overrides()
167   {
168     :current_environment => environment,
169     :global_scope => @topscope,             # 4x placeholder for new global scope
170     :loaders  => @loaders,                  # 4x loaders
171   }
172 end
environment() click to toggle source

Return the node's environment.

    # File lib/puppet/parser/compiler.rb
177 def environment
178   node.environment
179 end
evaluate_ast_node() click to toggle source

If ast nodes are enabled, then see if we can find and evaluate one.

@api private

    # File lib/puppet/parser/compiler.rb
207 def evaluate_ast_node
208   krt = environment.known_resource_types
209   return unless krt.nodes? #ast_nodes?
210 
211   # Now see if we can find the node.
212   astnode = nil
213   @node.names.each do |name|
214     astnode = krt.node(name.to_s.downcase)
215     break if astnode
216   end
217 
218   unless (astnode ||= krt.node("default"))
219     raise Puppet::ParseError, _("Could not find node statement with name 'default' or '%{names}'") % { names: node.names.join(", ") }
220   end
221 
222   # Create a resource to model this node, and then add it to the list
223   # of resources.
224   resource = astnode.ensure_in_catalog(topscope)
225 
226   resource.evaluate
227 
228   @node_scope = topscope.class_scope(astnode)
229 end
evaluate_classes(classes, scope, lazy_evaluate = true) click to toggle source

Evaluates each specified class in turn. If there are any classes that can't be found, an error is raised. This method really just creates resource objects that point back to the classes, and then the resources are themselves evaluated later in the process.

    # File lib/puppet/parser/compiler.rb
236 def evaluate_classes(classes, scope, lazy_evaluate = true)
237   raise Puppet::DevError, _("No source for scope passed to evaluate_classes") unless scope.source
238   class_parameters = nil
239   # if we are a param class, save the classes hash
240   # and transform classes to be the keys
241   if classes.class == Hash
242     class_parameters = classes
243     classes = classes.keys
244   end
245 
246   hostclasses = classes.collect do |name|
247     environment.known_resource_types.find_hostclass(name) or raise Puppet::Error, _("Could not find class %{name} for %{node}") % { name: name, node: node.name }
248   end
249 
250   if class_parameters
251     resources = ensure_classes_with_parameters(scope, hostclasses, class_parameters)
252     if !lazy_evaluate
253       resources.each(&:evaluate)
254     end
255 
256     resources
257   else
258     already_included, newly_included = ensure_classes_without_parameters(scope, hostclasses)
259     if !lazy_evaluate
260       newly_included.each(&:evaluate)
261     end
262 
263     already_included + newly_included
264   end
265 end
evaluate_node_classes() click to toggle source

Evaluate all of the classes specified by the node. Classes with parameters are evaluated as if they were declared. Classes without parameters or with an empty set of parameters are evaluated as if they were included. This means classes with an empty set of parameters won't conflict even if the class has already been included.

    # File lib/puppet/parser/compiler.rb
186 def evaluate_node_classes
187   if @node.classes.is_a? Hash
188     classes_with_params, classes_without_params = @node.classes.partition {|name,params| params and !params.empty?}
189 
190     # The results from Hash#partition are arrays of pairs rather than hashes,
191     # so we have to convert to the forms evaluate_classes expects (Hash, and
192     # Array of class names)
193     classes_with_params = Hash[classes_with_params]
194     classes_without_params.map!(&:first)
195   else
196     classes_with_params = {}
197     classes_without_params = @node.classes
198   end
199 
200   evaluate_classes(classes_with_params, @node_scope || topscope)
201   evaluate_classes(classes_without_params, @node_scope || topscope)
202 end
evaluate_relationships() click to toggle source
    # File lib/puppet/parser/compiler.rb
267 def evaluate_relationships
268   @relationships.each { |rel| rel.evaluate(catalog) }
269 end
newscope(parent, options = {}) click to toggle source

Create a new scope, with either a specified parent scope or using the top scope.

    # File lib/puppet/parser/compiler.rb
285 def newscope(parent, options = {})
286   parent ||= topscope
287   scope = Puppet::Parser::Scope.new(self, **options)
288   scope.parent = parent
289   scope
290 end
resource_overrides(resource) click to toggle source

Return any overrides for the given resource.

    # File lib/puppet/parser/compiler.rb
293 def resource_overrides(resource)
294   @resource_overrides[resource.ref]
295 end
validate_catalog(validation_stage) click to toggle source
    # File lib/puppet/parser/compiler.rb
161 def validate_catalog(validation_stage)
162   @catalog_validators.select { |vclass| vclass.validation_stage?(validation_stage) }.each { |vclass| vclass.new(@catalog).validate }
163 end
with_context_overrides(description = '', &block) click to toggle source
    # File lib/puppet/parser/compiler.rb
117 def with_context_overrides(description = '', &block)
118   Puppet.override( @context_overrides , description, &block)
119 end

Protected Instance Methods

evaluate_generators() click to toggle source

Iterate over collections and resources until we're sure that the whole compile is evaluated. This is necessary because both collections and defined resources can generate new resources, which themselves could be defined resources.

    # File lib/puppet/parser/compiler.rb
368 def evaluate_generators
369   count = 0
370   loop do
371     done = true
372 
373     Puppet::Util::Profiler.profile(_("Iterated (%{count}) on generators") % { count: count + 1 }, [:compiler, :iterate_on_generators]) do
374       # Call collections first, then definitions.
375       done = false if evaluate_collections
376       done = false if evaluate_definitions
377     end
378 
379     break if done
380 
381     count += 1
382 
383     if count > 1000
384       raise Puppet::ParseError, _("Somehow looped more than 1000 times while evaluating host catalog")
385     end
386   end
387 end
finish() click to toggle source

Make sure all of our resources and such have done any last work necessary.

    # File lib/puppet/parser/compiler.rb
433 def finish
434   evaluate_relationships
435 
436   resources.each do |resource|
437     # Add in any resource overrides.
438     overrides = resource_overrides(resource)
439     if overrides
440       overrides.each do |over|
441         resource.merge(over)
442       end
443 
444       # Remove the overrides, so that the configuration knows there
445       # are none left.
446       overrides.clear
447     end
448 
449     resource.finish if resource.respond_to?(:finish)
450   end
451 
452   add_resource_metaparams
453 end

Private Instance Methods

add_resource_metaparams() click to toggle source
    # File lib/puppet/parser/compiler.rb
456 def add_resource_metaparams
457   main = catalog.resource(:class, :main)
458   unless main
459     #TRANSLATORS "main" is a function name and should not be translated
460     raise _("Couldn't find main")
461   end
462 
463   names = Puppet::Type.metaparams.select do |name|
464     !Puppet::Parser::Resource.relationship_parameter?(name)
465   end
466   data = {}
467   catalog.walk(main, :out) do |source, target|
468     source_data = data[source] || metaparams_as_data(source, names)
469     if source_data
470       # only store anything in the data hash if we've actually got
471       # data
472       data[source] ||= source_data
473       source_data.each do |param, value|
474         target[param] = value if target[param].nil?
475       end
476       data[target] = source_data.merge(metaparams_as_data(target, names))
477     end
478 
479     target.merge_tags_from(source)
480   end
481 end
create_settings_scope() click to toggle source
    # File lib/puppet/parser/compiler.rb
585 def create_settings_scope
586   settings_type = create_settings_type
587   settings_resource = Puppet::Parser::Resource.new('class', SETTINGS, :scope => @topscope)
588 
589   @catalog.add_resource(settings_resource)
590   settings_type.evaluate_code(settings_resource)
591   settings_resource.instance_variable_set(:@evaluated, true) # Prevents settings from being reevaluated
592 
593   scope = @topscope.class_scope(settings_type)
594   scope.merge_settings(environment.name)
595 end
create_settings_type() click to toggle source
    # File lib/puppet/parser/compiler.rb
597 def create_settings_type
598   environment.lock.synchronize do
599     resource_types = environment.known_resource_types
600     settings_type = resource_types.hostclass(SETTINGS)
601     if settings_type.nil?
602       settings_type = Puppet::Resource::Type.new(:hostclass, SETTINGS)
603       resource_types.add(settings_type)
604     end
605 
606     settings_type
607   end
608 end
ensure_classes_with_parameters(scope, hostclasses, parameters) click to toggle source
    # File lib/puppet/parser/compiler.rb
299 def ensure_classes_with_parameters(scope, hostclasses, parameters)
300   hostclasses.collect do |klass|
301     klass.ensure_in_catalog(scope, parameters[klass.name] || {})
302   end
303 end
ensure_classes_without_parameters(scope, hostclasses) click to toggle source
    # File lib/puppet/parser/compiler.rb
305 def ensure_classes_without_parameters(scope, hostclasses)
306   already_included = []
307   newly_included = []
308   hostclasses.each do |klass|
309     class_scope = scope.class_scope(klass)
310     if class_scope
311       already_included << class_scope.resource
312     else
313       newly_included << klass.ensure_in_catalog(scope)
314     end
315   end
316 
317   [already_included, newly_included]
318 end
evaluate_collections() click to toggle source

Evaluate our collections and return true if anything returned an object. The 'true' is used to continue a loop, so it's important.

    # File lib/puppet/parser/compiler.rb
322 def evaluate_collections
323   return false if @collections.empty?
324 
325   exceptwrap do
326     # We have to iterate over a dup of the array because
327     # collections can delete themselves from the list, which
328     # changes its length and causes some collections to get missed.
329     Puppet::Util::Profiler.profile(_("Evaluated collections"), [:compiler, :evaluate_collections]) do
330       found_something = false
331       @collections.dup.each do |collection|
332         found_something = true if collection.evaluate
333       end
334       found_something
335     end
336   end
337 end
evaluate_definitions() click to toggle source

Make sure all of our resources have been evaluated into native resources. We return true if any resources have, so that we know to continue the evaluate_generators loop.

    # File lib/puppet/parser/compiler.rb
342 def evaluate_definitions
343   exceptwrap do
344     Puppet::Util::Profiler.profile(_("Evaluated definitions"), [:compiler, :evaluate_definitions]) do
345       urs = unevaluated_resources.each do |resource|
346         begin
347           resource.evaluate
348         rescue Puppet::Pops::Evaluator::PuppetStopIteration => detail
349           # needs to be handled specifically as the error has the file/line/position where this
350           # occurred rather than the resource
351           fail(Puppet::Pops::Issues::RUNTIME_ERROR, detail, {:detail => detail.message}, detail)
352 
353         rescue Puppet::Error => e
354           # PuppetError has the ability to wrap an exception, if so, use the wrapped exception's
355           # call stack instead
356           fail(Puppet::Pops::Issues::RUNTIME_ERROR, resource, {:detail => e.message}, e.original || e)
357         end
358       end
359       !urs.empty?
360     end
361   end
362 end
evaluate_main() click to toggle source

Find and evaluate our main object, if possible.

    # File lib/puppet/parser/compiler.rb
391 def evaluate_main
392   krt = environment.known_resource_types
393   @main = krt.find_hostclass('') || krt.add(Puppet::Resource::Type.new(:hostclass, ''))
394   @topscope.source = @main
395   @main_resource = Puppet::Parser::Resource.new('class', :main, :scope => @topscope, :source => @main)
396   @topscope.resource = @main_resource
397 
398   add_resource(@topscope, @main_resource)
399 
400   @main_resource.evaluate
401 end
fail_on_unevaluated() click to toggle source

Make sure the entire catalog is evaluated.

    # File lib/puppet/parser/compiler.rb
404 def fail_on_unevaluated
405   fail_on_unevaluated_overrides
406   fail_on_unevaluated_resource_collections
407 end
fail_on_unevaluated_overrides() click to toggle source

If there are any resource overrides remaining, then we could not find the resource they were supposed to override, so we want to throw an exception.

    # File lib/puppet/parser/compiler.rb
412 def fail_on_unevaluated_overrides
413   remaining = @resource_overrides.values.flatten.collect(&:ref)
414 
415   if !remaining.empty?
416     raise Puppet::ParseError, _("Could not find resource(s) %{resources} for overriding") % { resources: remaining.join(', ') }
417   end
418 end
fail_on_unevaluated_resource_collections() click to toggle source

Make sure there are no remaining collections that are waiting for resources that have not yet been instantiated. If this occurs it is an error (missing resource - it could not be realized).

    # File lib/puppet/parser/compiler.rb
424 def fail_on_unevaluated_resource_collections
425   remaining = @collections.collect(&:unresolved_resources).flatten.compact
426   if !remaining.empty?
427     raise Puppet::ParseError, _("Failed to realize virtual resources %{resources}") % { resources: remaining.join(', ') }
428   end
429 end
initvars() click to toggle source

Set up all of our internal variables.

    # File lib/puppet/parser/compiler.rb
499 def initvars
500   # The list of overrides.  This is used to cache overrides on objects
501   # that don't exist yet.  We store an array of each override.
502   @resource_overrides = Hash.new do |overs, ref|
503     overs[ref] = []
504   end
505 
506   # The list of collections that have been created.  This is a global list,
507   # but they each refer back to the scope that created them.
508   @collections = []
509 
510   # The list of relationships to evaluate.
511   @relationships = []
512 
513   # For maintaining the relationship between scopes and their resources.
514   @catalog = Puppet::Resource::Catalog.new(@node.name, @node.environment, @code_id)
515 
516   # MOVED HERE - SCOPE IS NEEDED (MOVE-SCOPE)
517   # Create the initial scope, it is needed early
518   @topscope = Puppet::Parser::Scope.new(self)
519 
520   # Initialize loaders and Pcore
521   @loaders = Puppet::Pops::Loaders.new(environment)
522 
523   # Need to compute overrides here, and remember them, because we are about to
524   # enter the magic zone of known_resource_types and initial import.
525   # Expensive entries in the context are bound lazily.
526   @context_overrides = context_overrides()
527 
528   # This construct ensures that initial import (triggered by instantiating
529   # the structure 'known_resource_types') has a configured context
530   # It cannot survive the initvars method, and is later reinstated
531   # as part of compiling...
532   #
533   Puppet.override( @context_overrides , _("For initializing compiler")) do
534     # THE MAGIC STARTS HERE ! This triggers parsing, loading etc.
535     @catalog.version = environment.known_resource_types.version
536     @loaders.pre_load
537   end
538 
539   @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @topscope))
540 
541   # local resource array to maintain resource ordering
542   @resources = []
543 
544   # Make sure any external node classes are in our class list
545   if @node.classes.class == Hash
546     @catalog.add_class(*@node.classes.keys)
547   else
548     @catalog.add_class(*@node.classes)
549   end
550 
551   @catalog_validators = []
552 end
metaparams_as_data(resource, params) click to toggle source
    # File lib/puppet/parser/compiler.rb
483 def metaparams_as_data(resource, params)
484   data = nil
485   params.each do |param|
486     unless resource[param].nil?
487       # Because we could be creating a hash for every resource,
488       # and we actually probably don't often have any data here at all,
489       # we're optimizing a bit by only creating a hash if there's
490       # any data to put in it.
491       data ||= {}
492       data[param] = resource[param]
493     end
494   end
495   data
496 end
sanitize_node(node) click to toggle source
    # File lib/puppet/parser/compiler.rb
554 def sanitize_node(node)
555   node.sanitize
556   node
557 end
set_node_parameters() click to toggle source

Set the node's parameters into the top-scope as variables.

    # File lib/puppet/parser/compiler.rb
560 def set_node_parameters
561   node.parameters.each do |param, value|
562     # We don't want to set @topscope['environment'] from the parameters,
563     # instead we want to get that from the node's environment itself in
564     # case a custom node terminus has done any mucking about with
565     # node.parameters.
566     next if param.to_s == 'environment'
567     # Ensure node does not leak Symbol instances in general
568     @topscope[param.to_s] = value.is_a?(Symbol) ? value.to_s : value
569   end
570   @topscope['environment'] = node.environment.name.to_s
571 
572   # These might be nil.
573   catalog.client_version = node.parameters["clientversion"]
574   catalog.server_version = node.parameters["serverversion"]
575   @topscope.set_trusted(node.trusted_data)
576 
577   @topscope.set_server_facts(node.server_facts)
578 
579   facts_hash = node.facts.nil? ? {} : node.facts.values
580   @topscope.set_facts(facts_hash)
581 end
unevaluated_resources() click to toggle source

Return an array of all of the unevaluated resources. These will be definitions, which need to get evaluated into native resources.

    # File lib/puppet/parser/compiler.rb
612 def unevaluated_resources
613   # The order of these is significant for speed due to short-circuiting
614   resources.reject { |resource| resource.evaluated? or resource.virtual? or resource.builtin_type? }
615 end