class Puppet::Resource::Catalog::Puppetdb

Constants

Relationships
UnorderedMetaparams

Metaparams that may contain arrays, but whose semantics are fundamentally unordered

Public Class Methods

new() click to toggle source

Run initial checks

# File lib/puppet/indirector/catalog/puppetdb.rb, line 10
def initialize
  Puppet::Util::Puppetdb::GlobalCheck.run
end

Public Instance Methods

add_environment(hash, environment) click to toggle source

Include environment in hash, returning the complete hash.

@param hash [Hash] original data hash @param environment [String] environment @return [Hash] returns original hash augmented with environment @api private

# File lib/puppet/indirector/catalog/puppetdb.rb, line 74
def add_environment(hash, environment)
  hash['environment'] = environment

  hash
end
add_namevar_aliases(hash, catalog) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 125
def add_namevar_aliases(hash, catalog)
  resources = hash['resources']
  profile "Add namevar aliases (resource count: #{resources.count})" do
    resources.each do |resource|
      real_resource = catalog.resource(resource['type'], resource['title'])

      # Resources with composite namevars can't be referred to by
      # anything other than their title when declaring
      # relationships. Trying to snag the :alias for these resources
      # will only return _part_ of the name (a problem with Puppet
      # proper), so skipping the adding of aliases for these resources
      # is both an optimization and a safeguard.
      next if real_resource.key_attributes.count > 1

      aliases = [real_resource[:alias]].flatten.compact

      # Non-isomorphic resources aren't unique based on namevar, so we can't
      # use it as an alias
      type = real_resource.resource_type
      if !type.respond_to?(:isomorphic?) or type.isomorphic?
        # This makes me a little sad.  It turns out that the "to_hash" method
        #  of Puppet::Resource can have side effects.  In particular, if the
        #  resource type specifies a title_pattern, calling "to_hash" will trigger
        #  the title_pattern processing, which can have the side effect of
        #  populating the namevar (potentially with a munged value).  Thus,
        #  it is important that we search for namevar aliases in that hash
        #  rather than in the resource itself.
        real_resource_hash = real_resource.to_hash

        name = real_resource_hash[real_resource.send(:namevar)]
        unless name.nil? or real_resource.title == name or aliases.include?(name)
          aliases << name
        end
      end

      resource['parameters']['alias'] = aliases unless aliases.empty?
    end
  end

  hash
end
add_parameters_if_missing(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 114
def add_parameters_if_missing(hash)
  resources = hash['resources']
  profile "Add parameters if missing (resource count: #{resources.count})" do
    resources.each do |resource|
      resource['parameters'] ||= {}
    end
  end

  hash
end
add_transaction_uuid(hash, transaction_uuid) click to toggle source

Include transaction_uuid in hash, returning the complete hash.

@param hash [Hash] original data hash @param transaction_uuid [String] transaction_uuid @return [Hash] returns original hash augmented with transaction_uuid @api private

# File lib/puppet/indirector/catalog/puppetdb.rb, line 86
def add_transaction_uuid(hash, transaction_uuid)
  hash['transaction-uuid'] = transaction_uuid

  hash
end
edge_to_s(specifier_resource, referred_resource, param) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 347
def edge_to_s(specifier_resource, referred_resource, param)
  "#{specifier_resource} { #{param} => #{referred_resource} }"
end
extract_extra_request_data(request) click to toggle source

@api private

# File lib/puppet/indirector/catalog/puppetdb.rb, line 27
def extract_extra_request_data(request)
  {
    :transaction_uuid => request.options[:transaction_uuid],
    :environment => request.environment,
  }
end
filter_keys(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 330
def filter_keys(hash)
  profile "Filter extraneous keys from the catalog" do
    hash.delete_if do |k,v|
      ! ['name', 'version', 'edges', 'resources'].include?(k)
    end
  end
end
find(request) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 22
def find(request)
  nil
end
map_aliases_to_title(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 198
def map_aliases_to_title(hash)
  resources = hash['resources']
  aliases = {}

  profile "Map aliases to title (resource count: #{resources.count})" do
    resources.each do |resource|
      names = resource['parameters']['alias'] || []
      resource_hash = {'type' => resource['type'], 'title' => resource['title']}
      names.each do |name|
        alias_array = [resource['type'], name]
        aliases[alias_array] = resource_hash
      end
    end
  end

  aliases
end
munge_catalog(catalog, extra_request_data = {}) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 34
def munge_catalog(catalog, extra_request_data = {})
  profile "Munge catalog" do
    hash = profile "Convert catalog to PSON data hash" do
      catalog.to_pson_data_hash
    end

    data = hash['data']

    add_parameters_if_missing(data)
    add_namevar_aliases(data, catalog)
    stringify_titles(data)
    stringify_version(data)
    sort_unordered_metaparams(data)
    munge_edges(data)
    synthesize_edges(data, catalog)
    filter_keys(data)
    add_transaction_uuid(data, extra_request_data[:transaction_uuid])
    add_environment(data, extra_request_data[:environment])

    data
  end
end
munge_edges(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 184
def munge_edges(hash)
  edges = hash['edges']
  profile "Munge edges (edge count: #{edges.count})" do
    edges.each do |edge|
      %w[source target].each do |vertex|
        edge[vertex] = resource_ref_to_hash(edge[vertex]) if edge[vertex].is_a?(String)
      end
      edge['relationship'] ||= 'contains'
    end

    hash
  end
end
resource_hash_to_ref(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 343
def resource_hash_to_ref(hash)
  "#{hash['type']}[#{hash['title']}]"
end
resource_ref_to_hash(ref) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 338
def resource_ref_to_hash(ref)
  ref =~ /^([^\[\]]+)\[(.+)\]$/m
  {'type' => $1, 'title' => $2}
end
save(request) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 14
def save(request)
  profile "catalog#save" do
    catalog = munge_catalog(request.instance, extract_extra_request_data(request))

    submit_command(request.key, catalog, CommandReplaceCatalog, 4)
  end
end
sort_unordered_metaparams(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 167
def sort_unordered_metaparams(hash)
  resources = hash['resources']
  profile "Sort unordered metaparams (resource count: #{resources.count})" do
    resources.each do |resource|
      params = resource['parameters']
      UnorderedMetaparams.each do |metaparam|
        if params[metaparam].kind_of? Array then
          values = params[metaparam].sort
          params[metaparam] = values unless values.empty?
        end
      end
    end
  end

  hash
end
stringify_titles(hash) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 103
def stringify_titles(hash)
  resources = hash['resources']
  profile "Stringify titles (resource count: #{resources.count})" do
    resources.each do |resource|
      resource['title'] = resource['title'].to_s
    end
  end

  hash
end
stringify_version(hash) click to toggle source

Version is an integer (time since epoch in millis). The wire format specifies version should be a string

@param hash [Hash] original data hash @return [Hash] returns a modified original hash

# File lib/puppet/indirector/catalog/puppetdb.rb, line 97
def stringify_version(hash)
  hash['version'] = hash['version'].to_s

  hash
end
synthesize_edges(hash, catalog) click to toggle source
# File lib/puppet/indirector/catalog/puppetdb.rb, line 216
def synthesize_edges(hash, catalog)
  profile "Synthesize edges" do
    aliases = map_aliases_to_title(hash)

    resource_table = {}
    profile "Build up resource_table" do
      hash['resources'].each do |resource|
        resource_table[ [resource['type'], resource['title']] ] = resource
      end
    end

    profile "Primary synthesis" do
      hash['resources'].each do |resource|
        # Standard virtual resources don't appear in the catalog. However,
        # exported resources which haven't been also collected will appears as
        # exported and virtual (collected ones will only be exported). They will
        # eventually be removed from the catalog, so we can't add edges involving
        # them. Puppet::Resource#to_pson_data_hash omits 'virtual', so we have to
        # look it up in the catalog to find that information. This isn't done in
        # a separate step because we don't actually want to send the field (it
        # will always be false). See ticket #16472.
        #
        # The outer conditional is here because Class[main] can't properly be
        # looked up using catalog.resource and will return nil. See ticket
        # #16473. Yay.
        if real_resource = catalog.resource(resource['type'], resource['title'])
          next if real_resource.virtual?
        end

        Relationships.each do |param,relation|
          if value = resource['parameters'][param]
            [value].flatten.each do |other_ref|
              edge = {'relationship' => relation[:relationship]}

              resource_hash = {'type' => resource['type'], 'title' => resource['title']}
              other_hash = resource_ref_to_hash(other_ref)

              # Puppet doesn't always seem to check this correctly. If we don't
              # users will later get an invalid relationship error instead.
              #
              # Primarily we are trying to catch the non-capitalized resourceref
              # case problem here: http://projects.puppetlabs.com/issues/19474
              # Once that problem is solved and older versions of Puppet that have
              # the bug are no longer supported we can probably remove this code.
              unless other_ref =~ /^[A-Z][a-z0-9_]*(::[A-Z][a-z0-9_]*)*\[.*\]/
                rel = edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)
                raise Puppet::Error, "Invalid relationship: #{rel}, because " +
                  "#{other_ref} doesn't seem to be in the correct format. " +
                  "Resource references should be formatted as: " +
                  "Classname['title'] or Modulename::Classname['title'] (take " +
                  "careful note of the capitalization)."
              end

              # This is an unfortunate hack.  Puppet does some weird things w/rt
              # munging off trailing slashes from file resources, and users may
              # legally specify relationships using a different number of trailing
              # slashes than the resource was originally declared with.
              # We do know that for any file resource in the catalog, there should
              # be a canonical entry for it that contains no trailing slashes.  So,
              # here, in case someone has specified a relationship to a file resource
              # and has used one or more trailing slashes when specifying the
              # relationship, we will munge off the trailing slashes before
              # we look up the resource in the catalog to create the edge.
              if other_hash['type'] == 'File' and other_hash['title'] =~ /\/$/
                other_hash['title'] = other_hash['title'].sub(/\/+$/, '')
              end

              other_array = [other_hash['type'], other_hash['title']]

              # Try to find the resource by type/title or look it up as an alias
              # and try that
              other_resource = resource_table[other_array]
              if other_resource.nil? and alias_hash = aliases[other_array]
                other_resource = resource_table[ alias_hash.values_at('type', 'title') ]
              end

              raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} doesn't seem to be in the catalog" unless other_resource

              # As above, virtual exported resources will eventually be removed,
              # so if a real resource refers to one, it's wrong. Non-virtual
              # exported resources are exported resources that were also
              # collected in this catalog, so they're okay. Virtual non-exported
              # resources can't appear in the catalog in the first place, so it
              # suffices to check for virtual.
              if other_real_resource = catalog.resource(other_resource['type'], other_resource['title'])
                if other_real_resource.virtual?
                  raise Puppet::Error, "Invalid relationship: #{edge_to_s(resource_hash_to_ref(resource_hash), other_ref, param)}, because #{other_ref} is exported but not collected"
                end
              end

              # If the ref was an alias, it will have a different title, so use
              # that
              other_hash['title'] = other_resource['title']

              if relation[:direction] == :forward
                edge.merge!('source' => resource_hash, 'target' => other_hash)
              else
                edge.merge!('source' => other_hash, 'target' => resource_hash)
              end
              hash['edges'] << edge
            end
          end
        end
      end
    end

    profile "Make edges unique" do
      hash['edges'].uniq!
    end

    hash
  end
end