# A resource that we're managing. This handles making sure that only subclasses
# can set parameters.
class Puppet::Parser::Resource
require 'puppet/parser/resource/param'
require 'puppet/parser/resource/reference'
ResParam = Struct.new :name, :value, :source, :line, :file
include Puppet::Util
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
include Puppet::Util::Logging
attr_accessor :source, :line, :file, :scope, :rails_id
attr_accessor :virtual, :override, :params, :translated
attr_reader :exported
attr_writer :tags
# Proxy a few methods to our @ref object.
[:builtin?, :type, :title].each do |method|
define_method(method) do
@ref.send(method)
end
end
# Set up some boolean test methods
[:exported, :translated, :override].each do |method|
newmeth = (method.to_s + "?").intern
define_method(newmeth) do
self.send(method)
end
end
def [](param)
param = symbolize(param)
if param == :title
return self.title
end
if @params.has_key?(param)
@params[param].value
else
nil
end
end
# Add default values from our definition.
def adddefaults
defaults = scope.lookupdefaults(self.type)
defaults.each do |name, param|
unless @params.include?(param.name)
self.debug "Adding default for %s" % param.name
@params[param.name] = param
end
end
end
# Add any metaparams defined in our scope. This actually adds any metaparams
# from any parent scope, and there's currently no way to turn that off.
def addmetaparams
Puppet::Type.eachmetaparam do |name|
next if self[name]
if val = scope.lookupvar(name.to_s, false)
unless val == :undefined
set Param.new(:name => name, :value => val,
:source => scope.source)
end
end
end
end
# Add any overrides for this object.
def addoverrides
overrides = scope.lookupoverrides(self)
overrides.each do |over|
self.merge(over)
end
overrides.clear
end
def builtin=(bool)
@ref.builtin = bool
end
# Retrieve the associated definition and evaluate it.
def evaluate
if klass = @ref.definedtype
finish()
scope.deleteresource(self)
return klass.evaluate(:scope => scope,
:type => self.type,
:title => self.title,
:arguments => self.to_hash,
:scope => self.scope,
:virtual => self.virtual,
:exported => self.exported
)
elsif builtin?
devfail "Cannot evaluate a builtin type"
else
self.fail "Cannot find definition %s" % self.type
end
ensure
@evaluated = true
end
def exported=(value)
if value
@virtual = true
@exported = value
else
@exported = value
end
end
def evaluated?
if defined? @evaluated and @evaluated
true
else
false
end
end
# Do any finishing work on this object, called before evaluation or
# before storage/translation.
def finish
addoverrides()
adddefaults()
addmetaparams()
end
def initialize(options)
options = symbolize_options(options)
# Collect the options necessary to make the reference.
refopts = [:type, :title].inject({}) do |hash, param|
hash[param] = options[param]
options.delete(param)
hash
end
@params = {}
tmpparams = nil
if tmpparams = options[:params]
options.delete(:params)
end
# Now set the rest of the options.
set_options(options)
@ref = Reference.new(refopts)
requiredopts(:scope, :source)
@ref.scope = self.scope
if tmpparams
tmpparams.each do |param|
# We use the method here, because it does type-checking.
set(param)
end
end
end
# Merge an override resource in.
def merge(resource)
# Some of these might fail, but they'll fail in the way we want.
resource.params.each do |name, param|
set(param)
end
end
def modify_rails(db_resource)
args = rails_args
args.each do |param, value|
db_resource[param] = value unless db_resource[param] == value
end
# Handle file specially
if (self.file and
(!db_resource.file or db_resource.file != self.file))
db_resource.file = self.file
end
updated_params = @params.inject({}) do |hash, ary|
hash[ary[0].to_s] = ary[1]
hash
end
db_resource.ar_hash_merge(db_resource.get_params_hash(db_resource.param_values), updated_params,
:create => Proc.new { |name, parameter|
parameter.to_rails(db_resource)
}, :delete => Proc.new { |values|
values.each { |value| db_resource.param_values.delete(value) }
}, :modify => Proc.new { |db, mem|
mem.modify_rails_values(db)
})
updated_tags = tags.inject({}) { |hash, tag|
hash[tag] = tag
hash
}
db_resource.ar_hash_merge(db_resource.get_tag_hash(),
updated_tags,
:create => Proc.new { |name, tag|
db_resource.add_resource_tag(name)
}, :delete => Proc.new { |tag|
db_resource.resource_tags.delete(tag)
}, :modify => Proc.new { |db, mem|
# nothing here
})
end
# This *significantly* reduces the number of calls to Puppet.[].
def paramcheck?
unless defined? @@paramcheck
@@paramcheck = Puppet[:paramcheck]
end
@@paramcheck
end
# Verify that all passed parameters are valid. This throws an error if
# there's a problem, so we don't have to worry about the return value.
def paramcheck(param)
param = param.to_s
# Now make sure it's a valid argument to our class. These checks
# are organized in order of commonhood -- most types, it's a valid
# argument and paramcheck is enabled.
if @ref.typeclass.validattr?(param)
true
elsif %w{name title}.include?(param) # always allow these
true
elsif paramcheck?
self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" %
[param.inspect, @ref.type]
end
end
# A temporary occasion, until I get paths in the scopes figured out.
def path
to_s
end
# Return the short version of our name.
def ref
@ref.to_s
end
# You have to pass a Resource::Param to this.
def set(param, value = nil, source = nil)
if value and source
param = Puppet::Parser::Resource::Param.new(
:name => param, :value => value, :source => source
)
elsif ! param.is_a?(Puppet::Parser::Resource::Param)
raise ArgumentError, "Must pass a parameter or all necessary values"
end
# Because definitions are now parse-time, I can paramcheck immediately.
paramcheck(param.name)
if current = @params[param.name]
# This is where we'd ignore any equivalent values if we wanted to,
# but that would introduce a lot of really bad ordering issues.
if param.source.child_of?(current.source)
if param.add
# Merge with previous value.
param.value = [ current.value, param.value ].flatten
end
# Replace it, keeping all of its info.
@params[param.name] = param
else
if Puppet[:trace]
puts caller
end
msg = "Parameter '%s' is already set on %s" %
[param.name, self.to_s]
if current.source.to_s != ""
msg += " by %s" % current.source
end
if current.file or current.line
fields = []
fields << current.file if current.file
fields << current.line.to_s if current.line
msg += " at %s" % fields.join(":")
end
msg += "; cannot redefine"
error = Puppet::ParseError.new(msg)
error.file = param.file if param.file
error.line = param.line if param.line
raise error
end
else
if self.source == param.source or param.source.child_of?(self.source)
@params[param.name] = param
else
fail Puppet::ParseError, "Only subclasses can set parameters"
end
end
end
def tags
unless defined? @tags
@tags = scope.tags
@tags << self.type
end
@tags
end
def to_hash
@params.inject({}) do |hash, ary|
param = ary[1]
# Skip "undef" values.
if param.value != :undef
hash[param.name] = param.value
end
hash
end
end
# Turn our parser resource into a Rails resource.
def to_rails(host)
args = rails_args
db_resource = host.resources.build(args)
# Handle file specially
db_resource.file = self.file
@params.each { |name, param|
param.to_rails(db_resource)
}
tags.each { |tag| db_resource.add_resource_tag(tag) }
return db_resource
end
def to_s
self.ref
end
# Translate our object to a transportable object.
def to_trans
unless builtin?
devfail "Tried to translate a non-builtin resource"
end
return nil if virtual?
# Now convert to a transobject
obj = Puppet::TransObject.new(@ref.title, @ref.type)
to_hash.each do |p, v|
if v.is_a?(Reference)
v = v.to_ref
elsif v.is_a?(Array)
v = v.collect { |av|
if av.is_a?(Reference)
av = av.to_ref
end
av
}
end
# If the value is an array with only one value, then
# convert it to a single value. This is largely so that
# the database interaction doesn't have to worry about
# whether it returns an array or a string.
obj[p.to_s] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
obj.file = self.file
obj.line = self.line
obj.tags = self.tags
return obj
end
def virtual?
self.virtual
end
private
def rails_args
return [:type, :title, :line, :exported].inject({}) do |hash, param|
# 'type' isn't a valid column name, so we have to use another name.
to = (param == :type) ? :restype : param
if value = self.send(param)
hash[to] = value
end
hash
end
end
end
# $Id: resource.rb 2693 2007-07-14 17:46:37Z luke $
syntax highlighted by Code2HTML, v. 0.9.1