diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index d2dbf7a1c..88563242e 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -1,366 +1,366 @@
# the class that actually walks our resource/property tree, collects the changes,
# and performs them
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/application'
class Puppet::Transaction
require 'puppet/transaction/change'
require 'puppet/transaction/event'
require 'puppet/transaction/event_manager'
require 'puppet/transaction/resource_harness'
require 'puppet/resource/status'
attr_accessor :component, :catalog, :ignoreschedules
attr_accessor :sorted_resources, :configurator
# The report, once generated.
attr_accessor :report
# Routes and stores any events and subscriptions.
attr_reader :event_manager
# Handles most of the actual interacting with resources
attr_reader :resource_harness
include Puppet::Util
include Puppet::Util::Tagging
# Wraps application run state check to flag need to interrupt processing
def stop_processing?
Puppet::Application.stop_requested?
end
# Add some additional times for reporting
def add_times(hash)
hash.each do |name, num|
report.add_times(name, num)
end
end
# Are there any failed resources in this transaction?
def any_failed?
report.resource_statuses.values.detect { |status| status.failed? }
end
# Apply all changes for a resource
def apply(resource, ancestor = nil)
status = resource_harness.evaluate(resource)
add_resource_status(status)
event_manager.queue_events(ancestor || resource, status.events)
rescue => detail
resource.err "Could not evaluate: #{detail}"
end
# Find all of the changed resources.
def changed?
report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) }
end
# Copy an important relationships from the parent to the newly-generated
# child resource.
def make_parent_child_relationship(resource, children)
depthfirst = resource.depthfirst?
children.each do |gen_child|
if depthfirst
edge = [gen_child, resource]
else
edge = [resource, gen_child]
end
relationship_graph.add_vertex(gen_child)
unless relationship_graph.edge?(edge[1], edge[0])
relationship_graph.add_edge(*edge)
else
resource.debug "Skipping automatic relationship to %s" % gen_child
end
end
end
# See if the resource generates new resources at evaluation time.
def eval_generate(resource)
generate_additional_resources(resource, :eval_generate)
end
# Evaluate a single resource.
def eval_resource(resource, ancestor = nil)
if skip?(resource)
resource_status(resource).skipped = true
else
eval_children_and_apply_resource(resource, ancestor)
end
# Check to see if there are any events queued for this resource
event_manager.process_events(resource)
end
def eval_children_and_apply_resource(resource, ancestor = nil)
resource_status(resource).scheduled = true
# We need to generate first regardless, because the recursive
# actions sometimes change how the top resource is applied.
children = eval_generate(resource)
if ! children.empty? and resource.depthfirst?
children.each do |child|
# The child will never be skipped when the parent isn't
eval_resource(child, ancestor || resource)
end
end
# Perform the actual changes
apply(resource, ancestor)
if ! children.empty? and ! resource.depthfirst?
children.each do |child|
eval_resource(child, ancestor || resource)
end
end
end
# This method does all the actual work of running a transaction. It
# collects all of the changes, executes them, and responds to any
# necessary events.
def evaluate
# Start logging.
Puppet::Util::Log.newdestination(@report)
prepare()
Puppet.info "Applying configuration version '%s'" % catalog.version if catalog.version
begin
@sorted_resources.each do |resource|
next if stop_processing?
if resource.is_a?(Puppet::Type::Component)
Puppet.warning "Somehow left a component in the relationship graph"
next
end
ret = nil
seconds = thinmark do
ret = eval_resource(resource)
end
if Puppet[:evaltrace] and @catalog.host_config?
resource.info "Evaluated in %0.2f seconds" % seconds
end
ret
end
ensure
# And then close the transaction log.
Puppet::Util::Log.close(@report)
end
Puppet.debug "Finishing transaction #{object_id}"
end
def events
event_manager.events
end
def failed?(resource)
s = resource_status(resource) and s.failed?
end
# Does this resource have any failed dependencies?
def failed_dependencies?(resource)
# First make sure there are no failed dependencies. To do this,
# we check for failures in any of the vertexes above us. It's not
# enough to check the immediate dependencies, which is why we use
# a tree from the reversed graph.
found_failed = false
relationship_graph.dependencies(resource).each do |dep|
next unless failed?(dep)
resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}"
found_failed = true
end
return found_failed
end
# A general method for recursively generating new resources from a
# resource.
def generate_additional_resources(resource, method)
return [] unless resource.respond_to?(method)
begin
made = resource.send(method)
rescue => detail
puts detail.backtrace if Puppet[:trace]
resource.err "Failed to generate additional resources using '%s': %s" % [method, detail]
end
return [] unless made
made = [made] unless made.is_a?(Array)
made.uniq.find_all do |res|
begin
res.tag(*resource.tags)
@catalog.add_resource(res) do |r|
r.finish
make_parent_child_relationship(resource, [r])
end
true
rescue Puppet::Resource::Catalog::DuplicateResourceError
res.info "Duplicate generated resource; skipping"
false
end
end
end
# Collect any dynamically generated resources. This method is called
# before the transaction starts.
def generate
list = @catalog.vertices
newlist = []
while ! list.empty?
list.each do |resource|
newlist += generate_additional_resources(resource, :generate)
end
list = newlist
newlist = []
end
end
# Generate a transaction report.
def generate_report
@report.calculate_metrics
return @report
end
# Should we ignore tags?
def ignore_tags?
! (@catalog.host_config? or Puppet[:name] == "puppet")
end
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
def initialize(catalog)
@catalog = catalog
@report = Report.new
@event_manager = Puppet::Transaction::EventManager.new(self)
@resource_harness = Puppet::Transaction::ResourceHarness.new(self)
end
# Prefetch any providers that support it. We don't support prefetching
# types, just providers.
def prefetch
prefetchers = {}
@catalog.vertices.each do |resource|
if provider = resource.provider and provider.class.respond_to?(:prefetch)
prefetchers[provider.class] ||= {}
prefetchers[provider.class][resource.name] = resource
end
end
# Now call prefetch, passing in the resources so that the provider instances can be replaced.
prefetchers.each do |provider, resources|
Puppet.debug "Prefetching %s resources for %s" % [provider.name, provider.resource_type.name]
begin
provider.prefetch(resources)
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
Puppet.err "Could not prefetch %s provider '%s': %s" % [provider.resource_type.name, provider.name, detail]
end
end
end
# Prepare to evaluate the resources in a transaction.
def prepare
# Now add any dynamically generated resources
generate()
# Then prefetch. It's important that we generate and then prefetch,
# so that any generated resources also get prefetched.
prefetch()
# This will throw an error if there are cycles in the graph.
@sorted_resources = relationship_graph.topsort
end
def relationship_graph
catalog.relationship_graph
end
# Send off the transaction report.
def send_report
begin
report = generate_report()
rescue => detail
Puppet.err "Could not generate report: %s" % detail
return
end
if Puppet[:summarize]
puts report.summary
end
if Puppet[:report]
begin
report.save()
rescue => detail
Puppet.err "Reporting failed: %s" % detail
end
end
end
def add_resource_status(status)
report.add_resource_status status
end
def resource_status(resource)
report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource))
end
# Is the resource currently scheduled?
def scheduled?(resource)
- self.ignoreschedules or resource_harness.scheduled?(resource)
+ self.ignoreschedules or resource_harness.scheduled?(resource_status(resource), resource)
end
# Should this resource be skipped?
def skip?(resource)
if missing_tags?(resource)
resource.debug "Not tagged with %s" % tags.join(", ")
elsif ! scheduled?(resource)
resource.debug "Not scheduled"
elsif failed_dependencies?(resource)
resource.warning "Skipping because of failed dependencies"
elsif resource.virtual?
resource.debug "Skipping because virtual"
else
return false
end
return true
end
# The tags we should be checking.
def tags
unless defined? @tags
self.tags = Puppet[:tags]
end
super
end
def handle_qualified_tags( qualified )
# The default behavior of Puppet::Util::Tagging is
# to split qualified tags into parts. That would cause
# qualified tags to match too broadly here.
return
end
# Is this resource tagged appropriately?
def missing_tags?(resource)
return false if ignore_tags?
return false if tags.empty?
not resource.tagged?(*tags)
end
end
require 'puppet/transaction/report'
diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb
index 05e569b92..17e8dfa79 100644
--- a/lib/puppet/transaction/resource_harness.rb
+++ b/lib/puppet/transaction/resource_harness.rb
@@ -1,115 +1,125 @@
require 'puppet/resource/status'
class Puppet::Transaction::ResourceHarness
extend Forwardable
def_delegators :@transaction, :relationship_graph
attr_reader :transaction
def allow_changes?(resource)
return true unless resource.purging? and resource.deleting?
return true unless deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? }
deplabel = deps.collect { |r| r.ref }.join(",")
plurality = deps.length > 1 ? "":"s"
resource.warning "#{deplabel} still depend#{plurality} on me -- not purging"
return false
end
def apply_changes(status, changes)
changes.each do |change|
status << change.apply
end
status.changed = true
end
+ # Used mostly for scheduling at this point.
+ def cached(resource, name)
+ Puppet::Util::Storage.cache(resource)[name]
+ end
+
+ # Used mostly for scheduling at this point.
+ def cache(resource, name, value)
+ Puppet::Util::Storage.cache(resource)[name] = value
+ end
+
def changes_to_perform(status, resource)
current = resource.retrieve
- resource.cache :checked, Time.now
+ cache resource, :checked, Time.now
return [] if ! allow_changes?(resource)
if param = resource.parameter(:ensure)
return [] if absent_and_not_being_created?(current, param)
return [Puppet::Transaction::Change.new(param, current[:ensure])] unless ensure_is_insync?(current, param)
return [] if ensure_should_be_absent?(current, param)
end
resource.properties.reject { |p| p.name == :ensure }.reject do |param|
param.should.nil?
end.reject do |param|
param_is_insync?(current, param)
end.collect do |param|
Puppet::Transaction::Change.new(param, current[param.name])
end
end
def evaluate(resource)
start = Time.now
status = Puppet::Resource::Status.new(resource)
if changes = changes_to_perform(status, resource) and ! changes.empty?
status.out_of_sync = true
status.change_count = changes.length
apply_changes(status, changes)
if ! resource.noop?
- resource.cache(:synced, Time.now)
+ cache(resource, :synced, Time.now)
resource.flush if resource.respond_to?(:flush)
end
end
return status
rescue => detail
resource.fail "Could not create resource status: #{detail}" unless status
puts detail.backtrace if Puppet[:trace]
resource.err "Could not evaluate: #{detail}"
status.failed = true
return status
ensure
(status.evaluation_time = Time.now - start) if status
end
def initialize(transaction)
@transaction = transaction
end
- def scheduled?(resource)
+ def scheduled?(status, resource)
return true if Puppet[:ignoreschedules]
return true unless schedule = schedule(resource)
# We use 'checked' here instead of 'synced' because otherwise we'll
# end up checking most resources most times, because they will generally
# have been synced a long time ago (e.g., a file only gets updated
# once a month on the server and its schedule is daily; the last sync time
# will have been a month ago, so we'd end up checking every run).
- return schedule.match?(resource.cached(:checked).to_i)
+ return schedule.match?(cached(resource, :checked).to_i)
end
def schedule(resource)
unless resource.catalog
resource.warning "Cannot schedule without a schedule-containing catalog"
return nil
end
return nil unless name = resource[:schedule]
resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}")
end
private
def absent_and_not_being_created?(current, param)
current[:ensure] == :absent and param.should.nil?
end
def ensure_is_insync?(current, param)
param.insync?(current[:ensure])
end
def ensure_should_be_absent?(current, param)
param.should == :absent
end
def param_is_insync?(current, param)
param.insync?(current[param.name])
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 4b5a65e1e..fc6161735 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,1994 +1,1982 @@
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)
}
return ens
end
# Deal with any options passed into parameters.
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(symbolize(param))
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
def self.metaparams
@@metaparams.collect { |param| param.name }
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Create a new metaparam. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
if options[:required_features]
param.required_features = options[:required_features]
end
handle_param_options(name, options)
param.metaparam = true
return param
end
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}
[ [ /(.*)/, [ [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
to_resource.uniqueness_key
end
# Create a new parameter. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newparam(name, options = {}, &block)
options[:attributes] ||= {}
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
if options[:required_features]
param.required_features = options[:required_features]
end
param.isnamevar if options[:namevar]
return param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(%s)" %
name
newproperty(name, options, &block)
end
# Create a new property. The first parameter must be the name of the property;
# this is how users will refer to the property when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the property. Defaults to Puppet::Property.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newproperty(name, options = {}, &block)
name = symbolize(name)
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError,
"Options must be a hash, not %s" % options.inspect
end
if @validproperties.include?(name)
raise Puppet::DevError, "Class %s already has a property named %s" %
[self.name, name]
end
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
if block
class_eval(&block)
end
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
return prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# Return the parameter names
def self.parameters
return [] unless defined? @parameters
@parameters.collect { |klass| klass.name }
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# Return the property class associated with a name
def self.propertybyname(name)
@validproperties[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
if self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name)
@validattrs[name] = true
else
@validattrs[name] = false
end
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
if @validproperties.include?(name)
return @validproperties[name]
else
return false
end
end
# Return the list of validproperties
def self.validproperties
return {} unless defined? @parameters
return @validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
unless defined? @parameters
raise Puppet::DevError, "Class %s has not defined parameters" % self
end
if @paramhash.include?(name) or @@metaparamhash.include?(name)
return true
else
return false
end
end
# This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
def self.valid_parameter?(name)
validattr?(name)
end
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
if synonym = self.class.attr_alias(name)
return synonym
else
return name
end
end
# Are we deleting this resource?
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Create a new property if it is valid but doesn't exist
# Returns: true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
return false
end
#
# 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)
unless self.class.validattr?(name)
fail("Invalid parameter %s(%s)" % [name, name.inspect])
end
if name == :name
name = name_var
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Abstract setting parameters and properties, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on properties, and otherwise just sets the appropriate parameter.
def []=(name,value)
name = attr_alias(name)
unless self.class.validattr?(name)
fail("Invalid parameter %s" % [name])
end
if name == :name
name = name_var
end
if value.nil?
raise Puppet::Error.new("Got nil value for %s" % name)
end
if obj = @parameters[name]
obj.value = value
return nil
else
self.newattr(name, :value => value)
end
nil
end
# remove a property from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
attr = symbolize(attr)
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
# iterate across the existing properties
def eachproperty
# properties() is a private method
properties().each { |property|
yield property
}
end
# Create a transaction event. Called by Transaction or by
# a property.
def event(options = {})
Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options))
end
# Let the catalog determine whether a given cached value is
# still valid or has expired.
def expirer
catalog
end
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
if prop = @parameters[name] and prop.is_a?(Puppet::Property)
return prop.should
else
return nil
end
end
# Create the actual attribute instance. Requires either the attribute
# name or class as the first argument, then an optional hash of
# attributes to set during initialization.
def newattr(name, options = {})
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type %s does not support parameter %s" % [self.class.name, name]
end
if @parameters.include?(name)
raise Puppet::Error, "Parameter '%s' is already defined in %s" %
[name, self.ref]
end
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
end
# Add resource information at creation time, so it's available
# during validation.
options[:resource] = self
begin
# make sure the parameter doesn't have any errors
return @parameters[name] = klass.new(options)
rescue => detail
error = Puppet::Error.new("Parameter %s failed: %s" %
[name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
end
# return the value of a parameter
def parameter(name)
@parameters[name.to_sym]
end
def parameters
@parameters.dup
end
# Is the named property defined?
def propertydefined?(name)
unless name.is_a? Symbol
name = name.intern
end
return @parameters.include?(name)
end
# Return an actual property instance by name; to return the value, use 'resource[param]'
# LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point.
def property(name)
if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)
return obj
else
return nil
end
end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
if value = parameter.default and ! value.nil?
parameter.value = value
else
@parameters.delete(parameter.name)
end
end
# Convert our object to a hash. This just includes properties.
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
def type
self.class.name
end
# Return a specific value for an attribute.
def value(name)
name = attr_alias(name)
if obj = @parameters[name] and obj.respond_to?(:value)
return obj.value
else
return nil
end
end
def version
return 0 unless catalog
catalog.version
end
# Return all of the property objects, in the order specified in the
# class.
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
# Defaults to true.
def self.isomorphic?
if defined? @isomorphic
return @isomorphic
else
return true
end
end
def isomorphic?
self.class.isomorphic?
end
# is the instance a managed instance? A 'yes' here means that
# the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package
# in a list
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if defined? @managed and @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
if defined? @depthfirst
return @depthfirst
else
return false
end
end
def depthfirst?
self.class.depthfirst?
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# Flush the provider, if it supports it. This is called by the
# transaction.
def flush
if self.provider and self.provider.respond_to?(:flush)
self.provider.flush
end
end
# if all contained objects are in sync, then we're in sync
# FIXME I don't think this is used on the type instances any more,
# it's really only used for testing
def insync?(is)
insync = true
if property = @parameters[:ensure]
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '%s'" %
[property.name]
end
ensureis = is[property]
if property.insync?(ensureis) and property.should == :absent
return true
end
end
properties.each { |property|
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '%s'" %
[property.name]
end
propis = is[property]
unless property.insync?(propis)
property.debug("Not in sync: %s vs %s" %
[propis.inspect, property.should.inspect])
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("%s sync status is %s" % [self,insync])
return insync
end
# retrieve the current value of all contained properties
def retrieve
if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
fail "Provider #{provider.class.name} is not functional on this host"
end
result = Puppet::Resource.new(type, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure))
result[:ensure] = ensure_state = ensure_prop.retrieve
else
ensure_state = nil
end
properties.each do |property|
next if property.name == :ensure
if ensure_state == :absent
result[property] = :absent
else
result[property] = property.retrieve
end
end
result
end
# Get a hash of the current properties. Returns a hash with
# the actual property instance as the key and the current value
# as the, um, value.
def currentpropvalues
# It's important to use the 'properties' method here, as it follows the order
# in which they're defined in the class. It also guarantees that 'ensure'
# is the first property, which is important for skipping 'retrieve' on
# all the properties if the resource is absent.
ensure_state = false
return properties().inject({}) do | prophash, property|
if property.name == :ensure
ensure_state = property.retrieve
prophash[property] = ensure_state
else
if ensure_state == :absent
prophash[property] = :absent
else
prophash[property] = property.retrieve
end
end
prophash
end
end
# Are we running in noop mode?
def noop?
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
def noop
noop?
end
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
raise "Global resource storage is deprecated"
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects[name] and self.isomorphic?
msg = "Object '%s[%s]' already exists" %
[newobj.class.name, name]
if exobj.file and exobj.line
msg += ("in file %s at line %s" %
[object.file, object.line])
end
if object.file and object.line
msg += ("and cannot be redefined in file %s at line %s" %
[object.file, object.line])
end
error = Puppet::Error.new(msg)
raise error
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
raise "Global resource aliasing is deprecated"
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias %s: object already exists" %
[name]
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object %s already has alias %s" %
[@aliases[name].name, name]
)
end
end
@aliases[name] = obj
end
# remove all of the instances of a single type
def self.clear
raise "Global resource removal is deprecated"
if defined? @objects
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
if defined? @aliases
@aliases.clear
end
end
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
new(args)
end
# remove a specified object
def self.delete(resource)
raise "Global resource removal is deprecated"
return unless defined? @objects
if @objects.include?(resource.title)
@objects.delete(resource.title)
end
if @aliases.include?(resource.title)
@aliases.delete(resource.title)
end
if @aliases.has_value?(resource)
names = []
@aliases.each do |name, otherres|
if otherres == resource
names << name
end
end
names.each { |name| @aliases.delete(name) }
end
end
# iterate across each of the type's instances
def self.each
raise "Global resource iteration is deprecated"
return unless defined? @objects
@objects.each { |name,instance|
yield instance
}
end
# does the type have an object with the given name?
def self.has_key?(name)
raise "Global resource access is deprecated"
return @objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
if provider_hash.empty?
raise Puppet::DevError, "%s has no providers and has not overridden 'instances'" % self.name
end
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
Puppet.warning "%s %s found in both %s and %s; skipping the %s version" %
[self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name]
next
end
provider_instances[instance.name] = instance
new(:name => instance.name, :provider => instance, :check => :all)
end
end.flatten.compact
end
# Return a list of one suitable provider per source, with the default provider first.
def self.providers_by_source
# Put the default provider first, then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if sources.include?(provider.source)
sources << provider.source
provider
end.compact
end
# Convert a simple hash into a Resource instance.
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
return resource
end
# Create the path for logging and such.
def pathbuilder
if p = parent
[p.pathbuilder, self.ref].flatten
else
[self.ref]
end
end
###############################
# Add all of the meta parameters.
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true"; @resource.noop = true
when false, :false, "false"; @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
that for your schedule::
schedule { daily:
period => daily,
range => \"2-4\"
}
exec { \"/usr/bin/apt-get update\":
schedule => daily
}
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:check) do
desc "Properties which should have their values retrieved
but which should not actually be modified. This is currently used
internally, but will eventually be used for querying, so that you
could specify that you wanted to check the install state of all
packages, and then query the Puppet client daemon to get reports
on all packages."
munge do |args|
# If they've specified all, collect all known properties
if args == :all
args = @resource.class.properties.find_all do |property|
# Only get properties supported by our provider
if @resource.provider
@resource.provider.class.supports_parameter?(property)
else
true
end
end.collect do |property|
property.name
end
end
unless args.is_a?(Array)
args = [args]
end
unless defined? @resource
self.devfail "No parent for %s, %s?" %
[self.class, self.name]
end
args.each { |property|
unless property.is_a?(Symbol)
property = property.intern
end
next if @resource.propertydefined?(property)
unless propertyklass = @resource.class.validproperty?(property)
if @resource.class.validattr?(property)
next
else
raise Puppet::Error, "%s is not a valid attribute for %s" %
[property, self.class.name]
end
end
next unless propertyklass.checkable?
@resource.newattr(property)
}
end
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
provide a symbolic name::
file { sshdconfig:
path => $operatingsystem ? {
solaris => \"/usr/local/etc/ssh/sshd_config\",
default => \"/etc/ssh/sshd_config\"
},
source => \"...\"
}
service { sshd:
subscribe => file[sshdconfig]
}
When you use this feature, the parser sets ``sshdconfig`` as the name,
and the library sets that as an alias for the file so the dependency
lookup for ``sshd`` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
the following code will not work::
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
file { sshdconfig:
mode => 644
}
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
See the `LanguageTutorial language tutorial`:trac: for more information.
"
munge do |aliases|
unless aliases.is_a?(Array)
aliases = [aliases]
end
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("%s can not create alias %s: object already exists" % [@resource.title, other])
end
next
end
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
host's configuration::
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 %s %s for %s" % [description, ref.to_s, resource.ref]
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |reference|
reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
unless related_resource = reference.resolve
self.fail "Could not retrieve dependency '%s' of %s" % [reference, @resource.ref]
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = related_resource
target = @resource
else
source = @resource
target = related_resource
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to %s" % [related_resource.ref])
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires %s" % [related_resource.ref])
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
desc "One or more objects that this object depends on.
This is used purely for guaranteeing that changes to required objects
happen before the dependent object. For instance::
# Create the destination directory before you copy things down
file { \"/usr/local/scripts\":
ensure => directory
}
file { \"/usr/local/scripts/myscript\":
source => \"puppet://server/module/myscript\",
mode => 755,
require => File[\"/usr/local/scripts\"]
}
Multiple dependencies can be specified by providing a comma-seperated list
of resources, enclosed in square brackets::
require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
command. For instance, if you had an ``exec`` command that ran
the ``myscript`` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
``exec`` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "One or more objects that this object depends on. Changes in the
subscribed to objects result in the dependent objects being
refreshed (e.g., a service will get restarted). For instance::
class nagios {
file { \"/etc/nagios/nagios.conf\":
source => \"puppet://server/module/nagios.conf\",
alias => nagconf # just to make things easier for me
}
service { nagios:
running => true,
subscribe => File[nagconf]
}
}
Currently the ``exec``, ``mount`` and ``service`` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object::
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => Exec["nagios-rebuid"]
}
exec { "nagios-rebuild":
command => "/usr/bin/make",
cwd => "/var/nagios/configuration"
}
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object::
file { "/etc/sshd_config":
source => "....",
notify => Service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
end
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 defined? @defaultprovider and @defaultprovider
suitable = suitableprovider()
# Find which providers are a default for this system.
defaults = suitable.find_all { |provider| provider.default? }
# If we don't have any default we use suitable providers
defaults = suitable if defaults.empty?
max = defaults.collect { |provider| provider.specificity }.max
defaults = defaults.find_all { |provider| provider.specificity == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for %s: %s; using %s" %
[self.name, defaults.collect { |i| i.name.to_s }.join(", "),
defaults[0].name]
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for %s" %
self.name
end
@defaultprovider = retval
end
return @defaultprovider
end
def self.provider_hash_by_type(type)
@provider_hashes ||= {}
@provider_hashes[type] ||= {}
end
def self.provider_hash
Puppet::Type.provider_hash_by_type(self.name)
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
# If we don't have it yet, try loading it.
unless provider_hash.has_key?(name)
@providerloader.load(name)
end
return provider_hash[name]
end
# Just list all of the providers.
def self.providers
provider_hash.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
return (provider_hash.has_key?(name) && provider_hash[name].suitable?)
end
# Create a new provider of a type. This method must be called
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
if obj = provider_hash[name]
Puppet.debug "Reloading %s %s provider" % [name, self.name]
unprovide(name)
end
parent = if pname = options[:parent]
options.delete(:parent)
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider %s of %s" %
[pname, name]
end
end
else
Puppet::Provider
end
options[:resource_type] ||= self
self.providify
provider = genclass(name,
:parent => parent,
:hash => provider_hash,
:prefix => "Provider",
:block => block,
:include => feature_module,
:extend => feature_module,
:attributes => options
)
return provider
end
# Make sure we have a :provider parameter defined. Only gets called if there
# are providers.
def self.providify
return if @paramhash.has_key? :provider
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
appropriate provider for your platform."
# This is so we can refer back to the type to get a list of
# providers for documentation.
class << self
attr_accessor :parenttype
end
# We need to add documentation for each provider.
def self.doc
@doc + " Available providers are:\n\n" + parenttype().providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
"* **%s**: %s" % [i, parenttype().provider(i).doc]
}.join("\n")
end
defaultto {
@resource.class.defaultprovider.name
}
validate do |provider_class|
provider_class = provider_class[0] if provider_class.is_a? Array
if provider_class.is_a?(Puppet::Provider)
provider_class = provider_class.class.name
end
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid %s provider '%s'" % [@resource.class.name, provider_class]
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
if provider.is_a? String
provider = provider.intern
end
@resource.provider = provider
if provider.is_a?(Puppet::Provider)
provider.class.name
else
provider
end
end
end.parenttype = self
end
def self.unprovide(name)
if provider_hash.has_key? name
rmclass(name,
:hash => provider_hash,
:prefix => "Provider"
)
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
if provider_hash.empty?
providerloader.loadall
end
provider_hash.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}.reject { |p| p.name == :fake } # For testing
end
def provider=(name)
if name.is_a?(Puppet::Provider)
@provider = name
@provider.resource = self
elsif klass = self.class.provider(name)
@provider = klass.new(self)
else
raise ArgumentError, "Could not find %s provider of %s" %
[name, self.class.name]
end
end
###############################
# All of the relationship code.
# Specify a block for generating a list of objects to autorequire. This
# makes it so that you don't have to manually specify things that you clearly
# require.
def self.autorequire(name, &block)
@autorequires ||= {}
@autorequires[name] = block
end
# Yield each of those autorequires in turn, yo.
def self.eachautorequire
@autorequires ||= {}
@autorequires.each { |type, block|
yield(type, block)
}
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire(rel_catalog = nil)
rel_catalog ||= catalog
raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
next unless typeobj = Puppet::Type.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
unless list.is_a?(Array)
list = [list]
end
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = rel_catalog.resource(type, dep)
next
end
end
reqs << Puppet::Relationship.new(dep, self)
}
}
return reqs
end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.to_edges
end
end.flatten.reject { |r| r.nil? }
end
# Define the initial list of tags.
def tags=(list)
tag(self.class.name)
tag(*list)
end
# Types (which map to resources in the languages) are entirely composed of
# attribute value pairs. Generally, Puppet calls any of these things an
# 'attribute', but these attributes always take one of three specific
# forms: parameters, metaparams, or properties.
# In naming methods, I have tried to consistently name the method so
# that it is clear whether it operates on all attributes (thus has 'attr' in
# the method name, or whether it operates on a specific type of attributes.
attr_writer :title
attr_writer :noop
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name
attr_accessor :self_refresh
include Enumerable, Puppet::Util::ClassGen
include Puppet::MetaType::Manager
include Puppet::Util
include Puppet::Util::Logging
end
# all of the variables that must be initialized for each subclass
def self.initvars
# all of the instances of this class
@objects = Hash.new
@aliases = Hash.new
@defaults = {}
unless defined? @parameters
@parameters = []
end
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@paramdoc = Hash.new { |hash,key|
if key.is_a?(String)
key = key.intern
end
if hash.include?(key)
hash[key]
else
"Param Documentation for %s not found" % key
end
}
unless defined? @doc
@doc = ""
end
end
def self.to_s
if defined? @name
"Puppet::Type::" + @name.to_s.capitalize
else
super
end
end
# Create a block to validate that our object is set up entirely. This will
# be run before the object is operated on.
def self.validate(&block)
define_method(:validate, &block)
#@validate = block
end
# The catalog that this resource is stored in.
attr_accessor :catalog
# is the resource exported
attr_accessor :exported
# is the resource virtual (it should not :-))
attr_accessor :virtual
# create a log at specified level
def log(msg)
Puppet::Util::Log.create(
:level => @parameters[:loglevel].value,
:message => msg,
:source => self
)
end
# instance methods related to instance intrinsics
# e.g., initialize() and name()
public
attr_reader :original_parameters
# initialize the type instance
def initialize(resource)
raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
# The list of parameter/property instances.
@parameters = {}
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog, :exported, :virtual].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
@tags = resource.tags
@original_parameters = resource.to_hash
set_name(@original_parameters)
set_default(:provider)
set_parameters(@original_parameters)
self.validate if self.respond_to?(:validate)
end
private
# Set our resource's name.
def set_name(hash)
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 %s on %s: %s" % [attr, self.class.name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
end
no_values.each do |attr|
set_default(attr)
end
end
public
# Set up all of our autorequires.
def finish
# Make sure all of our relationships are valid. Again, must be done
# when the entire catalog is instantiated.
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.validate_relationship
end
end.flatten.reject { |r| r.nil? }
end
- # Return a cached value
- def cached(name)
- Puppet::Util::Storage.cache(self)[name]
- #@cache[name] ||= nil
- end
-
- # Cache a value
- def cache(name, value)
- Puppet::Util::Storage.cache(self)[name] = value
- #@cache[name] = value
- end
-
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
return self[:name]
end
# Look up our parent in the catalog, if we have one.
def parent
return nil unless catalog
unless defined?(@parent)
if parents = catalog.adjacent(self, :direction => :in)
# We should never have more than one parent, so let's just ignore
# it if we happen to.
@parent = parents.shift
else
@parent = nil
end
end
@parent
end
# Return the "type[name]" style reference.
def ref
"%s[%s]" % [self.class.name.to_s.capitalize, self.title]
end
def self_refresh?
self.class.self_refresh
end
# Mark that we're purging.
def purging
@purging = true
end
# Is this resource being purged? Used by transactions to forbid
# deletion when there are dependencies.
def purging?
if defined? @purging
@purging
else
false
end
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless defined? @title and @title
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 %s for %s" %
[name_var, self.class.name]
end
end
return @title
end
# convert to a string
def to_s
self.ref
end
# Convert to a transportable object
def to_trans(ret = true)
trans = TransObject.new(self.title, self.class.name)
values = retrieve()
values.each do |name, value|
# sometimes we get symbols and sometimes we get Properties
# I think it's a bug, but I can't find it. ~JW
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'
return trans
end
def to_resource
# this 'type instance' versus 'resource' distinction seems artificial
# I'd like to see it collapsed someday ~JW
self.to_trans.to_resource
end
%w{exported virtual}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
end # Puppet::Type
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/spec/integration/transaction.rb b/spec/integration/transaction.rb
index f68287432..6a4dbae67 100755
--- a/spec/integration/transaction.rb
+++ b/spec/integration/transaction.rb
@@ -1,247 +1,247 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet_spec/files'
require 'puppet/transaction'
require 'puppet_spec/files'
describe Puppet::Transaction do
include PuppetSpec::Files
def mk_catalog(*resources)
catalog = Puppet::Resource::Catalog.new(Puppet::Node.new("mynode"))
resources.each { |res| catalog.add_resource res }
catalog
end
it "should not apply generated resources if the parent resource fails" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:file).new :path => "/foo/bar", :backup => false
catalog.add_resource resource
child_resource = Puppet::Type.type(:file).new :path => "/foo/bar/baz", :backup => false
resource.expects(:eval_generate).returns([child_resource])
transaction = Puppet::Transaction.new(catalog)
resource.expects(:retrieve).raises "this is a failure"
resource.stubs(:err)
child_resource.expects(:retrieve).never
transaction.evaluate
end
it "should not apply virtual resources" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:file).new :path => "/foo/bar", :backup => false
resource.virtual = true
catalog.add_resource resource
transaction = Puppet::Transaction.new(catalog)
resource.expects(:evaluate).never
transaction.evaluate
end
it "should apply exported resources" do
catalog = Puppet::Resource::Catalog.new
path = tmpfile("exported_files")
resource = Puppet::Type.type(:file).new :path => path, :backup => false, :ensure => :file
resource.exported = true
catalog.add_resource resource
catalog.apply
FileTest.should be_exist(path)
end
it "should not apply virtual exported resources" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:file).new :path => "/foo/bar", :backup => false
resource.exported = true
resource.virtual = true
catalog.add_resource resource
transaction = Puppet::Transaction.new(catalog)
resource.expects(:evaluate).never
transaction.evaluate
end
# Verify that one component requiring another causes the contained
# resources in the requiring component to get refreshed.
it "should propagate events from a contained resource through its container to its dependent container's contained resources" do
transaction = nil
file = Puppet::Type.type(:file).new :path => tmpfile("event_propagation"), :ensure => :present
execfile = File.join(tmpdir("exec_event"), "exectestingness2")
exec = Puppet::Type.type(:exec).new :command => "touch #{execfile}", :path => ENV['PATH']
catalog = mk_catalog(file)
fcomp = Puppet::Type.type(:component).new(:name => "Foo[file]")
catalog.add_resource fcomp
catalog.add_edge(fcomp, file)
ecomp = Puppet::Type.type(:component).new(:name => "Foo[exec]")
catalog.add_resource ecomp
catalog.add_resource exec
catalog.add_edge(ecomp, exec)
ecomp[:subscribe] = Puppet::Resource.new(:foo, "file")
exec[:refreshonly] = true
exec.expects(:refresh)
catalog.apply
end
# Make sure that multiple subscriptions get triggered.
it "should propagate events to all dependent resources" do
path = tmpfile("path")
file1 = tmpfile("file1")
file2 = tmpfile("file2")
file = Puppet::Type.type(:file).new(
:path => path,
:ensure => "file"
)
exec1 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch %s" % file1,
:refreshonly => true,
:subscribe => Puppet::Resource.new(:file, path)
)
exec2 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch %s" % file2,
:refreshonly => true,
:subscribe => Puppet::Resource.new(:file, path)
)
catalog = mk_catalog(file, exec1, exec2)
catalog.apply
FileTest.should be_exist(file1)
FileTest.should be_exist(file2)
end
it "should not let one failed refresh result in other refreshes failing" do
path = tmpfile("path")
newfile = tmpfile("file")
file = Puppet::Type.type(:file).new(
:path => path,
:ensure => "file"
)
exec1 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch /this/cannot/possibly/exist",
:logoutput => true,
:refreshonly => true,
:subscribe => file,
:title => "one"
)
exec2 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch %s" % newfile,
:logoutput => true,
:refreshonly => true,
:subscribe => [file, exec1],
:title => "two"
)
exec1.stubs(:err)
catalog = mk_catalog(file, exec1, exec2)
catalog.apply
FileTest.should be_exists(newfile)
end
it "should still trigger skipped resources" do
catalog = mk_catalog()
catalog.add_resource(*Puppet::Type.type(:schedule).mkdefaultschedules)
Puppet[:ignoreschedules] = false
file = Puppet::Type.type(:file).new(
:name => tmpfile("file"),
:ensure => "file",
:backup => false
)
fname = tmpfile("exec")
exec = Puppet::Type.type(:exec).new(
:name => "touch #{fname}",
:path => "/usr/bin:/bin",
:schedule => "monthly",
:subscribe => Puppet::Resource.new("file", file.name)
)
catalog.add_resource(file, exec)
# Run it once
catalog.apply
FileTest.should be_exists(fname)
# Now remove it, so it can get created again
File.unlink(fname)
file[:content] = "some content"
catalog.apply
FileTest.should be_exists(fname)
# Now remove it, so it can get created again
File.unlink(fname)
# And tag our exec
exec.tag("testrun")
# And our file, so it runs
file.tag("norun")
Puppet[:tags] = "norun"
file[:content] = "totally different content"
catalog.apply
FileTest.should be_exists(fname)
end
it "should not attempt to evaluate resources with failed dependencies" do
exec = Puppet::Type.type(:exec).new(
:command => "/bin/mkdir /this/path/cannot/possibly/exit",
:title => "mkdir"
)
file1 = Puppet::Type.type(:file).new(
:title => "file1",
:path => tmpfile("file1"),
:require => exec,
:ensure => :file
)
file2 = Puppet::Type.type(:file).new(
:title => "file2",
:path => tmpfile("file2"),
:require => file1,
:ensure => :file
)
catalog = mk_catalog(exec, file1, file2)
catalog.apply
FileTest.should_not be_exists(file1[:path])
FileTest.should_not be_exists(file2[:path])
end
# #801 -- resources only checked in noop should be rescheduled immediately.
it "should immediately reschedule noop resources" do
Puppet::Type.type(:schedule).mkdefaultschedules
resource = Puppet::Type.type(:notify).new(:name => "mymessage", :noop => true)
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
trans = catalog.apply
- trans.resource_harness.should be_scheduled(resource)
+ trans.resource_harness.should be_scheduled(trans.resource_status(resource), resource)
end
end
diff --git a/spec/unit/transaction.rb b/spec/unit/transaction.rb
index 0c3d901f5..c5b0ea4ec 100755
--- a/spec/unit/transaction.rb
+++ b/spec/unit/transaction.rb
@@ -1,437 +1,437 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/transaction'
def without_warnings
flag = $VERBOSE
$VERBOSE = nil
yield
$VERBOSE = flag
end
describe Puppet::Transaction do
before do
@basepath = Puppet.features.posix? ? "/what/ever" : "C:/tmp"
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
end
it "should delegate its event list to the event manager" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.expects(:events).returns %w{my events}
@transaction.events.should == %w{my events}
end
it "should delegate adding times to its report" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.report.expects(:add_times).with(:foo, 10)
@transaction.report.expects(:add_times).with(:bar, 20)
@transaction.add_times :foo => 10, :bar => 20
end
it "should be able to accept resource status instances" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.resource_status(resource).should equal(status)
end
it "should be able to look resource status up by resource reference" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.resource_status(resource.to_s).should equal(status)
end
# This will basically only ever be used during testing.
it "should automatically create resource statuses if asked for a non-existent status" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
@transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status)
end
it "should add provided resource statuses to its report" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.report.resource_statuses[resource.to_s].should equal(status)
end
it "should calculate metrics on and report the report when asked to generate a report" do
@transaction.report.expects(:calculate_metrics)
@transaction.generate_report.should equal(@transaction.report)
end
it "should consider a resource to be failed if a status instance exists for that resource and indicates it is failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
status.failed = "some message"
@transaction.add_resource_status(status)
@transaction.should be_failed(resource)
end
it "should not consider a resource to be failed if a status instance exists for that resource but indicates it is not failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.should_not be_failed(resource)
end
it "should consider there to be failed resources if any statuses are marked failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
status.failed = "some message"
@transaction.add_resource_status(status)
@transaction.should be_any_failed
end
it "should not consider there to be failed resources if no statuses are marked failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.should_not be_any_failed
end
it "should be possible to replace the report object" do
report = Puppet::Transaction::Report.new
@transaction.report = report
@transaction.report.should == report
end
it "should consider a resource to have failed dependencies if any of its dependencies are failed"
describe "when initializing" do
it "should create an event manager" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager)
@transaction.event_manager.transaction.should equal(@transaction)
end
it "should create a resource harness" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness)
@transaction.resource_harness.transaction.should equal(@transaction)
end
end
describe "when evaluating a resource" do
before do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.stubs(:eval_children_and_apply_resource)
@transaction.stubs(:skip?).returns false
@resource = Puppet::Type.type(:file).new :path => @basepath
end
it "should check whether the resource should be skipped" do
@transaction.expects(:skip?).with(@resource).returns false
@transaction.eval_resource(@resource)
end
it "should eval and apply children" do
@transaction.expects(:eval_children_and_apply_resource).with(@resource, nil)
@transaction.eval_resource(@resource)
end
it "should process events" do
@transaction.event_manager.expects(:process_events).with(@resource)
@transaction.eval_resource(@resource)
end
describe "and the resource should be skipped" do
before do
@transaction.expects(:skip?).with(@resource).returns true
end
it "should mark the resource's status as skipped" do
@transaction.eval_resource(@resource)
@transaction.resource_status(@resource).should be_skipped
end
end
end
describe "when applying a resource" do
before do
@resource = Puppet::Type.type(:file).new :path => @basepath
@status = Puppet::Resource::Status.new(@resource)
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.stubs(:queue_events)
@transaction.resource_harness.stubs(:evaluate).returns(@status)
end
it "should use its resource harness to apply the resource" do
@transaction.resource_harness.expects(:evaluate).with(@resource)
@transaction.apply(@resource)
end
it "should add the resulting resource status to its status list" do
@transaction.apply(@resource)
@transaction.resource_status(@resource).should be_instance_of(Puppet::Resource::Status)
end
it "should queue any events added to the resource status" do
@status.expects(:events).returns %w{a b}
@transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"])
@transaction.apply(@resource)
end
it "should log and skip any resources that cannot be applied" do
@transaction.resource_harness.expects(:evaluate).raises ArgumentError
@resource.expects(:err)
@transaction.apply(@resource)
@transaction.report.resource_statuses[@resource.to_s].should be_nil
end
end
describe "when generating resources" do
it "should finish all resources" do
generator = stub 'generator', :depthfirst? => true, :tags => []
resource = stub 'resource', :tag => nil
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.expects(:generate).returns [resource]
@catalog.expects(:add_resource).yields(resource)
resource.expects(:finish)
@transaction.generate_additional_resources(generator, :generate)
end
it "should skip generated resources that conflict with existing resources" do
generator = mock 'generator', :tags => []
resource = stub 'resource', :tag => nil
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.expects(:generate).returns [resource]
@catalog.expects(:add_resource).raises(Puppet::Resource::Catalog::DuplicateResourceError.new("foo"))
resource.expects(:finish).never
resource.expects(:info) # log that it's skipped
@transaction.generate_additional_resources(generator, :generate).should be_empty
end
it "should copy all tags to the newly generated resources" do
child = stub 'child'
generator = stub 'resource', :tags => ["one", "two"]
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.stubs(:generate).returns [child]
@catalog.stubs(:add_resource)
child.expects(:tag).with("one", "two")
@transaction.generate_additional_resources(generator, :generate)
end
end
describe "when skipping a resource" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
end
it "should skip resource with missing tags" do
@transaction.stubs(:missing_tags?).returns(true)
@transaction.should be_skip(@resource)
end
it "should skip unscheduled resources" do
@transaction.stubs(:scheduled?).returns(false)
@transaction.should be_skip(@resource)
end
it "should skip resources with failed dependencies" do
@transaction.stubs(:failed_dependencies?).returns(true)
@transaction.should be_skip(@resource)
end
it "should skip virtual resource" do
@resource.stubs(:virtual?).returns true
@transaction.should be_skip(@resource)
end
end
describe "when determining if tags are missing" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
@transaction.stubs(:ignore_tags?).returns false
end
it "should not be missing tags if tags are being ignored" do
@transaction.expects(:ignore_tags?).returns true
@resource.expects(:tagged?).never
@transaction.should_not be_missing_tags(@resource)
end
it "should not be missing tags if the transaction tags are empty" do
@transaction.tags = []
@resource.expects(:tagged?).never
@transaction.should_not be_missing_tags(@resource)
end
it "should otherwise let the resource determine if it is missing tags" do
tags = ['one', 'two']
@transaction.tags = tags
@resource.expects(:tagged?).with(*tags).returns(false)
@transaction.should be_missing_tags(@resource)
end
end
describe "when determining if a resource should be scheduled" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
end
it "should always schedule resources if 'ignoreschedules' is set" do
@transaction.ignoreschedules = true
@transaction.resource_harness.expects(:scheduled?).never
@transaction.should be_scheduled(@resource)
end
it "should let the resource harness determine whether the resource should be scheduled" do
- @transaction.resource_harness.expects(:scheduled?).with(@resource).returns "feh"
+ @transaction.resource_harness.expects(:scheduled?).with(@transaction.resource_status(@resource), @resource).returns "feh"
@transaction.scheduled?(@resource).should == "feh"
end
end
describe "when prefetching" do
it "should match resources by name, not title" do
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
# Have both a title and name
resource = Puppet::Type.type(:sshkey).create :title => "foo", :name => "bar", :type => :dsa, :key => "eh"
@catalog.add_resource resource
resource.provider.class.expects(:prefetch).with("bar" => resource)
@transaction.prefetch
end
end
it "should return all resources for which the resource status indicates the resource has changed when determinig changed resources" do
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
names = []
2.times do |i|
name = File.join(@basepath, "file#{i}")
resource = Puppet::Type.type(:file).new :path => name
names << resource.to_s
@catalog.add_resource resource
@transaction.add_resource_status Puppet::Resource::Status.new(resource)
end
@transaction.resource_status(names[0]).changed = true
@transaction.changed?.should == [@catalog.resource(names[0])]
end
describe 'when checking application run state' do
before do
without_warnings { Puppet::Application = Class.new(Puppet::Application) }
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
end
after do
without_warnings { Puppet::Application = Puppet::Application.superclass }
end
it 'should return true for :stop_processing? if Puppet::Application.stop_requested? is true' do
Puppet::Application.stubs(:stop_requested?).returns(true)
@transaction.stop_processing?.should be_true
end
it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do
Puppet::Application.stubs(:stop_requested?).returns(false)
@transaction.stop_processing?.should be_false
end
describe 'within an evaluate call' do
before do
@resource = stub 'resource', :ref => 'some_ref'
@catalog.add_resource @resource
@transaction.stubs(:prepare)
@transaction.sorted_resources = [@resource]
end
it 'should stop processing if :stop_processing? is true' do
@transaction.expects(:stop_processing?).returns(true)
@transaction.expects(:eval_resource).never
@transaction.evaluate
end
it 'should continue processing if :stop_processing? is false' do
@transaction.expects(:stop_processing?).returns(false)
@transaction.expects(:eval_resource).returns(nil)
@transaction.evaluate
end
end
end
end
describe Puppet::Transaction, " when determining tags" do
before do
@config = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@config)
end
it "should default to the tags specified in the :tags setting" do
Puppet.expects(:[]).with(:tags).returns("one")
@transaction.tags.should == %w{one}
end
it "should split tags based on ','" do
Puppet.expects(:[]).with(:tags).returns("one,two")
@transaction.tags.should == %w{one two}
end
it "should use any tags set after creation" do
Puppet.expects(:[]).with(:tags).never
@transaction.tags = %w{one two}
@transaction.tags.should == %w{one two}
end
it "should always convert assigned tags to an array" do
@transaction.tags = "one::two"
@transaction.tags.should == %w{one::two}
end
it "should accept a comma-delimited string" do
@transaction.tags = "one, two"
@transaction.tags.should == %w{one two}
end
it "should accept an empty string" do
@transaction.tags = ""
@transaction.tags.should == []
end
end
diff --git a/spec/unit/transaction/resource_harness.rb b/spec/unit/transaction/resource_harness.rb
index 334dbff77..ee2726d07 100755
--- a/spec/unit/transaction/resource_harness.rb
+++ b/spec/unit/transaction/resource_harness.rb
@@ -1,329 +1,344 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/transaction/resource_harness'
describe Puppet::Transaction::ResourceHarness do
before do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@resource = Puppet::Type.type(:file).new :path => "/my/file"
@harness = Puppet::Transaction::ResourceHarness.new(@transaction)
@current_state = Puppet::Resource.new(:file, "/my/file")
@resource.stubs(:retrieve).returns @current_state
@status = Puppet::Resource::Status.new(@resource)
Puppet::Resource::Status.stubs(:new).returns @status
end
it "should accept a transaction at initialization" do
harness = Puppet::Transaction::ResourceHarness.new(@transaction)
harness.transaction.should equal(@transaction)
end
it "should delegate to the transaction for its relationship graph" do
@transaction.expects(:relationship_graph).returns "relgraph"
Puppet::Transaction::ResourceHarness.new(@transaction).relationship_graph.should == "relgraph"
end
describe "when evaluating a resource" do
it "should create and return a resource status instance for the resource" do
@harness.evaluate(@resource).should be_instance_of(Puppet::Resource::Status)
end
it "should fail if no status can be created" do
Puppet::Resource::Status.expects(:new).raises ArgumentError
lambda { @harness.evaluate(@resource) }.should raise_error
end
it "should retrieve the current state of the resource" do
@resource.expects(:retrieve).returns @current_state
@harness.evaluate(@resource)
end
it "should mark the resource as failed and return if the current state cannot be retrieved" do
@resource.expects(:retrieve).raises ArgumentError
@harness.evaluate(@resource).should be_failed
end
it "should use the status and retrieved state to determine which changes need to be made" do
@harness.expects(:changes_to_perform).with(@status, @resource).returns []
@harness.evaluate(@resource)
end
it "should mark the status as out of sync and apply the created changes if there are any" do
changes = %w{mychanges}
@harness.expects(:changes_to_perform).returns changes
@harness.expects(:apply_changes).with(@status, changes)
@harness.evaluate(@resource).should be_out_of_sync
end
it "should cache the last-synced time" do
changes = %w{mychanges}
@harness.stubs(:changes_to_perform).returns changes
@harness.stubs(:apply_changes)
- @resource.expects(:cache).with { |name, time| name == :synced and time.is_a?(Time) }
+ @harness.expects(:cache).with { |resource, name, time| name == :synced and time.is_a?(Time) }
@harness.evaluate(@resource)
end
it "should flush the resource when applying changes if appropriate" do
changes = %w{mychanges}
@harness.stubs(:changes_to_perform).returns changes
@harness.stubs(:apply_changes)
@resource.expects(:flush)
@harness.evaluate(@resource)
end
it "should use the status and retrieved state to determine which changes need to be made" do
@harness.expects(:changes_to_perform).with(@status, @resource).returns []
@harness.evaluate(@resource)
end
it "should not attempt to apply changes if none need to be made" do
@harness.expects(:changes_to_perform).returns []
@harness.expects(:apply_changes).never
@harness.evaluate(@resource).should_not be_out_of_sync
end
it "should store the resource's evaluation time in the resource status" do
@harness.evaluate(@resource).evaluation_time.should be_instance_of(Float)
end
it "should set the change count to the total number of changes" do
changes = %w{a b c d}
@harness.expects(:changes_to_perform).returns changes
@harness.expects(:apply_changes).with(@status, changes)
@harness.evaluate(@resource).change_count.should == 4
end
end
describe "when creating changes" do
before do
@current_state = Puppet::Resource.new(:file, "/my/file")
@resource.stubs(:retrieve).returns @current_state
Puppet.features.stubs(:root?).returns true
end
it "should retrieve the current values from the resource" do
@resource.expects(:retrieve).returns @current_state
@harness.changes_to_perform(@status, @resource)
end
it "should cache that the resource was checked" do
- @resource.expects(:cache).with { |name, time| name == :checked and time.is_a?(Time) }
+ @harness.expects(:cache).with { |resource, name, time| name == :checked and time.is_a?(Time) }
@harness.changes_to_perform(@status, @resource)
end
it "should create changes with the appropriate property and current value" do
@resource[:ensure] = :present
@current_state[:ensure] = :absent
change = stub 'change'
Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:ensure), :absent).returns change
@harness.changes_to_perform(@status, @resource)[0].should equal(change)
end
it "should not attempt to manage properties that do not have desired values set" do
mode = @resource.newattr(:mode)
@current_state[:mode] = :absent
mode.expects(:insync?).never
@harness.changes_to_perform(@status, @resource)
end
describe "and the 'ensure' parameter is present but not in sync" do
it "should return a single change for the 'ensure' parameter" do
@resource[:ensure] = :present
@resource[:mode] = "755"
@current_state[:ensure] = :absent
@current_state[:mode] = :absent
@resource.stubs(:retrieve).returns @current_state
changes = @harness.changes_to_perform(@status, @resource)
changes.length.should == 1
changes[0].property.name.should == :ensure
end
end
describe "and the 'ensure' parameter should be set to 'absent', and is correctly set to 'absent'" do
it "should return no changes" do
@resource[:ensure] = :absent
@resource[:mode] = "755"
@current_state[:ensure] = :absent
@current_state[:mode] = :absent
@harness.changes_to_perform(@status, @resource).should == []
end
end
describe "and the 'ensure' parameter is 'absent' and there is no 'desired value'" do
it "should return no changes" do
@resource.newattr(:ensure)
@resource[:mode] = "755"
@current_state[:ensure] = :absent
@current_state[:mode] = :absent
@harness.changes_to_perform(@status, @resource).should == []
end
end
describe "and non-'ensure' parameters are not in sync" do
it "should return a change for each parameter that is not in sync" do
@resource[:ensure] = :present
@resource[:mode] = "755"
@resource[:owner] = 0
@current_state[:ensure] = :present
@current_state[:mode] = 0444
@current_state[:owner] = 50
mode = stub 'mode_change'
owner = stub 'owner_change'
Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:mode), 0444).returns mode
Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:owner), 50).returns owner
changes = @harness.changes_to_perform(@status, @resource)
changes.length.should == 2
changes.should be_include(mode)
changes.should be_include(owner)
end
end
describe "and all parameters are in sync" do
it "should return an empty array" do
@resource[:ensure] = :present
@resource[:mode] = "755"
@current_state[:ensure] = :present
@current_state[:mode] = 0755
@harness.changes_to_perform(@status, @resource).should == []
end
end
end
describe "when applying changes" do
before do
@change1 = stub 'change1', :apply => stub("event", :status => "success")
@change2 = stub 'change2', :apply => stub("event", :status => "success")
@changes = [@change1, @change2]
end
it "should apply the change" do
@change1.expects(:apply).returns( stub("event", :status => "success") )
@change2.expects(:apply).returns( stub("event", :status => "success") )
@harness.apply_changes(@status, @changes)
end
it "should mark the resource as changed" do
@harness.apply_changes(@status, @changes)
@status.should be_changed
end
it "should queue the resulting event" do
@harness.apply_changes(@status, @changes)
@status.events.should be_include(@change1.apply)
@status.events.should be_include(@change2.apply)
end
end
describe "when determining whether the resource can be changed" do
before do
@resource.stubs(:purging?).returns true
@resource.stubs(:deleting?).returns true
end
it "should be true if the resource is not being purged" do
@resource.expects(:purging?).returns false
@harness.should be_allow_changes(@resource)
end
it "should be true if the resource is not being deleted" do
@resource.expects(:deleting?).returns false
@harness.should be_allow_changes(@resource)
end
it "should be true if the resource has no dependents" do
@harness.relationship_graph.expects(:dependents).with(@resource).returns []
@harness.should be_allow_changes(@resource)
end
it "should be true if all dependents are being deleted" do
dep = stub 'dependent', :deleting? => true
@harness.relationship_graph.expects(:dependents).with(@resource).returns [dep]
@resource.expects(:purging?).returns true
@harness.should be_allow_changes(@resource)
end
it "should be false if the resource's dependents are not being deleted" do
dep = stub 'dependent', :deleting? => false, :ref => "myres"
@resource.expects(:warning)
@harness.relationship_graph.expects(:dependents).with(@resource).returns [dep]
@harness.should_not be_allow_changes(@resource)
end
end
describe "when finding the schedule" do
before do
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
end
it "should warn and return nil if the resource has no catalog" do
@resource.catalog = nil
@resource.expects(:warning)
@harness.schedule(@resource).should be_nil
end
it "should return nil if the resource specifies no schedule" do
@harness.schedule(@resource).should be_nil
end
it "should fail if the named schedule cannot be found" do
@resource[:schedule] = "whatever"
@resource.expects(:fail)
@harness.schedule(@resource)
end
it "should return the named schedule if it exists" do
sched = Puppet::Type.type(:schedule).new(:name => "sched")
@catalog.add_resource(sched)
@resource[:schedule] = "sched"
@harness.schedule(@resource).to_s.should == sched.to_s
end
end
describe "when determining if a resource is scheduled" do
before do
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
+ @status = Puppet::Resource::Status.new(@resource)
end
it "should return true if 'ignoreschedules' is set" do
Puppet[:ignoreschedules] = true
@resource[:schedule] = "meh"
- @harness.should be_scheduled(@resource)
+ @harness.should be_scheduled(@status, @resource)
end
it "should return true if the resource has no schedule set" do
- @harness.should be_scheduled(@resource)
+ @harness.should be_scheduled(@status, @resource)
end
it "should return the result of matching the schedule with the cached 'checked' time if a schedule is set" do
t = Time.now
- @resource.expects(:cached).with(:checked).returns(t)
+ @harness.expects(:cached).with(@resource, :checked).returns(t)
sched = Puppet::Type.type(:schedule).new(:name => "sched")
@catalog.add_resource(sched)
@resource[:schedule] = "sched"
sched.expects(:match?).with(t.to_i).returns "feh"
- @harness.scheduled?(@resource).should == "feh"
+ @harness.scheduled?(@status, @resource).should == "feh"
end
end
+
+ it "should be able to cache data in the Storage module" do
+ data = {}
+ Puppet::Util::Storage.expects(:cache).with(@resource).returns data
+ @harness.cache(@resource, :foo, "something")
+
+ data[:foo].should == "something"
+ end
+
+ it "should be able to retrieve data from the cache" do
+ data = {:foo => "other"}
+ Puppet::Util::Storage.expects(:cache).with(@resource).returns data
+ @harness.cached(@resource, :foo).should == "other"
+ end
end