diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index 63d028c0c..428b9df50 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -1,416 +1,416 @@
# 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'
require 'puppet/util/tagging'
require 'puppet/file_collection/lookup'
require 'puppet/parser/yaml_trimmer'
include Puppet::FileCollection::Lookup
include Puppet::Util
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
include Puppet::Util::Logging
include Puppet::Util::Tagging
include Puppet::Parser::YamlTrimmer
attr_accessor :source, :scope, :rails_id
attr_accessor :virtual, :override, :translated, :catalog
attr_reader :exported, :evaluated, :params
# Determine whether the provided parameter name is a relationship parameter.
def self.relationship_parameter?(name)
unless defined?(@relationship_names)
@relationship_names = Puppet::Type.relationship_params.collect { |p| p.name }
end
@relationship_names.include?(name)
end
# 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, :virtual, :evaluated].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
def []=(param, value)
set_parameter(param, value)
end
def builtin=(bool)
@ref.builtin = bool
end
def eachparam
@params.each do |name, param|
yield param
end
end
# Retrieve the associated definition and evaluate it.
def evaluate
if klass = @ref.definedtype
finish()
return klass.evaluate_code(self)
elsif builtin?
devfail "Cannot evaluate a builtin type"
else
self.fail "Cannot find definition %s" % self.type
end
ensure
@evaluated = true
end
# Mark this resource as both exported and virtual,
# or remove the exported mark.
def exported=(value)
if value
@virtual = true
@exported = value
else
@exported = value
end
end
# Do any finishing work on this object, called before evaluation or
# before storage/translation.
def finish
return if finished?
@finished = true
add_defaults()
add_metaparams()
add_scope_tags()
validate()
end
# Has this resource already been finished?
def finished?
defined?(@finished) and @finished
end
def initialize(options)
# Set all of the options we can.
options.each do |option, value|
if respond_to?(option.to_s + "=")
send(option.to_s + "=", value)
options.delete(option)
end
end
unless self.scope
raise ArgumentError, "Resources require a scope"
end
@source ||= scope.source
options = symbolize_options(options)
# Set up our reference.
if type = options[:type] and title = options[:title]
options.delete(:type)
options.delete(:title)
else
raise ArgumentError, "Resources require a type and title"
end
@ref = Reference.new(:type => type, :title => title, :scope => self.scope)
@params = {}
# Define all of the parameters
if params = options[:params]
options.delete(:params)
params.each do |param|
# Don't set the same parameter twice
if @params[param.name]
self.fail Puppet::ParseError, "Duplicate parameter '%s' for on %s" %
[param.name, self.to_s]
end
set_parameter(param)
end
end
# Throw an exception if we've got any arguments left to set.
unless options.empty?
raise ArgumentError, "Resources do not accept %s" % options.keys.collect { |k| k.to_s }.join(", ")
end
tag(@ref.type)
tag(@ref.title) if valid_tag?(@ref.title.to_s)
end
# Is this resource modeling an isomorphic resource type?
def isomorphic?
if builtin?
return @ref.builtintype.isomorphic?
else
return true
end
end
# Merge an override resource in. This will throw exceptions if
# any overrides aren't allowed.
def merge(resource)
# Test the resource scope, to make sure the resource is even allowed
# to override.
unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source)
raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file)
end
# Some of these might fail, but they'll fail in the way we want.
resource.params.each do |name, param|
override_parameter(param)
end
end
# Unless we're running >= 0.25, we're in compat mode.
def metaparam_compatibility_mode?
! (catalog and version = catalog.client_version and version = version.split(".") and version[0] == "0" and version[1].to_i >= 25)
end
# Return the resource name, or the title if no name
# was specified.
def name
unless defined? @name
@name = self[:name] || self.title
end
@name
end
# This *significantly* reduces the number of calls to Puppet.[].
def paramcheck?
unless defined? @@paramcheck
@@paramcheck = Puppet[:paramcheck]
end
@@paramcheck
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
# Define a parameter in our resource.
# if we ever receive a parameter named 'tag', set
# the resource tags with its value.
def set_parameter(param, value = nil)
if value
param = Puppet::Parser::Resource::Param.new(
:name => param, :value => value, :source => self.source
)
elsif ! param.is_a?(Puppet::Parser::Resource::Param)
raise ArgumentError, "Must pass a parameter or all necessary values"
end
tag(*param.value) if param.name == :tag
# And store it in our parameter hash.
@params[param.name] = param
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
# Create a Puppet::Resource instance from this parser resource.
# We plan, at some point, on not needing to do this conversion, but
# it's sufficient for now.
def to_resource
result = Puppet::Resource.new(type, title)
to_hash.each do |p, v|
if v.is_a?(Puppet::Parser::Resource::Reference)
v = Puppet::Resource::Reference.new(v.type, v.title)
elsif v.is_a?(Array)
# flatten resource references arrays
if v.flatten.find { |av| av.is_a?(Puppet::Parser::Resource::Reference) }
v = v.flatten
end
v = v.collect do |av|
if av.is_a?(Puppet::Parser::Resource::Reference)
av = Puppet::Resource::Reference.new(av.type, av.title)
end
av
end
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.
result[p] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
result.file = self.file
result.line = self.line
result.exported = self.exported
result.virtual = self.virtual
result.tag(*self.tags)
return result
end
def to_s
self.ref
end
# Translate our object to a transportable object.
def to_trans
return nil if virtual?
return to_resource.to_trans
end
# Convert this resource to a RAL resource. We hackishly go via the
# transportable stuff.
def to_ral
to_resource.to_ral
end
private
# Add default values from our definition.
def add_defaults
scope.lookupdefaults(self.type).each do |name, param|
unless @params.include?(name)
self.debug "Adding default for %s" % name
@params[name] = param.dup
end
end
end
def add_backward_compatible_relationship_param(name)
# Skip metaparams for which we get no value.
return unless val = scope.lookupvar(name.to_s, false) and val != :undefined
# The default case: just set the value
set_parameter(name, val) and return unless @params[name]
# For relationship params, though, join the values (a la #446).
@params[name].value = [@params[name].value, val].flatten
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 add_metaparams
compat_mode = metaparam_compatibility_mode?
Puppet::Type.eachmetaparam do |name|
if self.class.relationship_parameter?(name)
add_backward_compatible_relationship_param(name) if compat_mode
next
end
next if @params[name]
# Skip metaparams for which we get no value.
next unless val = scope.lookupvar(name.to_s, false) and val != :undefined
set_parameter(name, val)
end
end
def add_scope_tags
if scope_resource = scope.resource
tag(*scope_resource.tags)
end
end
# Accept a parameter from an override.
def override_parameter(param)
# This can happen if the override is defining a new parameter, rather
# than replacing an existing one.
(set_parameter(param) and return) unless current = @params[param.name]
# The parameter is already set. Fail if they're not allowed to override it.
unless param.source.child_of?(current.source)
puts caller if Puppet[:trace]
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"
raise Puppet::ParseError.new(msg, param.line, param.file)
end
# If we've gotten this far, we're allowed to override.
# Merge with previous value, if the parameter was generated with the +> syntax.
# It's important that we use the new param instance here, not the old one,
# so that the source is registered correctly for later overrides.
param.value = [current.value, param.value].flatten if param.add
set_parameter(param)
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)
+ if @ref.typeclass.valid_parameter?(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, @ref.type]
end
end
# Make sure the resource's parameters are all valid for the type.
def validate
@params.each do |name, param|
# Make sure it's a valid parameter.
paramcheck(name)
end
end
end
diff --git a/lib/puppet/provider/ldap.rb b/lib/puppet/provider/ldap.rb
index be6683891..38668e5e5 100644
--- a/lib/puppet/provider/ldap.rb
+++ b/lib/puppet/provider/ldap.rb
@@ -1,135 +1,135 @@
require 'puppet/provider'
# The base class for LDAP providers.
class Puppet::Provider::Ldap < Puppet::Provider
require 'puppet/util/ldap/manager'
class << self
attr_reader :manager
end
# Look up all instances at our location. Yay.
def self.instances
return [] unless list = manager.search
list.collect { |entry| new(entry) }
end
# Specify the ldap manager for this provider, which is
# used to figure out how we actually interact with ldap.
def self.manages(*args)
@manager = Puppet::Util::Ldap::Manager.new
@manager.manages(*args)
# Set up our getter/setter methods.
mk_resource_methods
return @manager
end
# Query all of our resources from ldap.
def self.prefetch(resources)
resources.each do |name, resource|
if result = manager.find(name)
result[:ensure] = :present
resource.provider = new(result)
else
resource.provider = new(:ensure => :absent)
end
end
end
def manager
self.class.manager
end
def create
@property_hash[:ensure] = :present
self.class.resource_type.validproperties.each do |property|
if val = resource.should(property)
@property_hash[property] = val
end
end
end
def delete
@property_hash[:ensure] = :absent
end
def exists?
@property_hash[:ensure] != :absent
end
# Apply our changes to ldap, yo.
def flush
# Just call the manager's update() method.
@property_hash.delete(:groups)
@ldap_properties.delete(:groups)
manager.update(name, ldap_properties, properties)
@property_hash.clear
@ldap_properties.clear
end
def initialize(*args)
raise(Puppet::DevError, "No LDAP Configuration defined for %s" % self.class) unless self.class.manager
raise(Puppet::DevError, "Invalid LDAP Configuration defined for %s" % self.class) unless self.class.manager.valid?
super
@property_hash = @property_hash.inject({}) do |result, ary|
param, values = ary
# Skip any attributes we don't manage.
- next result unless self.class.resource_type.validattr?(param)
+ next result unless self.class.resource_type.valid_parameter?(param)
paramclass = self.class.resource_type.attrclass(param)
unless values.is_a?(Array)
result[param] = values
next result
end
# Only use the first value if the attribute class doesn't manage
# arrays of values.
if paramclass.superclass == Puppet::Parameter or paramclass.array_matching == :first
result[param] = values[0]
else
result[param] = values
end
result
end
# Make a duplicate, so that we have a copy for comparison
# at the end.
@ldap_properties = @property_hash.dup
end
# Return the current state of ldap.
def ldap_properties
@ldap_properties.dup
end
# Return (and look up if necessary) the desired state.
def properties
if @property_hash.empty?
@property_hash = query || {:ensure => :absent}
if @property_hash.empty?
@property_hash[:ensure] = :absent
end
end
@property_hash.dup
end
# Collect the current attributes from ldap. Returns
# the results, but also stores the attributes locally,
# so we have something to compare against when we update.
# LAK:NOTE This is normally not used, because we rely on prefetching.
def query
# Use the module function.
unless attributes = manager.find(name)
@ldap_properties = {}
return nil
end
@ldap_properties = attributes
return @ldap_properties.dup
end
end
diff --git a/lib/puppet/provider/nameservice.rb b/lib/puppet/provider/nameservice.rb
index cc517ee5f..57441ddf6 100644
--- a/lib/puppet/provider/nameservice.rb
+++ b/lib/puppet/provider/nameservice.rb
@@ -1,315 +1,315 @@
require 'puppet'
# This is the parent class of all NSS classes. They're very different in
# their backend, but they're pretty similar on the front-end. This class
# provides a way for them all to be as similar as possible.
class Puppet::Provider::NameService < Puppet::Provider
class << self
def autogen_default(param)
if defined? @autogen_defaults
return @autogen_defaults[symbolize(param)]
else
return nil
end
end
def autogen_defaults(hash)
@autogen_defaults ||= {}
hash.each do |param, value|
@autogen_defaults[symbolize(param)] = value
end
end
def initvars
@checks = {}
super
end
def instances
objects = []
listbyname do |name|
objects << new(:name => name, :ensure => :present)
end
objects
end
def option(name, option)
name = name.intern if name.is_a? String
if defined? @options and @options.include? name and @options[name].include? option
return @options[name][option]
else
return nil
end
end
def options(name, hash)
- unless resource_type.validattr?(name)
+ unless resource_type.valid_parameter?(name)
raise Puppet::DevError, "%s is not a valid attribute for %s" %
[name, resource_type.name]
end
@options ||= {}
@options[name] ||= {}
# Set options individually, so we can call the options method
# multiple times.
hash.each do |param, value|
@options[name][param] = value
end
end
# List everything out by name. Abstracted a bit so that it works
# for both users and groups.
def listbyname
names = []
Etc.send("set%sent" % section())
begin
while ent = Etc.send("get%sent" % section())
names << ent.name
if block_given?
yield ent.name
end
end
ensure
Etc.send("end%sent" % section())
end
return names
end
def resource_type=(resource_type)
super
@resource_type.validproperties.each do |prop|
next if prop == :ensure
unless public_method_defined?(prop)
define_method(prop) { get(prop) || :absent}
end
unless public_method_defined?(prop.to_s + "=")
define_method(prop.to_s + "=") { |*vals| set(prop, *vals) }
end
end
end
# This is annoying, but there really aren't that many options,
# and this *is* built into Ruby.
def section
unless defined? @resource_type
raise Puppet::DevError,
"Cannot determine Etc section without a resource type"
end
if @resource_type.name == :group
"gr"
else
"pw"
end
end
def validate(name, value)
name = name.intern if name.is_a? String
if @checks.include? name
block = @checks[name][:block]
unless block.call(value)
raise ArgumentError, "Invalid value %s: %s" %
[value, @checks[name][:error]]
end
end
end
def verify(name, error, &block)
name = name.intern if name.is_a? String
@checks[name] = {:error => error, :block => block}
end
private
def op(property)
@ops[property.name] || ("-" + property.name)
end
end
# Autogenerate a value. Mostly used for uid/gid, but also used heavily
# with DirectoryServices, because DirectoryServices is stupid.
def autogen(field)
field = symbolize(field)
id_generators = {:user => :uid, :group => :gid}
if id_generators[@resource.class.name] == field
return autogen_id(field)
else
if value = self.class.autogen_default(field)
return value
elsif respond_to?("autogen_%s" % [field])
return send("autogen_%s" % field)
else
return nil
end
end
end
# Autogenerate either a uid or a gid. This is hard-coded: we can only
# generate one field type per class.
def autogen_id(field)
highest = 0
group = method = nil
case @resource.class.name
when :user; group = :passwd; method = :uid
when :group; group = :group; method = :gid
else
raise Puppet::DevError, "Invalid resource name %s" % resource
end
# Make sure we don't use the same value multiple times
if defined? @@prevauto
@@prevauto += 1
else
Etc.send(group) { |obj|
if obj.gid > highest
unless obj.send(method) > 65000
highest = obj.send(method)
end
end
}
@@prevauto = highest + 1
end
return @@prevauto
end
def create
if exists?
info "already exists"
# The object already exists
return nil
end
begin
execute(self.addcmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not create %s %s: %s" %
[@resource.class.name, @resource.name, detail]
end
end
def delete
unless exists?
info "already absent"
# the object already doesn't exist
return nil
end
begin
execute(self.deletecmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not delete %s %s: %s" %
[@resource.class.name, @resource.name, detail]
end
end
def ensure
if exists?
:present
else
:absent
end
end
# Does our object exist?
def exists?
if getinfo(true)
return true
else
return false
end
end
# Retrieve a specific value by name.
def get(param)
if hash = getinfo(false)
return hash[param]
else
return nil
end
end
# Retrieve what we can about our object
def getinfo(refresh)
if @objectinfo.nil? or refresh == true
@etcmethod ||= ("get" + self.class.section().to_s + "nam").intern
begin
@objectinfo = Etc.send(@etcmethod, @resource[:name])
rescue ArgumentError => detail
@objectinfo = nil
end
end
# Now convert our Etc struct into a hash.
if @objectinfo
return info2hash(@objectinfo)
else
return nil
end
end
# The list of all groups the user is a member of. Different
# user mgmt systems will need to override this method.
def groups
groups = []
# Reset our group list
Etc.setgrent
user = @resource[:name]
# Now iterate across all of the groups, adding each one our
# user is a member of
while group = Etc.getgrent
members = group.mem
if members.include? user
groups << group.name
end
end
# We have to close the file, so each listing is a separate
# reading of the file.
Etc.endgrent
groups.join(",")
end
# Convert the Etc struct into a hash.
def info2hash(info)
hash = {}
self.class.resource_type.validproperties.each do |param|
method = posixmethod(param)
if info.respond_to? method
hash[param] = info.send(posixmethod(param))
end
end
return hash
end
def initialize(resource)
super
@objectinfo = nil
end
def set(param, value)
self.class.validate(param, value)
cmd = modifycmd(param, value)
unless cmd.is_a?(Array)
raise Puppet::DevError, "Nameservice command must be an array"
end
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
end
end
end
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index e47501791..010cd956e 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -1,285 +1,325 @@
require 'puppet'
require 'puppet/util/tagging'
-#require 'puppet/resource/reference'
require 'puppet/util/pson'
# The simplest resource class. Eventually it will function as the
# base class for all resource-like behaviour.
class Puppet::Resource
+ require 'puppet/resource/reference'
include Puppet::Util::Tagging
+
+ require 'puppet/resource/type_collection_helper'
+ include Puppet::Resource::TypeCollectionHelper
+
extend Puppet::Util::Pson
include Enumerable
- attr_accessor :file, :line, :catalog, :exported, :virtual
- attr_writer :type, :title
+ attr_accessor :file, :line, :catalog, :exported, :virtual, :namespace, :validate_parameters
+ attr_writer :type, :title, :environment
require 'puppet/indirector'
extend Puppet::Indirector
indirects :resource, :terminus_class => :ral
ATTRIBUTES = [:file, :line, :exported]
def self.from_pson(pson)
raise ArgumentError, "No resource type provided in pson data" unless type = pson['type']
raise ArgumentError, "No resource title provided in pson data" unless title = pson['title']
resource = new(type, title)
if params = pson['parameters']
params.each { |param, value| resource[param] = value }
end
if tags = pson['tags']
tags.each { |tag| resource.tag(tag) }
end
ATTRIBUTES.each do |a|
if value = pson[a.to_s]
resource.send(a.to_s + "=", value)
end
end
resource.exported ||= false
resource
end
def to_pson_data_hash
data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param|
next hash unless value = self.send(param)
hash[param.to_s] = value
hash
end
data["exported"] ||= false
params = self.to_hash.inject({}) do |hash, ary|
param, value = ary
# Don't duplicate the title as the namevar
next hash if param == namevar and value == title
hash[param] = value
hash
end
data["parameters"] = params unless params.empty?
data
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Proxy these methods to the parameters hash. It's likely they'll
# be overridden at some point, but this works for now.
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
@parameters.send(method, *args)
end
end
# Set a given parameter. Converts all passed names
# to lower-case symbols.
def []=(param, value)
+ validate_parameter(param) if validate_parameters
@parameters[parameter_name(param)] = value
end
# Return a given parameter's value. Converts all passed names
# to lower-case symbols.
def [](param)
@parameters[parameter_name(param)]
end
# Compatibility method.
def builtin?
builtin_type?
end
# Is this a builtin resource type?
def builtin_type?
@reference.builtin_type?
end
# Iterate over each param/value pair, as required for Enumerable.
def each
@parameters.each { |p,v| yield p, v }
end
def include?(parameter)
super || @parameters.keys.include?( parameter_name(parameter) )
end
%w{exported virtual}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
# Create our resource.
def initialize(type, title, attributes = {})
+ # Doing this, instead of including it in the class,
+ # is the only way I could get the load order to work
+ # here.
+ extend Puppet::Node::Environment::Helper
+
@parameters = {}
+ @namespace = ""
(attributes[:parameters] || {}).each do |param, value|
self[param] = value
end
attributes.each do |attr, value|
next if attr == :parameters
send(attr.to_s + "=", value)
end
@reference = Puppet::Resource::Reference.new(type, title)
tag(@reference.type)
tag(@reference.title) if valid_tag?(@reference.title)
end
# Provide a reference to our resource in the canonical form.
def ref
@reference.to_s
end
+ def resource_type
+ case type.to_s.downcase
+ when "class"; find_hostclass
+ when "node"; find_node
+ else
+ find_builtin_resource_type || find_defined_resource_type
+ end
+ end
+
# Get our title information from the reference, since it will canonize it for us.
def title
@reference.title
end
# Get our type information from the reference, since it will canonize it for us.
def type
@reference.type
end
# Produce a simple hash of our parameters.
def to_hash
result = @parameters.dup
unless result.include?(namevar)
result[namevar] = title
end
result
end
def to_s
return ref
end
# Convert our resource to Puppet code.
def to_manifest
"%s { '%s':\n%s\n}" % [self.type.to_s.downcase, self.title,
@parameters.collect { |p, v|
if v.is_a? Array
" #{p} => [\'#{v.join("','")}\']"
else
" #{p} => \'#{v}\'"
end
}.join(",\n")
]
end
def to_ref
ref
end
# Convert our resource to a RAL resource instance. Creates component
# instances for resource types that don't exist.
def to_ral
if typeklass = Puppet::Type.type(self.type)
return typeklass.new(self)
else
return Puppet::Type::Component.new(self)
end
end
# Translate our object to a backward-compatible transportable object.
def to_trans
if @reference.builtin_type?
result = to_transobject
else
result = to_transbucket
end
result.file = self.file
result.line = self.line
return result
end
# Create an old-style TransObject instance, for builtin resource types.
def to_transobject
# Now convert to a transobject
result = Puppet::TransObject.new(@reference.title, @reference.type)
to_hash.each do |p, v|
if v.is_a?(Puppet::Resource::Reference)
v = v.to_trans_ref
elsif v.is_a?(Array)
v = v.collect { |av|
if av.is_a?(Puppet::Resource::Reference)
av = av.to_trans_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.
result[p.to_s] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
result.tags = self.tags
return result
end
def name
# this is potential namespace conflict
# between the notion of an "indirector name"
# and a "resource name"
[ type, title ].join('/')
end
def to_resource
self
end
+ def valid_parameter?(name)
+ resource_type.valid_parameter?(name)
+ end
+
+ def validate_parameter(name)
+ raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name)
+ end
+
private
+ def find_node
+ known_resource_types.node(title)
+ end
+
+ def find_hostclass
+ name = title == :main ? "" : title
+ known_resource_types.find_hostclass(namespace, name)
+ end
+
+ def find_builtin_resource_type
+ Puppet::Type.type(type.to_s.downcase.to_sym)
+ end
+
+ def find_defined_resource_type
+ known_resource_types.find_definition(namespace, type.to_s.downcase)
+ end
+
# Produce a canonical method name.
def parameter_name(param)
param = param.to_s.downcase.to_sym
if param == :name and n = namevar()
param = namevar
end
param
end
# The namevar for our resource type. If the type doesn't exist,
# always use :name.
def namevar
if t = resource_type
t.namevar
else
:name
end
end
- # Retrieve the resource type.
- def resource_type
- Puppet::Type.type(type)
- end
-
# Create an old-style TransBucket instance, for non-builtin resource types.
def to_transbucket
bucket = Puppet::TransBucket.new([])
bucket.type = self.type
bucket.name = self.title
# TransBuckets don't support parameters, which is why they're being deprecated.
return bucket
end
end
diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb
index 9baf1983e..d47658284 100644
--- a/lib/puppet/resource/type.rb
+++ b/lib/puppet/resource/type.rb
@@ -1,236 +1,236 @@
require 'puppet/parser/parser'
require 'puppet/util/warnings'
require 'puppet/util/errors'
require 'puppet/util/inline_docs'
require 'puppet/parser/ast/leaf'
class Puppet::Resource::Type
include Puppet::Util::InlineDocs
include Puppet::Util::Warnings
include Puppet::Util::Errors
RESOURCE_SUPERTYPES = [:hostclass, :node, :definition]
attr_accessor :file, :line, :doc, :code, :parent, :code_collection
attr_reader :type, :namespace, :arguments, :behaves_like
# Are we a child of the passed class? Do a recursive search up our
# parentage tree to figure it out.
def child_of?(klass)
return false unless parent
return true if klass == parent_type
return parent_type.child_of?(klass)
end
# Now evaluate the code associated with this class or definition.
def evaluate_code(resource)
# Create a new scope.
scope = subscope(resource.scope, resource)
set_resource_parameters(resource, scope)
return nil unless c = self.code
return c.safeevaluate(scope)
end
def initialize(type, name, options = {})
@type = type.to_s.downcase.to_sym
raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_SUPERTYPES.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])
end
# This is only used for node names, and really only when the node name
# is a regexp.
def match(string)
return string.to_s.downcase == name unless name_is_regex?
return @name =~ string
end
# Add code from a new instance to our code.
def merge(other)
fail ArgumentError, "#{name} is not a class; cannot add code to it" unless type == :hostclass
fail ArgumentError, "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass
if parent and other.parent and parent != other.parent
fail ArgumentError, "Cannot merge classes with different parent classes"
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
unless self.code.is_a?(array_class)
self.code = array_class.new(:children => [self.code])
end
if other.code.is_a?(array_class)
code.children += other.code.children
else
code.children << other.code
end
end
# Make an instance of our resource type. This is only possible
# for those classes and nodes that don't have any arguments, and is
# only useful for things like the 'include' function.
def mk_plain_resource(scope)
type == :definition and raise ArgumentError, "Cannot create resources for defined resource types"
resource_type = type == :hostclass ? :class : :node
# Make sure our parent class has been evaluated, if we have one.
if parent and ! scope.catalog.resource(resource_type, parent)
parent_type.mk_plain_resource(scope)
end
# 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.
if resource = scope.catalog.resource(resource_type, name)
return resource
end
resource = Puppet::Parser::Resource.new(:type => resource_type, :title => name, :scope => scope, :source => self)
scope.compiler.add_resource(scope, resource)
scope.catalog.tag(*resource.tags)
resource
end
def name
return @name unless @name.is_a?(Regexp)
return @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'')
end
def name_is_regex?
@name.is_a?(Regexp)
end
def parent_type
return nil unless parent
unless @parent_type ||= code_collection.send(type, parent)
fail Puppet::ParseError, "Could not find parent resource type '#{parent}'"
end
@parent_type
end
# Set any arguments passed by the resource as variables in the scope.
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 validattr?(param)
+ fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param)
exceptwrap { scope.setvar(param.to_s, value) }
set[param] = true
end
# Verify that all required arguments are either present or
# have been provided with defaults.
arguments.each do |param, default|
param = param.to_sym
next if set.include?(param)
# Even if 'default' is a false value, it's an AST value, so this works fine
fail Puppet::ParseError, "Must pass #{param} to #{resource.ref}" unless default
scope.setvar(param.to_s, default.safeevaluate(scope))
end
scope.setvar("title", resource.title) unless set.include? :title
scope.setvar("name", resource.name) unless set.include? :name
scope.class_set(self.name,scope)
end
# Create a new subscope in which to evaluate our code.
def subscope(scope, resource)
scope.newscope :resource => resource, :namespace => self.namespace, :source => self
end
# Check whether a given argument is valid.
- def validattr?(param)
+ 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 true if arguments.include?(param)
return false
end
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
private
def convert_from_ast(name)
value = name.value
if value.is_a?(Puppet::Parser::AST::Regex)
name = value.value
else
name = value
end
end
# Split an fq name into a namespace and name
def namesplit(fullname)
ary = fullname.split("::")
n = ary.pop || ""
ns = ary.join("::")
return ns, n
end
def set_name_and_namespace(name)
if name.is_a?(Regexp)
@name = name
@namespace = ""
else
@name = name.to_s.downcase
@namespace, ignored_shortname = namesplit(@name)
end
end
def warn_if_metaparam(param, default)
return unless Puppet::Type.metaparamclass(param)
if default
warnonce "#{param} is a metaparam; this value will inherit to all contained resources"
else
raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition"
end
end
end
diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb
index 68977dca0..1970d9f1e 100644
--- a/lib/puppet/transportable.rb
+++ b/lib/puppet/transportable.rb
@@ -1,259 +1,259 @@
require 'puppet'
require 'puppet/resource/reference'
require 'yaml'
module Puppet
# The transportable objects themselves. Basically just a hash with some
# metadata and a few extra methods. I used to have the object actually
# be a subclass of Hash, but I could never correctly dump them using
# YAML.
class TransObject
include Enumerable
attr_accessor :type, :name, :file, :line, :catalog
attr_writer :tags
%w{has_key? include? length delete empty? << [] []=}.each { |method|
define_method(method) do |*args|
@params.send(method, *args)
end
}
def each
@params.each { |p,v| yield p, v }
end
def initialize(name,type)
@type = type.to_s.downcase
@name = name
@params = {}
@tags = []
end
def longname
return [@type,@name].join('--')
end
def ref
unless defined? @ref
@ref = Puppet::Resource::Reference.new(@type, @name)
end
@ref.to_s
end
def tags
return @tags
end
# Convert a defined type into a component.
def to_component
trans = TransObject.new(ref, :component)
@params.each { |param,value|
- next unless Puppet::Type::Component.validattr?(param)
+ next unless Puppet::Type::Component.valid_parameter?(param)
Puppet.debug "Defining %s on %s" % [param, ref]
trans[param] = value
}
trans.catalog = self.catalog
Puppet::Type::Component.create(trans)
end
def to_hash
@params.dup
end
def to_s
return "%s(%s) => %s" % [@type,@name,super]
end
def to_manifest
"%s { '%s':\n%s\n}" %
[self.type.to_s, self.name,
@params.collect { |p, v|
if v.is_a? Array
" #{p} => [\'#{v.join("','")}\']"
else
" #{p} => \'#{v}\'"
end
}.join(",\n")
]
end
# Create a normalized resource from our TransObject.
def to_resource
result = Puppet::Resource.new(type, name, :parameters => @params.dup)
result.tag(*tags)
result
end
def to_yaml_properties
instance_variables.reject { |v| %w{@ref}.include?(v) }
end
def to_ref
ref
end
def to_ral
to_resource.to_ral
end
end
# Just a linear container for objects. Behaves mostly like an array, except
# that YAML will correctly dump them even with their instance variables.
class TransBucket
include Enumerable
attr_accessor :name, :type, :file, :line, :classes, :keyword, :top, :catalog
%w{delete shift include? length empty? << []}.each { |method|
define_method(method) do |*args|
#Puppet.warning "Calling %s with %s" % [method, args.inspect]
@children.send(method, *args)
#Puppet.warning @params.inspect
end
}
# Recursively yield everything.
def delve(&block)
@children.each do |obj|
block.call(obj)
if obj.is_a? self.class
obj.delve(&block)
else
obj
end
end
end
def each
@children.each { |c| yield c }
end
# Turn our heirarchy into a flat list
def flatten
@children.collect do |obj|
if obj.is_a? Puppet::TransBucket
obj.flatten
else
obj
end
end.flatten
end
def initialize(children = [])
@children = children
end
def push(*args)
args.each { |arg|
case arg
when Puppet::TransBucket, Puppet::TransObject
# nada
else
raise Puppet::DevError,
"TransBuckets cannot handle objects of type %s" %
arg.class
end
}
@children += args
end
# Convert to a parseable manifest
def to_manifest
unless self.top
unless defined? @keyword and @keyword
raise Puppet::DevError, "No keyword; cannot convert to manifest"
end
end
str = "#{@keyword} #{@name} {\n%s\n}"
str % @children.collect { |child|
child.to_manifest
}.collect { |str|
if self.top
str
else
str.gsub(/^/, " ") # indent everything once
end
}.join("\n\n") # and throw in a blank line
end
def to_yaml_properties
instance_variables
end
# Create a resource graph from our structure.
def to_catalog(clear_on_failure = true)
catalog = Puppet::Resource::Catalog.new(Facter.value("hostname"))
# This should really use the 'delve' method, but this
# whole class is going away relatively soon, hopefully,
# so it's not worth it.
delver = proc do |obj|
obj.catalog = catalog
unless container = catalog.resource(obj.to_ref)
container = obj.to_ral
catalog.add_resource container
end
obj.each do |child|
child.catalog = catalog
unless resource = catalog.resource(child.to_ref)
resource = child.to_ral
catalog.add_resource resource
end
catalog.add_edge(container, resource)
if child.is_a?(self.class)
delver.call(child)
end
end
end
begin
delver.call(self)
catalog.finalize
rescue => detail
# This is important until we lose the global resource references.
catalog.clear if (clear_on_failure)
raise
end
return catalog
end
def to_ref
unless defined? @ref
if self.type and self.name
@ref = Puppet::Resource::Reference.new(self.type, self.name)
elsif self.type and ! self.name # This is old-school node types
@ref = Puppet::Resource::Reference.new("node", self.type)
elsif ! self.type and self.name
@ref = Puppet::Resource::Reference.new("component", self.name)
else
@ref = nil
end
end
@ref.to_s if @ref
end
def to_ral
to_resource.to_ral
end
# Create a normalized resource from our TransObject.
def to_resource
params = defined?(@parameters) ? @parameters.dup : {}
Puppet::Resource.new(type, name, params)
end
def param(param,value)
unless defined? @parameters
@parameters = {}
end
@parameters[param] = value
end
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 2fb4abca8..31728c374 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,2046 +1,2051 @@
require 'puppet'
require 'puppet/util/log'
require 'puppet/util/metric'
require 'puppet/property'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/resource/reference'
require 'puppet/util/cacher'
require 'puppet/file_collection/lookup'
require 'puppet/util/tagging'
# see the bottom of the file for the rest of the inclusions
module Puppet
class Type
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::Cacher
include Puppet::FileCollection::Lookup
include Puppet::Util::Tagging
###############################
# Code related to resource type attributes.
class << self
include Puppet::Util::ClassGen
include Puppet::Util::Warnings
attr_reader :properties
end
def self.states
warnonce "The states method is deprecated; use properties"
properties()
end
# All parameters, in the appropriate order. The namevar comes first, then
# the provider, then the properties, and finally the params and metaparams
# in the order they were specified in the files.
def self.allattrs
# Cache this, since it gets called multiple times
namevar = self.namevar
order = [namevar]
if self.parameters.include?(:provider)
order << :provider
end
order << [self.properties.collect { |property| property.name },
self.parameters - [:provider],
self.metaparams].flatten.reject { |param|
# we don't want our namevar in there multiple times
param == namevar
}
order.flatten!
return order
end
# Retrieve an attribute alias, if there is one.
def self.attr_alias(param)
@attr_aliases[symbolize(param)]
end
# Create an alias to an existing attribute. This will cause the aliased
# attribute to be valid when setting and retrieving values on the instance.
def self.set_attr_alias(hash)
hash.each do |new, old|
@attr_aliases[symbolize(new)] = symbolize(old)
end
end
# Find the class associated with any given attribute.
def self.attrclass(name)
@attrclasses ||= {}
# We cache the value, since this method gets called such a huge number
# of times (as in, hundreds of thousands in a given run).
unless @attrclasses.include?(name)
@attrclasses[name] = case self.attrtype(name)
when :property; @validproperties[name]
when :meta; @@metaparamhash[name]
when :param; @paramhash[name]
end
end
@attrclasses[name]
end
# What type of parameter are we dealing with? Cache the results, because
# this method gets called so many times.
def self.attrtype(attr)
@attrtypes ||= {}
unless @attrtypes.include?(attr)
@attrtypes[attr] = case
when @validproperties.include?(attr); :property
when @paramhash.include?(attr); :param
when @@metaparamhash.include?(attr); :meta
end
end
@attrtypes[attr]
end
# Copy an existing class parameter. This allows other types to avoid
# duplicating a parameter definition, and is mostly used by subclasses
# of the File class.
def self.copyparam(klass, name)
param = klass.attrclass(name)
unless param
raise Puppet::DevError, "Class %s has no param %s" % [klass, name]
end
@parameters << param
@parameters.each { |p| @paramhash[name] = p }
if param.isnamevar?
@namevar = param.name
end
end
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Create the 'ensure' class. This is a separate method so other types
# can easily call it and create their own 'ensure' values.
def self.ensurable(&block)
if block_given?
self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block)
else
self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do
self.defaultvalues
end
end
end
# Should we add the 'ensure' property to this class?
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
ens = [:exists?, :create, :destroy].inject { |set, method|
set &&= self.public_method_defined?(method)
}
return ens
end
# Deal with any options passed into parameters.
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(symbolize(param))
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
def self.metaparams
@@metaparams.collect { |param| param.name }
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Create a new metaparam. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
if options[:required_features]
param.required_features = options[:required_features]
end
handle_param_options(name, options)
param.metaparam = true
return param
end
# Find the namevar
def self.namevar_parameter
@namevar_parameter ||= (
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
if params.length > 1
raise Puppet::DevError, "Found multiple namevars for %s" % self.name
elsif params.length == 1
params.first
else
raise Puppet::DevError, "No namevar for %s" % self.name
end
)
end
def self.namevar
@namevar ||= namevar_parameter.name
end
def self.canonicalize_ref(s)
namevar_parameter.canonicalize(s)
end
# Create a new parameter. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newparam(name, options = {}, &block)
options[:attributes] ||= {}
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
if options[:required_features]
param.required_features = options[:required_features]
end
param.isnamevar if options[:namevar]
if param.isnamevar?
@namevar = param.name
end
return param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(%s)" %
name
newproperty(name, options, &block)
end
# Create a new property. The first parameter must be the name of the property;
# this is how users will refer to the property when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the property. Defaults to Puppet::Property.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newproperty(name, options = {}, &block)
name = symbolize(name)
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError,
"Options must be a hash, not %s" % options.inspect
end
if @validproperties.include?(name)
raise Puppet::DevError, "Class %s already has a property named %s" %
[self.name, name]
end
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
if block
class_eval(&block)
end
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
return prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# Return the parameter names
def self.parameters
return [] unless defined? @parameters
@parameters.collect { |klass| klass.name }
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# Return the property class associated with a name
def self.propertybyname(name)
@validproperties[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
if self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name)
@validattrs[name] = true
else
@validattrs[name] = false
end
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
if @validproperties.include?(name)
return @validproperties[name]
else
return false
end
end
# Return the list of validproperties
def self.validproperties
return {} unless defined? @parameters
return @validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
unless defined? @parameters
raise Puppet::DevError, "Class %s has not defined parameters" % self
end
if @paramhash.include?(name) or @@metaparamhash.include?(name)
return true
else
return false
end
end
+ # This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
+ def self.valid_parameter?(name)
+ validattr?(name)
+ end
+
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
if synonym = self.class.attr_alias(name)
return synonym
else
return name
end
end
# Are we deleting this resource?
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Create a new property if it is valid but doesn't exist
# Returns: true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
return false
end
# abstract accessing parameters and properties, and normalize
# access to always be symbols, not strings
# This returns a value, not an object. It returns the 'is'
# value, but you can also specifically return 'is' and 'should'
# values using 'object.is(:property)' or 'object.should(:property)'.
def [](name)
name = attr_alias(name)
unless self.class.validattr?(name)
fail("Invalid parameter %s(%s)" % [name, name.inspect])
end
if name == :name
name = self.class.namevar
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Abstract setting parameters and properties, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on properties, and otherwise just sets the appropriate parameter.
def []=(name,value)
name = attr_alias(name)
unless self.class.validattr?(name)
fail("Invalid parameter %s" % [name])
end
if name == :name
name = self.class.namevar
end
if value.nil?
raise Puppet::Error.new("Got nil value for %s" % name)
end
if obj = @parameters[name]
obj.value = value
return nil
else
self.newattr(name, :value => value)
end
nil
end
# remove a property from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
attr = symbolize(attr)
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
# iterate across the existing properties
def eachproperty
# properties() is a private method
properties().each { |property|
yield property
}
end
# Create a transaction event. Called by Transaction or by
# a property.
def event(options = {})
Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options))
end
# Let the catalog determine whether a given cached value is
# still valid or has expired.
def expirer
catalog
end
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
if prop = @parameters[name] and prop.is_a?(Puppet::Property)
return prop.should
else
return nil
end
end
# Create the actual attribute instance. Requires either the attribute
# name or class as the first argument, then an optional hash of
# attributes to set during initialization.
def newattr(name, options = {})
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type %s does not support parameter %s" % [self.class.name, name]
end
if @parameters.include?(name)
raise Puppet::Error, "Parameter '%s' is already defined in %s" %
[name, self.ref]
end
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
end
# Add resource information at creation time, so it's available
# during validation.
options[:resource] = self
begin
# make sure the parameter doesn't have any errors
return @parameters[name] = klass.new(options)
rescue => detail
error = Puppet::Error.new("Parameter %s failed: %s" %
[name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
end
# return the value of a parameter
def parameter(name)
@parameters[name.to_sym]
end
def parameters
@parameters.dup
end
# Is the named property defined?
def propertydefined?(name)
unless name.is_a? Symbol
name = name.intern
end
return @parameters.include?(name)
end
# Return an actual property instance by name; to return the value, use 'resource[param]'
# LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point.
def property(name)
if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)
return obj
else
return nil
end
end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
if value = parameter.default and ! value.nil?
parameter.value = value
else
@parameters.delete(parameter.name)
end
end
# Convert our object to a hash. This just includes properties.
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
def type
self.class.name
end
# Return a specific value for an attribute.
def value(name)
name = attr_alias(name)
if obj = @parameters[name] and obj.respond_to?(:value)
return obj.value
else
return nil
end
end
def version
return 0 unless catalog
catalog.version
end
# Return all of the property objects, in the order specified in the
# class.
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
# Defaults to true.
def self.isomorphic?
if defined? @isomorphic
return @isomorphic
else
return true
end
end
def isomorphic?
self.class.isomorphic?
end
# is the instance a managed instance? A 'yes' here means that
# the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package
# in a list
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if defined? @managed and @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
if defined? @depthfirst
return @depthfirst
else
return false
end
end
def depthfirst?
self.class.depthfirst?
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# Flush the provider, if it supports it. This is called by the
# transaction.
def flush
if self.provider and self.provider.respond_to?(:flush)
self.provider.flush
end
end
# if all contained objects are in sync, then we're in sync
# FIXME I don't think this is used on the type instances any more,
# it's really only used for testing
def insync?(is)
insync = true
if property = @parameters[:ensure]
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '%s'" %
[property.name]
end
ensureis = is[property]
if property.insync?(ensureis) and property.should == :absent
return true
end
end
properties.each { |property|
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '%s'" %
[property.name]
end
propis = is[property]
unless property.insync?(propis)
property.debug("Not in sync: %s vs %s" %
[propis.inspect, property.should.inspect])
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("%s sync status is %s" % [self,insync])
return insync
end
# retrieve the current value of all contained properties
def retrieve
if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
fail "Provider #{provider.class.name} is not functional on this host"
end
result = Puppet::Resource.new(type, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure))
result[:ensure] = ensure_state = ensure_prop.retrieve
else
ensure_state = nil
end
properties.each do |property|
next if property.name == :ensure
if ensure_state == :absent
result[property] = :absent
else
result[property] = property.retrieve
end
end
result
end
# Get a hash of the current properties. Returns a hash with
# the actual property instance as the key and the current value
# as the, um, value.
def currentpropvalues
# It's important to use the 'properties' method here, as it follows the order
# in which they're defined in the class. It also guarantees that 'ensure'
# is the first property, which is important for skipping 'retrieve' on
# all the properties if the resource is absent.
ensure_state = false
return properties().inject({}) do | prophash, property|
if property.name == :ensure
ensure_state = property.retrieve
prophash[property] = ensure_state
else
if ensure_state == :absent
prophash[property] = :absent
else
prophash[property] = property.retrieve
end
end
prophash
end
end
# Are we running in noop mode?
def noop?
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
def noop
noop?
end
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
raise "Global resource storage is deprecated"
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects[name] and self.isomorphic?
msg = "Object '%s[%s]' already exists" %
[newobj.class.name, name]
if exobj.file and exobj.line
msg += ("in file %s at line %s" %
[object.file, object.line])
end
if object.file and object.line
msg += ("and cannot be redefined in file %s at line %s" %
[object.file, object.line])
end
error = Puppet::Error.new(msg)
raise error
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
raise "Global resource aliasing is deprecated"
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias %s: object already exists" %
[name]
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object %s already has alias %s" %
[@aliases[name].name, name]
)
end
end
@aliases[name] = obj
end
# remove all of the instances of a single type
def self.clear
raise "Global resource removal is deprecated"
if defined? @objects
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
if defined? @aliases
@aliases.clear
end
end
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
new(args)
end
# remove a specified object
def self.delete(resource)
raise "Global resource removal is deprecated"
return unless defined? @objects
if @objects.include?(resource.title)
@objects.delete(resource.title)
end
if @aliases.include?(resource.title)
@aliases.delete(resource.title)
end
if @aliases.has_value?(resource)
names = []
@aliases.each do |name, otherres|
if otherres == resource
names << name
end
end
names.each { |name| @aliases.delete(name) }
end
end
# iterate across each of the type's instances
def self.each
raise "Global resource iteration is deprecated"
return unless defined? @objects
@objects.each { |name,instance|
yield instance
}
end
# does the type have an object with the given name?
def self.has_key?(name)
raise "Global resource access is deprecated"
return @objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
if provider_hash.empty?
raise Puppet::DevError, "%s has no providers and has not overridden 'instances'" % self.name
end
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
Puppet.warning "%s %s found in both %s and %s; skipping the %s version" %
[self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name]
next
end
provider_instances[instance.name] = instance
new(:name => instance.name, :provider => instance, :check => :all)
end
end.flatten.compact
end
# Return a list of one suitable provider per source, with the default provider first.
def self.providers_by_source
# Put the default provider first, then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if sources.include?(provider.source)
sources << provider.source
provider
end.compact
end
# Convert a simple hash into a Resource instance. This is a convenience method,
# so people can create RAL resources with a hash and get the same behaviour
# as we get internally when we use Resource instances.
# This should only be used directly from Ruby -- it's not used when going through
# normal Puppet usage.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
if title = hash[:title]
hash.delete(:title)
else
if self.namevar != :name
if hash.include?(:name) and hash.include?(self.namevar)
raise Puppet::Error, "Cannot provide both name and %s to resources of type %s" % [self.namevar, self.name]
end
if title = hash[self.namevar]
hash.delete(self.namevar)
end
end
unless title ||= hash[:name]
raise Puppet::Error, "You must specify a name or title for resources"
end
end
# Now create our resource.
resource = Puppet::Resource.new(self.name, title)
[:catalog].each do |attribute|
if value = hash[attribute]
hash.delete(attribute)
resource.send(attribute.to_s + "=", value)
end
end
hash.each do |param, value|
resource[param] = value
end
return resource
end
# Create the path for logging and such.
def pathbuilder
if p = parent
[p.pathbuilder, self.ref].flatten
else
[self.ref]
end
end
###############################
# Add all of the meta parameters.
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true"; @resource.noop = true
when false, :false, "false"; @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
that for your schedule::
schedule { daily:
period => daily,
range => \"2-4\"
}
exec { \"/usr/bin/apt-get update\":
schedule => daily
}
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:check) do
desc "Propertys which should have their values retrieved
but which should not actually be modified. This is currently used
internally, but will eventually be used for querying, so that you
could specify that you wanted to check the install state of all
packages, and then query the Puppet client daemon to get reports
on all packages."
munge do |args|
# If they've specified all, collect all known properties
if args == :all
args = @resource.class.properties.find_all do |property|
# Only get properties supported by our provider
if @resource.provider
@resource.provider.class.supports_parameter?(property)
else
true
end
end.collect do |property|
property.name
end
end
unless args.is_a?(Array)
args = [args]
end
unless defined? @resource
self.devfail "No parent for %s, %s?" %
[self.class, self.name]
end
args.each { |property|
unless property.is_a?(Symbol)
property = property.intern
end
next if @resource.propertydefined?(property)
unless propertyklass = @resource.class.validproperty?(property)
if @resource.class.validattr?(property)
next
else
raise Puppet::Error, "%s is not a valid attribute for %s" %
[property, self.class.name]
end
end
next unless propertyklass.checkable?
@resource.newattr(property)
}
end
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
provide a symbolic name::
file { sshdconfig:
path => $operatingsystem ? {
solaris => \"/usr/local/etc/ssh/sshd_config\",
default => \"/etc/ssh/sshd_config\"
},
source => \"...\"
}
service { sshd:
subscribe => file[sshdconfig]
}
When you use this feature, the parser sets ``sshdconfig`` as the name,
and the library sets that as an alias for the file so the dependency
lookup for ``sshd`` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
the following code will not work::
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
file { sshdconfig:
mode => 644
}
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
See the `LanguageTutorial language tutorial`:trac: for more information.
"
munge do |aliases|
unless aliases.is_a?(Array)
aliases = [aliases]
end
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("%s can not create alias %s: object already exists" % [@resource.title, other])
end
next
end
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
host's configuration::
puppetd --test --tags mytag
This way, when you're testing a configuration you can run just the
portion you're testing."
munge do |tags|
tags = [tags] unless tags.is_a? Array
tags.each do |tag|
@resource.tag(tag)
end
end
end
class RelationshipMetaparam < Puppet::Parameter
class << self
attr_accessor :direction, :events, :callback, :subclasses
end
@subclasses = []
def self.inherited(sub)
@subclasses << sub
end
def munge(references)
references = [references] unless references.is_a?(Array)
references.collect do |ref|
if ref.is_a?(Puppet::Resource::Reference)
ref
else
Puppet::Resource::Reference.new(ref)
end
end
end
def validate_relationship
@value.each do |ref|
unless @resource.catalog.resource(ref.to_s)
description = self.class.direction == :in ? "dependency" : "dependent"
fail "Could not find %s %s for %s" % [description, ref.to_s, resource.ref]
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |reference|
reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
unless related_resource = reference.resolve
self.fail "Could not retrieve dependency '%s' of %s" % [reference, @resource.ref]
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = related_resource
target = @resource
else
source = @resource
target = related_resource
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to %s" % [related_resource.ref])
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires %s" % [related_resource.ref])
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
desc "One or more objects that this object depends on.
This is used purely for guaranteeing that changes to required objects
happen before the dependent object. For instance::
# Create the destination directory before you copy things down
file { \"/usr/local/scripts\":
ensure => directory
}
file { \"/usr/local/scripts/myscript\":
source => \"puppet://server/module/myscript\",
mode => 755,
require => File[\"/usr/local/scripts\"]
}
Multiple dependencies can be specified by providing a comma-seperated list
of resources, enclosed in square brackets::
require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
command. For instance, if you had an ``exec`` command that ran
the ``myscript`` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
``exec`` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "One or more objects that this object depends on. Changes in the
subscribed to objects result in the dependent objects being
refreshed (e.g., a service will get restarted). For instance::
class nagios {
file { \"/etc/nagios/nagios.conf\":
source => \"puppet://server/module/nagios.conf\",
alias => nagconf # just to make things easier for me
}
service { nagios:
running => true,
subscribe => File[nagconf]
}
}
Currently the ``exec``, ``mount`` and ``service`` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object::
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => Exec["nagios-rebuid"]
}
exec { "nagios-rebuild":
command => "/usr/bin/make",
cwd => "/var/nagios/configuration"
}
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object::
file { "/etc/sshd_config":
source => "....",
notify => Service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
end
###############################
# All of the provider plumbing for the resource types.
require 'puppet/provider'
require 'puppet/util/provider_features'
# Add the feature handling module.
extend Puppet::Util::ProviderFeatures
attr_reader :provider
# the Type class attribute accessors
class << self
attr_accessor :providerloader
attr_writer :defaultprovider
end
# Find the default provider.
def self.defaultprovider
unless defined? @defaultprovider and @defaultprovider
suitable = suitableprovider()
# Find which providers are a default for this system.
defaults = suitable.find_all { |provider| provider.default? }
# If we don't have any default we use suitable providers
defaults = suitable if defaults.empty?
max = defaults.collect { |provider| provider.specificity }.max
defaults = defaults.find_all { |provider| provider.specificity == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for %s: %s; using %s" %
[self.name, defaults.collect { |i| i.name.to_s }.join(", "),
defaults[0].name]
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for %s" %
self.name
end
@defaultprovider = retval
end
return @defaultprovider
end
def self.provider_hash_by_type(type)
@provider_hashes ||= {}
@provider_hashes[type] ||= {}
end
def self.provider_hash
Puppet::Type.provider_hash_by_type(self.name)
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
# If we don't have it yet, try loading it.
unless provider_hash.has_key?(name)
@providerloader.load(name)
end
return provider_hash[name]
end
# Just list all of the providers.
def self.providers
provider_hash.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
return (provider_hash.has_key?(name) && provider_hash[name].suitable?)
end
# Create a new provider of a type. This method must be called
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
if obj = provider_hash[name]
Puppet.debug "Reloading %s %s provider" % [name, self.name]
unprovide(name)
end
parent = if pname = options[:parent]
options.delete(:parent)
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider %s of %s" %
[pname, name]
end
end
else
Puppet::Provider
end
options[:resource_type] ||= self
self.providify
provider = genclass(name,
:parent => parent,
:hash => provider_hash,
:prefix => "Provider",
:block => block,
:include => feature_module,
:extend => feature_module,
:attributes => options
)
return provider
end
# Make sure we have a :provider parameter defined. Only gets called if there
# are providers.
def self.providify
return if @paramhash.has_key? :provider
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
appropriate provider for your platform."
# This is so we can refer back to the type to get a list of
# providers for documentation.
class << self
attr_accessor :parenttype
end
# We need to add documentation for each provider.
def self.doc
@doc + " Available providers are:\n\n" + parenttype().providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
"* **%s**: %s" % [i, parenttype().provider(i).doc]
}.join("\n")
end
defaultto {
@resource.class.defaultprovider.name
}
validate do |provider_class|
provider_class = provider_class[0] if provider_class.is_a? Array
if provider_class.is_a?(Puppet::Provider)
provider_class = provider_class.class.name
end
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid %s provider '%s'" % [@resource.class.name, provider_class]
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
if provider.is_a? String
provider = provider.intern
end
@resource.provider = provider
if provider.is_a?(Puppet::Provider)
provider.class.name
else
provider
end
end
end.parenttype = self
end
def self.unprovide(name)
if provider_hash.has_key? name
rmclass(name,
:hash => provider_hash,
:prefix => "Provider"
)
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
if provider_hash.empty?
providerloader.loadall
end
provider_hash.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}.reject { |p| p.name == :fake } # For testing
end
def provider=(name)
if name.is_a?(Puppet::Provider)
@provider = name
@provider.resource = self
elsif klass = self.class.provider(name)
@provider = klass.new(self)
else
raise ArgumentError, "Could not find %s provider of %s" %
[name, self.class.name]
end
end
###############################
# All of the relationship code.
# Specify a block for generating a list of objects to autorequire. This
# makes it so that you don't have to manually specify things that you clearly
# require.
def self.autorequire(name, &block)
@autorequires ||= {}
@autorequires[name] = block
end
# Yield each of those autorequires in turn, yo.
def self.eachautorequire
@autorequires ||= {}
@autorequires.each { |type, block|
yield(type, block)
}
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire(rel_catalog = nil)
rel_catalog ||= catalog
raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
next unless typeobj = Puppet::Type.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
unless list.is_a?(Array)
list = [list]
end
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = rel_catalog.resource(type, dep)
next
end
end
reqs << Puppet::Relationship.new(dep, self)
}
}
return reqs
end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.to_edges
end
end.flatten.reject { |r| r.nil? }
end
###############################
# All of the scheduling code.
# Look up the schedule and set it appropriately. This is done after
# the instantiation phase, so that the schedule can be anywhere in the
# file.
def schedule
unless catalog
warning "Cannot schedule without a schedule-containing catalog"
return nil
end
unless defined? @schedule
if name = self[:schedule]
if sched = catalog.resource(:schedule, name)
@schedule = sched
else
self.fail "Could not find schedule %s" % name
end
else
@schedule = nil
end
end
@schedule
end
# Check whether we are scheduled to run right now or not.
def scheduled?
return true if Puppet[:ignoreschedules]
return true unless schedule = self.schedule
# We use 'checked' here instead of 'synced' because otherwise we'll
# end up checking most resources most times, because they will generally
# have been synced a long time ago (e.g., a file only gets updated
# once a month on the server and its schedule is daily; the last sync time
# will have been a month ago, so we'd end up checking every run).
return schedule.match?(self.cached(:checked).to_i)
end
# Define the initial list of tags.
def tags=(list)
tag(self.class.name)
tag(*list)
end
# Types (which map to resources in the languages) are entirely composed of
# attribute value pairs. Generally, Puppet calls any of these things an
# 'attribute', but these attributes always take one of three specific
# forms: parameters, metaparams, or properties.
# In naming methods, I have tried to consistently name the method so
# that it is clear whether it operates on all attributes (thus has 'attr' in
# the method name, or whether it operates on a specific type of attributes.
attr_writer :title
attr_writer :noop
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name
attr_accessor :self_refresh
include Enumerable, Puppet::Util::ClassGen
include Puppet::MetaType::Manager
include Puppet::Util
include Puppet::Util::Logging
end
# all of the variables that must be initialized for each subclass
def self.initvars
# all of the instances of this class
@objects = Hash.new
@aliases = Hash.new
@defaults = {}
unless defined? @parameters
@parameters = []
end
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@paramdoc = Hash.new { |hash,key|
if key.is_a?(String)
key = key.intern
end
if hash.include?(key)
hash[key]
else
"Param Documentation for %s not found" % key
end
}
unless defined? @doc
@doc = ""
end
end
def self.to_s
if defined? @name
"Puppet::Type::" + @name.to_s.capitalize
else
super
end
end
# Create a block to validate that our object is set up entirely. This will
# be run before the object is operated on.
def self.validate(&block)
define_method(:validate, &block)
#@validate = block
end
# The catalog that this resource is stored in.
attr_accessor :catalog
# is the resource exported
attr_accessor :exported
# is the resource virtual (it should not :-))
attr_accessor :virtual
# create a log at specified level
def log(msg)
Puppet::Util::Log.create(
:level => @parameters[:loglevel].value,
:message => msg,
:source => self
)
end
# instance methods related to instance intrinsics
# e.g., initialize() and name()
public
attr_reader :original_parameters
# initialize the type instance
def initialize(resource)
raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
# The list of parameter/property instances.
@parameters = {}
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog, :exported, :virtual].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
@tags = resource.tags
@original_parameters = resource.to_hash
set_name(@original_parameters)
set_default(:provider)
set_parameters(@original_parameters)
self.validate if self.respond_to?(:validate)
end
private
# Set our resource's name.
def set_name(hash)
n = self.class.namevar
self[n] = hash[n]
hash.delete(n)
end
# Set all of the parameters from a hash, in the appropriate order.
def set_parameters(hash)
# Use the order provided by allattrs, but add in any
# extra attributes from the resource so we get failures
# on invalid attributes.
no_values = []
(self.class.allattrs + hash.keys).uniq.each do |attr|
begin
# Set any defaults immediately. This is mostly done so
# that the default provider is available for any other
# property validation.
if hash.has_key?(attr)
self[attr] = hash[attr]
else
no_values << attr
end
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
end
no_values.each do |attr|
set_default(attr)
end
end
public
# Set up all of our autorequires.
def finish
# Scheduling has to be done when the whole config is instantiated, so
# that file order doesn't matter in finding them.
self.schedule
# Make sure all of our relationships are valid. Again, must be done
# when the entire catalog is instantiated.
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.validate_relationship
end
end.flatten.reject { |r| r.nil? }
end
# Return a cached value
def cached(name)
Puppet::Util::Storage.cache(self)[name]
#@cache[name] ||= nil
end
# Cache a value
def cache(name, value)
Puppet::Util::Storage.cache(self)[name] = value
#@cache[name] = value
end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
return self[:name]
end
# Look up our parent in the catalog, if we have one.
def parent
return nil unless catalog
unless defined?(@parent)
if parents = catalog.adjacent(self, :direction => :in)
# We should never have more than one parent, so let's just ignore
# it if we happen to.
@parent = parents.shift
else
@parent = nil
end
end
@parent
end
# Return the "type[name]" style reference.
def ref
"%s[%s]" % [self.class.name.to_s.capitalize, self.title]
end
def self_refresh?
self.class.self_refresh
end
# Mark that we're purging.
def purging
@purging = true
end
# Is this resource being purged? Used by transactions to forbid
# deletion when there are dependencies.
def purging?
if defined? @purging
@purging
else
false
end
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless defined? @title and @title
namevar = self.class.namevar
if self.class.validparameter?(namevar)
@title = self[:name]
elsif self.class.validproperty?(namevar)
@title = self.should(namevar)
else
self.devfail "Could not find namevar %s for %s" %
[namevar, self.class.name]
end
end
return @title
end
# convert to a string
def to_s
self.ref
end
# Convert to a transportable object
def to_trans(ret = true)
trans = TransObject.new(self.title, self.class.name)
values = retrieve()
values.each do |name, value|
trans[name.name] = value
end
@parameters.each do |name, param|
# Avoid adding each instance name as both the name and the namevar
next if param.class.isnamevar? and param.value == self.title
# We've already got property values
next if param.is_a?(Puppet::Property)
trans[name] = param.value
end
trans.tags = self.tags
# FIXME I'm currently ignoring 'parent' and 'path'
return trans
end
def to_resource
# this 'type instance' versus 'resource' distinction seems artificial
# I'd like to see it collapsed someday ~JW
self.to_trans.to_resource
end
%w{exported virtual}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
end # Puppet::Type
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index 5fed1760e..bf9007ab4 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -1,87 +1,87 @@
require 'puppet'
require 'puppet/type'
require 'puppet/transaction'
Puppet::Type.newtype(:component) do
include Enumerable
newparam(:name) do
desc "The name of the component. Generally optional."
isnamevar
end
# Override how parameters are handled so that we support the extra
# parameters that are used with defined resource types.
def [](param)
- return super if self.class.validattr?(param)
+ return super if self.class.valid_parameter?(param)
@extra_parameters[param.to_sym]
end
# Override how parameters are handled so that we support the extra
# parameters that are used with defined resource types.
def []=(param, value)
- return super if self.class.validattr?(param)
+ return super if self.class.valid_parameter?(param)
@extra_parameters[param.to_sym] = value
end
# Initialize a new component
def initialize(*args)
@extra_parameters = {}
super
if catalog and ! catalog.resource(ref)
catalog.alias(self, ref)
end
end
# Component paths are special because they function as containers.
def pathbuilder
if reference.type == "Class"
# 'main' is the top class, so we want to see '//' instead of
# its name.
if reference.title == "main"
myname = ""
else
myname = reference.title
end
else
myname = reference.to_s
end
if p = self.parent
return [p.pathbuilder, myname]
else
return [myname]
end
end
def ref
reference.to_s
end
# We want our title to just be the whole reference, rather than @title.
def title
ref
end
def title=(str)
@reference = Puppet::Resource::Reference.new(str)
end
def refresh
catalog.adjacent(self).each do |child|
if child.respond_to?(:refresh)
child.refresh
child.log "triggering %s" % :refresh
end
end
end
def to_s
reference.to_s
end
private
attr_reader :reference
end
diff --git a/spec/unit/provider/ldap.rb b/spec/unit/provider/ldap.rb
index fd5d1bdc3..6c5820883 100755
--- a/spec/unit/provider/ldap.rb
+++ b/spec/unit/provider/ldap.rb
@@ -1,248 +1,248 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2008-3-21.
# Copyright (c) 2006. All rights reserved.
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/provider/ldap'
describe Puppet::Provider::Ldap do
before do
@class = Class.new(Puppet::Provider::Ldap)
end
it "should be able to define its manager" do
manager = mock 'manager'
Puppet::Util::Ldap::Manager.expects(:new).returns manager
@class.stubs :mk_resource_methods
manager.expects(:manages).with(:one)
@class.manages(:one).should equal(manager)
@class.manager.should equal(manager)
end
it "should be able to prefetch instances from ldap" do
@class.should respond_to(:prefetch)
end
it "should create its resource getter/setter methods when the manager is defined" do
manager = mock 'manager'
Puppet::Util::Ldap::Manager.expects(:new).returns manager
@class.expects :mk_resource_methods
manager.stubs(:manages)
@class.manages(:one).should equal(manager)
end
it "should have an instances method" do
@class.should respond_to(:instances)
end
describe "when providing a list of instances" do
it "should convert all results returned from the manager's :search method into provider instances" do
manager = mock 'manager'
@class.stubs(:manager).returns manager
manager.expects(:search).returns %w{one two three}
@class.expects(:new).with("one").returns(1)
@class.expects(:new).with("two").returns(2)
@class.expects(:new).with("three").returns(3)
@class.instances.should == [1,2,3]
end
end
it "should have a prefetch method" do
@class.should respond_to(:prefetch)
end
describe "when prefetching" do
before do
@manager = mock 'manager'
@class.stubs(:manager).returns @manager
@resource = mock 'resource'
@resources = {"one" => @resource}
end
it "should find an entry for each passed resource" do
@manager.expects(:find).with("one").returns nil
@class.stubs(:new)
@resource.stubs(:provider=)
@class.prefetch(@resources)
end
describe "resources that do not exist" do
it "should create a provider with :ensure => :absent" do
result = mock 'result'
@manager.expects(:find).with("one").returns nil
@class.expects(:new).with(:ensure => :absent).returns "myprovider"
@resource.expects(:provider=).with("myprovider")
@class.prefetch(@resources)
end
end
describe "resources that exist" do
it "should create a provider with the results of the find" do
@manager.expects(:find).with("one").returns("one" => "two")
@class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider"
@resource.expects(:provider=).with("myprovider")
@class.prefetch(@resources)
end
it "should set :ensure to :present in the returned values" do
@manager.expects(:find).with("one").returns("one" => "two")
@class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider"
@resource.expects(:provider=).with("myprovider")
@class.prefetch(@resources)
end
end
end
describe "when being initialized" do
it "should fail if no manager has been defined" do
lambda { @class.new }.should raise_error(Puppet::DevError)
end
it "should fail if the manager is invalid" do
manager = stub "manager", :valid? => false
@class.stubs(:manager).returns manager
lambda { @class.new }.should raise_error(Puppet::DevError)
end
describe "with a hash" do
before do
@manager = stub "manager", :valid? => true
@class.stubs(:manager).returns @manager
@resource_class = mock 'resource_class'
@class.stubs(:resource_type).returns @resource_class
@property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property
@resource_class.stubs(:attrclass).with(:one).returns(@property_class)
- @resource_class.stubs(:validattr?).returns true
+ @resource_class.stubs(:valid_parameter?).returns true
end
it "should store a copy of the hash as its ldap_properties" do
instance = @class.new(:one => :two)
instance.ldap_properties.should == {:one => :two}
end
it "should only store the first value of each value array for those attributes that do not match all values" do
@property_class.expects(:array_matching).returns :first
instance = @class.new(:one => %w{two three})
instance.properties.should == {:one => "two"}
end
it "should store the whole value array for those attributes that match all values" do
@property_class.expects(:array_matching).returns :all
instance = @class.new(:one => %w{two three})
instance.properties.should == {:one => %w{two three}}
end
it "should only use the first value for attributes that are not properties" do
# Yay. hackish, but easier than mocking everything.
@resource_class.expects(:attrclass).with(:a).returns Puppet::Type.type(:user).attrclass(:name)
@property_class.stubs(:array_matching).returns :all
instance = @class.new(:one => %w{two three}, :a => %w{b c})
instance.properties.should == {:one => %w{two three}, :a => "b"}
end
it "should discard any properties not valid in the resource class" do
- @resource_class.expects(:validattr?).with(:a).returns false
+ @resource_class.expects(:valid_parameter?).with(:a).returns false
@property_class.stubs(:array_matching).returns :all
instance = @class.new(:one => %w{two three}, :a => %w{b})
instance.properties.should == {:one => %w{two three}}
end
end
end
describe "when an instance" do
before do
@manager = stub "manager", :valid? => true
@class.stubs(:manager).returns @manager
@instance = @class.new
@property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property
- @resource_class = stub 'resource_class', :attrclass => @property_class, :validattr? => true, :validproperties => [:one, :two]
+ @resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:one, :two]
@class.stubs(:resource_type).returns @resource_class
end
it "should have a method for creating the ldap entry" do
@instance.should respond_to(:create)
end
it "should have a method for removing the ldap entry" do
@instance.should respond_to(:delete)
end
it "should have a method for returning the class's manager" do
@instance.manager.should equal(@manager)
end
it "should indicate when the ldap entry already exists" do
@instance = @class.new(:ensure => :present)
@instance.exists?.should be_true
end
it "should indicate when the ldap entry does not exist" do
@instance = @class.new(:ensure => :absent)
@instance.exists?.should be_false
end
describe "is being flushed" do
it "should call the manager's :update method with its name, current attributes, and desired attributes" do
@instance.stubs(:name).returns "myname"
@instance.stubs(:ldap_properties).returns(:one => :two)
@instance.stubs(:properties).returns(:three => :four)
@manager.expects(:update).with(@instance.name, {:one => :two}, {:three => :four})
@instance.flush
end
end
describe "is being created" do
before do
@rclass = mock 'resource_class'
@rclass.stubs(:validproperties).returns([:one, :two])
@resource = mock 'resource'
@resource.stubs(:class).returns @rclass
@resource.stubs(:should).returns nil
@instance.stubs(:resource).returns @resource
end
it "should set its :ensure value to :present" do
@instance.create
@instance.properties[:ensure].should == :present
end
it "should set all of the other attributes from the resource" do
@resource.expects(:should).with(:one).returns "oneval"
@resource.expects(:should).with(:two).returns "twoval"
@instance.create
@instance.properties[:one].should == "oneval"
@instance.properties[:two].should == "twoval"
end
end
describe "is being deleted" do
it "should set its :ensure value to :absent" do
@instance.delete
@instance.properties[:ensure].should == :absent
end
end
end
end
diff --git a/spec/unit/resource.rb b/spec/unit/resource.rb
index bc47fa7ed..73bbfd865 100755
--- a/spec/unit/resource.rb
+++ b/spec/unit/resource.rb
@@ -1,529 +1,621 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/resource'
describe Puppet::Resource do
[:catalog, :file, :line].each do |attr|
it "should have an #{attr} attribute" do
resource = Puppet::Resource.new("file", "/my/file")
resource.should respond_to(attr)
resource.should respond_to(attr.to_s + "=")
end
end
describe "when initializing" do
it "should require the type and title" do
lambda { Puppet::Resource.new }.should raise_error(ArgumentError)
end
it "should create a resource reference with its type and title" do
ref = Puppet::Resource::Reference.new("file", "/f")
Puppet::Resource::Reference.expects(:new).with("file", "/f").returns ref
Puppet::Resource.new("file", "/f")
end
it "should tag itself with its type" do
Puppet::Resource.new("file", "/f").should be_tagged("file")
end
it "should tag itself with its title if the title is a valid tag" do
Puppet::Resource.new("file", "bar").should be_tagged("bar")
end
it "should not tag itself with its title if the title is a not valid tag" do
Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar")
end
it "should allow setting of attributes" do
Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo"
Puppet::Resource.new("file", "/bar", :exported => true).should be_exported
end
end
it "should use the resource reference to determine its type" do
ref = Puppet::Resource::Reference.new("file", "/f")
Puppet::Resource::Reference.expects(:new).returns ref
resource = Puppet::Resource.new("file", "/f")
ref.expects(:type).returns "mytype"
resource.type.should == "mytype"
end
it "should use its resource reference to determine its title" do
ref = Puppet::Resource::Reference.new("file", "/f")
Puppet::Resource::Reference.expects(:new).returns ref
resource = Puppet::Resource.new("file", "/f")
ref.expects(:title).returns "mytitle"
resource.title.should == "mytitle"
end
it "should use its resource reference to determine whether it is builtin" do
ref = Puppet::Resource::Reference.new("file", "/f")
Puppet::Resource::Reference.expects(:new).returns ref
resource = Puppet::Resource.new("file", "/f")
ref.expects(:builtin_type?).returns "yep"
resource.builtin_type?.should == "yep"
end
it "should call its builtin_type? method when 'builtin?' is called" do
resource = Puppet::Resource.new("file", "/f")
resource.expects(:builtin_type?).returns "foo"
resource.builtin?.should == "foo"
end
it "should use its resource reference to produce its canonical reference string" do
ref = Puppet::Resource::Reference.new("file", "/f")
Puppet::Resource::Reference.expects(:new).returns ref
resource = Puppet::Resource.new("file", "/f")
ref.expects(:to_s).returns "Foo[bar]"
resource.ref.should == "Foo[bar]"
end
it "should be taggable" do
Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging)
end
it "should have an 'exported' attribute" do
resource = Puppet::Resource.new("file", "/f")
resource.exported = true
resource.exported.should == true
resource.should be_exported
end
- it "should support an environment attribute"
+ it "should support an environment attribute" do
+ Puppet::Resource.new("file", "/my/file", :environment => :foo).environment.name.should == :foo
+ end
+
+ it "should support a namespace attribute" do
+ Puppet::Resource.new("file", "/my/file", :namespace => :foo).namespace.should == :foo
+ end
+
+ it "should default to a namespace of an empty string" do
+ Puppet::Resource.new("file", "/my/file").namespace.should == ""
+ end
+
+ it "should be able to look up its resource type when the type is a builtin resource" do
+ Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file))
+ end
+
+ it "should be able to look up its resource type via its environment when the type is a defined resource type" do
+ resource = Puppet::Resource.new("foobar", "/my/file")
+ type = Puppet::Resource::Type.new(:definition, "foobar")
+ resource.environment.known_resource_types.add type
+
+ resource.resource_type.should equal(type)
+ end
+
+ it "should be able to look up its resource type via its environment when the type is a node" do
+ resource = Puppet::Resource.new("node", "foobar")
+ node = Puppet::Resource::Type.new(:node, "foobar")
+ resource.environment.known_resource_types.add node
+
+ resource.resource_type.should equal(node)
+ end
+
+ it "should be able to look up its resource type via its environment when the type is a class" do
+ resource = Puppet::Resource.new("class", "foobar")
+ klass = Puppet::Resource::Type.new(:hostclass, "foobar")
+ resource.environment.known_resource_types.add klass
- it "should convert its environment into an environment instance if one is provided"
+ resource.resource_type.should equal(klass)
+ end
+
+ it "should use its namespace when looking up defined resource types" do
+ resource = Puppet::Resource.new("bar", "/my/file", :namespace => "foo")
+ type = Puppet::Resource::Type.new(:definition, "foo::bar")
+ resource.environment.known_resource_types.add type
+
+ resource.resource_type.should equal(type)
+ end
- it "should support a namespace attribute"
+ it "should use its namespace when looking up host classes" do
+ resource = Puppet::Resource.new("class", "bar", :namespace => "foo")
+ type = Puppet::Resource::Type.new(:hostclass, "foo::bar")
+ resource.environment.known_resource_types.add type
+
+ resource.resource_type.should equal(type)
+ end
+
+ it "should return nil when looking up resource types that don't exist" do
+ Puppet::Resource.new("foobar", "bar").resource_type.should be_nil
+ end
+
+ it "should fail when an invalid parameter is used and parameter validation is enabled" do
+ type = Puppet::Resource::Type.new(:definition, "foobar")
+ Puppet::Node::Environment.new.known_resource_types.add type
+ resource = Puppet::Resource.new("foobar", "/my/file", :validate_parameters => true)
+ lambda { resource[:yay] = true }.should raise_error(ArgumentError)
+ end
+
+ it "should not fail when an invalid parameter is used and parameter validation is disabled" do
+ type = Puppet::Resource::Type.new(:definition, "foobar")
+ Puppet::Node::Environment.new.known_resource_types.add type
+ resource = Puppet::Resource.new("foobar", "/my/file")
+ resource[:yay] = true
+ end
+
+ it "should not fail when a valid parameter is used and parameter validation is enabled" do
+ type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"yay" => nil})
+ Puppet::Node::Environment.new.known_resource_types.add type
+ resource = Puppet::Resource.new("foobar", "/my/file", :validate_parameters => true)
+ resource[:yay] = true
+ end
describe "when managing parameters" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
end
- it "should be able to check whether parameters are valid when the resource models builtin resources"
+ it "should correctly detect when provided parameters are not valid for builtin types" do
+ Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar")
+ end
+
+ it "should correctly detect when provided parameters are valid for builtin types" do
+ Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode")
+ end
+
+ it "should correctly detect when provided parameters are not valid for defined resource types" do
+ type = Puppet::Resource::Type.new(:definition, "foobar")
+ Puppet::Node::Environment.new.known_resource_types.add type
+ Puppet::Resource.new("foobar", "/my/file").should_not be_valid_parameter("myparam")
+ end
- it "should be able to check whether parameters are valid when the resource models defined resources"
+ it "should correctly detect when provided parameters are valid for defined resource types" do
+ type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil})
+ Puppet::Node::Environment.new.known_resource_types.add type
+ Puppet::Resource.new("foobar", "/my/file").should be_valid_parameter("myparam")
+ end
it "should allow setting and retrieving of parameters" do
@resource[:foo] = "bar"
@resource[:foo].should == "bar"
end
it "should allow setting of parameters at initialization" do
Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar"
end
it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do
@resource[:foo] = "bar"
@resource["foo"].should == "bar"
end
it "should canonicalize set parameter names to treat symbols and strings equivalently" do
@resource["foo"] = "bar"
@resource[:foo].should == "bar"
end
it "should set the namevar when asked to set the name" do
Puppet::Type.type(:file).stubs(:namevar).returns :myvar
@resource[:name] = "/foo"
@resource[:myvar].should == "/foo"
end
it "should return the namevar when asked to return the name" do
Puppet::Type.type(:file).stubs(:namevar).returns :myvar
@resource[:myvar] = "/foo"
@resource[:name].should == "/foo"
end
it "should be able to set the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
+ resource[:name] = "eh"
lambda { resource[:name] = "eh" }.should_not raise_error
end
it "should be able to return the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
resource[:name].should == "eh"
end
it "should be able to iterate over parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "bare"
params = {}
@resource.each do |key, value|
params[key] = value
end
params.should == {:foo => "bar", :fee => "bare"}
end
it "should include Enumerable" do
@resource.class.ancestors.should be_include(Enumerable)
end
it "should have a method for testing whether a parameter is included" do
@resource[:foo] = "bar"
@resource.should be_has_key(:foo)
@resource.should_not be_has_key(:eh)
end
it "should have a method for providing the list of parameters" do
@resource[:foo] = "bar"
@resource[:bar] = "foo"
keys = @resource.keys
keys.should be_include(:foo)
keys.should be_include(:bar)
end
it "should have a method for providing the number of parameters" do
@resource[:foo] = "bar"
@resource.length.should == 1
end
it "should have a method for deleting parameters" do
@resource[:foo] = "bar"
@resource.delete(:foo)
@resource[:foo].should be_nil
end
it "should have a method for testing whether the parameter list is empty" do
@resource.should be_empty
@resource[:foo] = "bar"
@resource.should_not be_empty
end
it "should be able to produce a hash of all existing parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "yay"
hash = @resource.to_hash
hash[:foo].should == "bar"
hash[:fee].should == "yay"
end
it "should not provide direct access to the internal parameters hash when producing a hash" do
hash = @resource.to_hash
hash[:foo] = "bar"
@resource[:foo].should be_nil
end
it "should use the title as the namevar to the hash if no namevar is present" do
Puppet::Type.type(:file).stubs(:namevar).returns :myvar
@resource.to_hash[:myvar].should == "/my/file"
end
it "should set :name to the title if :name is not present for non-builtin types" do
resource = Puppet::Resource.new :foo, "bar"
resource.to_hash[:name].should == "bar"
end
end
describe "when serializing" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
@resource["one"] = "test"
@resource["two"] = "other"
end
it "should be able to be dumped to yaml" do
proc { YAML.dump(@resource) }.should_not raise_error
end
it "should produce an equivalent yaml object" do
text = YAML.dump(@resource)
newresource = YAML.load(text)
newresource.title.should == @resource.title
newresource.type.should == @resource.type
%w{one two}.each do |param|
newresource[param].should == @resource[param]
end
end
end
describe "when converting to a RAL resource" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
@resource["one"] = "test"
@resource["two"] = "other"
end
it "should use the resource type's :create method to create the resource if the resource is of a builtin type" do
type = mock 'resource type'
type.expects(:new).with(@resource).returns(:myresource)
Puppet::Type.expects(:type).with(@resource.type).returns(type)
@resource.to_ral.should == :myresource
end
it "should convert to a component instance if the resource type is not of a builtin type" do
component = mock 'component type'
Puppet::Type::Component.expects(:new).with(@resource).returns "meh"
Puppet::Type.expects(:type).with(@resource.type).returns(nil)
@resource.to_ral.should == "meh"
end
end
it "should be able to convert itself to Puppet code" do
Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_manifest)
end
describe "when converting to puppet code" do
before do
@resource = Puppet::Resource.new("one::two", "/my/file", :parameters => {:noop => true, :foo => %w{one two}})
end
it "should print the type and title" do
@resource.to_manifest.should be_include("one::two { '/my/file':\n")
end
it "should print each parameter, with the value single-quoted" do
@resource.to_manifest.should be_include(" noop => 'true'")
end
it "should print array values appropriately" do
@resource.to_manifest.should be_include(" foo => ['one','two']")
end
end
it "should be able to convert itself to a TransObject instance" do
Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_trans)
end
describe "when converting to a TransObject" do
describe "and the resource is not an instance of a builtin type" do
before do
@resource = Puppet::Resource.new("foo", "bar")
end
it "should return a simple TransBucket if it is not an instance of a builtin type" do
bucket = @resource.to_trans
bucket.should be_instance_of(Puppet::TransBucket)
bucket.type.should == @resource.type
bucket.name.should == @resource.title
end
it "should copy over the resource's file" do
@resource.file = "/foo/bar"
@resource.to_trans.file.should == "/foo/bar"
end
it "should copy over the resource's line" do
@resource.line = 50
@resource.to_trans.line.should == 50
end
end
describe "and the resource is an instance of a builtin type" do
before do
@resource = Puppet::Resource.new("file", "bar")
end
it "should return a TransObject if it is an instance of a builtin resource type" do
trans = @resource.to_trans
trans.should be_instance_of(Puppet::TransObject)
trans.type.should == "file"
trans.name.should == @resource.title
end
it "should copy over the resource's file" do
@resource.file = "/foo/bar"
@resource.to_trans.file.should == "/foo/bar"
end
it "should copy over the resource's line" do
@resource.line = 50
@resource.to_trans.line.should == 50
end
# Only TransObjects support tags, annoyingly
it "should copy over the resource's tags" do
@resource.tag "foo"
@resource.to_trans.tags.should == @resource.tags
end
it "should copy the resource's parameters into the transobject and convert the parameter name to a string" do
@resource[:foo] = "bar"
@resource.to_trans["foo"].should == "bar"
end
it "should be able to copy arrays of values" do
@resource[:foo] = %w{yay fee}
@resource.to_trans["foo"].should == %w{yay fee}
end
it "should reduce single-value arrays to just a value" do
@resource[:foo] = %w{yay}
@resource.to_trans["foo"].should == "yay"
end
it "should convert resource references into the backward-compatible form" do
@resource[:foo] = Puppet::Resource::Reference.new(:file, "/f")
@resource.to_trans["foo"].should == %w{file /f}
end
it "should convert resource references into the backward-compatible form even when within arrays" do
@resource[:foo] = ["a", Puppet::Resource::Reference.new(:file, "/f")]
@resource.to_trans["foo"].should == ["a", %w{file /f}]
end
end
end
describe "when converting to pson" do
confine "Missing 'pson' library" => Puppet.features.pson?
def pson_output_should
@resource.class.expects(:pson_create).with { |hash| yield hash }
end
it "should include the pson util module" do
Puppet::Resource.metaclass.ancestors.should be_include(Puppet::Util::Pson)
end
# LAK:NOTE For all of these tests, we convert back to the resource so we can
# trap the actual data structure then.
it "should set its type to the provided type" do
Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo"
end
it "should include all tags from the resource" do
resource = Puppet::Resource.new("File", "/foo")
resource.tag("yay")
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).tags.should == resource.tags
end
it "should include the file if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.file = "/my/file"
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).file.should == "/my/file"
end
it "should include the line if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.line = 50
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).line.should == 50
end
it "should include the 'exported' value if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.exported = true
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_true
end
it "should set 'exported' to false if no value is set" do
resource = Puppet::Resource.new("File", "/foo")
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_false
end
it "should set all of its parameters as the 'parameters' entry" do
resource = Puppet::Resource.new("File", "/foo")
resource[:foo] = %w{bar eh}
resource[:fee] = %w{baz}
result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson))
result["foo"].should == %w{bar eh}
result["fee"].should == %w{baz}
end
end
describe "when converting from pson" do
confine "Missing 'pson' library" => Puppet.features.pson?
def pson_result_should
Puppet::Resource.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'type' => "file",
'title' => "yay",
}
end
it "should set its type to the provided type" do
Puppet::Resource.from_pson(@data).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_pson(@data).title.should == "yay"
end
it "should tag the resource with any provided tags" do
@data['tags'] = %w{foo bar}
resource = Puppet::Resource.from_pson(@data)
resource.tags.should be_include("foo")
resource.tags.should be_include("bar")
end
it "should set its file to the provided file" do
@data['file'] = "/foo/bar"
Puppet::Resource.from_pson(@data).file.should == "/foo/bar"
end
it "should set its line to the provided line" do
@data['line'] = 50
Puppet::Resource.from_pson(@data).line.should == 50
end
it "should 'exported' to true if set in the pson data" do
@data['exported'] = true
Puppet::Resource.from_pson(@data).exported.should be_true
end
it "should 'exported' to false if not set in the pson data" do
Puppet::Resource.from_pson(@data).exported.should be_false
end
it "should fail if no title is provided" do
@data.delete('title')
lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError)
end
it "should fail if no type is provided" do
@data.delete('type')
lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError)
end
it "should set each of the provided parameters" do
@data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}}
resource = Puppet::Resource.from_pson(@data)
resource['foo'].should == %w{one two}
resource['fee'].should == %w{three four}
end
it "should convert single-value array parameters to normal values" do
@data['parameters'] = {'foo' => %w{one}}
resource = Puppet::Resource.from_pson(@data)
resource['foo'].should == %w{one}
end
end
describe "it should implement to_resource" do
resource = Puppet::Resource.new("file", "/my/file")
resource.to_resource.should == resource
end
describe "because it is an indirector model" do
it "should include Puppet::Indirector" do
Puppet::Resource.should be_is_a(Puppet::Indirector)
end
it "should have a default terminus" do
Puppet::Resource.indirection.terminus_class.should == :ral
end
it "should have a name" do
Puppet::Resource.new("file", "/my/file").name.should == "File//my/file"
end
end
end
diff --git a/spec/unit/resource/type.rb b/spec/unit/resource/type.rb
index 98b38c9b1..0a2f447ef 100755
--- a/spec/unit/resource/type.rb
+++ b/spec/unit/resource/type.rb
@@ -1,533 +1,533 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/resource/type'
describe Puppet::Resource::Type do
it "should have a 'name' attribute" do
Puppet::Resource::Type.new(:hostclass, "foo").name.should == "foo"
end
[:code, :doc, :line, :file, :code_collection].each do |attr|
it "should have a '#{attr}' attribute" do
type = Puppet::Resource::Type.new(:hostclass, "foo")
type.send(attr.to_s + "=", "yay")
type.send(attr).should == "yay"
end
end
describe "when a node" do
it "should allow a regex as its name" do
lambda { Puppet::Resource::Type.new(:node, /foo/) }.should_not raise_error
end
it "should allow a AST::HostName instance as its name" do
regex = Puppet::Parser::AST::Regex.new(:value => /foo/)
name = Puppet::Parser::AST::HostName.new(:value => regex)
lambda { Puppet::Resource::Type.new(:node, name) }.should_not raise_error
end
it "should match against the regexp in the AST::HostName when a HostName instance is provided" do
regex = Puppet::Parser::AST::Regex.new(:value => /\w/)
name = Puppet::Parser::AST::HostName.new(:value => regex)
node = Puppet::Resource::Type.new(:node, name)
node.match("foo").should be_true
end
it "should return the value of the hostname if provided a string-form AST::HostName instance as the name" do
name = Puppet::Parser::AST::HostName.new(:value => "foo")
node = Puppet::Resource::Type.new(:node, name)
node.name.should == "foo"
end
describe "and the name is a regex" do
it "should have a method that indicates that this is the case" do
Puppet::Resource::Type.new(:node, /w/).should be_name_is_regex
end
it "should set its namespace to ''" do
Puppet::Resource::Type.new(:node, /w/).namespace.should == ""
end
it "should return the regex converted to a string when asked for its name" do
Puppet::Resource::Type.new(:node, /ww/).name.should == "ww"
end
it "should downcase the regex when returning the name as a string" do
Puppet::Resource::Type.new(:node, /W/).name.should == "w"
end
it "should remove non-alpha characters when returning the name as a string" do
Puppet::Resource::Type.new(:node, /w*w/).name.should_not include("*")
end
it "should remove leading dots when returning the name as a string" do
Puppet::Resource::Type.new(:node, /.ww/).name.should_not =~ /^\./
end
it "should have a method for matching its regex name against a provided name" do
Puppet::Resource::Type.new(:node, /.ww/).should respond_to(:match)
end
it "should return true when its regex matches the provided name" do
Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true
end
it "should return false when its regex does not match the provided name" do
(!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).should be_false
end
it "should return true when its name, as a string, is matched against an equal string" do
Puppet::Resource::Type.new(:node, "foo").match("foo").should be_true
end
it "should return false when its name is matched against an unequal string" do
Puppet::Resource::Type.new(:node, "foo").match("bar").should be_false
end
it "should match names insensitive to case" do
Puppet::Resource::Type.new(:node, "fOo").match("foO").should be_true
end
end
it "should return the name converted to a string when the name is not a regex" do
pending "Need to define ResourceTypeCollection behaviour first"
name = Puppet::Parser::AST::HostName.new(:value => "foo")
Puppet::Resource::Type.new(:node, name).name.should == "foo"
end
it "should return the name converted to a string when the name is a regex" do
pending "Need to define ResourceTypeCollection behaviour first"
name = Puppet::Parser::AST::HostName.new(:value => /regex/)
Puppet::Resource::Type.new(:node, name).name.should == /regex/.to_s
end
it "should mark any created scopes as a node scope" do
pending "Need to define ResourceTypeCollection behaviour first"
name = Puppet::Parser::AST::HostName.new(:value => /regex/)
Puppet::Resource::Type.new(:node, name).name.should == /regex/.to_s
end
end
describe "when initializing" do
it "should require a resource super type" do
Puppet::Resource::Type.new(:hostclass, "foo").type.should == :hostclass
end
it "should fail if provided an invalid resource super type" do
lambda { Puppet::Resource::Type.new(:nope, "foo") }.should raise_error(ArgumentError)
end
it "should set its name to the downcased, stringified provided name" do
Puppet::Resource::Type.new(:hostclass, "Foo::Bar".intern).name.should == "foo::bar"
end
it "should set its namespace to the downcased, stringified qualified portion of the name" do
Puppet::Resource::Type.new(:hostclass, "Foo::Bar::Baz".intern).namespace.should == "foo::bar"
end
%w{code line file doc}.each do |arg|
it "should set #{arg} if provided" do
type = Puppet::Resource::Type.new(:hostclass, "foo", arg.to_sym => "something")
type.send(arg).should == "something"
end
end
it "should set any provided arguments with the keys as symbols" do
type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"})
- type.should be_validattr("foo")
- type.should be_validattr("baz")
+ type.should be_valid_parameter("foo")
+ type.should be_valid_parameter("baz")
end
it "should set any provided arguments with they keys as strings" do
type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"})
- type.should be_validattr(:foo)
- type.should be_validattr(:baz)
+ type.should be_valid_parameter(:foo)
+ type.should be_valid_parameter(:baz)
end
it "should function if provided no arguments" do
type = Puppet::Resource::Type.new(:hostclass, "foo")
- type.should_not be_validattr(:foo)
+ type.should_not be_valid_parameter(:foo)
end
end
describe "when testing the validity of an attribute" do
it "should return true if the parameter was typed at initialization" do
- Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_validattr("foo")
+ Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_valid_parameter("foo")
end
it "should return true if it is a metaparam" do
- Puppet::Resource::Type.new(:hostclass, "foo").should be_validattr("require")
+ Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("require")
end
it "should return true if the parameter is named 'name'" do
- Puppet::Resource::Type.new(:hostclass, "foo").should be_validattr("name")
+ Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("name")
end
it "should return false if it is not a metaparam and was not provided at initialization" do
- Puppet::Resource::Type.new(:hostclass, "foo").should_not be_validattr("yayness")
+ Puppet::Resource::Type.new(:hostclass, "foo").should_not be_valid_parameter("yayness")
end
end
describe "when creating a subscope" do
before do
@scope = stub 'scope', :newscope => nil
@resource = stub 'resource'
@type = Puppet::Resource::Type.new(:hostclass, "foo")
end
it "should return a new scope created with the provided scope as the parent" do
@scope.expects(:newscope).returns "foo"
@type.subscope(@scope, @resource).should == "foo"
end
it "should set the source as itself" do
@scope.expects(:newscope).with { |args| args[:source] == @type }
@type.subscope(@scope, @resource)
end
it "should set the scope's namespace to its namespace" do
@type.expects(:namespace).returns "yayness"
@scope.expects(:newscope).with { |args| args[:namespace] == "yayness" }
@type.subscope(@scope, @resource)
end
it "should set the scope's resource to the provided resource" do
@scope.expects(:newscope).with { |args| args[:resource] == @resource }
@type.subscope(@scope, @resource)
end
end
describe "when setting its parameters in the scope" do
before do
@scope = stub 'scope', :newscope => nil, :setvar => nil
@resource = stub 'resource', :title => "yay", :name => "yea", :ref => "Foo[bar]"
@type = Puppet::Resource::Type.new(:hostclass, "foo")
end
it "should set each of the resource's parameters as variables in the scope" do
@type.set_arguments :foo => nil, :boo => nil
@resource.expects(:to_hash).returns(:foo => "bar", :boo => "baz")
@scope.expects(:setvar).with("foo", "bar")
@scope.expects(:setvar).with("boo", "baz")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should set the variables as strings" do
@type.set_arguments :foo => nil
@resource.expects(:to_hash).returns(:foo => "bar")
@scope.expects(:setvar).with("foo", "bar")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should fail if any of the resource's parameters are not valid attributes" do
@type.set_arguments :foo => nil
@resource.expects(:to_hash).returns(:boo => "baz")
lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError)
end
it "should evaluate and set its default values as variables for parameters not provided by the resource" do
@type.set_arguments :foo => stub("value", :safeevaluate => "something")
@resource.expects(:to_hash).returns({})
@scope.expects(:setvar).with("foo", "something")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should fail if the resource does not provide a value for a required argument" do
@type.set_arguments :foo => nil
@resource.expects(:to_hash).returns({})
lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError)
end
it "should set the resource's title as a variable if not otherwise provided" do
@resource.expects(:to_hash).returns({})
@resource.expects(:title).returns 'teetle'
@scope.expects(:setvar).with("title", "teetle")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should set the resource's name as a variable if not otherwise provided" do
@resource.expects(:to_hash).returns({})
@resource.expects(:name).returns 'nombre'
@scope.expects(:setvar).with("name", "nombre")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
end
describe "when describing and managing parent classes" do
before do
@code = Puppet::Resource::TypeCollection.new("env")
@parent = Puppet::Resource::Type.new(:hostclass, "bar")
@code.add @parent
@child = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar")
@code.add @child
end
it "should be able to define a parent" do
Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar")
end
it "should use the code collection to find the parent resource type" do
@child.parent_type.should equal(@parent)
end
it "should be able to find parent nodes" do
parent = Puppet::Resource::Type.new(:node, "bar")
@code.add parent
child = Puppet::Resource::Type.new(:node, "foo", :parent => "bar")
@code.add child
child.parent_type.should equal(parent)
end
it "should cache a reference to the parent type" do
@code.expects(:hostclass).once.with("bar").returns @parent
@child.parent_type
@child.parent_type
end
it "should correctly state when it is another type's child" do
@child.should be_child_of(@parent)
end
it "should be considered the child of a parent's parent" do
@grandchild = Puppet::Resource::Type.new(:hostclass, "baz", :parent => "foo")
@code.add @grandchild
@grandchild.should be_child_of(@parent)
end
it "should correctly state when it is not another type's child" do
@notchild = Puppet::Resource::Type.new(:hostclass, "baz")
@code.add @notchild
@notchild.should_not be_child_of(@parent)
end
end
describe "when evaluating its code" do
before do
@compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode"))
@scope = Puppet::Parser::Scope.new :compiler => @compiler
@resource = stub 'resource', :title => "yay", :name => "yea", :ref => "Foo[bar]", :scope => @scope
@type = Puppet::Resource::Type.new(:hostclass, "foo")
@type.stubs(:set_resource_parameters)
end
it "should set all of its parameters in a subscope" do
subscope = stub 'subscope', :compiler => @compiler
@type.expects(:subscope).with(@scope, @resource).returns subscope
@type.expects(:set_resource_parameters).with(@resource, subscope)
@type.evaluate_code(@resource)
end
it "should store the class scope" do
subscope = stub 'subscope'
subscope.expects(:class_set).with('foo',subscope)
@type.expects(:subscope).with(@scope, @resource).returns subscope
@type.evaluate_code(@resource)
end
it "should evaluate the code if any is provided" do
code = stub 'code'
@type.stubs(:code).returns code
@type.stubs(:subscope).returns stub("subscope", :compiler => @compiler)
code.expects(:safeevaluate).with @type.subscope
@type.evaluate_code(@resource)
end
it "should noop if there is no code" do
@type.expects(:code).returns nil
@type.evaluate_code(@resource)
end
end
describe "when creating a resource" do
before do
@node = Puppet::Node.new("foo")
@compiler = Puppet::Parser::Compiler.new(@node)
@scope = Puppet::Parser::Scope.new(:compiler => @compiler)
@top = Puppet::Resource::Type.new :hostclass, "top"
@middle = Puppet::Resource::Type.new :hostclass, "middle", :parent => "top"
@code = Puppet::Resource::TypeCollection.new("env")
@code.add @top
@code.add @middle
end
it "should create a resource instance" do
@top.mk_plain_resource(@scope).should be_instance_of(Puppet::Parser::Resource)
end
it "should set its resource type to 'class' when it is a hostclass" do
Puppet::Resource::Type.new(:hostclass, "top").mk_plain_resource(@scope).type.should == "Class"
end
it "should set its resource type to 'node' when it is a node" do
Puppet::Resource::Type.new(:node, "top").mk_plain_resource(@scope).type.should == "Node"
end
it "should fail when it is a definition" do
lambda { Puppet::Resource::Type.new(:definition, "top").mk_plain_resource(@scope) }.should raise_error(ArgumentError)
end
it "should add the created resource to the scope's catalog" do
@top.mk_plain_resource(@scope)
@compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource)
end
it "should evaluate the parent class if one exists" do
@middle.mk_plain_resource(@scope)
@compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource)
end
it "should fail to evaluate if a parent class is defined but cannot be found" do
othertop = Puppet::Resource::Type.new :hostclass, "something", :parent => "yay"
@code.add othertop
lambda { othertop.mk_plain_resource(@scope) }.should raise_error(Puppet::ParseError)
end
it "should not create a new resource if one already exists" do
@compiler.catalog.expects(:resource).with(:class, "top").returns("something")
@compiler.catalog.expects(:add_resource).never
@top.mk_plain_resource(@scope)
end
it "should return the existing resource when not creating a new one" do
@compiler.catalog.expects(:resource).with(:class, "top").returns("something")
@compiler.catalog.expects(:add_resource).never
@top.mk_plain_resource(@scope).should == "something"
end
it "should not create a new parent resource if one already exists and it has a parent class" do
@top.mk_plain_resource(@scope)
top_resource = @compiler.catalog.resource(:class, "top")
@middle.mk_plain_resource(@scope)
@compiler.catalog.resource(:class, "top").should equal(top_resource)
end
# #795 - tag before evaluation.
it "should tag the catalog with the resource tags when it is evaluated" do
@middle.mk_plain_resource(@scope)
@compiler.catalog.should be_tagged("middle")
end
it "should tag the catalog with the parent class tags when it is evaluated" do
@middle.mk_plain_resource(@scope)
@compiler.catalog.should be_tagged("top")
end
end
describe "when merging code from another instance" do
def code(str)
Puppet::Parser::AST::Leaf.new :value => str
end
it "should fail unless it is a class" do
lambda { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.should raise_error(ArgumentError)
end
it "should fail unless the source instance is a class" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
source = Puppet::Resource::Type.new(:node, "foo")
lambda { dest.merge(source) }.should raise_error(ArgumentError)
end
it "should fail if both classes have different parent classes" do
code = Puppet::Resource::TypeCollection.new("env")
{"a" => "b", "c" => "d"}.each do |parent, child|
code.add Puppet::Resource::Type.new(:hostclass, parent)
code.add Puppet::Resource::Type.new(:hostclass, child, :parent => parent)
end
lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(ArgumentError)
end
it "should copy the other class's parent if it has not parent" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
parent = Puppet::Resource::Type.new(:hostclass, "parent")
source = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "parent")
dest.merge(source)
dest.parent.should == "parent"
end
it "should copy the other class's documentation as its docs if it has no docs" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness")
dest.merge(source)
dest.doc.should == "yayness"
end
it "should append the other class's docs to its docs if it has any" do
dest = Puppet::Resource::Type.new(:hostclass, "bar", :doc => "fooness")
source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness")
dest.merge(source)
dest.doc.should == "foonessyayness"
end
it "should turn its code into an ASTArray if necessary" do
dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => code("foo"))
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar"))
dest.merge(source)
dest.code.should be_instance_of(Puppet::Parser::AST::ASTArray)
end
it "should set the other class's code as its code if it has none" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar"))
dest.merge(source)
dest.code.value.should == "bar"
end
it "should append the other class's code to its code if it has any" do
dcode = Puppet::Parser::AST::ASTArray.new :children => [code("dest")]
dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => dcode)
scode = Puppet::Parser::AST::ASTArray.new :children => [code("source")]
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => scode)
dest.merge(source)
dest.code.children.collect { |l| l.value }.should == %w{dest source}
end
end
end
diff --git a/spec/unit/type.rb b/spec/unit/type.rb
index 97dc113a6..73f249faa 100755
--- a/spec/unit/type.rb
+++ b/spec/unit/type.rb
@@ -1,482 +1,494 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
describe Puppet::Type do
it "should include the Cacher module" do
Puppet::Type.ancestors.should be_include(Puppet::Util::Cacher)
end
+ it "should consider a parameter to be valid if it is a valid parameter" do
+ Puppet::Type.type(:mount).should be_valid_parameter(:path)
+ end
+
+ it "should consider a parameter to be valid if it is a valid property" do
+ Puppet::Type.type(:mount).should be_valid_parameter(:fstype)
+ end
+
+ it "should consider a parameter to be valid if it is a valid metaparam" do
+ Puppet::Type.type(:mount).should be_valid_parameter(:noop)
+ end
+
it "should use its catalog as its expirer" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.catalog = catalog
resource.expirer.should equal(catalog)
end
it "should do nothing when asked to expire when it has no catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
lambda { resource.expire }.should_not raise_error
end
it "should be able to retrieve a property by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve a parameter by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name))
end
it "should be able to retrieve a property by name using the :parameter method" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve all set properties" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
props = resource.properties
props.should_not be_include(nil)
[:fstype, :ensure, :pass].each do |name|
props.should be_include(resource.parameter(name))
end
end
it "should have a method for setting default values for resources" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:set_default)
end
it "should do nothing for attributes that have no defaults and no specified value" do
Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil
end
it "should have a method for adding tags" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:tags)
end
it "should use the tagging module" do
Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging)
end
it "should delegate to the tagging module when tags are added" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag).with(:mount)
resource.expects(:tag).with(:tag1, :tag2)
resource.tags = [:tag1,:tag2]
end
it "should add the current type as tag" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag)
resource.expects(:tag).with(:mount)
resource.tags = [:tag1,:tag2]
end
it "should have a method to know if the resource is exported" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:exported?)
end
it "should have a method to know if the resource is virtual" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:virtual?)
end
it "should consider its version to be its catalog version" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
resource.version.should == 50
end
it "should consider its version to be zero if it has no catalog" do
Puppet::Type.type(:mount).new(:name => "foo").version.should == 0
end
it "should provide source_descriptors" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
resource.source_descriptors.should == {:version=>50, :tags=>["mount", "foo"], :path=>"/Mount[foo]"}
end
it "should consider its type to be the name of its class" do
Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount
end
describe "when creating an event" do
before do
@resource = Puppet::Type.type(:mount).new :name => "foo"
end
it "should have the resource's reference as the resource" do
@resource.event.resource.should == "Mount[foo]"
end
it "should have the resource's log level as the default log level" do
@resource[:loglevel] = :warning
@resource.event.default_log_level.should == :warning
end
{:file => "/my/file", :line => 50, :tags => %{foo bar}, :version => 50}.each do |attr, value|
it "should set the #{attr}" do
@resource.stubs(attr).returns value
@resource.event.send(attr).should == value
end
end
it "should allow specification of event attributes" do
@resource.event(:status => "noop").status.should == "noop"
end
end
describe "when choosing a default provider" do
it "should choose the provider with the highest specificity" do
# Make a fake type
type = Puppet::Type.newtype(:defaultprovidertest) do
newparam(:name) do end
end
basic = type.provide(:basic) {}
greater = type.provide(:greater) {}
basic.stubs(:specificity).returns 1
greater.stubs(:specificity).returns 2
type.defaultprovider.should equal(greater)
end
end
describe "when initializing" do
describe "and passed a TransObject" do
it "should fail" do
trans = Puppet::TransObject.new("/foo", :mount)
lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError)
end
end
describe "and passed a Puppet::Resource instance" do
it "should set its title to the title of the resource if the resource type is equal to the current type" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"})
Puppet::Type.type(:mount).new(resource).title.should == "/foo"
end
it "should set its title to the resource reference if the resource type is not equal to the current type" do
resource = Puppet::Resource.new(:user, "foo")
Puppet::Type.type(:mount).new(resource).title.should == "User[foo]"
end
[:line, :file, :catalog, :exported, :virtual].each do |param|
it "should copy '#{param}' from the resource if present" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.send(param.to_s + "=", "foo")
resource.send(param.to_s + "=", "foo")
Puppet::Type.type(:mount).new(resource).send(param).should == "foo"
end
end
it "should copy any tags from the resource" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.tag "one", "two"
tags = Puppet::Type.type(:mount).new(resource).tags
tags.should be_include("one")
tags.should be_include("two")
end
it "should copy the resource's parameters as its own" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"})
params = Puppet::Type.type(:mount).new(resource).to_hash
params[:fstype].should == "boo"
params[:atboot].should == true
end
end
describe "and passed a Hash" do
it "should extract the title from the hash" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as strings" do
Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as symbols" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should use the name from the hash as the title if no explicit title is provided" do
Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
Puppet::Type.type(:file).new(:path => "/yay").title.should == "/yay"
end
it "should fail if the namevar is not equal to :name and both :name and the namevar are provided" do
lambda { Puppet::Type.type(:file).new(:path => "/yay", :name => "/foo") }.should raise_error(Puppet::Error)
@type.stubs(:namevar).returns :myname
end
[:catalog].each do |param|
it "should extract '#{param}' from the hash if present" do
Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo"
end
end
it "should use any remaining hash keys as its parameters" do
resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo")
resource[:fstype].must == "boo"
resource[:atboot].must == true
end
end
it "should fail if any invalid attributes have been provided" do
lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error)
end
it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do
resource = Puppet::Resource.new(:mount, "/foo")
Puppet::Type.type(:mount).new(resource).name.should == "/foo"
end
it "should fail if no title, name, or namevar are provided" do
lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error)
end
it "should set the attributes in the order returned by the class's :allattrs method" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end
Puppet::Type.type(:mount).new(resource)
set[-1].should == :noop
set[-2].should == :atboot
end
it "should always set the name and then default provider before anything else" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end
Puppet::Type.type(:mount).new(resource)
set[0].should == :name
set[1].should == :provider
end
# This one is really hard to test :/
it "should each default immediately if no value is provided" do
defaults = []
Puppet::Type.type(:package).any_instance.stubs(:set_default).with { |value| defaults << value; true }
Puppet::Type.type(:package).new :name => "whatever"
defaults[0].should == :provider
end
it "should retain a copy of the originally provided parameters" do
Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false}
end
it "should delete the name via the namevar from the originally provided parameters" do
Puppet::Type.type(:file).new(:name => "/foo").original_parameters[:path].should be_nil
end
end
it "should have a class method for converting a hash into a Puppet::Resource instance" do
Puppet::Type.type(:mount).must respond_to(:hash2resource)
end
describe "when converting a hash to a Puppet::Resource instance" do
before do
@type = Puppet::Type.type(:mount)
end
it "should treat a :title key as the title of the resource" do
@type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo"
end
it "should use the name from the hash as the title if no explicit title is provided" do
@type.hash2resource(:name => "foo").title.should == "foo"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
@type.stubs(:namevar).returns :myname
@type.hash2resource(:myname => "foo").title.should == "foo"
end
it "should fail if the namevar is not equal to :name and both :name and the namevar are provided" do
@type.stubs(:namevar).returns :myname
lambda { @type.hash2resource(:myname => "foo", :name => 'bar') }.should raise_error(Puppet::Error)
end
[:catalog].each do |attr|
it "should use any provided #{attr}" do
@type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh"
end
end
it "should set all provided parameters on the resource" do
@type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"}
end
it "should not set the title as a parameter on the resource" do
@type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil
end
it "should not set the catalog as a parameter on the resource" do
@type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil
end
it "should treat hash keys equivalently whether provided as strings or symbols" do
resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo")
resource.title.should == "eh"
resource[:name].should == "foo"
resource[:fstype].should == "boo"
end
end
describe "when retrieving current property values" do
before do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.property(:ensure).stubs(:retrieve).returns :absent
end
it "should fail if its provider is unsuitable" do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.provider.class.expects(:suitable?).returns false
lambda { @resource.retrieve }.should raise_error(Puppet::Error)
end
it "should return a Puppet::Resource instance with its type and title set appropriately" do
result = @resource.retrieve
result.should be_instance_of(Puppet::Resource)
result.type.should == "Mount"
result.title.should == "foo"
end
it "should set the name of the returned resource if its own name and title differ" do
@resource[:name] = "my name"
@resource.title = "other name"
@resource.retrieve[:name].should == "my name"
end
it "should provide a value for all set properties" do
values = @resource.retrieve
[:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil }
end
it "should provide a value for 'ensure' even if no desired value is provided" do
@resource = Puppet::Type.type(:file).new(:path => "/my/file/that/can't/exist")
end
it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do
@resource.property(:ensure).expects(:retrieve).returns :absent
@resource.property(:fstype).expects(:retrieve).never
@resource.retrieve[:fstype].should == :absent
end
it "should include the result of retrieving each property's current value if the resource is present" do
@resource.property(:ensure).expects(:retrieve).returns :present
@resource.property(:fstype).expects(:retrieve).returns 15
@resource.retrieve[:fstype] == 15
end
end
describe "when in a catalog" do
before do
@catalog = Puppet::Resource::Catalog.new
@container = Puppet::Type.type(:component).new(:name => "container")
@one = Puppet::Type.type(:file).new(:path => "/file/one")
@two = Puppet::Type.type(:file).new(:path => "/file/two")
@catalog.add_resource @container
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.add_edge @container, @one
@catalog.add_edge @container, @two
end
it "should have no parent if there is no in edge" do
@container.parent.should be_nil
end
it "should set its parent to its in edge" do
@one.parent.ref.should == @container.ref
end
after do
@catalog.clear(true)
end
end
describe "when managing relationships" do
end
end
describe Puppet::Type::RelationshipMetaparam do
it "should be a subclass of Puppet::Parameter" do
Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter)
end
it "should be able to produce a list of subclasses" do
Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses)
end
describe "when munging relationships" do
before do
@resource = Puppet::Type.type(:mount).new :name => "/foo"
@metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource
end
it "should accept Puppet::Resource::Reference instances" do
ref = Puppet::Resource::Reference.new(:file, "/foo")
@metaparam.munge(ref)[0].should equal(ref)
end
it "should turn any string into a Puppet::Resource::Reference" do
@metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource::Reference)
end
end
it "should be able to validate relationships" do
Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship)
end
it "should fail if any specified resource is not found in the catalog" do
catalog = mock 'catalog'
resource = stub 'resource', :catalog => catalog, :ref => "resource"
param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]})
catalog.expects(:resource).with("Foo[bar]").returns "something"
catalog.expects(:resource).with("Class[test]").returns nil
param.expects(:fail).with { |string| string.include?("Class[test]") }
param.validate_relationship
end
end
diff --git a/test/language/resource.rb b/test/language/resource.rb
index 69e82fde4..c1d116f9e 100755
--- a/test/language/resource.rb
+++ b/test/language/resource.rb
@@ -1,172 +1,172 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'puppettest'
require 'puppettest/resourcetesting'
class TestResource < PuppetTest::TestCase
include PuppetTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
Parser = Puppet::Parser
AST = Parser::AST
Resource = Puppet::Parser::Resource
Reference = Puppet::Parser::Resource::Reference
def setup
super
Puppet[:trace] = false
end
def teardown
mocha_verify
end
# Make sure we paramcheck our params
def test_validate
res = mkresource
params = res.instance_variable_get("@params")
params[:one] = :two
params[:three] = :four
res.expects(:paramcheck).with(:one)
res.expects(:paramcheck).with(:three)
res.send(:validate)
end
def test_set_parameter
res = mkresource
params = res.instance_variable_get("@params")
# First test the simple case: It's already a parameter
param = stub('param', :name => "pname")
param.expects(:is_a?).with(Resource::Param).returns(true)
res.send(:set_parameter, param)
assert_equal(param, params["pname"], "Parameter was not added to hash")
# Now the case where there's no value but it's not a param
param = mock('param')
param.expects(:is_a?).with(Resource::Param).returns(false)
assert_raise(ArgumentError, "Did not fail when a non-param was passed") do
res.send(:set_parameter, param)
end
# and the case where a value is passed in
param = stub :name => "pname", :value => "whatever"
Resource::Param.expects(:new).with(:name => "pname", :value => "myvalue", :source => res.source).returns(param)
res.send(:set_parameter, "pname", "myvalue")
assert_equal(param, params["pname"], "Did not put param in hash")
end
def test_paramcheck
# There are three cases here:
# It's a valid parameter
res = mkresource
ref = mock('ref')
res.instance_variable_set("@ref", ref)
klass = mock("class")
ref.expects(:typeclass).returns(klass).times(4)
- klass.expects(:validattr?).with("good").returns(true)
+ klass.expects(:valid_parameter?).with("good").returns(true)
assert(res.send(:paramcheck, :good), "Did not allow valid param")
# It's name or title
- klass.expects(:validattr?).with("name").returns(false)
+ klass.expects(:valid_parameter?).with("name").returns(false)
assert(res.send(:paramcheck, :name), "Did not allow name")
- klass.expects(:validattr?).with("title").returns(false)
+ klass.expects(:valid_parameter?).with("title").returns(false)
assert(res.send(:paramcheck, :title), "Did not allow title")
# It's not actually allowed
- klass.expects(:validattr?).with("other").returns(false)
+ klass.expects(:valid_parameter?).with("other").returns(false)
res.expects(:fail)
ref.expects(:type)
res.send(:paramcheck, :other)
end
def test_evaluate
# First try the most common case, we're not a builtin type.
res = mkresource
ref = res.instance_variable_get("@ref")
type = mock("type")
ref.expects(:definedtype).returns(type)
res.expects(:finish)
res.scope = mock("scope")
type.expects(:evaluate_code).with(res)
res.evaluate
end
def test_proxymethods
res = Parser::Resource.new :type => "evaltest", :title => "yay",
:source => mock("source"), :scope => mkscope
assert_equal("Evaltest", res.type)
assert_equal("yay", res.title)
assert_equal(false, res.builtin?)
end
# This is a bit of a weird one -- the user should not actually know
# that components exist, so we want references to act like they're not
# builtin
def test_components_are_not_builtin
ref = Parser::Resource::Reference.new(:type => "component", :title => "yay")
assert_nil(ref.builtintype, "Definition was considered builtin")
end
# The second part of #539 - make sure resources pass the arguments
# correctly.
def test_title_with_definitions
parser = mkparser
define = parser.newdefine "yayness",
:code => resourcedef("file", "/tmp",
"owner" => varref("name"), "mode" => varref("title"))
klass = parser.find_hostclass("", "")
should = {:name => :owner, :title => :mode}
[
{:name => "one", :title => "two"},
{:title => "three"},
].each do |hash|
config = mkcompiler parser
args = {:type => "yayness", :title => hash[:title],
:source => klass, :scope => config.topscope}
if hash[:name]
args[:params] = {:name => hash[:name]}
else
args[:params] = {} # override the defaults
end
res = nil
assert_nothing_raised("Could not create res with %s" % hash.inspect) do
res = mkresource(args)
end
assert_nothing_raised("Could not eval res with %s" % hash.inspect) do
res.evaluate
end
made = config.topscope.findresource("File[/tmp]")
assert(made, "Did not create resource with %s" % hash.inspect)
should.each do |orig, param|
assert_equal(hash[orig] || hash[:title], made[param],
"%s was not set correctly with %s" % [param, hash.inspect])
end
end
end
# part of #629 -- the undef keyword. Make sure 'undef' params get skipped.
def test_undef_and_to_hash
res = mkresource :type => "file", :title => "/tmp/testing",
:source => mock("source"), :scope => mkscope,
:params => {:owner => :undef, :mode => "755"}
hash = nil
assert_nothing_raised("Could not convert resource with undef to hash") do
hash = res.to_hash
end
assert_nil(hash[:owner], "got a value for an undef parameter")
end
end
diff --git a/test/lib/puppettest/fakes.rb b/test/lib/puppettest/fakes.rb
index db6ca4d80..0aedb59f6 100644
--- a/test/lib/puppettest/fakes.rb
+++ b/test/lib/puppettest/fakes.rb
@@ -1,204 +1,204 @@
require 'puppettest'
module PuppetTest
# A baseclass for the faketypes.
class FakeModel
include Puppet::Util
class << self
attr_accessor :name, :realresource
@name = :fakeresource
end
def self.namevar
@realresource.namevar
end
def self.validproperties
Puppet::Type.type(@name).validproperties
end
def self.validproperty?(name)
Puppet::Type.type(@name).validproperty?(name)
end
def self.to_s
"Fake%s" % @name.to_s.capitalize
end
def [](param)
if @realresource.attrtype(param) == :property
@is[param]
else
@params[param]
end
end
def []=(param, value)
param = symbolize(param)
- unless @realresource.validattr?(param)
+ unless @realresource.valid_parameter?(param)
raise Puppet::DevError, "Invalid attribute %s for %s" %
[param, @realresource.name]
end
if @realresource.attrtype(param) == :property
@should[param] = value
else
@params[param] = value
end
end
def initialize(name)
@realresource = Puppet::Type.type(self.class.name)
raise "Could not find type #{self.class.name}" unless @realresource
@is = {}
@should = {}
@params = {}
self[@realresource.namevar] = name
end
def inspect
"%s(%s)" % [self.class.to_s.sub(/.+::/, ''), super()]
end
def is(param)
@is[param]
end
def should(param)
@should[param]
end
def to_hash
hash = @params.dup
[@is, @should].each do |h|
h.each do |p, v|
hash[p] = v
end
end
hash
end
def name
self[:name]
end
end
class FakeProvider
attr_accessor :resource
class << self
attr_accessor :name, :resource_type, :methods
end
# A very low number, so these never show up as defaults via the standard
# algorithms.
def self.defaultnum
-50
end
# Set up methods to fake things
def self.apimethods(*ary)
@resource_type.validproperties.each do |property|
ary << property unless ary.include? property
end
attr_accessor(*ary)
@methods = ary
end
def self.default?
false
end
def self.initvars
@calls = Hash.new do |hash, key|
hash[key] = 0
end
end
def self.source
self.name
end
def self.supports_parameter?(param)
true
end
def self.suitable?
true
end
def clear
@resource = nil
end
def initialize(resource)
@resource = resource
end
def properties
self.class.resource_type.validproperties.inject({}) do |props, name|
props[name] = self.send(name) || :absent
props
end
end
end
class FakeParsedProvider < FakeProvider
def hash
ret = {}
instance_variables.each do |v|
v = v.sub("@", '')
if val = self.send(v)
ret[v.intern] = val
end
end
return ret
end
def store(hash)
hash.each do |n, v|
method = n.to_s + "="
if respond_to? method
send(method, v)
end
end
end
end
@@fakeresources = {}
@@fakeproviders = {}
def fakeresource(type, name, options = {})
type = type.intern if type.is_a? String
unless @@fakeresources.include? type
@@fakeresources[type] = Class.new(FakeModel)
@@fakeresources[type].name = type
resource = Puppet::Type.type(type)
raise("Could not find type %s" % type) unless resource
@@fakeresources[type].realresource = resource
end
obj = @@fakeresources[type].new(name)
options.each do |name, val|
obj[name] = val
end
obj
end
module_function :fakeresource
def fakeprovider(type, resource)
type = type.intern if type.is_a? String
unless @@fakeproviders.include? type
@@fakeproviders[type] = Class.new(FakeModel) do
@name = type
end
end
@@fakeproviders[type].new(resource)
end
module_function :fakeprovider
end