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
@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.
The UUID of the catalog
Some metadata to help us compile and generally respond to the current state.
The id of the code input to the compiler.
A String representing the environment for this catalog
The actual environment instance that was used during compilation
Whether this catalog was retrieved from the cache, which affects whether it is written back out again.
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.
Inlined file metadata for non-recursive find A hash of title => metadata
The host name this is a catalog for.
Inlined file metadata for recursive search A hash of title => { source => [metadata, …] }
How long this catalog took to retrieve. Used for reporting stats.
Some metadata to help us compile and generally respond to the current state.
The catalog version. Used for testing whether a catalog is up to date.
Public Class Methods
# 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
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 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
# 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 `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
# 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
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 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
# File lib/puppet/resource/catalog.rb 283 def classes 284 @classes.dup 285 end
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
@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 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 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
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
# File lib/puppet/resource/catalog.rb 309 def host_config? 310 host_config 311 end
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
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 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
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
# File lib/puppet/resource/catalog.rb 398 def resource_keys 399 @resource_table.keys 400 end
# 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
# File lib/puppet/resource/catalog.rb 402 def resources 403 @resources.collect do |key| 404 @resource_table[key] 405 end 406 end
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
# 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
Convert our catalog into a RAL catalog.
# File lib/puppet/resource/catalog.rb 495 def to_ral 496 to_catalog :to_ral 497 end
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
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
Produce the graph files if requested.
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
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
# 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
# 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
# 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
# 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
# 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
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
# File lib/puppet/resource/catalog.rb 560 def prioritizer 561 @prioritizer = Puppet::Graph::SequentialPrioritizer.new 562 end
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
# File lib/puppet/resource/catalog.rb 652 def virtual_not_exported?(resource) 653 resource.virtual && !resource.exported 654 end