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:]+)\[(.*)\]$/
  [$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