# class Puppet::Resource::Type

### Constants

RESOURCE_EXTERNAL_NAMES_TO_KINDS
RESOURCE_KINDS
RESOURCE_KINDS_TO_EXTERNAL_NAMES

  We have reached a point where we’ve established some naming conventions

      in our documentation that don't entirely match up with our internal names
      for things.  Ideally we'd change the internal representation to match the
      conventions expressed in our docs, but that would be a fairly far-reaching
      and risky change.  For the time being, we're settling for mapping the
      internal names to the external ones (and vice-versa) during serialization
      and deserialization.  These two hashes is here to help with that mapping.

### Attributes

arguments[R]
behaves_like[R]
code[RW]
doc[RW]
file[RW]
line[RW]
module_name[R]
namespace[R]
parent[RW]
resource_type_collection[RW]
ruby_code[RW]
type[R]

This should probably be renamed to ‘kind’ eventually, in accordance with the changes

    made for serialization and API usability (#14137).  At the moment that seems like
    it would touch a whole lot of places in the code, though.  --cprice 2012-04-23

### Public Class Methods

from_pson(data) click to toggle source

    # File lib/puppet/resource/type.rb, line 46
    def self.from_pson(data)
      name = data.delete('name') or raise ArgumentError, "Resource Type names must be specified"
      kind = data.delete('kind') || "definition"

      unless type = RESOURCE_EXTERNAL_NAMES_TO_KINDS[kind]
        raise ArgumentError, "Unsupported resource kind '#{kind}'"
      end

      data = data.inject({}) { |result, ary| result[ary[0].intern] = ary[1]; result }

      # This is a bit of a hack; when we serialize, we use the term "parameters" because that
      #  is the terminology that we use in our documentation.  However, internally to this
      #  class we use the term "arguments".  Ideally we'd change the implementation to be consistent
      #  with the documentation, but that would be challenging right now because it could potentially
      #  touch a lot of places in the code, not to mention that we already have another meaning for
      #  "parameters" internally.  So, for now, we will simply transform the internal "arguments"
      #  value to "parameters" when serializing, and the opposite when deserializing.
      #     --cprice 2012-04-23
      data[:arguments] = data.delete(:parameters)

      new(type, name, data)
    end

new(type, name, options = {}) click to toggle source

    # File lib/puppet/resource/type.rb, line 141
    def initialize(type, name, options = {})
      @type = type.to_s.downcase.to_sym
      raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_KINDS.include?(@type)

      name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName)

      set_name_and_namespace(name)

      [:code, :doc, :line, :file, :parent].each do |param|
        next unless value = options[param]
        send(param.to_s + "=", value)
      end

      set_arguments(options[:arguments])

      @module_name = options[:module_name]
    end

### Public Instance Methods

assign_parameter_values(parameters, resource) click to toggle source

    # File lib/puppet/resource/type.rb, line 249
    def assign_parameter_values(parameters, resource)
      return unless parameters
      scope = resource.scope || {}

      # It'd be nice to assign default parameter values here,
      # but we can't because they often rely on local variables
      # created during set_resource_parameters.
      parameters.each do |name, value|
        resource.set_parameter name, value
      end
    end

child_of?(klass) click to toggle source

Are we a child of the passed class? Do a recursive search up our parentage tree to figure it out.

    # File lib/puppet/resource/type.rb, line 117
    def child_of?(klass)
      return false unless parent

      return(klass == parent_type ? true : parent_type.child_of?(klass))
    end

ensure_in_catalog(scope, parameters=nil) click to toggle source

Make an instance of the resource type, and place it in the catalog if it isn’t in the catalog already. This is only possible for classes and nodes. No parameters are be supplied–if this is a parameterized class, then all parameters take on their default values.

    # File lib/puppet/resource/type.rb, line 208
    def ensure_in_catalog(scope, parameters=nil)
      type == :definition and raise ArgumentError, "Cannot create resources for defined resource types"
      resource_type = type == :hostclass ? :class : :node

      # Do nothing if the resource already exists; this makes sure we don't
      # get multiple copies of the class resource, which helps provide the
      # singleton nature of classes.
      # we should not do this for classes with parameters
      # if parameters are passed, we should still try to create the resource
      # even if it exists so that we can fail
      # this prevents us from being able to combine param classes with include
      if resource = scope.catalog.resource(resource_type, name) and !parameters
        return resource
      end
      resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self)
      assign_parameter_values(parameters, resource)
      instantiate_resource(scope, resource)
      scope.compiler.add_resource(scope, resource)
      resource
    end

evaluate_code(resource) click to toggle source

Now evaluate the code associated with this class or definition.

    # File lib/puppet/resource/type.rb, line 124
    def evaluate_code(resource)

      static_parent = evaluate_parent_type(resource)
      scope = static_parent || resource.scope

      scope = scope.newscope(:namespace => namespace, :source => self, :resource => resource) unless resource.title == :main
      scope.compiler.add_class(name) unless definition?

      set_resource_parameters(resource, scope)

      resource.add_edge_to_stage

      code.safeevaluate(scope) if code

      evaluate_ruby_code(resource, scope) if ruby_code
    end

instantiate_resource(scope, resource) click to toggle source

    # File lib/puppet/resource/type.rb, line 229
    def instantiate_resource(scope, resource)
      # Make sure our parent class has been evaluated, if we have one.
      if parent && !scope.catalog.resource(resource.type, parent)
        parent_type(scope).ensure_in_catalog(scope)
      end

      if ['Class', 'Node'].include? resource.type
        scope.catalog.tag(*resource.tags)
      end
    end

match(string) click to toggle source

This is only used for node names, and really only when the node name is a regexp.

    # File lib/puppet/resource/type.rb, line 161
    def match(string)
      return string.to_s.downcase == name unless name_is_regex?

      @name =~ string
    end

merge(other) click to toggle source

Add code from a new instance to our code.

    # File lib/puppet/resource/type.rb, line 168
    def merge(other)
      fail "#{name} is not a class; cannot add code to it" unless type == :hostclass
      fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass
      fail "Cannot have code outside of a class/node/define because 'freeze_main' is enabled" if name == "" and Puppet.settings[:freeze_main]

      if parent and other.parent and parent != other.parent
        fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{other.parent})"
      end

      # We know they're either equal or only one is set, so keep whichever parent is specified.
      self.parent ||= other.parent

      if other.doc
        self.doc ||= ""
        self.doc += other.doc
      end

      # This might just be an empty, stub class.
      return unless other.code

      unless self.code
        self.code = other.code
        return
      end

      array_class = Puppet::Parser::AST::ASTArray
      self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class)

      if other.code.is_a?(array_class)
        code.children += other.code.children
      else
        code.children << other.code
      end
    end

name() click to toggle source

    # File lib/puppet/resource/type.rb, line 240
    def name
      return @name unless @name.is_a?(Regexp)
      @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'')
    end

name_is_regex?() click to toggle source

    # File lib/puppet/resource/type.rb, line 245
    def name_is_regex?
      @name.is_a?(Regexp)
    end

parent_type(scope = nil) click to toggle source

MQR TODO:

The change(s) introduced by the fix for #4270 are mostly silly & should be removed, though we didn’t realize it at the time. If it can be established/ ensured that nodes never call #parent_type and that resource_types are always (as they should be) members of exactly one #resource_type_collection the following method could / should be replaced with:

def #parent_type

    @parent_type ||= parent && (
      resource_type_collection.find_or_load([name],parent,type.to_sym) ||
      fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{resource_type_collection.environment}"
    )

end

…and then the rest of the changes around passing in scope reverted.

    # File lib/puppet/resource/type.rb, line 278
    def parent_type(scope = nil)
      return nil unless parent

      unless @parent_type
        raise "Must pass scope to parent_type when called first time" unless scope
        unless @parent_type = scope.environment.known_resource_types.send("find_#{type}", [name], parent)
          fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}"
        end
      end

      @parent_type
    end

set_arguments(arguments) click to toggle source

    # File lib/puppet/resource/type.rb, line 336
    def set_arguments(arguments)
      @arguments = {}
      return if arguments.nil?

      arguments.each do |arg, default|
        arg = arg.to_s
        warn_if_metaparam(arg, default)
        @arguments[arg] = default
      end
    end

set_resource_parameters(resource, scope) click to toggle source

Set any arguments passed by the resource as variables in the scope.

    # File lib/puppet/resource/type.rb, line 292
    def set_resource_parameters(resource, scope)
      set = {}
      resource.to_hash.each do |param, value|
        param = param.to_sym
        fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param)

        exceptwrap { scope[param.to_s] = value }

        set[param] = true
      end

      if @type == :hostclass
        scope["title"] = resource.title.to_s.downcase unless set.include? :title
        scope["name"] =  resource.name.to_s.downcase  unless set.include? :name
      else
        scope["title"] = resource.title               unless set.include? :title
        scope["name"] =  resource.name                unless set.include? :name
      end
      scope["module_name"] = module_name if module_name and ! set.include? :module_name

      if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name)
        scope["caller_module_name"] = caller_name
      end
      scope.class_set(self.name,scope) if hostclass? or node?

      # Evaluate the default parameters, now that all other variables are set
      default_params = resource.set_default_parameters(scope)
      default_params.each { |param| scope[param] = resource[param] }

      # This has to come after the above parameters so that default values
      # can use their values
      resource.validate_complete
    end

to_pson(*args) click to toggle source

It seems wrong that we have a ‘to_pson’ method on this class, but not a ‘to_yaml’.

    As a result, if you use the REST API to retrieve one or more objects of this type,
    you will receive different data if you use 'Accept: yaml' vs 'Accept: pson'.  That
    seems really, really wrong.  The "Accept" header should never affect what data is
    being returned--only the format of the data.  If the data itself is going to differ,
    then there should be a different request URL.  Documenting the REST API becomes
    a much more complex problem when the "Accept" header can change the semantics
    of the response.  --cprice 2012-04-23

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

to_pson_data_hash() click to toggle source

This method doesn’t seem like it has anything to do with PSON in particular, and it shouldn’t.

    It's just transforming to a simple object that can be serialized and de-serialized via
    any transport format.  Should probably be renamed if we get a chance to clean up our
    serialization / deserialization, and there are probably many other similar methods in
    other classes.
    --cprice 2012-04-23

    # File lib/puppet/resource/type.rb, line 76
    def to_pson_data_hash
      data = [:doc, :line, :file, :parent].inject({}) do |hash, param|
        next hash unless (value = self.send(param)) and (value != "")
        hash[param.to_s] = value
        hash
      end

      # This is a bit of a hack; when we serialize, we use the term "parameters" because that
      #  is the terminology that we use in our documentation.  However, internally to this
      #  class we use the term "arguments".  Ideally we'd change the implementation to be consistent
      #  with the documentation, but that would be challenging right now because it could potentially
      #  touch a lot of places in the code, not to mention that we already have another meaning for
      #  "parameters" internally.  So, for now, we will simply transform the internal "arguments"
      #  value to "parameters" when serializing, and the opposite when deserializing.
      #     --cprice 2012-04-23
      data['parameters'] = arguments.dup unless arguments.empty?

      data['name'] = name

      unless RESOURCE_KINDS_TO_EXTERNAL_NAMES.has_key?(type)
        raise ArgumentError, "Unsupported resource kind '#{type}'"
      end
      data['kind'] = RESOURCE_KINDS_TO_EXTERNAL_NAMES[type]
      data
    end

valid_parameter?(param) click to toggle source

Check whether a given argument is valid.

    # File lib/puppet/resource/type.rb, line 327
    def valid_parameter?(param)
      param = param.to_s

      return true if param == "name"
      return true if Puppet::Type.metaparam?(param)
      return false unless defined?(@arguments)
      return(arguments.include?(param) ? true : false)
    end