diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index 29d60fc66..c97f93b23 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -1,304 +1,298 @@
require 'puppet/util/methodhelper'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/docs'
-require 'puppet/util/cacher'
class Puppet::Parameter
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::MethodHelper
- include Puppet::Util::Cacher
require 'puppet/parameter/value_collection'
class << self
include Puppet::Util
include Puppet::Util::Docs
attr_reader :validater, :munger, :name, :default, :required_features, :value_collection
attr_accessor :metaparam
# Define the default value for a given parameter or parameter. This
# means that 'nil' is an invalid default value. This defines
# the 'default' instance method.
def defaultto(value = nil, &block)
if block
define_method(:default, &block)
else
if value.nil?
raise Puppet::DevError,
"Either a default value or block must be provided"
end
define_method(:default) do value end
end
end
# Return a documentation string. If there are valid values,
# then tack them onto the string.
def doc
@doc ||= ""
unless defined?(@addeddocvals)
@doc += value_collection.doc
if f = self.required_features
@doc += " Requires features #{f.flatten.collect { |f| f.to_s }.join(" ")}."
end
@addeddocvals = true
end
@doc
end
def nodefault
undef_method :default if public_method_defined? :default
end
# Store documentation for this parameter.
def desc(str)
@doc = str
end
def initvars
@value_collection = ValueCollection.new
end
# This is how we munge the value. Basically, this is our
# opportunity to convert the value from one form into another.
def munge(&block)
# I need to wrap the unsafe version in begin/rescue parameterments,
# but if I directly call the block then it gets bound to the
# class's context, not the instance's, thus the two methods,
# instead of just one.
define_method(:unsafe_munge, &block)
end
# Does the parameter supports reverse munge?
# This will be called when something wants to access the parameter
# in a canonical form different to what the storage form is.
def unmunge(&block)
define_method(:unmunge, &block)
end
# Mark whether we're the namevar.
def isnamevar
@isnamevar = true
@required = true
end
# Is this parameter the namevar? Defaults to false.
def isnamevar?
@isnamevar
end
# This parameter is required.
def isrequired
@required = true
end
# Specify features that are required for this parameter to work.
def required_features=(*args)
@required_features = args.flatten.collect { |a| a.to_s.downcase.intern }
end
# Is this parameter required? Defaults to false.
def required?
@required
end
# Verify that we got a good value
def validate(&block)
define_method(:unsafe_validate, &block)
end
# Define a new value for our parameter.
def newvalues(*names)
@value_collection.newvalues(*names)
end
def aliasvalue(name, other)
@value_collection.aliasvalue(name, other)
end
end
# Just a simple method to proxy instance methods to class methods
def self.proxymethods(*values)
values.each { |val|
define_method(val) do
self.class.send(val)
end
}
end
# And then define one of these proxies for each method in our
# ParamHandler class.
proxymethods("required?", "isnamevar?")
attr_accessor :resource
# LAK 2007-05-09: Keep the @parent around for backward compatibility.
attr_accessor :parent
[:line, :file, :version].each do |param|
define_method(param) do
resource.send(param)
end
end
def devfail(msg)
self.fail(Puppet::DevError, msg)
end
- def expirer
- resource.catalog
- end
-
def fail(*args)
type = nil
if args[0].is_a?(Class)
type = args.shift
else
type = Puppet::Error
end
error = type.new(args.join(" "))
error.line = @resource.line if @resource and @resource.line
error.file = @resource.file if @resource and @resource.file
raise error
end
# Basic parameter initialization.
def initialize(options = {})
options = symbolize_options(options)
if resource = options[:resource]
self.resource = resource
options.delete(:resource)
else
raise Puppet::DevError, "No resource set for #{self.class.name}"
end
set_options(options)
end
def log(msg)
send_log(resource[:loglevel], msg)
end
# Is this parameter a metaparam?
def metaparam?
self.class.metaparam
end
# each parameter class must define the name method, and parameter
# instances do not change that name this implicitly means that a given
# object can only have one parameter instance of a given parameter
# class
def name
self.class.name
end
# for testing whether we should actually do anything
def noop
@noop ||= false
tmp = @noop || self.resource.noop || Puppet[:noop] || false
#debug "noop is #{tmp}"
tmp
end
# return the full path to us, for logging and rollback; not currently
# used
def pathbuilder
if @resource
return [@resource.pathbuilder, self.name]
else
return [self.name]
end
end
# If the specified value is allowed, then munge appropriately.
# If the developer uses a 'munge' hook, this method will get overridden.
def unsafe_munge(value)
self.class.value_collection.munge(value)
end
# no unmunge by default
def unmunge(value)
value
end
# A wrapper around our munging that makes sure we raise useful exceptions.
def munge(value)
begin
ret = unsafe_munge(value)
rescue Puppet::Error => detail
Puppet.debug "Reraising #{detail}"
raise
rescue => detail
raise Puppet::DevError, "Munging failed for value #{value.inspect} in class #{self.name}: #{detail}", detail.backtrace
end
ret
end
# Verify that the passed value is valid.
# If the developer uses a 'validate' hook, this method will get overridden.
def unsafe_validate(value)
self.class.value_collection.validate(value)
end
# A protected validation method that only ever raises useful exceptions.
def validate(value)
begin
unsafe_validate(value)
rescue ArgumentError => detail
fail detail.to_s
rescue Puppet::Error, TypeError
raise
rescue => detail
raise Puppet::DevError, "Validate method failed for class #{self.name}: #{detail}", detail.backtrace
end
end
def remove
@resource = nil
end
def value
unmunge(@value) unless @value.nil?
end
# Store the value provided. All of the checking should possibly be
# late-binding (e.g., users might not exist when the value is assigned
# but might when it is asked for).
def value=(value)
validate(value)
@value = munge(value)
end
# Retrieve the resource's provider. Some types don't have providers, in which
# case we return the resource object itself.
def provider
@resource.provider
end
# The properties need to return tags so that logs correctly collect them.
def tags
unless defined?(@tags)
@tags = []
# This might not be true in testing
@tags = @resource.tags if @resource.respond_to? :tags
@tags << self.name.to_s
end
@tags
end
def to_s
name.to_s
end
end
require 'puppet/parameter/path'
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index be6302595..ca9f25a5a 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -1,663 +1,643 @@
require 'puppet/node'
require 'puppet/indirector'
require 'puppet/simple_graph'
require 'puppet/transaction'
-require 'puppet/util/cacher'
require 'puppet/util/pson'
require 'puppet/util/tagging'
# This class models a node catalog. It is the thing
# meant to be passed from server to client, and it contains all
# of the information in the catalog, including the resources
# and the relationships between them.
class Puppet::Resource::Catalog < Puppet::SimpleGraph
class DuplicateResourceError < Puppet::Error; end
extend Puppet::Indirector
indirects :catalog, :terminus_setting => :catalog_terminus
include Puppet::Util::Tagging
extend Puppet::Util::Pson
- include Puppet::Util::Cacher::Expirer
# The host name this is a catalog for.
attr_accessor :name
# The catalog version. Used for testing whether a catalog
# is up to date.
attr_accessor :version
# How long this catalog took to retrieve. Used for reporting stats.
attr_accessor :retrieval_duration
# Whether this is a host catalog, which behaves very differently.
# In particular, reports are sent, graphs are made, and state is
# stored in the state database. If this is set incorrectly, then you often
# end up in infinite loops, because catalogs are used to make things
# that the host catalog needs.
attr_accessor :host_config
# Whether this catalog was retrieved from the cache, which affects
# whether it is written back out again.
attr_accessor :from_cache
# Some metadata to help us compile and generally respond to the current state.
attr_accessor :client_version, :server_version
# Add classes to our class list.
def add_class(*classes)
classes.each do |klass|
@classes << klass
end
# Add the class names as tags, too.
tag(*classes)
end
def title_key_for_ref( ref )
ref =~ /^([-\w:]+)\[(.*)\]$/m
[$1, $2]
end
# Add a resource to our graph and to our resource table.
# This is actually a relatively complicated method, because it handles multiple
# aspects of Catalog behaviour:
# * Add the resource to the resource table
# * Add the resource to the resource graph
# * Add the resource to the relationship graph
# * Add any aliases that make sense for the resource (e.g., name != title)
def add_resource(*resource)
add_resource(*resource[0..-2]) if resource.length > 1
resource = resource.pop
raise ArgumentError, "Can only add objects that respond to :ref, not instances of #{resource.class}" unless resource.respond_to?(:ref)
fail_on_duplicate_type_and_title(resource)
title_key = title_key_for_ref(resource.ref)
@transient_resources << resource if applying?
@resource_table[title_key] = resource
# If the name and title differ, set up an alias
if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.respond_to?(:isomorphic?) and resource.name != resource.title
self.alias(resource, resource.uniqueness_key) if resource.isomorphic?
end
resource.catalog = self if resource.respond_to?(:catalog=)
add_vertex(resource)
@relationship_graph.add_vertex(resource) if @relationship_graph
end
# Create an alias for a resource.
def alias(resource, key)
resource.ref =~ /^(.+)\[/
class_name = $1 || resource.class.name
newref = [class_name, key].flatten
if key.is_a? String
ref_string = "#{class_name}[#{key}]"
return if ref_string == resource.ref
end
# LAK:NOTE It's important that we directly compare the references,
# because sometimes an alias is created before the resource is
# added to the catalog, so comparing inside the below if block
# isn't sufficient.
if existing = @resource_table[newref]
return if existing == resource
resource_definition = " at #{resource.file}:#{resource.line}" if resource.file and resource.line
existing_definition = " at #{existing.file}:#{existing.line}" if existing.file and existing.line
msg = "Cannot alias #{resource.ref} to #{key.inspect}#{resource_definition}; resource #{newref.inspect} already defined#{existing_definition}"
raise ArgumentError, msg
end
@resource_table[newref] = resource
@aliases[resource.ref] ||= []
@aliases[resource.ref] << newref
end
# Apply our catalog to the local host. Valid options
# are:
# :tags - set the tags that restrict what resources run
# during the transaction
# :ignoreschedules - tell the transaction to ignore schedules
# when determining the resources to run
def apply(options = {})
@applying = true
- # Expire all of the resource data -- this ensures that all
- # data we're operating against is entirely current.
- expire
-
Puppet::Util::Storage.load if host_config?
transaction = Puppet::Transaction.new(self, options[:report])
register_report = options[:report].nil?
transaction.tags = options[:tags] if options[:tags]
transaction.ignoreschedules = true if options[:ignoreschedules]
transaction.for_network_device = options[:network_device]
transaction.add_times :config_retrieval => self.retrieval_duration || 0
begin
Puppet::Util::Log.newdestination(transaction.report) if register_report
begin
transaction.evaluate
ensure
Puppet::Util::Log.close(transaction.report) if register_report
end
rescue Puppet::Error => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not apply complete catalog: #{detail}"
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Got an uncaught exception of type #{detail.class}: #{detail}"
ensure
# Don't try to store state unless we're a host config
# too recursive.
Puppet::Util::Storage.store if host_config?
end
yield transaction if block_given?
return transaction
ensure
@applying = false
- cleanup
end
# Are we in the middle of applying the catalog?
def applying?
@applying
end
def clear(remove_resources = true)
super()
# We have to do this so that the resources clean themselves up.
@resource_table.values.each { |resource| resource.remove } if remove_resources
@resource_table.clear
if @relationship_graph
@relationship_graph.clear
@relationship_graph = nil
end
end
def classes
@classes.dup
end
# Create a new resource and register it in the catalog.
def create_resource(type, options)
unless klass = Puppet::Type.type(type)
raise ArgumentError, "Unknown resource type #{type}"
end
return unless resource = klass.new(options)
add_resource(resource)
resource
end
- def dependent_data_expired?(ts)
- if applying?
- return super
- else
- return true
- end
- end
-
# Turn our catalog graph into an old-style tree of TransObjects and TransBuckets.
# LAK:NOTE(20081211): This is a pre-0.25 backward compatibility method.
# It can be removed as soon as xmlrpc is killed.
def extract
top = nil
current = nil
buckets = {}
unless main = resource(:stage, "main")
raise Puppet::DevError, "Could not find 'main' stage; cannot generate catalog"
end
if stages = vertices.find_all { |v| v.type == "Stage" and v.title != "main" } and ! stages.empty?
Puppet.warning "Stages are not supported by 0.24.x client; stage(s) #{stages.collect { |s| s.to_s }.join(', ') } will be ignored"
end
bucket = nil
walk(main, :out) do |source, target|
# The sources are always non-builtins.
unless tmp = buckets[source.to_s]
if tmp = buckets[source.to_s] = source.to_trans
bucket = tmp
else
# This is because virtual resources return nil. If a virtual
# container resource contains realized resources, we still need to get
# to them. So, we keep a reference to the last valid bucket
# we returned and use that if the container resource is virtual.
end
end
bucket = tmp || bucket
if child = target.to_trans
raise "No bucket created for #{source}" unless bucket
bucket.push child
# It's important that we keep a reference to any TransBuckets we've created, so
# we don't create multiple buckets for children.
buckets[target.to_s] = child unless target.builtin?
end
end
# Retrieve the bucket for the top-level scope and set the appropriate metadata.
unless result = buckets[main.to_s]
# This only happens when the catalog is entirely empty.
result = buckets[main.to_s] = main.to_trans
end
result.classes = classes
# Clear the cache to encourage the GC
buckets.clear
result
end
# Make sure all of our resources are "finished".
def finalize
make_default_resources
@resource_table.values.each { |resource| resource.finish }
write_graph(:resources)
end
def host_config?
host_config
end
def initialize(name = nil)
super()
@name = name if name
@classes = []
@resource_table = {}
@transient_resources = []
@applying = false
@relationship_graph = nil
@host_config = true
@aliases = {}
if block_given?
yield(self)
finalize
end
end
# Make the default objects necessary for function.
def make_default_resources
# We have to add the resources to the catalog, or else they won't get cleaned up after
# the transaction.
# First create the default scheduling objects
Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) }
# And filebuckets
if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket
add_resource(bucket) unless resource(bucket.ref)
end
end
# Create a graph of all of the relationships in our catalog.
def relationship_graph
unless @relationship_graph
# It's important that we assign the graph immediately, because
# the debug messages below use the relationships in the
# relationship graph to determine the path to the resources
# spitting out the messages. If this is not set,
# then we get into an infinite loop.
@relationship_graph = Puppet::SimpleGraph.new
# First create the dependency graph
self.vertices.each do |vertex|
@relationship_graph.add_vertex vertex
vertex.builddepends.each do |edge|
@relationship_graph.add_edge(edge)
end
end
# Lastly, add in any autorequires
@relationship_graph.vertices.each do |vertex|
vertex.autorequire(self).each do |edge|
unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones.
unless @relationship_graph.edge?(edge.target, edge.source)
vertex.debug "Autorequiring #{edge.source}"
@relationship_graph.add_edge(edge)
else
vertex.debug "Skipping automatic relationship with #{(edge.source == vertex ? edge.target : edge.source)}"
end
end
end
end
@relationship_graph.write_graph(:relationships) if host_config?
# Then splice in the container information
splice!(@relationship_graph)
@relationship_graph.write_graph(:expanded_relationships) if host_config?
end
@relationship_graph
end
# Impose our container information on another graph by using it
# to replace any container vertices X with a pair of verticies
# { admissible_X and completed_X } such that that
#
# 0) completed_X depends on admissible_X
# 1) contents of X each depend on admissible_X
# 2) completed_X depends on each on the contents of X
# 3) everything which depended on X depens on completed_X
# 4) admissible_X depends on everything X depended on
# 5) the containers and their edges must be removed
#
# Note that this requires attention to the possible case of containers
# which contain or depend on other containers, but has the advantage
# that the number of new edges created scales linearly with the number
# of contained verticies regardless of how containers are related;
# alternatives such as replacing container-edges with content-edges
# scale as the product of the number of external dependences, which is
# to say geometrically in the case of nested / chained containers.
#
Default_label = { :callback => :refresh, :event => :ALL_EVENTS }
def splice!(other)
stage_class = Puppet::Type.type(:stage)
whit_class = Puppet::Type.type(:whit)
component_class = Puppet::Type.type(:component)
containers = vertices.find_all { |v| (v.is_a?(component_class) or v.is_a?(stage_class)) and vertex?(v) }
#
# These two hashes comprise the aforementioned attention to the possible
# case of containers that contain / depend on other containers; they map
# containers to their sentinals but pass other verticies through. Thus we
# can "do the right thing" for references to other verticies that may or
# may not be containers.
#
admissible = Hash.new { |h,k| k }
completed = Hash.new { |h,k| k }
containers.each { |x|
admissible[x] = whit_class.new(:name => "admissible_#{x.ref}", :catalog => self)
completed[x] = whit_class.new(:name => "completed_#{x.ref}", :catalog => self)
}
#
# Implement the six requierments listed above
#
containers.each { |x|
contents = adjacent(x, :direction => :out)
other.add_edge(admissible[x],completed[x]) if contents.empty? # (0)
contents.each { |v|
other.add_edge(admissible[x],admissible[v],Default_label) # (1)
other.add_edge(completed[v], completed[x], Default_label) # (2)
}
# (3) & (5)
other.adjacent(x,:direction => :in,:type => :edges).each { |e|
other.add_edge(completed[e.source],admissible[x],e.label)
other.remove_edge! e
}
# (4) & (5)
other.adjacent(x,:direction => :out,:type => :edges).each { |e|
other.add_edge(completed[x],admissible[e.target],e.label)
other.remove_edge! e
}
}
containers.each { |x| other.remove_vertex! x } # (5)
end
# Remove the resource from our catalog. Notice that we also call
# 'remove' on the resource, at least until resource classes no longer maintain
# references to the resource instances.
def remove_resource(*resources)
resources.each do |resource|
@resource_table.delete(resource.ref)
if aliases = @aliases[resource.ref]
aliases.each { |res_alias| @resource_table.delete(res_alias) }
@aliases.delete(resource.ref)
end
remove_vertex!(resource) if vertex?(resource)
@relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
resource.remove
end
end
# Look a resource up by its reference (e.g., File[/etc/passwd]).
def resource(type, title = nil)
# Always create a resource reference, so that it always canonizes how we
# are referring to them.
if title
res = Puppet::Resource.new(type, title)
else
# If they didn't provide a title, then we expect the first
# argument to be of the form 'Class[name]', which our
# Reference class canonizes for us.
res = Puppet::Resource.new(nil, type)
end
title_key = [res.type, res.title.to_s]
uniqueness_key = [res.type, res.uniqueness_key].flatten
@resource_table[title_key] || @resource_table[uniqueness_key]
end
def resource_refs
resource_keys.collect{ |type, name| name.is_a?( String ) ? "#{type}[#{name}]" : nil}.compact
end
def resource_keys
@resource_table.keys
end
def resources
@resource_table.values.uniq
end
def self.from_pson(data)
result = new(data['name'])
if tags = data['tags']
result.tag(*tags)
end
if version = data['version']
result.version = version
end
if resources = data['resources']
resources = PSON.parse(resources) if resources.is_a?(String)
resources.each do |res|
resource_from_pson(result, res)
end
end
if edges = data['edges']
edges = PSON.parse(edges) if edges.is_a?(String)
edges.each do |edge|
edge_from_pson(result, edge)
end
end
if classes = data['classes']
result.add_class(*classes)
end
result
end
def self.edge_from_pson(result, edge)
# If no type information was presented, we manually find
# the class.
edge = Puppet::Relationship.from_pson(edge) if edge.is_a?(Hash)
unless source = result.resource(edge.source)
raise ArgumentError, "Could not convert from pson: Could not find relationship source #{edge.source.inspect}"
end
edge.source = source
unless target = result.resource(edge.target)
raise ArgumentError, "Could not convert from pson: Could not find relationship target #{edge.target.inspect}"
end
edge.target = target
result.add_edge(edge)
end
def self.resource_from_pson(result, res)
res = Puppet::Resource.from_pson(res) if res.is_a? Hash
result.add_resource(res)
end
PSON.register_document_type('Catalog',self)
def to_pson_data_hash
{
'document_type' => 'Catalog',
'data' => {
'tags' => tags,
'name' => name,
'version' => version,
'resources' => vertices.collect { |v| v.to_pson_data_hash },
'edges' => edges. collect { |e| e.to_pson_data_hash },
'classes' => classes
},
'metadata' => {
'api_version' => 1
}
}
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Convert our catalog into a RAL catalog.
def to_ral
to_catalog :to_ral
end
# Convert our catalog into a catalog of Puppet::Resource instances.
def to_resource
to_catalog :to_resource
end
# filter out the catalog, applying +block+ to each resource.
# If the block result is false, the resource will
# be kept otherwise it will be skipped
def filter(&block)
to_catalog :to_resource, &block
end
# Store the classes in the classfile.
def write_class_file
::File.open(Puppet[:classfile], "w") do |f|
f.puts classes.join("\n")
end
rescue => detail
Puppet.err "Could not create class file #{Puppet[:classfile]}: #{detail}"
end
# Produce the graph files if requested.
def write_graph(name)
# We only want to graph the main host catalog.
return unless host_config?
super
end
private
- def cleanup
- # Expire any cached data the resources are keeping.
- expire
- end
-
# Verify that the given resource isn't defined elsewhere.
def fail_on_duplicate_type_and_title(resource)
# Short-curcuit the common case,
return unless existing_resource = @resource_table[title_key_for_ref(resource.ref)]
# If we've gotten this far, it's a real conflict
msg = "Duplicate definition: #{resource.ref} is already defined"
msg << " in file #{existing_resource.file} at line #{existing_resource.line}" if existing_resource.file and existing_resource.line
msg << "; cannot redefine" if resource.line or resource.file
raise DuplicateResourceError.new(msg)
end
# An abstracted method for converting one catalog into another type of catalog.
# This pretty much just converts all of the resources from one class to another, using
# a conversion method.
def to_catalog(convert)
result = self.class.new(self.name)
result.version = self.version
map = {}
vertices.each do |resource|
next if virtual_not_exported?(resource)
next if block_given? and yield resource
#This is hackity hack for 1094
#Aliases aren't working in the ral catalog because the current instance of the resource
#has a reference to the catalog being converted. . . So, give it a reference to the new one
#problem solved. . .
if resource.class == Puppet::Resource
resource = resource.dup
resource.catalog = result
elsif resource.is_a?(Puppet::TransObject)
resource = resource.dup
resource.catalog = result
elsif resource.is_a?(Puppet::Parser::Resource)
resource = resource.to_resource
resource.catalog = result
end
if resource.is_a?(Puppet::Resource) and convert.to_s == "to_resource"
newres = resource
else
newres = resource.send(convert)
end
# We can't guarantee that resources don't munge their names
# (like files do with trailing slashes), so we have to keep track
# of what a resource got converted to.
map[resource.ref] = newres
result.add_resource newres
end
message = convert.to_s.gsub "_", " "
edges.each do |edge|
# Skip edges between virtual resources.
next if virtual_not_exported?(edge.source)
next if block_given? and yield edge.source
next if virtual_not_exported?(edge.target)
next if block_given? and yield edge.target
unless source = map[edge.source.ref]
raise Puppet::DevError, "Could not find resource #{edge.source.ref} when converting #{message} resources"
end
unless target = map[edge.target.ref]
raise Puppet::DevError, "Could not find resource #{edge.target.ref} when converting #{message} resources"
end
result.add_edge(source, target, edge.label)
end
map.clear
result.add_class(*self.classes)
result.tag(*self.tags)
result
end
def virtual_not_exported?(resource)
resource.respond_to?(:virtual?) and resource.virtual? and (resource.respond_to?(:exported?) and not resource.exported?)
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 4472387d1..803b5c6a0 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,1933 +1,1925 @@
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/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 key_attributes come 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
key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams
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
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)
}
ens
end
def self.apply_to_device
@apply_to = :device
end
def self.apply_to_host
@apply_to = :host
end
def self.apply_to_all
@apply_to = :both
end
def self.apply_to
@apply_to ||= :host
end
def self.can_apply_to(target)
[ target == :device ? :device : :host, :both ].include?(apply_to)
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.
param.required_features = options[:required_features] if options[:required_features]
handle_param_options(name, options)
param.metaparam = true
param
end
def self.key_attribute_parameters
@key_attribute_parameters ||= (
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
)
end
def self.key_attributes
key_attribute_parameters.collect { |p| p.name }
end
def self.title_patterns
case key_attributes.length
when 0; []
when 1;
identity = lambda {|x| x}
[ [ /(.*)/m, [ [key_attributes.first, identity ] ] ] ]
else
raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes"
end
end
def uniqueness_key
self.class.key_attributes.sort_by { |attribute_name| attribute_name.to_s }.map{ |attribute_name| self[attribute_name] }
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.
param.required_features = options[:required_features] if options[:required_features]
param.isnamevar if options[:namevar]
param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(#{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 #{options.inspect}"
end
raise Puppet::DevError, "Class #{self.name} already has a property named #{name}" if @validproperties.include?(name)
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
class_eval(&block) if block
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
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)
@validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name))
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
@validproperties.include?(name) && @validproperties[name]
end
# Return the list of validproperties
def self.validproperties
return {} unless defined?(@parameters)
@validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters)
!!(@paramhash.include?(name) or @@metaparamhash.include?(name))
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
false
end
#
# The name_var is the key_attribute in the case that there is only one.
#
def name_var
key_attributes = self.class.key_attributes
(key_attributes.length == 1) && key_attributes.first
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)
fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name)
if name == :name && nv = name_var
name = nv
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)
fail("Invalid parameter #{name}") unless self.class.validattr?(name)
if name == :name && nv = name_var
name = nv
end
raise Puppet::Error.new("Got nil value for #{name}") if value.nil?
property = self.newattr(name)
if property
begin
# make sure the parameter doesn't have any errors
property.value = value
rescue => detail
error = Puppet::Error.new("Parameter #{name} failed: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
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}.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)
(prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil
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)
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}"
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
return @parameters[name] if @parameters.include?(name)
@parameters[name] = klass.new(:resource => self)
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)
name = name.intern unless name.is_a? Symbol
@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)
(obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)) ? obj : nil
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)
(obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil
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 @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.
def depthfirst?
false
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
self.provider.flush if self.provider and self.provider.respond_to?(:flush)
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 '#{property.name}'"
end
ensureis = is[property]
if property.safe_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 '#{property.name}'"
end
propis = is[property]
unless property.safe_insync?(propis)
property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}")
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("#{self} sync status is #{insync}")
insync
end
# retrieve the current value of all contained properties
def retrieve
fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
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
def retrieve_resource
resource = retrieve
resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash
resource
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 we're not a host_config, we're almost certainly part of
# Settings, and we want to ignore 'noop'
return false if catalog and ! catalog.host_config?
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 '#{newobj.class.name}[#{name}]' already exists"
msg += ("in file #{object.file} at line #{object.line}") if exobj.file and exobj.line
msg += ("and cannot be redefined in file #{object.file} at line #{object.line}") if object.file and object.line
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 #{name}: object already exists"
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object #{@aliases[name].name} already has alias #{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
@aliases.clear if defined?(@aliases)
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)
@objects.delete(resource.title) if @objects.include?(resource.title)
@aliases.delete(resource.title) if @aliases.include?(resource.title)
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"
@objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty?
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
all_properties = self.properties.find_all do |property|
provider.supports_parameter?(property)
end.collect do |property|
property.name
end
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
result = new(:name => instance.name, :provider => instance)
properties.each { |name| result.newattr(name) }
result
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.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
title = hash.delete(:title)
title ||= hash[:name]
title ||= hash[key_attributes.first] if key_attributes.length == 1
raise Puppet::Error, "Title or name must be provided" unless title
# 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
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(:audit) do
desc "Marks a subset of this resource's unmanaged attributes for auditing. Accepts an
attribute name, an array of attribute names, or `all`.
Auditing a resource attribute has two effects: First, whenever a catalog
is applied with puppet apply or puppet agent, Puppet will check whether
that attribute of the resource has been modified, comparing its current
value to the previous run; any change will be logged alongside any actions
performed by Puppet while applying the catalog.
Secondly, marking a resource attribute for auditing will include that
attribute in inspection reports generated by puppet inspect; see the
puppet inspect documentation for more details.
Managed attributes for a resource can also be audited, but note that
changes made by Puppet will be logged as additional modifications. (I.e.
if a user manually edits a file whose contents are audited and managed,
puppet agent's next two runs will both log an audit notice: the first run
will log the user's edit and then revert the file to the desired state,
and the second run will log the edit made by Puppet.)"
validate do |list|
list = Array(list).collect {|p| p.to_sym}
unless list == [:all]
list.each do |param|
next if @resource.class.validattr?(param)
fail "Cannot audit #{param}: not a valid attribute for #{resource}"
end
end
end
munge do |args|
properties_to_audit(args).each do |param|
next unless resource.class.validproperty?(param)
resource.newattr(param)
end
end
def all_properties
resource.class.properties.find_all do |property|
resource.provider.nil? or resource.provider.class.supports_parameter?(property)
end.collect do |property|
property.name
end
end
def properties_to_audit(list)
if !list.kind_of?(Array) && list.to_sym == :all
list = all_properties
else
list = Array(list).collect { |p| p.to_sym }
end
end
end
newmetaparam(:check) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This parameter has been deprecated in favor of 'audit'."
munge do |args|
resource.warning "'check' attribute is deprecated; use 'audit' instead"
resource[:audit] = args
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 title:
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 title,
and the library sets that as an alias for the file so the dependency
lookup in `Service['sshd']` works. You can use this metaparameter 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 [Language Guide](http://docs.puppetlabs.com/guides/language_guide.html) for more information.
"
munge do |aliases|
aliases = [aliases] unless aliases.is_a?(Array)
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("#{@resource.title} can not create alias #{other}: object already exists")
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:
puppet agent --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)
ref
else
Puppet::Resource.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 #{description} #{ref} for #{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 '#{reference}' of #{@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 #{related_resource.ref}")
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires #{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 "References to 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 "References to one or more objects that this object depends on. This
metaparameter creates a dependency relationship like **require,**
and also causes the dependent object to be refreshed when the
subscribed object is changed. For instance:
class nagios {
file { 'nagconf':
path => \"/etc/nagios/nagios.conf\"
source => \"puppet://server/module/nagios.conf\",
}
service { 'nagios':
ensure => running,
subscribe => File['nagconf']
}
}
Currently the `exec`, `mount` and `service` types support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{References to one or more objects that depend on this object. 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 %{References to one or more objects that depend on this object. This
parameter is the opposite of **subscribe** --- it creates a
dependency relationship like **before,** and also causes the
dependent object(s) to be refreshed when this object is changed. For
instance:
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
newmetaparam(:stage) do
desc %{Which run stage a given resource should reside in. This just creates
a dependency on or from the named milestone. For instance, saying that
this is in the 'bootstrap' stage creates a dependency on the 'bootstrap'
milestone.
By default, all classes get directly added to the
'main' stage. You can create new stages as resources:
stage { ['pre', 'post']: }
To order stages, use standard relationships:
stage { 'pre': before => Stage['main'] }
Or use the new relationship syntax:
Stage['pre'] -> Stage['main'] -> Stage['post']
Then use the new class parameters to specify a stage:
class { 'foo': stage => 'pre' }
Stages can only be set on classes, not individual resources. This will
fail:
file { '/foo': stage => 'pre', ensure => file }
}
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 @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 #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}"
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for #{self.name}"
end
@defaultprovider = retval
end
@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.
@providerloader.load(name) unless provider_hash.has_key?(name)
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)
(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 unprovide(name)
Puppet.debug "Reloading #{name} #{self.name} provider"
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 #{pname} of #{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
)
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|
"* **#{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
provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider)
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid #{@resource.class.name} provider '#{provider_class}'"
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
provider = provider.intern if provider.is_a? String
@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 @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
rmclass(name, :hash => provider_hash, :prefix => "Provider")
end
# Return an array of all of the suitable providers.
def self.suitableprovider
providerloader.loadall if provider_hash.empty?
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 #{name} provider of #{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)
list = [list] unless list.is_a?(Array)
# Collect the current prereqs
list.each { |dep|
# 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)
}
}
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
# 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 = {}
@parameters ||= []
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@paramdoc = Hash.new { |hash,key|
key = key.intern if key.is_a?(String)
if hash.include?(key)
hash[key]
else
"Param Documentation for #{key} not found"
end
}
@doc ||= ""
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)
self[name_var] = hash.delete(name_var) if name_var
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 #{attr} on #{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
# 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
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
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
"#{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 @title
if self.class.validparameter?(name_var)
@title = self[:name]
elsif self.class.validproperty?(name_var)
@title = self.should(name_var)
else
self.devfail "Could not find namevar #{name_var} for #{self.class.name}"
end
end
@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_resource
values.each do |name, value|
name = name.name if name.respond_to? :name
trans[name] = value
end
@parameters.each do |name, param|
# Avoid adding each instance name twice
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'
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
def virtual?; !!@virtual; end
def exported?; !!@exported; end
def appliable_to_device?
self.class.can_apply_to(:device)
end
def appliable_to_host?
self.class.can_apply_to(:host)
end
end
end
require 'puppet/provider'
# Always load these types.
Puppet::Type.type(:component)
diff --git a/spec/unit/parameter_spec.rb b/spec/unit/parameter_spec.rb
index 04556c013..1ed211957 100755
--- a/spec/unit/parameter_spec.rb
+++ b/spec/unit/parameter_spec.rb
@@ -1,170 +1,160 @@
#!/usr/bin/env rspec
require 'spec_helper'
require 'puppet/parameter'
describe Puppet::Parameter do
before do
@class = Class.new(Puppet::Parameter) do
@name = :foo
end
@class.initvars
@resource = mock 'resource'
@resource.stub_everything
@parameter = @class.new :resource => @resource
end
it "should create a value collection" do
@class = Class.new(Puppet::Parameter)
@class.value_collection.should be_nil
@class.initvars
@class.value_collection.should be_instance_of(Puppet::Parameter::ValueCollection)
end
it "should return its name as a string when converted to a string" do
@parameter.to_s.should == @parameter.name.to_s
end
- it "should be able to use cached attributes" do
- Puppet::Parameter.ancestors.should be_include(Puppet::Util::Cacher)
- end
-
- it "should use the resource catalog for expiration" do
- catalog = mock 'catalog'
- @resource.stubs(:catalog).returns catalog
- @parameter.expirer.should equal(catalog)
- end
-
[:line, :file, :version].each do |data|
it "should return its resource's #{data} as its #{data}" do
@resource.expects(data).returns "foo"
@parameter.send(data).should == "foo"
end
end
it "should return the resource's tags plus its name as its tags" do
@resource.expects(:tags).returns %w{one two}
@parameter.tags.should == %w{one two foo}
end
it "should provide source_descriptors" do
@resource.expects(:line).returns 10
@resource.expects(:file).returns "file"
@resource.expects(:tags).returns %w{one two}
@parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :file => "file", :line => 10}
end
describe "when returning the value" do
it "should return nil if no value is set" do
@parameter.value.should be_nil
end
it "should validate the value" do
@parameter.expects(:validate).with("foo")
@parameter.value = "foo"
end
it "should munge the value and use any result as the actual value" do
@parameter.expects(:munge).with("foo").returns "bar"
@parameter.value = "foo"
@parameter.value.should == "bar"
end
it "should unmunge the value when accessing the actual value" do
@parameter.class.unmunge do |value| value.to_sym end
@parameter.value = "foo"
@parameter.value.should == :foo
end
it "should return the actual value by default when unmunging" do
@parameter.unmunge("bar").should == "bar"
end
it "should return any set value" do
@parameter.value = "foo"
@parameter.value.should == "foo"
end
end
describe "when validating values" do
it "should do nothing if no values or regexes have been defined" do
@parameter.validate("foo")
end
it "should catch abnormal failures thrown during validation" do
@class.validate { |v| raise "This is broken" }
lambda { @parameter.validate("eh") }.should raise_error(Puppet::DevError)
end
it "should fail if the value is not a defined value or alias and does not match a regex" do
@class.newvalues :foo
lambda { @parameter.validate("bar") }.should raise_error(Puppet::Error)
end
it "should succeed if the value is one of the defined values" do
@class.newvalues :foo
lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do
@class.newvalues :foo
lambda { @parameter.validate("foo") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do
@class.newvalues "foo"
lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined aliases" do
@class.newvalues :foo
@class.aliasvalue :bar, :foo
lambda { @parameter.validate("bar") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value matches one of the regexes" do
@class.newvalues %r{\d}
lambda { @parameter.validate("10") }.should_not raise_error(ArgumentError)
end
end
describe "when munging values" do
it "should do nothing if no values or regexes have been defined" do
@parameter.munge("foo").should == "foo"
end
it "should catch abnormal failures thrown during munging" do
@class.munge { |v| raise "This is broken" }
lambda { @parameter.munge("eh") }.should raise_error(Puppet::DevError)
end
it "should return return any matching defined values" do
@class.newvalues :foo, :bar
@parameter.munge("foo").should == :foo
end
it "should return any matching aliases" do
@class.newvalues :foo
@class.aliasvalue :bar, :foo
@parameter.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
@class.newvalues %r{\w}
@parameter.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
@class.newvalues :foo
@parameter.munge("bar").should == "bar"
end
end
describe "when logging" do
it "should use its resource's log level and the provided message" do
@resource.expects(:[]).with(:loglevel).returns :notice
@parameter.expects(:send_log).with(:notice, "mymessage")
@parameter.log "mymessage"
end
end
end
diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb
index 7d513fdc6..a5743afa3 100755
--- a/spec/unit/resource/catalog_spec.rb
+++ b/spec/unit/resource/catalog_spec.rb
@@ -1,1110 +1,1085 @@
#!/usr/bin/env rspec
require 'spec_helper'
describe Puppet::Resource::Catalog, "when compiling" do
include PuppetSpec::Files
before do
@basepath = make_absolute("/somepath")
# stub this to not try to create state.yaml
Puppet::Util::Storage.stubs(:store)
end
- it "should be an Expirer" do
- Puppet::Resource::Catalog.ancestors.should be_include(Puppet::Util::Cacher::Expirer)
- end
-
- it "should always be expired if it's not applying" do
- @catalog = Puppet::Resource::Catalog.new("host")
- @catalog.expects(:applying?).returns false
- @catalog.should be_dependent_data_expired(Time.now)
- end
-
- it "should not be expired if it's applying and the timestamp is late enough" do
- @catalog = Puppet::Resource::Catalog.new("host")
- @catalog.expire
- @catalog.expects(:applying?).returns true
- @catalog.should_not be_dependent_data_expired(Time.now)
- end
-
it "should be able to write its list of classes to the class file" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.add_class "foo", "bar"
Puppet.settings.expects(:value).with(:classfile).returns "/class/file"
fh = mock 'filehandle'
File.expects(:open).with("/class/file", "w").yields fh
fh.expects(:puts).with "foo\nbar"
@catalog.write_class_file
end
it "should have a client_version attribute" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.client_version = 5
@catalog.client_version.should == 5
end
it "should have a server_version attribute" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.server_version = 5
@catalog.server_version.should == 5
end
describe "when compiling" do
it "should accept tags" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one")
config.tags.should == %w{one}
end
it "should accept multiple tags at once" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one", "two")
config.tags.should == %w{one two}
end
it "should convert all tags to strings" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one", :two)
config.tags.should == %w{one two}
end
it "should tag with both the qualified name and the split name" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one::two")
config.tags.include?("one").should be_true
config.tags.include?("one::two").should be_true
end
it "should accept classes" do
config = Puppet::Resource::Catalog.new("mynode")
config.add_class("one")
config.classes.should == %w{one}
config.add_class("two", "three")
config.classes.should == %w{one two three}
end
it "should tag itself with passed class names" do
config = Puppet::Resource::Catalog.new("mynode")
config.add_class("one")
config.tags.should == %w{one}
end
end
describe "when extracting transobjects" do
def mkscope
@node = Puppet::Node.new("mynode")
@compiler = Puppet::Parser::Compiler.new(@node)
# XXX This is ridiculous.
@compiler.send(:evaluate_main)
@scope = @compiler.topscope
end
def mkresource(type, name)
Puppet::Parser::Resource.new(type, name, :source => @source, :scope => @scope)
end
it "should fail if no 'main' stage can be found" do
lambda { Puppet::Resource::Catalog.new("mynode").extract }.should raise_error(Puppet::DevError)
end
it "should warn if any non-main stages are present" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@source = mock 'source'
main = mkresource("stage", "main")
config.add_resource(main)
other = mkresource("stage", "other")
config.add_resource(other)
Puppet.expects(:warning)
config.extract
end
it "should always create a TransBucket for the 'main' stage" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@source = mock 'source'
main = mkresource("stage", "main")
config.add_resource(main)
result = config.extract
result.type.should == "Stage"
result.name.should == "main"
end
# Now try it with a more complicated graph -- a three tier graph, each tier
it "should transform arbitrarily deep graphs into isomorphic trees" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@scope.stubs(:tags).returns([])
@source = mock 'source'
# Create our scopes.
top = mkresource "stage", "main"
config.add_resource top
topbucket = []
topbucket.expects(:classes=).with([])
top.expects(:to_trans).returns(topbucket)
topres = mkresource "file", "/top"
topres.expects(:to_trans).returns(:topres)
config.add_edge top, topres
middle = mkresource "class", "middle"
middle.expects(:to_trans).returns([])
config.add_edge top, middle
midres = mkresource "file", "/mid"
midres.expects(:to_trans).returns(:midres)
config.add_edge middle, midres
bottom = mkresource "class", "bottom"
bottom.expects(:to_trans).returns([])
config.add_edge middle, bottom
botres = mkresource "file", "/bot"
botres.expects(:to_trans).returns(:botres)
config.add_edge bottom, botres
toparray = config.extract
# This is annoying; it should look like:
# [[[:botres], :midres], :topres]
# but we can't guarantee sort order.
toparray.include?(:topres).should be_true
midarray = toparray.find { |t| t.is_a?(Array) }
midarray.include?(:midres).should be_true
botarray = midarray.find { |t| t.is_a?(Array) }
botarray.include?(:botres).should be_true
end
end
describe " when converting to a Puppet::Resource catalog" do
before do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@top = Puppet::TransObject.new 'top', "class"
@topobject = Puppet::TransObject.new '/topobject', "file"
@middle = Puppet::TransObject.new 'middle', "class"
@middleobject = Puppet::TransObject.new '/middleobject', "file"
@bottom = Puppet::TransObject.new 'bottom', "class"
@bottomobject = Puppet::TransObject.new '/bottomobject', "file"
@resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
@original.add_resource(*@resources)
@original.add_edge(@top, @topobject)
@original.add_edge(@top, @middle)
@original.add_edge(@middle, @middleobject)
@original.add_edge(@middle, @bottom)
@original.add_edge(@bottom, @bottomobject)
@catalog = @original.to_resource
end
it "should copy over the version" do
@original.version = "foo"
@original.to_resource.version.should == "foo"
end
it "should convert parser resources to plain resources" do
resource = Puppet::Parser::Resource.new(:file, "foo", :scope => stub("scope", :environment => nil, :namespaces => nil), :source => stub("source"))
catalog = Puppet::Resource::Catalog.new("whev")
catalog.add_resource(resource)
new = catalog.to_resource
new.resource(:file, "foo").class.should == Puppet::Resource
end
it "should add all resources as Puppet::Resource instances" do
@resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Resource) }
end
it "should copy the tag list to the new catalog" do
@catalog.tags.sort.should == @original.tags.sort
end
it "should copy the class list to the new catalog" do
@catalog.classes.should == @original.classes
end
it "should duplicate the original edges" do
@original.edges.each do |edge|
@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true
end
end
it "should set itself as the catalog for each converted resource" do
@catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) }
end
end
describe "when converting to a RAL catalog" do
before do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@top = Puppet::Resource.new :class, 'top'
@topobject = Puppet::Resource.new :file, @basepath+'/topobject'
@middle = Puppet::Resource.new :class, 'middle'
@middleobject = Puppet::Resource.new :file, @basepath+'/middleobject'
@bottom = Puppet::Resource.new :class, 'bottom'
@bottomobject = Puppet::Resource.new :file, @basepath+'/bottomobject'
@resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
@original.add_resource(*@resources)
@original.add_edge(@top, @topobject)
@original.add_edge(@top, @middle)
@original.add_edge(@middle, @middleobject)
@original.add_edge(@middle, @bottom)
@original.add_edge(@bottom, @bottomobject)
@catalog = @original.to_ral
end
it "should add all resources as RAL instances" do
@resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Type) }
end
it "should copy the tag list to the new catalog" do
@catalog.tags.sort.should == @original.tags.sort
end
it "should copy the class list to the new catalog" do
@catalog.classes.should == @original.classes
end
it "should duplicate the original edges" do
@original.edges.each do |edge|
@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true
end
end
it "should set itself as the catalog for each converted resource" do
@catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) }
end
# This tests #931.
it "should not lose track of resources whose names vary" do
changer = Puppet::TransObject.new 'changer', 'test'
config = Puppet::Resource::Catalog.new('test')
config.add_resource(changer)
config.add_resource(@top)
config.add_edge(@top, changer)
resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil
#changer is going to get duplicated as part of a fix for aliases 1094
changer.expects(:dup).returns(changer)
changer.expects(:to_ral).returns(resource)
newconfig = nil
proc { @catalog = config.to_ral }.should_not raise_error
@catalog.resource("Test[changer2]").should equal(resource)
end
after do
# Remove all resource instances.
@catalog.clear(true)
end
end
describe "when filtering" do
before :each do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@r1 = stub_everything 'r1', :ref => "File[/a]"
@r1.stubs(:respond_to?).with(:ref).returns(true)
@r1.stubs(:dup).returns(@r1)
@r1.stubs(:is_a?).returns(Puppet::Resource).returns(true)
@r2 = stub_everything 'r2', :ref => "File[/b]"
@r2.stubs(:respond_to?).with(:ref).returns(true)
@r2.stubs(:dup).returns(@r2)
@r2.stubs(:is_a?).returns(Puppet::Resource).returns(true)
@resources = [@r1,@r2]
@original.add_resource(@r1,@r2)
end
it "should transform the catalog to a resource catalog" do
@original.expects(:to_catalog).with { |h,b| h == :to_resource }
@original.filter
end
it "should scan each catalog resource in turn and apply filtering block" do
@resources.each { |r| r.expects(:test?) }
@original.filter do |r|
r.test?
end
end
it "should filter out resources which produce true when the filter block is evaluated" do
@original.filter do |r|
r == @r1
end.resource("File[/a]").should be_nil
end
it "should not consider edges against resources that were filtered out" do
@original.add_edge(@r1,@r2)
@original.filter do |r|
r == @r1
end.edge?(@r1,@r2).should_not be
end
end
describe "when functioning as a resource container" do
before do
@catalog = Puppet::Resource::Catalog.new("host")
@one = Puppet::Type.type(:notify).new :name => "one"
@two = Puppet::Type.type(:notify).new :name => "two"
@dupe = Puppet::Type.type(:notify).new :name => "one"
end
it "should provide a method to add one or more resources" do
@catalog.add_resource @one, @two
@catalog.resource(@one.ref).should equal(@one)
@catalog.resource(@two.ref).should equal(@two)
end
it "should add resources to the relationship graph if it exists" do
relgraph = @catalog.relationship_graph
@catalog.add_resource @one
relgraph.should be_vertex(@one)
end
it "should set itself as the resource's catalog if it is not a relationship graph" do
@one.expects(:catalog=).with(@catalog)
@catalog.add_resource @one
end
it "should make all vertices available by resource reference" do
@catalog.add_resource(@one)
@catalog.resource(@one.ref).should equal(@one)
@catalog.vertices.find { |r| r.ref == @one.ref }.should equal(@one)
end
it "should canonize how resources are referred to during retrieval when both type and title are provided" do
@catalog.add_resource(@one)
@catalog.resource("notify", "one").should equal(@one)
end
it "should canonize how resources are referred to during retrieval when just the title is provided" do
@catalog.add_resource(@one)
@catalog.resource("notify[one]", nil).should equal(@one)
end
it "should not allow two resources with the same resource reference" do
@catalog.add_resource(@one)
proc { @catalog.add_resource(@dupe) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError)
end
it "should not store objects that do not respond to :ref" do
proc { @catalog.add_resource("thing") }.should raise_error(ArgumentError)
end
it "should remove all resources when asked" do
@catalog.add_resource @one
@catalog.add_resource @two
@one.expects :remove
@two.expects :remove
@catalog.clear(true)
end
it "should support a mechanism for finishing resources" do
@one.expects :finish
@two.expects :finish
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.finalize
end
it "should make default resources when finalizing" do
@catalog.expects(:make_default_resources)
@catalog.finalize
end
it "should add default resources to the catalog upon creation" do
@catalog.make_default_resources
@catalog.resource(:schedule, "daily").should_not be_nil
end
it "should optionally support an initialization block and should finalize after such blocks" do
@one.expects :finish
@two.expects :finish
config = Puppet::Resource::Catalog.new("host") do |conf|
conf.add_resource @one
conf.add_resource @two
end
end
it "should inform the resource that it is the resource's catalog" do
@one.expects(:catalog=).with(@catalog)
@catalog.add_resource @one
end
it "should be able to find resources by reference" do
@catalog.add_resource @one
@catalog.resource(@one.ref).should equal(@one)
end
it "should be able to find resources by reference or by type/title tuple" do
@catalog.add_resource @one
@catalog.resource("notify", "one").should equal(@one)
end
it "should have a mechanism for removing resources" do
@catalog.add_resource @one
@one.expects :remove
@catalog.remove_resource(@one)
@catalog.resource(@one.ref).should be_nil
@catalog.vertex?(@one).should be_false
end
it "should have a method for creating aliases for resources" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@catalog.resource("notify", "other").should equal(@one)
end
it "should ignore conflicting aliases that point to the aliased resource" do
@catalog.alias(@one, "other")
lambda { @catalog.alias(@one, "other") }.should_not raise_error
end
it "should create aliases for isomorphic resources whose names do not match their titles" do
resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something")
@catalog.add_resource(resource)
@catalog.resource(:file, @basepath+"/something").should equal(resource)
end
it "should not create aliases for non-isomorphic resources whose names do not match their titles", :fails_on_windows => true do
resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin})
@catalog.add_resource(resource)
# Yay, I've already got a 'should' method
@catalog.resource(:exec, "echo").object_id.should == nil.object_id
end
# This test is the same as the previous, but the behaviour should be explicit.
it "should alias using the class name from the resource reference, not the resource class name" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@catalog.resource("notify", "other").should equal(@one)
end
it "should fail to add an alias if the aliased name already exists" do
@catalog.add_resource @one
proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError)
end
it "should not fail when a resource has duplicate aliases created" do
@catalog.add_resource @one
proc { @catalog.alias @one, "one" }.should_not raise_error
end
it "should not create aliases that point back to the resource" do
@catalog.alias(@one, "one")
@catalog.resource(:notify, "one").should be_nil
end
it "should be able to look resources up by their aliases" do
@catalog.add_resource @one
@catalog.alias @one, "two"
@catalog.resource(:notify, "two").should equal(@one)
end
it "should remove resource aliases when the target resource is removed" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@one.expects :remove
@catalog.remove_resource(@one)
@catalog.resource("notify", "other").should be_nil
end
it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do
resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah"
resource.expects(:isomorphic?).returns(true)
@catalog.add_resource(resource)
@catalog.resource(:file, "other").should equal(resource)
@catalog.resource(:file, @basepath+"/something").ref.should == resource.ref
end
it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do
resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah"
resource.expects(:isomorphic?).returns(false)
@catalog.add_resource(resource)
@catalog.resource(:file, resource.title).should equal(resource)
# We can't use .should here, because the resources respond to that method.
raise "Aliased non-isomorphic resource" if @catalog.resource(:file, resource.name)
end
it "should provide a method to create additional resources that also registers the resource" do
args = {:name => "/yay", :ensure => :file}
resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay"
Puppet::Type.type(:file).expects(:new).with(args).returns(resource)
@catalog.create_resource :file, args
@catalog.resource("File[/yay]").should equal(resource)
end
describe "when adding resources with multiple namevars" do
before :each do
Puppet::Type.newtype(:multiple) do
newparam(:color, :namevar => true)
newparam(:designation, :namevar => true)
def self.title_patterns
[ [
/^(\w+) (\w+)$/,
[
[:color, lambda{|x| x}],
[:designation, lambda{|x| x}]
]
] ]
end
end
end
it "should add an alias using the uniqueness key" do
@resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5")
@catalog.add_resource(@resource)
@catalog.resource(:multiple, "some resource").must == @resource
@catalog.resource("Multiple[some resource]").must == @resource
@catalog.resource("Multiple[red 5]").must == @resource
end
it "should conflict with a resource with the same uniqueness key" do
@resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5")
@other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "5")
@catalog.add_resource(@resource)
expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "5"\].*resource \["Multiple", "red", "5"\] already defined/)
end
it "should conflict when its uniqueness key matches another resource's title" do
@resource = Puppet::Type.type(:file).new(:title => "/tmp/foo")
@other = Puppet::Type.type(:file).new(:title => "another file", :path => "/tmp/foo")
@catalog.add_resource(@resource)
expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias File\[another file\] to \["\/tmp\/foo"\].*resource \["File", "\/tmp\/foo"\] already defined/)
end
it "should conflict when its uniqueness key matches the uniqueness key derived from another resource's title" do
@resource = Puppet::Type.type(:multiple).new(:title => "red leader")
@other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "leader")
@catalog.add_resource(@resource)
expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "leader"\].*resource \["Multiple", "red", "leader"\] already defined/)
end
end
end
describe "when applying" do
before :each do
@catalog = Puppet::Resource::Catalog.new("host")
@transaction = Puppet::Transaction.new(@catalog)
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:add_times)
@transaction.stubs(:for_network_device=)
Puppet.settings.stubs(:use)
end
it "should create and evaluate a transaction" do
@transaction.expects(:evaluate)
@catalog.apply
end
it "should provide the catalog retrieval time to the transaction" do
@catalog.retrieval_duration = 5
@transaction.expects(:add_times).with(:config_retrieval => 5)
@catalog.apply
end
it "should use a retrieval time of 0 if none is set in the catalog" do
@catalog.retrieval_duration = nil
@transaction.expects(:add_times).with(:config_retrieval => 0)
@catalog.apply
end
it "should return the transaction" do
@catalog.apply.should equal(@transaction)
end
it "should yield the transaction if a block is provided" do
@catalog.apply do |trans|
trans.should equal(@transaction)
end
end
it "should default to being a host catalog" do
@catalog.host_config.should be_true
end
it "should be able to be set to a non-host_config" do
@catalog.host_config = false
@catalog.host_config.should be_false
end
it "should pass supplied tags on to the transaction" do
@transaction.expects(:tags=).with(%w{one two})
@catalog.apply(:tags => %w{one two})
end
it "should set ignoreschedules on the transaction if specified in apply()" do
@transaction.expects(:ignoreschedules=).with(true)
@catalog.apply(:ignoreschedules => true)
end
- it "should expire cached data in the resources both before and after the transaction" do
- @catalog.expects(:expire).times(2)
- @catalog.apply
- end
-
describe "host catalogs" do
# super() doesn't work in the setup method for some reason
before do
@catalog.host_config = true
Puppet::Util::Storage.stubs(:store)
end
it "should initialize the state database before applying a catalog" do
Puppet::Util::Storage.expects(:load)
# Short-circuit the apply, so we know we're loading before the transaction
Puppet::Transaction.expects(:new).raises ArgumentError
proc { @catalog.apply }.should raise_error(ArgumentError)
end
it "should sync the state database after applying" do
Puppet::Util::Storage.expects(:store)
@transaction.stubs :any_failed? => false
@catalog.apply
end
after { Puppet.settings.clear }
end
describe "non-host catalogs" do
before do
@catalog.host_config = false
end
it "should never send reports" do
Puppet[:report] = true
Puppet[:summarize] = true
@catalog.apply
end
it "should never modify the state database" do
Puppet::Util::Storage.expects(:load).never
Puppet::Util::Storage.expects(:store).never
@catalog.apply
end
after { Puppet.settings.clear }
end
end
describe "when creating a relationship graph" do
before do
Puppet::Type.type(:component)
@catalog = Puppet::Resource::Catalog.new("host")
@compone = Puppet::Type::Component.new :name => "one"
@comptwo = Puppet::Type::Component.new :name => "two", :require => "Class[one]"
@file = Puppet::Type.type(:file)
@one = @file.new :path => @basepath+"/one"
@two = @file.new :path => @basepath+"/two"
@sub = @file.new :path => @basepath+"/two/subdir"
@catalog.add_edge @compone, @one
@catalog.add_edge @comptwo, @two
@three = @file.new :path => @basepath+"/three"
@four = @file.new :path => @basepath+"/four", :require => "File[#{@basepath}/three]"
@five = @file.new :path => @basepath+"/five"
@catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five, @sub
@relationships = @catalog.relationship_graph
end
it "should be able to create a relationship graph" do
@relationships.should be_instance_of(Puppet::SimpleGraph)
end
it "should not have any components" do
@relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil
end
it "should have all non-component resources from the catalog" do
# The failures print out too much info, so i just do a class comparison
@relationships.vertex?(@five).should be_true
end
it "should have all resource relationships set as edges" do
@relationships.edge?(@three, @four).should be_true
end
it "should copy component relationships to all contained resources" do
@relationships.path_between(@one, @two).should be
end
it "should add automatic relationships to the relationship graph" do
@relationships.edge?(@two, @sub).should be_true
end
it "should get removed when the catalog is cleaned up" do
@relationships.expects(:clear)
@catalog.clear
@catalog.instance_variable_get("@relationship_graph").should be_nil
end
it "should write :relationships and :expanded_relationships graph files if the catalog is a host catalog" do
@catalog.clear
graph = Puppet::SimpleGraph.new
Puppet::SimpleGraph.expects(:new).returns graph
graph.expects(:write_graph).with(:relationships)
graph.expects(:write_graph).with(:expanded_relationships)
@catalog.host_config = true
@catalog.relationship_graph
end
it "should not write graph files if the catalog is not a host catalog" do
@catalog.clear
graph = Puppet::SimpleGraph.new
Puppet::SimpleGraph.expects(:new).returns graph
graph.expects(:write_graph).never
@catalog.host_config = false
@catalog.relationship_graph
end
it "should create a new relationship graph after clearing the old one" do
@relationships.expects(:clear)
@catalog.clear
@catalog.relationship_graph.should be_instance_of(Puppet::SimpleGraph)
end
it "should remove removed resources from the relationship graph if it exists" do
@catalog.remove_resource(@one)
@catalog.relationship_graph.vertex?(@one).should be_false
end
end
describe "when writing dot files" do
before do
@catalog = Puppet::Resource::Catalog.new("host")
@name = :test
@file = File.join(Puppet[:graphdir], @name.to_s + ".dot")
end
it "should only write when it is a host catalog" do
File.expects(:open).with(@file).never
@catalog.host_config = false
Puppet[:graph] = true
@catalog.write_graph(@name)
end
after do
Puppet.settings.clear
end
end
describe "when indirecting" do
before do
@real_indirection = Puppet::Resource::Catalog.indirection
@indirection = stub 'indirection', :name => :catalog
-
- Puppet::Util::Cacher.expire
end
it "should use the value of the 'catalog_terminus' setting to determine its terminus class" do
# Puppet only checks the terminus setting the first time you ask
# so this returns the object to the clean state
# at the expense of making this test less pure
Puppet::Resource::Catalog.indirection.reset_terminus_class
Puppet.settings[:catalog_terminus] = "rest"
Puppet::Resource::Catalog.indirection.terminus_class.should == :rest
end
it "should allow the terminus class to be set manually" do
Puppet::Resource::Catalog.indirection.terminus_class = :rest
Puppet::Resource::Catalog.indirection.terminus_class.should == :rest
end
after do
- Puppet::Util::Cacher.expire
@real_indirection.reset_terminus_class
end
end
describe "when converting to yaml" do
before do
@catalog = Puppet::Resource::Catalog.new("me")
@catalog.add_edge("one", "two")
end
it "should be able to be dumped to yaml" do
YAML.dump(@catalog).should be_instance_of(String)
end
end
describe "when converting from yaml" do
before do
@catalog = Puppet::Resource::Catalog.new("me")
@catalog.add_edge("one", "two")
text = YAML.dump(@catalog)
@newcatalog = YAML.load(text)
end
it "should get converted back to a catalog" do
@newcatalog.should be_instance_of(Puppet::Resource::Catalog)
end
it "should have all vertices" do
@newcatalog.vertex?("one").should be_true
@newcatalog.vertex?("two").should be_true
end
it "should have all edges" do
@newcatalog.edge?("one", "two").should be_true
end
end
end
describe Puppet::Resource::Catalog, "when converting to pson", :if => Puppet.features.pson? do
before do
@catalog = Puppet::Resource::Catalog.new("myhost")
end
def pson_output_should
@catalog.class.expects(:pson_create).with { |hash| yield hash }.returns(:something)
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 document_type to 'Catalog'" do
pson_output_should { |hash| hash['document_type'] == "Catalog" }
PSON.parse @catalog.to_pson
end
it "should set its data as a hash" do
pson_output_should { |hash| hash['data'].is_a?(Hash) }
PSON.parse @catalog.to_pson
end
[:name, :version, :tags, :classes].each do |param|
it "should set its #{param} to the #{param} of the resource" do
@catalog.send(param.to_s + "=", "testing") unless @catalog.send(param)
pson_output_should { |hash| hash['data'][param.to_s] == @catalog.send(param) }
PSON.parse @catalog.to_pson
end
end
it "should convert its resources to a PSON-encoded array and store it as the 'resources' data" do
one = stub 'one', :to_pson_data_hash => "one_resource", :ref => "Foo[one]"
two = stub 'two', :to_pson_data_hash => "two_resource", :ref => "Foo[two]"
@catalog.add_resource(one)
@catalog.add_resource(two)
# TODO this should really guarantee sort order
PSON.parse(@catalog.to_pson,:create_additions => false)['data']['resources'].sort.should == ["one_resource", "two_resource"].sort
end
it "should convert its edges to a PSON-encoded array and store it as the 'edges' data" do
one = stub 'one', :to_pson_data_hash => "one_resource", :ref => 'Foo[one]'
two = stub 'two', :to_pson_data_hash => "two_resource", :ref => 'Foo[two]'
three = stub 'three', :to_pson_data_hash => "three_resource", :ref => 'Foo[three]'
@catalog.add_edge(one, two)
@catalog.add_edge(two, three)
@catalog.edges_between(one, two )[0].expects(:to_pson_data_hash).returns "one_two_pson"
@catalog.edges_between(two, three)[0].expects(:to_pson_data_hash).returns "two_three_pson"
PSON.parse(@catalog.to_pson,:create_additions => false)['data']['edges'].sort.should == %w{one_two_pson two_three_pson}.sort
end
end
describe Puppet::Resource::Catalog, "when converting from pson", :if => Puppet.features.pson? do
def pson_result_should
Puppet::Resource::Catalog.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'name' => "myhost"
}
@pson = {
'document_type' => 'Puppet::Resource::Catalog',
'data' => @data,
'metadata' => {}
}
@catalog = Puppet::Resource::Catalog.new("myhost")
Puppet::Resource::Catalog.stubs(:new).returns @catalog
end
it "should be extended with the PSON utility module" do
Puppet::Resource::Catalog.singleton_class.ancestors.should be_include(Puppet::Util::Pson)
end
it "should create it with the provided name" do
Puppet::Resource::Catalog.expects(:new).with('myhost').returns @catalog
PSON.parse @pson.to_pson
end
it "should set the provided version on the catalog if one is set" do
@data['version'] = 50
PSON.parse @pson.to_pson
@catalog.version.should == @data['version']
end
it "should set any provided tags on the catalog" do
@data['tags'] = %w{one two}
PSON.parse @pson.to_pson
@catalog.tags.should == @data['tags']
end
it "should set any provided classes on the catalog" do
@data['classes'] = %w{one two}
PSON.parse @pson.to_pson
@catalog.classes.should == @data['classes']
end
it 'should convert the resources list into resources and add each of them' do
@data['resources'] = [Puppet::Resource.new(:file, "/foo"), Puppet::Resource.new(:file, "/bar")]
@catalog.expects(:add_resource).times(2).with { |res| res.type == "File" }
PSON.parse @pson.to_pson
end
it 'should convert resources even if they do not include "type" information' do
@data['resources'] = [Puppet::Resource.new(:file, "/foo")]
@data['resources'][0].expects(:to_pson).returns '{"title":"/foo","tags":["file"],"type":"File"}'
@catalog.expects(:add_resource).with { |res| res.type == "File" }
PSON.parse @pson.to_pson
end
it 'should convert the edges list into edges and add each of them' do
one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh")
two = Puppet::Relationship.new("tsource", "ttarget", :event => "two", :callback => "refresh")
@data['edges'] = [one, two]
@catalog.stubs(:resource).returns("eh")
@catalog.expects(:add_edge).with { |edge| edge.event == "one" }
@catalog.expects(:add_edge).with { |edge| edge.event == "two" }
PSON.parse @pson.to_pson
end
it "should be able to convert relationships that do not include 'type' information" do
one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh")
one.expects(:to_pson).returns "{\"event\":\"one\",\"callback\":\"refresh\",\"source\":\"osource\",\"target\":\"otarget\"}"
@data['edges'] = [one]
@catalog.stubs(:resource).returns("eh")
@catalog.expects(:add_edge).with { |edge| edge.event == "one" }
PSON.parse @pson.to_pson
end
it "should set the source and target for each edge to the actual resource" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.expects(:resource).with("source").returns("source_resource")
@catalog.expects(:resource).with("target").returns("target_resource")
@catalog.expects(:add_edge).with { |edge| edge.source == "source_resource" and edge.target == "target_resource" }
PSON.parse @pson.to_pson
end
it "should fail if the source resource cannot be found" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.expects(:resource).with("source").returns(nil)
@catalog.stubs(:resource).with("target").returns("target_resource")
lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError)
end
it "should fail if the target resource cannot be found" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.stubs(:resource).with("source").returns("source_resource")
@catalog.expects(:resource).with("target").returns(nil)
lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError)
end
describe "#title_key_for_ref" do
it "should parse a resource ref string into a pair" do
@catalog.title_key_for_ref("Title[name]").should == ["Title", "name"]
end
it "should parse a resource ref string into a pair, even if there's a newline inside the name" do
@catalog.title_key_for_ref("Title[na\nme]").should == ["Title", "na\nme"]
end
end
end
diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb
index 73150af48..218c626d2 100755
--- a/spec/unit/type_spec.rb
+++ b/spec/unit/type_spec.rb
@@ -1,660 +1,644 @@
#!/usr/bin/env rspec
require 'spec_helper'
describe Puppet::Type, :'fails_on_windows' => true do
include PuppetSpec::Files
- 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 == {: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
it "should use any provided noop value" do
Puppet::Type.type(:mount).new(:name => "foo", :noop => true).must be_noop
end
it "should use the global noop value if none is provided" do
Puppet[:noop] = true
Puppet::Type.type(:mount).new(:name => "foo").must be_noop
end
it "should not be noop if in a non-host_config catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
resource.should_not be_noop
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}}.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 creating a provider" do
before :each do
@type = Puppet::Type.newtype(:provider_test_type)
end
after :each do
@type.provider_hash.clear
end
it "should create a subclass of Puppet::Provider for the provider" do
provider = @type.provide(:test_provider)
provider.ancestors.should include(Puppet::Provider)
end
it "should use a parent class if specified" do
parent_provider = @type.provide(:parent_provider)
child_provider = @type.provide(:child_provider, :parent => parent_provider)
child_provider.ancestors.should include(parent_provider)
end
it "should use a parent class if specified by name" do
parent_provider = @type.provide(:parent_provider)
child_provider = @type.provide(:child_provider, :parent => :parent_provider)
child_provider.ancestors.should include(parent_provider)
end
it "should raise an error when the parent class can't be found" do
expect {
@type.provide(:child_provider, :parent => :parent_provider)
}.to raise_error(Puppet::DevError, /Could not find parent provider.+parent_provider/)
end
it "should ensure its type has a 'provider' parameter" do
@type.provide(:test_provider)
@type.parameters.should include(:provider)
end
it "should remove a previously registered provider with the same name" do
old_provider = @type.provide(:test_provider)
new_provider = @type.provide(:test_provider)
old_provider.should_not equal(new_provider)
end
it "should register itself as a provider for the type" do
provider = @type.provide(:test_provider)
provider.should == @type.provider(:test_provider)
end
it "should create a provider when a provider with the same name previously failed" do
@type.provide(:test_provider) do
raise "failed to create this provider"
end rescue nil
provider = @type.provide(:test_provider)
provider.ancestors.should include(Puppet::Provider)
provider.should == @type.provider(:test_provider)
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
yay = make_absolute('/yay')
Puppet::Type.type(:file).new(:path => yay).title.should == yay
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.returns(stub_everything("a property"))
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.returns(stub_everything("a property"))
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 => make_absolute('/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(:key_attributes).returns([ :myname ])
@type.hash2resource(:myname => "foo").title.should == "foo"
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_resource }.should raise_error(Puppet::Error)
end
it "should return a Puppet::Resource instance with its type and title set appropriately" do
result = @resource.retrieve_resource
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_resource[:name].should == "my name"
end
it "should provide a value for all set properties" do
values = @resource.retrieve_resource
[: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 => make_absolute("/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_resource[: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_resource[:fstype] == 15
end
end
describe ".title_patterns" do
describe "when there's one namevar" do
before do
@type_class = Puppet::Type.type(:notify)
@type_class.stubs(:key_attributes).returns([:one])
end
it "should have a default pattern for when there's one namevar" do
patterns = @type_class.title_patterns
patterns.length.should == 1
patterns[0].length.should == 2
end
it "should have a regexp that captures the entire string" do
patterns = @type_class.title_patterns
string = "abc\n\tdef"
patterns[0][0] =~ string
$1.should == "abc\n\tdef"
end
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 => make_absolute("/file/one"))
@two = Puppet::Type.type(:file).new(:path => make_absolute("/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
it "should have a 'stage' metaparam" do
Puppet::Type.metaparamclass(:stage).should be_instance_of(Class)
end
end
describe Puppet::Type::RelationshipMetaparam, :fails_on_windows => true do
include PuppetSpec::Files
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", :'fails_on_windows' => true do
before do
@path = make_absolute('/foo')
@resource = Puppet::Type.type(:mount).new :name => @path
@metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource
end
it "should accept Puppet::Resource instances" do
ref = Puppet::Resource.new(:file, @path)
@metaparam.munge(ref)[0].should equal(ref)
end
it "should turn any string into a Puppet::Resource" do
@metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource)
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
describe Puppet::Type.metaparamclass(:check), :fails_on_windows => true do
include PuppetSpec::Files
it "should warn and create an instance of ':audit'" do
file = Puppet::Type.type(:file).new :path => make_absolute('/foo')
file.expects(:warning)
file[:check] = :mode
file[:audit].should == [:mode]
end
end
describe Puppet::Type.metaparamclass(:audit), :fails_on_windows => true do
include PuppetSpec::Files
before do
@resource = Puppet::Type.type(:file).new :path => make_absolute('/foo')
end
it "should default to being nil" do
@resource[:audit].should be_nil
end
it "should specify all possible properties when asked to audit all properties" do
@resource[:audit] = :all
list = @resource.class.properties.collect { |p| p.name }
@resource[:audit].should == list
end
it "should accept the string 'all' to specify auditing all possible properties" do
@resource[:audit] = 'all'
list = @resource.class.properties.collect { |p| p.name }
@resource[:audit].should == list
end
it "should fail if asked to audit an invalid property" do
lambda { @resource[:audit] = :foobar }.should raise_error(Puppet::Error)
end
it "should create an attribute instance for each auditable property" do
@resource[:audit] = :mode
@resource.parameter(:mode).should_not be_nil
end
it "should accept properties specified as a string" do
@resource[:audit] = "mode"
@resource.parameter(:mode).should_not be_nil
end
it "should not create attribute instances for parameters, only properties" do
@resource[:audit] = :noop
@resource.parameter(:noop).should be_nil
end
describe "when generating the uniqueness key" do
it "should include all of the key_attributes in alphabetical order by attribute name" do
Puppet::Type.type(:file).stubs(:key_attributes).returns [:path, :mode, :owner]
Puppet::Type.type(:file).stubs(:title_patterns).returns(
[ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ]
)
myfile = make_absolute('/my/file')
res = Puppet::Type.type(:file).new( :title => myfile, :path => myfile, :owner => 'root', :content => 'hello' )
res.uniqueness_key.should == [ nil, 'root', myfile]
end
end
end