class Catalog

This class models a node catalog. It is the thing meant to be passed from server to client, and it contains all of the information in the catalog, including the resources and the relationships between them.

@api public

Attributes

catalog_format[RW]

@return [Integer] catalog format version number. This value is constant

for a given version of Puppet; it is incremented when a new release of
Puppet changes the API for the various objects that make up the catalog.
catalog_uuid[RW]

The UUID of the catalog

client_version[RW]

Some metadata to help us compile and generally respond to the current state.

code_id[RW]

The id of the code input to the compiler.

environment[RW]

A String representing the environment for this catalog

environment_instance[RW]

The actual environment instance that was used during compilation

from_cache[RW]

Whether this catalog was retrieved from the cache, which affects whether it is written back out again.

host_config[RW]

Whether this is a host catalog, which behaves very differently. In particular, reports are sent, graphs are made, and state is stored in the state database. If this is set incorrectly, then you often end up in infinite loops, because catalogs are used to make things that the host catalog needs.

metadata[RW]

Inlined file metadata for non-recursive find A hash of title => metadata

name[RW]

The host name this is a catalog for.

recursive_metadata[RW]

Inlined file metadata for recursive search A hash of title => { source => [metadata, …] }

retrieval_duration[RW]

How long this catalog took to retrieve. Used for reporting stats.

server_version[RW]

Some metadata to help us compile and generally respond to the current state.

version[RW]

The catalog version. Used for testing whether a catalog is up to date.

Public Class Methods

from_data_hash(data) click to toggle source
    # File lib/puppet/resource/catalog.rb
408 def self.from_data_hash(data)
409   result = new(data['name'], Puppet::Node::Environment::NONE)
410 
411   result.tag(*data['tags']) if data['tags'] 
412   result.version = data['version'] if data['version']
413   result.code_id = data['code_id'] if data['code_id']
414   result.catalog_uuid = data['catalog_uuid'] if data['catalog_uuid']
415   result.catalog_format = data['catalog_format'] || 0
416 
417   environment = data['environment']
418   if environment
419     result.environment = environment
420     result.environment_instance = Puppet::Node::Environment.remote(environment.to_sym)
421   end
422 
423   result.add_resource(
424     *data['resources'].collect do |res|
425       Puppet::Resource.from_data_hash(res)
426     end
427   ) if data['resources']
428 
429   if data['edges']
430     data['edges'].each do |edge_hash|
431       edge = Puppet::Relationship.from_data_hash(edge_hash)
432       source = result.resource(edge.source)
433       unless source
434         raise ArgumentError, _("Could not intern from data: Could not find relationship source %{source} for %{target}") %
435             { source: edge.source.inspect, target: edge.target.to_s }
436       end
437       edge.source = source
438 
439       target = result.resource(edge.target)
440       unless target
441         raise ArgumentError, _("Could not intern from data: Could not find relationship target %{target} for %{source}") %
442             { target: edge.target.inspect, source: edge.source.to_s }
443       end
444       edge.target = target
445 
446       result.add_edge(edge)
447     end
448   end
449 
450   result.add_class(*data['classes']) if data['classes']
451 
452   result.metadata = data['metadata'].inject({}) { |h, (k, v)| h[k] = Puppet::FileServing::Metadata.from_data_hash(v); h } if data['metadata']
453 
454   recursive_metadata = data['recursive_metadata']
455   if recursive_metadata
456     result.recursive_metadata = recursive_metadata.inject({}) do |h, (title, source_to_meta_hash)|
457       h[title] = source_to_meta_hash.inject({}) do |inner_h, (source, metas)|
458         inner_h[source] = metas.map {|meta| Puppet::FileServing::Metadata.from_data_hash(meta)}
459         inner_h
460       end
461       h
462     end
463   end
464 
465   result
466 end
new(name = nil, environment = Puppet::Node::Environment::NONE, code_id = nil) { |self| ... } click to toggle source
Calls superclass method Puppet::Graph::SimpleGraph::new
    # File lib/puppet/resource/catalog.rb
313 def initialize(name = nil, environment = Puppet::Node::Environment::NONE, code_id = nil)
314   super()
315   @name = name
316   @catalog_uuid = SecureRandom.uuid
317   @catalog_format = 2
318   @metadata = {}
319   @recursive_metadata = {}
320   @classes = []
321   @resource_table = {}
322   @resources = []
323   @relationship_graph = nil
324 
325   @host_config = true
326   @environment_instance = environment
327   @environment = environment.to_s
328   @code_id = code_id
329 
330   @aliases = {}
331 
332   if block_given?
333     yield(self)
334     finalize
335   end
336 end

Public Instance Methods

add_class(*classes) click to toggle source

Add classes to our class list.

   # File lib/puppet/resource/catalog.rb
75 def add_class(*classes)
76   classes.each do |klass|
77     @classes << klass
78   end
79 
80   # Add the class names as tags, too.
81   tag(*classes)
82 end
add_resource(*resources) click to toggle source
    # File lib/puppet/resource/catalog.rb
125 def add_resource(*resources)
126   resources.each do |resource|
127     add_one_resource(resource)
128   end
129 end
add_resource_after(other, *resources) click to toggle source

Add `resources` to the catalog after `other`. WARNING: adding multiple resources will produce the reverse ordering, e.g. calling `add_resource_after(A, [B,C])` will result in `[A,C,B]`.

    # File lib/puppet/resource/catalog.rb
113 def add_resource_after(other, *resources)
114   resources.each do |resource|
115     other_title_key = title_key_for_ref(other.ref)
116     idx = @resources.index(other_title_key)
117     if idx.nil?
118       raise ArgumentError, _("Cannot add resource %{resource_1} after %{resource_2} because %{resource_2} is not yet in the catalog") %
119           { resource_1: resource.ref, resource_2: other.ref }
120     end
121     add_one_resource(resource, idx+1)
122   end
123 end
add_resource_before(other, *resources) click to toggle source
    # File lib/puppet/resource/catalog.rb
 98 def add_resource_before(other, *resources)
 99   resources.each do |resource|
100     other_title_key = title_key_for_ref(other.ref)
101     idx = @resources.index(other_title_key)
102     if idx.nil?
103       raise ArgumentError, _("Cannot add resource %{resource_1} before %{resource_2} because %{resource_2} is not yet in the catalog") %
104           { resource_1: resource.ref, resource_2: other.ref }
105     end
106     add_one_resource(resource, idx)
107   end
108 end
alias(resource, key) click to toggle source

Create an alias for a resource.

    # File lib/puppet/resource/catalog.rb
183 def alias(resource, key)
184   ref = resource.ref
185   ref =~ /^(.+)\[/
186   class_name = $1 || resource.class.name
187 
188   newref = [class_name, key].flatten
189 
190   if key.is_a? String
191     ref_string = "#{class_name}[#{key}]"
192     return if ref_string == ref
193   end
194 
195   # LAK:NOTE It's important that we directly compare the references,
196   # because sometimes an alias is created before the resource is
197   # added to the catalog, so comparing inside the below if block
198   # isn't sufficient.
199   existing = @resource_table[newref]
200   if existing
201     return if existing == resource
202     resource_declaration = Puppet::Util::Errors.error_location(resource.file, resource.line)
203     msg = if resource_declaration.empty?
204             #TRANSLATORS 'alias' should not be translated
205             _("Cannot alias %{resource} to %{key}; resource %{newref} already declared") %
206                 { resource: ref, key: key.inspect, newref: newref.inspect }
207           else
208             #TRANSLATORS 'alias' should not be translated
209             _("Cannot alias %{resource} to %{key} at %{resource_declaration}; resource %{newref} already declared") %
210                 { resource: ref, key: key.inspect, resource_declaration: resource_declaration, newref: newref.inspect }
211           end
212     msg += Puppet::Util::Errors.error_location_with_space(existing.file, existing.line)
213     raise ArgumentError, msg
214   end
215   @resource_table[newref] = resource
216   @aliases[ref] ||= []
217   @aliases[ref] << newref
218 end
apply(options = {}) { |transaction| ... } click to toggle source

Apply our catalog to the local host. @param options [Hash{Symbol => Object}] a hash of options @option options [Puppet::Transaction::Report] :report

The report object to log this transaction to. This is optional,
and the resulting transaction will create a report if not
supplied.

@return [Puppet::Transaction] the transaction created for this

application

@api public

    # File lib/puppet/resource/catalog.rb
231 def apply(options = {})
232   Puppet::Util::Storage.load if host_config?
233 
234   transaction = create_transaction(options)
235 
236   begin
237     transaction.report.as_logging_destination do
238       transaction_evaluate_time = Puppet::Util.thinmark do
239         transaction.evaluate
240       end
241       transaction.report.add_times(:transaction_evaluation, transaction_evaluate_time)
242     end
243   ensure
244     # Don't try to store state unless we're a host config
245     # too recursive.
246     Puppet::Util::Storage.store if host_config?
247   end
248 
249   yield transaction if block_given?
250 
251   transaction
252 end
classes() click to toggle source
    # File lib/puppet/resource/catalog.rb
283 def classes
284   @classes.dup
285 end
clear(remove_resources = true) click to toggle source
Calls superclass method Puppet::Graph::SimpleGraph#clear
    # File lib/puppet/resource/catalog.rb
270 def clear(remove_resources = true)
271   super()
272   # We have to do this so that the resources clean themselves up.
273   @resource_table.values.each { |resource| resource.remove } if remove_resources
274   @resource_table.clear
275   @resources = []
276 
277   if @relationship_graph
278     @relationship_graph.clear
279     @relationship_graph = nil
280   end
281 end
container_of(resource) click to toggle source

@param resource [A Resource] a resource in the catalog @return [A Resource, nil] the resource that contains the given resource @api public

    # File lib/puppet/resource/catalog.rb
134 def container_of(resource)
135   adjacent(resource, :direction => :in)[0]
136 end
create_resource(type, options) click to toggle source

Create a new resource and register it in the catalog.

    # File lib/puppet/resource/catalog.rb
288 def create_resource(type, options)
289   klass = Puppet::Type.type(type)
290   unless klass
291     raise ArgumentError, _("Unknown resource type %{type}") % { type: type }
292   end
293   resource = klass.new(options)
294   return unless resource
295 
296   add_resource(resource)
297   resource
298 end
filter(&block) click to toggle source

filter out the catalog, applying block to each resource. If the block result is false, the resource will be kept otherwise it will be skipped

    # File lib/puppet/resource/catalog.rb
507 def filter(&block)
508   # to_catalog must take place in a context where current_environment is set to the same env as the
509   # environment set in the catalog (if it is set)
510   # See PUP-3755
511   if environment_instance
512     Puppet.override({:current_environment => environment_instance}) do
513       to_catalog :to_resource, &block
514     end
515   else
516     # If catalog has no environment_instance, hope that the caller has made sure the context has the
517     # correct current_environment
518     to_catalog :to_resource, &block
519   end
520 end
finalize() click to toggle source

Make sure all of our resources are “finished”.

    # File lib/puppet/resource/catalog.rb
301 def finalize
302   make_default_resources
303 
304   @resource_table.values.each { |resource| resource.finish }
305 
306   write_graph(:resources)
307 end
host_config?() click to toggle source
    # File lib/puppet/resource/catalog.rb
309 def host_config?
310   host_config
311 end
make_default_resources() click to toggle source

Make the default objects necessary for function.

    # File lib/puppet/resource/catalog.rb
339 def make_default_resources
340   # We have to add the resources to the catalog, or else they won't get cleaned up after
341   # the transaction.
342 
343   # First create the default scheduling objects
344   Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) }
345 
346   # And filebuckets
347   bucket = Puppet::Type.type(:filebucket).mkdefaultbucket
348   if bucket
349     add_resource(bucket) unless resource(bucket.ref)
350   end
351 end
relationship_graph(given_prioritizer = nil) click to toggle source

The relationship_graph form of the catalog. This contains all of the dependency edges that are used for determining order.

@param given_prioritizer [Puppet::Graph::Prioritizer] The prioritization

strategy to use when constructing the relationship graph. Defaults the
being determined by the `ordering` setting.

@return [Puppet::Graph::RelationshipGraph] @api public

    # File lib/puppet/resource/catalog.rb
262 def relationship_graph(given_prioritizer = nil)
263   if @relationship_graph.nil?
264     @relationship_graph = Puppet::Graph::RelationshipGraph.new(given_prioritizer || prioritizer)
265     @relationship_graph.populate_from(self)
266   end
267   @relationship_graph
268 end
remove_resource(*resources) click to toggle source

Remove the resource from our catalog. Notice that we also call 'remove' on the resource, at least until resource classes no longer maintain references to the resource instances.

    # File lib/puppet/resource/catalog.rb
356 def remove_resource(*resources)
357   resources.each do |resource|
358     ref = resource.ref
359     title_key = title_key_for_ref(ref)
360     @resource_table.delete(title_key)
361     aliases = @aliases[ref]
362     if aliases
363       aliases.each { |res_alias| @resource_table.delete(res_alias) }
364       @aliases.delete(ref)
365     end
366     remove_vertex!(resource) if vertex?(resource)
367     @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
368     @resources.delete(title_key)
369     # Only Puppet::Type kind of resources respond to :remove, not Puppet::Resource
370     resource.remove if resource.respond_to?(:remove)
371   end
372 end
resource(type, title = nil) click to toggle source

Look a resource up by its reference (e.g., File).

    # File lib/puppet/resource/catalog.rb
375 def resource(type, title = nil)
376   # Retain type if it's a type
377   type_name = type.is_a?(Puppet::CompilableResourceType) || type.is_a?(Puppet::Resource::Type) ? type.name : type
378   type_name, title = Puppet::Resource.type_and_title(type_name, title)
379   type = type_name if type.is_a?(String)
380   title_key   = [type_name, title.to_s]
381   result = @resource_table[title_key]
382   if result.nil?
383     # an instance has to be created in order to construct the unique key used when
384     # searching for aliases
385     res = Puppet::Resource.new(type, title, { :environment => @environment_instance })
386 
387     # Must check with uniqueness key because of aliases or if resource transforms title in title
388     # to attribute mappings.
389     result = @resource_table[[type_name, res.uniqueness_key].flatten]
390   end
391   result
392 end
resource_keys() click to toggle source
    # File lib/puppet/resource/catalog.rb
398 def resource_keys
399   @resource_table.keys
400 end
resource_refs() click to toggle source
    # File lib/puppet/resource/catalog.rb
394 def resource_refs
395   resource_keys.collect{ |type, name| name.is_a?( String ) ? "#{type}[#{name}]" : nil}.compact
396 end
resources() click to toggle source
    # File lib/puppet/resource/catalog.rb
402 def resources
403   @resources.collect do |key|
404     @resource_table[key]
405   end
406 end
title_key_for_ref( ref ) click to toggle source

Returns [typename, title] when given a String with “Type”. Returns [nil, nil] if '[' ']' not detected.

   # File lib/puppet/resource/catalog.rb
87 def title_key_for_ref( ref )
88   s = ref.index('[')
89   e = ref.rindex(']')
90   if s && e && e > s
91     a = [ref[0, s], ref[s+1, e-s-1]]
92   else
93     a = [nil, nil]
94   end
95   return a
96 end
to_data_hash() click to toggle source
    # File lib/puppet/resource/catalog.rb
468 def to_data_hash
469   metadata_hash = metadata.inject({}) { |h, (k, v)| h[k] = v.to_data_hash; h }
470   recursive_metadata_hash = recursive_metadata.inject({}) do |h, (title, source_to_meta_hash)|
471     h[title] = source_to_meta_hash.inject({}) do |inner_h, (source, metas)|
472       inner_h[source] = metas.map {|meta| meta.to_data_hash}
473       inner_h
474     end
475     h
476   end
477 
478   {
479     'tags'      => tags.to_a,
480     'name'      => name,
481     'version'   => version,
482     'code_id'   => code_id,
483     'catalog_uuid' => catalog_uuid,
484     'catalog_format' => catalog_format,
485     'environment'  => environment.to_s,
486     'resources' => @resources.map { |v| @resource_table[v].to_data_hash },
487     'edges'     => edges.map { |e| e.to_data_hash },
488     'classes'   => classes,
489   }.merge(metadata_hash.empty? ?
490     {} : {'metadata' => metadata_hash}).merge(recursive_metadata_hash.empty? ?
491       {} : {'recursive_metadata' => recursive_metadata_hash})
492 end
to_ral() click to toggle source

Convert our catalog into a RAL catalog.

    # File lib/puppet/resource/catalog.rb
495 def to_ral
496   to_catalog :to_ral
497 end
to_resource() click to toggle source

Convert our catalog into a catalog of Puppet::Resource instances.

    # File lib/puppet/resource/catalog.rb
500 def to_resource
501   to_catalog :to_resource
502 end
write_class_file() click to toggle source

Store the classes in the classfile.

    # File lib/puppet/resource/catalog.rb
523 def write_class_file
524   # classfile paths may contain UTF-8
525   # https://puppet.com/docs/puppet/latest/configuration.html#classfile
526   classfile = Puppet.settings.setting(:classfile)
527   Puppet::FileSystem.open(classfile.value, classfile.mode.to_i(8), "w:UTF-8") do |f|
528     f.puts classes.join("\n")
529   end
530 rescue => detail
531   Puppet.err _("Could not create class file %{file}: %{detail}") % { file: Puppet[:classfile], detail: detail }
532 end
write_graph(name) click to toggle source

Produce the graph files if requested.

Calls superclass method Puppet::Graph::SimpleGraph#write_graph
    # File lib/puppet/resource/catalog.rb
551 def write_graph(name)
552   # We only want to graph the main host catalog.
553   return unless host_config?
554 
555   super
556 end
write_resource_file() click to toggle source

Store the list of resources we manage

    # File lib/puppet/resource/catalog.rb
535 def write_resource_file
536   # resourcefile contains resources that may be UTF-8 names
537   # https://puppet.com/docs/puppet/latest/configuration.html#resourcefile
538   resourcefile = Puppet.settings.setting(:resourcefile)
539   Puppet::FileSystem.open(resourcefile.value, resourcefile.mode.to_i(8), "w:UTF-8") do |f|
540     to_print = resources.map do |resource|
541       next unless resource.managed?
542       "#{resource.ref.downcase}"
543     end.compact
544     f.puts to_print.join("\n")
545   end
546 rescue => detail
547   Puppet.err _("Could not create resource file %{file}: %{detail}") % { file: Puppet[:resourcefile], detail: detail }
548 end

Private Instance Methods

add_one_resource(resource, idx=-1) click to toggle source
    # File lib/puppet/resource/catalog.rb
138 def add_one_resource(resource, idx=-1)
139   title_key = title_key_for_ref(resource.ref)
140   if @resource_table[title_key]
141     fail_on_duplicate_type_and_title(resource, title_key)
142   end
143 
144   add_resource_to_table(resource, title_key, idx)
145   create_resource_aliases(resource)
146 
147   resource.catalog = self if resource.respond_to?(:catalog=)
148   add_resource_to_graph(resource)
149 end
add_resource_to_graph(resource) click to toggle source
    # File lib/puppet/resource/catalog.rb
158 def add_resource_to_graph(resource)
159   add_vertex(resource)
160   @relationship_graph.add_vertex(resource) if @relationship_graph
161 end
add_resource_to_table(resource, title_key, idx) click to toggle source
    # File lib/puppet/resource/catalog.rb
152 def add_resource_to_table(resource, title_key, idx)
153   @resource_table[title_key] = resource
154   @resources.insert(idx, title_key)
155 end
create_resource_aliases(resource) click to toggle source
    # File lib/puppet/resource/catalog.rb
164 def create_resource_aliases(resource)
165   # Explicit aliases must always be processed
166   # The alias setting logic checks, and does not error if the alias is set to an already set alias
167   # for the same resource (i.e. it is ok if alias == title
168   explicit_aliases = [resource[:alias]].flatten.compact
169   explicit_aliases.each {| given_alias | self.alias(resource, given_alias) }
170 
171   # Skip creating uniqueness key alias and checking collisions for non-isomorphic resources.
172   return unless resource.respond_to?(:isomorphic?) and resource.isomorphic?
173 
174   # Add an alias if the uniqueness key is valid and not the title, which has already been checked.
175   ukey = resource.uniqueness_key
176   if ukey.any? and ukey != [resource.title]
177     self.alias(resource, ukey)
178   end
179 end
create_transaction(options) click to toggle source
    # File lib/puppet/resource/catalog.rb
564 def create_transaction(options)
565   transaction = Puppet::Transaction.new(self, options[:report], prioritizer)
566   transaction.tags = options[:tags] if options[:tags]
567   transaction.ignoreschedules = true if options[:ignoreschedules]
568   transaction.for_network_device = Puppet.lookup(:network_device) { nil } || options[:network_device]
569 
570   transaction
571 end
fail_on_duplicate_type_and_title(resource, title_key) click to toggle source

Verify that the given resource isn't declared elsewhere.

    # File lib/puppet/resource/catalog.rb
574 def fail_on_duplicate_type_and_title(resource, title_key)
575   # Short-circuit the common case,
576   existing_resource = @resource_table[title_key]
577   return unless existing_resource
578 
579   # If we've gotten this far, it's a real conflict
580   error_location_str = Puppet::Util::Errors.error_location(existing_resource.file, existing_resource.line)
581   msg = if error_location_str.empty?
582           _("Duplicate declaration: %{resource} is already declared; cannot redeclare") % { resource: resource.ref }
583         else
584           _("Duplicate declaration: %{resource} is already declared at %{error_location}; cannot redeclare") % { resource: resource.ref, error_location: error_location_str }
585         end
586   raise DuplicateResourceError.new(msg, resource.file, resource.line)
587 end
prioritizer() click to toggle source
    # File lib/puppet/resource/catalog.rb
560 def prioritizer
561   @prioritizer = Puppet::Graph::SequentialPrioritizer.new
562 end
to_catalog(convert) { |resource| ... } click to toggle source

An abstracted method for converting one catalog into another type of catalog. This pretty much just converts all of the resources from one class to another, using a conversion method.

    # File lib/puppet/resource/catalog.rb
592 def to_catalog(convert)
593   result = self.class.new(self.name, self.environment_instance)
594 
595   result.version = self.version
596   result.code_id = self.code_id
597   result.catalog_uuid = self.catalog_uuid
598   result.catalog_format = self.catalog_format
599   result.metadata = self.metadata
600   result.recursive_metadata = self.recursive_metadata
601 
602   map = {}
603   resources.each do |resource|
604     next if virtual_not_exported?(resource)
605     next if block_given? and yield resource
606 
607     newres = resource.copy_as_resource
608     newres.catalog = result
609 
610     if convert != :to_resource
611       newres = newres.to_ral
612     end
613 
614     # We can't guarantee that resources don't munge their names
615     # (like files do with trailing slashes), so we have to keep track
616     # of what a resource got converted to.
617     map[resource.ref] = newres
618 
619     result.add_resource newres
620   end
621 
622   message = convert.to_s.tr "_", " "
623   edges.each do |edge|
624     # Skip edges between virtual resources.
625     next if virtual_not_exported?(edge.source)
626     next if block_given? and yield edge.source
627 
628     next if virtual_not_exported?(edge.target)
629     next if block_given? and yield edge.target
630 
631     source = map[edge.source.ref]
632     unless source
633       raise Puppet::DevError, _("Could not find resource %{resource} when converting %{message} resources") % { resource: edge.source.ref, message: message }
634     end
635 
636     target = map[edge.target.ref]
637     unless target
638       raise Puppet::DevError, _("Could not find resource %{resource} when converting %{message} resources") % { resource: edge.target.ref, message: message }
639     end
640 
641     result.add_edge(source, target, edge.label)
642   end
643 
644   map.clear
645 
646   result.add_class(*self.classes)
647   result.merge_tags_from(self)
648 
649   result
650 end
virtual_not_exported?(resource) click to toggle source
    # File lib/puppet/resource/catalog.rb
652 def virtual_not_exported?(resource)
653   resource.virtual && !resource.exported
654 end