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.
# File lib/puppet/resource/catalog.rb, line 428 def self.edge_from_pson(result, edge) # If no type information was presented, we manually find # the class. edge = Puppet::Relationship.from_pson(edge) if edge.is_a?(Hash) unless source = result.resource(edge.source) raise ArgumentError, "Could not convert from pson: Could not find relationship source #{edge.source.inspect}" end edge.source = source unless target = result.resource(edge.target) raise ArgumentError, "Could not convert from pson: Could not find relationship target #{edge.target.inspect}" end edge.target = target result.add_edge(edge) end
# File lib/puppet/resource/catalog.rb, line 392 def self.from_pson(data) result = new(data['name']) if tags = data['tags'] result.tag(*tags) end if version = data['version'] result.version = version end if environment = data['environment'] result.environment = environment end if resources = data['resources'] resources = PSON.parse(resources) if resources.is_a?(String) resources.each do |res| resource_from_pson(result, res) end end if edges = data['edges'] edges = PSON.parse(edges) if edges.is_a?(String) edges.each do |edge| edge_from_pson(result, edge) end end if classes = data['classes'] result.add_class(*classes) end result end
# File lib/puppet/resource/catalog.rb, line 210 def initialize(name = nil) super() @name = name if name @classes = [] @resource_table = {} @transient_resources = [] @applying = false @relationship_graph = nil @host_config = true @aliases = {} if block_given? yield(self) finalize end end
# File lib/puppet/resource/catalog.rb, line 445 def self.resource_from_pson(result, res) res = Puppet::Resource.from_pson(res) if res.is_a? Hash result.add_resource(res) end
Add classes to our class list.
# File lib/puppet/resource/catalog.rb, line 51 def add_class(*classes) classes.each do |klass| @classes << klass end # Add the class names as tags, too. tag(*classes) end
Add a resource to our graph and to our resource table. This is actually a relatively complicated method, because it handles multiple aspects of Catalog behaviour:
Add the resource to the resource table
Add the resource to the resource graph
Add the resource to the relationship graph
Add any aliases that make sense for the resource (e.g., name != title)
# File lib/puppet/resource/catalog.rb, line 72 def add_resource(*resource) add_resource(*resource[0..-2]) if resource.length > 1 resource = resource.pop raise ArgumentError, "Can only add objects that respond to :ref, not instances of #{resource.class}" unless resource.respond_to?(:ref) fail_on_duplicate_type_and_title(resource) title_key = title_key_for_ref(resource.ref) @transient_resources << resource if applying? @resource_table[title_key] = resource # If the name and title differ, set up an alias if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.respond_to?(:isomorphic?) and resource.name != resource.title self.alias(resource, resource.uniqueness_key) if resource.isomorphic? end resource.catalog = self if resource.respond_to?(:catalog=) add_vertex(resource) @relationship_graph.add_vertex(resource) if @relationship_graph end
Create an alias for a resource.
# File lib/puppet/resource/catalog.rb, line 94 def alias(resource, key) resource.ref =~ /^(.+)\[/ class_name = $1 || resource.class.name newref = [class_name, key].flatten if key.is_a? String ref_string = "#{class_name}[#{key}]" return if ref_string == resource.ref end # LAK:NOTE It's important that we directly compare the references, # because sometimes an alias is created before the resource is # added to the catalog, so comparing inside the below if block # isn't sufficient. if existing = @resource_table[newref] return if existing == resource resource_declaration = " at #{resource.file}:#{resource.line}" if resource.file and resource.line existing_declaration = " at #{existing.file}:#{existing.line}" if existing.file and existing.line msg = "Cannot alias #{resource.ref} to #{key.inspect}#{resource_declaration}; resource #{newref.inspect} already declared#{existing_declaration}" raise ArgumentError, msg end @resource_table[newref] = resource @aliases[resource.ref] ||= [] @aliases[resource.ref] << newref end
Apply our catalog to the local host. Valid options are:
:tags - set the tags that restrict what resources run
during the transaction
:ignoreschedules - tell the transaction to ignore schedules
when determining the resources to run
# File lib/puppet/resource/catalog.rb, line 127 def apply(options = {}) @applying = true Puppet::Util::Storage.load if host_config? transaction = Puppet::Transaction.new(self, options[:report]) register_report = options[:report].nil? transaction.tags = options[:tags] if options[:tags] transaction.ignoreschedules = true if options[:ignoreschedules] transaction.for_network_device = options[:network_device] transaction.add_times :config_retrieval => self.retrieval_duration || 0 begin Puppet::Util::Log.newdestination(transaction.report) if register_report begin transaction.evaluate ensure Puppet::Util::Log.close(transaction.report) if register_report end rescue Puppet::Error => detail Puppet.log_exception(detail, "Could not apply complete catalog: #{detail}") rescue => detail Puppet.log_exception(detail, "Got an uncaught exception of type #{detail.class}: #{detail}") ensure # Don't try to store state unless we're a host config # too recursive. Puppet::Util::Storage.store if host_config? end yield transaction if block_given? return transaction ensure @applying = false end
Are we in the middle of applying the catalog?
# File lib/puppet/resource/catalog.rb, line 166 def applying? @applying end
# File lib/puppet/resource/catalog.rb, line 182 def classes @classes.dup end
# File lib/puppet/resource/catalog.rb, line 170 def clear(remove_resources = true) super() # We have to do this so that the resources clean themselves up. @resource_table.values.each { |resource| resource.remove } if remove_resources @resource_table.clear if @relationship_graph @relationship_graph.clear @relationship_graph = nil end end
Create a new resource and register it in the catalog.
# File lib/puppet/resource/catalog.rb, line 187 def create_resource(type, options) unless klass = Puppet::Type.type(type) raise ArgumentError, "Unknown resource type #{type}" end return unless resource = klass.new(options) add_resource(resource) resource 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, line 486 def filter(&block) to_catalog :to_resource, &block end
Make sure all of our resources are “finished”.
# File lib/puppet/resource/catalog.rb, line 198 def finalize make_default_resources @resource_table.values.each { |resource| resource.finish } write_graph(:resources) end
# File lib/puppet/resource/catalog.rb, line 206 def host_config? host_config end
Make the default objects necessary for function.
# File lib/puppet/resource/catalog.rb, line 230 def make_default_resources # We have to add the resources to the catalog, or else they won't get cleaned up after # the transaction. # First create the default scheduling objects Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) } # And filebuckets if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket add_resource(bucket) unless resource(bucket.ref) end end
Create a graph of all of the relationships in our catalog.
# File lib/puppet/resource/catalog.rb, line 244 def relationship_graph unless @relationship_graph # It's important that we assign the graph immediately, because # the debug messages below use the relationships in the # relationship graph to determine the path to the resources # spitting out the messages. If this is not set, # then we get into an infinite loop. @relationship_graph = Puppet::SimpleGraph.new # First create the dependency graph self.vertices.each do |vertex| @relationship_graph.add_vertex vertex vertex.builddepends.each do |edge| @relationship_graph.add_edge(edge) end end # Lastly, add in any autorequires @relationship_graph.vertices.each do |vertex| vertex.autorequire(self).each do |edge| unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones. unless @relationship_graph.edge?(edge.target, edge.source) vertex.debug "Autorequiring #{edge.source}" @relationship_graph.add_edge(edge) else vertex.debug "Skipping automatic relationship with #{(edge.source == vertex ? edge.target : edge.source)}" end end end end @relationship_graph.write_graph(:relationships) if host_config? # Then splice in the container information splice!(@relationship_graph) @relationship_graph.write_graph(:expanded_relationships) if host_config? end @relationship_graph 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, line 349 def remove_resource(*resources) resources.each do |resource| title_key = title_key_for_ref(resource.ref) @resource_table.delete(title_key) if aliases = @aliases[resource.ref] aliases.each { |res_alias| @resource_table.delete(res_alias) } @aliases.delete(resource.ref) end remove_vertex!(resource) if vertex?(resource) @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) resource.remove end end
Look a resource up by its reference (e.g., File).
# File lib/puppet/resource/catalog.rb, line 364 def resource(type, title = nil) # Always create a resource reference, so that it always canonizes how we # are referring to them. if title res = Puppet::Resource.new(type, title) else # If they didn't provide a title, then we expect the first # argument to be of the form 'Class[name]', which our # Reference class canonizes for us. res = Puppet::Resource.new(nil, type) end title_key = [res.type, res.title.to_s] uniqueness_key = [res.type, res.uniqueness_key].flatten @resource_table[title_key] || @resource_table[uniqueness_key] end
# File lib/puppet/resource/catalog.rb, line 384 def resource_keys @resource_table.keys end
# File lib/puppet/resource/catalog.rb, line 380 def resource_refs resource_keys.collect{ |type, name| name.is_a?( String ) ? "#{type}[#{name}]" : nil}.compact end
# File lib/puppet/resource/catalog.rb, line 388 def resources @resource_table.values.uniq end
# File lib/puppet/resource/catalog.rb, line 304 def splice!(other) stage_class = Puppet::Type.type(:stage) whit_class = Puppet::Type.type(:whit) component_class = Puppet::Type.type(:component) containers = vertices.find_all { |v| (v.is_a?(component_class) or v.is_a?(stage_class)) and vertex?(v) } # # These two hashes comprise the aforementioned attention to the possible # case of containers that contain / depend on other containers; they map # containers to their sentinels but pass other verticies through. Thus we # can "do the right thing" for references to other verticies that may or # may not be containers. # admissible = Hash.new { |h,k| k } completed = Hash.new { |h,k| k } containers.each { |x| admissible[x] = whit_class.new(:name => "admissible_#{x.ref}", :catalog => self) completed[x] = whit_class.new(:name => "completed_#{x.ref}", :catalog => self) } # # Implement the six requierments listed above # containers.each { |x| contents = adjacent(x, :direction => :out) other.add_edge(admissible[x],completed[x]) if contents.empty? # (0) contents.each { |v| other.add_edge(admissible[x],admissible[v],Default_label) # (1) other.add_edge(completed[v], completed[x], Default_label) # (2) } # (3) & (5) other.adjacent(x,:direction => :in,:type => :edges).each { |e| other.add_edge(completed[e.source],admissible[x],e.label) other.remove_edge! e } # (4) & (5) other.adjacent(x,:direction => :out,:type => :edges).each { |e| other.add_edge(completed[x],admissible[e.target],e.label) other.remove_edge! e } } containers.each { |x| other.remove_vertex! x } # (5) end
# File lib/puppet/resource/catalog.rb, line 60 def title_key_for_ref( ref ) ref =~ /^([-\w:]+)\[(.*)\]$/ [$1, $2] end
# File lib/puppet/resource/catalog.rb, line 469 def to_pson(*args) to_pson_data_hash.to_pson(*args) end
# File lib/puppet/resource/catalog.rb, line 451 def to_pson_data_hash { 'document_type' => 'Catalog', 'data' => { 'tags' => tags, 'name' => name, 'version' => version, 'environment' => environment.to_s, 'resources' => vertices.collect { |v| v.to_pson_data_hash }, 'edges' => edges. collect { |e| e.to_pson_data_hash }, 'classes' => classes }, 'metadata' => { 'api_version' => 1 } } end
Convert our catalog into a RAL catalog.
# File lib/puppet/resource/catalog.rb, line 474 def to_ral to_catalog :to_ral end
Convert our catalog into a catalog of Puppet::Resource instances.
# File lib/puppet/resource/catalog.rb, line 479 def to_resource to_catalog :to_resource end
Store the classes in the classfile.
# File lib/puppet/resource/catalog.rb, line 491 def write_class_file ::File.open(Puppet[:classfile], "w") do |f| f.puts classes.join("\n") end rescue => detail Puppet.err "Could not create class file #{Puppet[:classfile]}: #{detail}" end
Produce the graph files if requested.
# File lib/puppet/resource/catalog.rb, line 517 def write_graph(name) # We only want to graph the main host catalog. return unless host_config? super end
Store the list of resources we manage
# File lib/puppet/resource/catalog.rb, line 500 def write_resource_file ::File.open(Puppet[:resourcefile], "w") do |f| to_print = resources.map do |resource| next unless resource.managed? if resource.name_var "#{resource.type}[#{resource[resource.name_var]}]" else "#{resource.ref.downcase}" end end.compact f.puts to_print.join("\n") end rescue => detail Puppet.err "Could not create resource file #{Puppet[:resourcefile]}: #{detail}" end
Verify that the given resource isn’t declared elsewhere.
# File lib/puppet/resource/catalog.rb, line 527 def fail_on_duplicate_type_and_title(resource) # Short-curcuit the common case, return unless existing_resource = @resource_table[title_key_for_ref(resource.ref)] # If we've gotten this far, it's a real conflict msg = "Duplicate declaration: #{resource.ref} is already declared" msg << " in file #{existing_resource.file} at line #{existing_resource.line}" if existing_resource.file and existing_resource.line msg << "; cannot redeclare" if resource.line or resource.file raise DuplicateResourceError.new(msg) 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, line 544 def to_catalog(convert) result = self.class.new(self.name) result.version = self.version result.environment = self.environment map = {} vertices.each do |resource| next if virtual_not_exported?(resource) next if block_given? and yield resource #This is hackity hack for 1094 #Aliases aren't working in the ral catalog because the current instance of the resource #has a reference to the catalog being converted. . . So, give it a reference to the new one #problem solved. . . if resource.class == Puppet::Resource resource = resource.dup resource.catalog = result elsif resource.is_a?(Puppet::Parser::Resource) resource = resource.to_resource resource.catalog = result end if resource.is_a?(Puppet::Resource) and convert.to_s == "to_resource" newres = resource else newres = resource.send(convert) end # We can't guarantee that resources don't munge their names # (like files do with trailing slashes), so we have to keep track # of what a resource got converted to. map[resource.ref] = newres result.add_resource newres end message = convert.to_s.gsub "_", " " edges.each do |edge| # Skip edges between virtual resources. next if virtual_not_exported?(edge.source) next if block_given? and yield edge.source next if virtual_not_exported?(edge.target) next if block_given? and yield edge.target unless source = map[edge.source.ref] raise Puppet::DevError, "Could not find resource #{edge.source.ref} when converting #{message} resources" end unless target = map[edge.target.ref] raise Puppet::DevError, "Could not find resource #{edge.target.ref} when converting #{message} resources" end result.add_edge(source, target, edge.label) end map.clear result.add_class(*self.classes) result.tag(*self.tags) result end
# File lib/puppet/resource/catalog.rb, line 609 def virtual_not_exported?(resource) resource.respond_to?(:virtual?) and resource.virtual? and (resource.respond_to?(:exported?) and not resource.exported?) end