# 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.

### Public Class Methods

edge_from_pson(result, edge) click to toggle source

    # 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

from_pson(data) click to toggle source

    # 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

new(name = nil) { |self| ... } click to toggle source
Calls superclass method Puppet::SimpleGraph.new

    # 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

resource_from_pson(result, res) click to toggle source

    # 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

### Public Instance Methods

add_class(*classes) click to toggle source

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_resource(*resource) click to toggle source

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

alias(resource, key) click to toggle source

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(options = {}) { |transaction| ... } click to toggle source

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

applying?() click to toggle source

Are we in the middle of applying the catalog?

    # File lib/puppet/resource/catalog.rb, line 166
    def applying?
      @applying
    end

classes() click to toggle source

    # File lib/puppet/resource/catalog.rb, line 182
    def classes
      @classes.dup
    end

clear(remove_resources = true) click to toggle source
Calls superclass method Puppet::SimpleGraph#clear

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

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(&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, line 486
    def filter(&block)
      to_catalog :to_resource, &block
    end

finalize() click to toggle source

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

host_config?() click to toggle source

    # File lib/puppet/resource/catalog.rb, line 206
    def host_config?
      host_config
    end

make_default_resources() click to toggle source

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

relationship_graph() click to toggle source

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_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, 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

resource(type, title = nil) click to toggle source

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

resource_keys() click to toggle source

    # File lib/puppet/resource/catalog.rb, line 384
    def resource_keys
      @resource_table.keys
    end

resource_refs() click to toggle source

    # 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

resources() click to toggle source

    # File lib/puppet/resource/catalog.rb, line 388
    def resources
      @resource_table.values.uniq
    end

splice!(other) click to toggle source

    # 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

title_key_for_ref( ref ) click to toggle source

    # File lib/puppet/resource/catalog.rb, line 60
    def title_key_for_ref( ref )
      ref =~ /^([-\w:]+)\[(.*)\]$/m
      [$1, $2]
    end

to_pson(*args) click to toggle source

    # File lib/puppet/resource/catalog.rb, line 469
    def to_pson(*args)
      to_pson_data_hash.to_pson(*args)
    end

to_pson_data_hash() click to toggle source

    # 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

to_ral() click to toggle source

Convert our catalog into a RAL catalog.

    # File lib/puppet/resource/catalog.rb, line 474
    def to_ral
      to_catalog :to_ral
    end

to_resource() click to toggle source

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

write_class_file() click to toggle source

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

write_graph(name) click to toggle source

Produce the graph files if requested.

Calls superclass method Puppet::SimpleGraph#write_graph

    # 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

write_resource_file() click to toggle source

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

### Private Instance Methods

fail_on_duplicate_type_and_title(resource) click to toggle source

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

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, 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

virtual_not_exported?(resource) click to toggle source

    # 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