class 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