# The virtual base class for properties, which are the self-contained building
# blocks for actually doing work on the system.
require 'puppet'
require 'puppet/propertychange'
require 'puppet/parameter'
module Puppet
class Property < Puppet::Parameter
# Because 'should' uses an array, we have a special method for handling
# it. We also want to keep copies of the original values, so that
# they can be retrieved and compared later when merging.
attr_reader :shouldorig
attr_writer :noop
class << self
attr_accessor :unmanaged
attr_reader :name
# Return array matching info, defaulting to just matching
# the first value.
def array_matching
unless defined?(@array_matching)
@array_matching = :first
end
@array_matching
end
# Set whether properties should match all values or just the first one.
def array_matching=(value)
value = value.intern if value.is_a?(String)
unless [:first, :all].include?(value)
raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'"
end
@array_matching = value
end
def checkable
@checkable = true
end
def uncheckable
@checkable = false
end
def checkable?
if defined? @checkable
return @checkable
else
return true
end
end
end
# Look up a value's name, so we can find options and such.
def self.value_name(value)
name = symbolize(value)
if @parametervalues[name]
return name
elsif ary = self.match?(value)
return ary[0]
else
return nil
end
end
# Retrieve an option set when a value was defined.
def self.value_option(name, option)
if option.is_a?(String)
option = symbolize(option)
end
if hash = @parameteroptions[name]
hash[option]
else
nil
end
end
# Create the value management variables.
def self.initvars
@parametervalues = {}
@aliasvalues = {}
@parameterregexes = {}
@parameteroptions = {}
end
# Define a new valid value for a property. You must provide the value itself,
# usually as a symbol, or a regex to match the value.
#
# The first argument to the method is either the value itself or a regex.
# The second argument is an option hash; valid options are:
# * :event: The event that should be returned when this value is set.
# * :call: When to call any associated block. The default value
# is ``instead``, which means to call the value instead of calling the
# provider. You can also specify ``before`` or ``after``, which will
# call both the block and the provider, according to the order you specify
# (the ``first`` refers to when the block is called, not the provider).
def self.newvalue(name, options = {}, &block)
name = name.intern if name.is_a? String
@parameteroptions[name] = {}
paramopts = @parameteroptions[name]
# Symbolize everything
options.each do |opt, val|
paramopts[symbolize(opt)] = symbolize(val)
end
# By default, call the block instead of the provider.
if block_given?
paramopts[:call] ||= :instead
else
paramopts[:call] ||= :none
end
# If there was no block given, we still want to store the information
# for validation, but we won't be defining a method
block ||= true
case name
when Symbol
if @parametervalues.include?(name)
Puppet.warning "%s reassigning value %s" % [self.name, name]
end
@parametervalues[name] = block
if block_given?
method = "set_" + name.to_s
settor = paramopts[:settor] || (self.name.to_s + "=")
define_method(method, &block)
paramopts[:method] = method
end
when Regexp
# The regexes are handled in parameter.rb. This value is used
# for validation.
@parameterregexes[name] = block
# This is used for looking up the block for execution.
if block_given?
paramopts[:block] = block
end
else
raise ArgumentError, "Invalid value %s of type %s" %
[name, name.class]
end
end
# Call the provider method.
def call_provider(value)
begin
provider.send(self.class.name.to_s + "=", value)
rescue NoMethodError
self.fail "The %s provider can not handle attribute %s" %
[provider.class.name, self.class.name]
end
end
# Call the dynamically-created method associated with our value, if
# there is one.
def call_valuemethod(name, value)
event = nil
if method = self.class.value_option(name, :method) and self.respond_to?(method)
self.debug "setting %s (currently %s)" % [value, self.retrieve]
begin
event = self.send(method)
rescue Puppet::Error
raise
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
error = Puppet::Error.new("Could not set %s on %s: %s" %
[value, self.class.name, detail], @resource.line, @resource.file)
error.set_backtrace detail.backtrace
raise error
end
elsif block = self.class.value_option(name, :block)
# FIXME It'd be better here to define a method, so that
# the blocks could return values.
# If the regex was defined with no associated block, then just pass
# through and the correct event will be passed back.
event = self.instance_eval(&block)
end
return event, name
end
# How should a property change be printed as a string?
def change_to_s(currentvalue, newvalue)
begin
if currentvalue == :absent
return "defined '%s' as '%s'" %
[self.name, self.should_to_s(newvalue)]
elsif newvalue == :absent or newvalue == [:absent]
return "undefined %s from '%s'" %
[self.name, self.is_to_s(currentvalue)]
else
return "%s changed '%s' to '%s'" %
[self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)]
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
raise Puppet::DevError, "Could not convert change %s to string: %s" %
[self.name, detail]
end
end
# Figure out which event to return.
def event(name, event = nil)
if value_event = self.class.value_option(name, :event)
return value_event
else
if event and event.is_a?(Symbol)
if event == :nochange
return nil
else
return event
end
else
if self.class.name == :ensure
event = case self.should
when :present: (@resource.class.name.to_s + "_created").intern
when :absent: (@resource.class.name.to_s + "_removed").intern
else
(@resource.class.name.to_s + "_changed").intern
end
else
event = (@resource.class.name.to_s + "_changed").intern
end
end
end
return event
end
# initialize our property
def initialize(hash = {})
super
end
def inspect
str = "Property('%s', " % self.name
if defined? @should and @should
str += "@should = '%s')" % @should.join(", ")
else
str += "@should = nil)"
end
end
# Determine whether the property is in-sync or not. If @should is
# not defined or is set to a non-true value, then we do not have
# a valid value for it and thus consider the property to be in-sync
# since we cannot fix it. Otherwise, we expect our should value
# to be an array, and if @is matches any of those values, then
# we consider it to be in-sync.
def insync?(is)
#debug "%s value is '%s', should be '%s'" %
# [self,self.is.inspect,self.should.inspect]
unless defined? @should and @should
return true
end
unless @should.is_a?(Array)
self.devfail "%s's should is not array" % self.class.name
end
# an empty array is analogous to no should values
if @should.empty?
return true
end
# Look for a matching value
if match_all?
return (is == @should or is == @should.collect { |v| v.to_s })
else
@should.each { |val|
if is == val or is == val.to_s
return true
end
}
end
# otherwise, return false
return false
end
# because the @should and @is vars might be in weird formats,
# we need to set up a mechanism for pretty printing of the values
# default to just the values, but this way individual properties can
# override these methods
def is_to_s(currentvalue)
currentvalue
end
# Send a log message.
def log(msg)
unless @resource[:loglevel]
self.devfail "Parent %s has no loglevel" %
@resource.name
end
Puppet::Util::Log.create(
:level => @resource[:loglevel],
:message => msg,
:source => self
)
end
# Should we match all values, or just the first?
def match_all?
self.class.array_matching == :all
end
# each property class must define the name() method, and property instances
# do not change that name
# this implicitly means that a given object can only have one property
# instance of a given property class
def name
return self.class.name
end
# for testing whether we should actually do anything
def noop
# This is only here to make testing easier.
if @resource.respond_to?(:noop?)
@resource.noop?
else
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
end
# By default, call the method associated with the property name on our
# provider. In other words, if the property name is 'gid', we'll call
# 'provider.gid' to retrieve the current value.
def retrieve
is = provider.send(self.class.name)
# puts "IS is: " + is.to_s
# puts "and its an array!!!" if is.is_a? Array
return is
end
# Set our value, using the provider, an associated block, or both.
def set(value)
# Set a name for looking up associated options like the event.
name = self.class.value_name(value)
call = self.class.value_option(name, :call)
# If we're supposed to call the block first or instead, call it now
if call == :before or call == :instead
event, tmp = call_valuemethod(name, value)
end
unless call == :instead
if @resource.provider
call_provider(value)
else
# They haven't provided a block, and our parent does not have
# a provider, so we have no idea how to handle this.
self.fail "%s cannot handle values of type %s" %
[self.class.name, value.inspect]
end
end
if call == :after
event, tmp = call_valuemethod(name, value)
end
return event(name, event)
end
# Only return the first value
def should
if defined? @should
unless @should.is_a?(Array)
self.devfail "should for %s on %s is not an array" %
[self.class.name, @resource.name]
end
if match_all?
return @should
else
return @should[0]
end
else
return nil
end
end
# Set the should value.
def should=(values)
unless values.is_a?(Array)
values = [values]
end
@shouldorig = values
if self.respond_to?(:validate)
values.each { |val|
validate(val)
}
end
if self.respond_to?(:munge)
@should = values.collect { |val|
self.munge(val)
}
else
@should = values
end
end
def should_to_s(newvalue)
newvalue = [newvalue] unless newvalue.is_a? Array
if defined? newvalue
newvalue.join(" ")
else
return nil
end
end
# The default 'sync' method only selects among a list of registered
# values.
def sync
# if self.insync?
# self.info "already in sync"
# return nil
# end
unless self.class.values
self.devfail "No values defined for %s" %
self.class.name
end
if value = self.should
set(value)
else
self.devfail "Got a nil value for should"
end
end
# The properties need to return tags so that logs correctly collect them.
def tags
unless defined? @tags
@tags = []
# This might not be true in testing
if @resource.respond_to? :tags
@tags = @resource.tags
end
@tags << self.name
end
@tags
end
def to_s
return "%s(%s)" % [@resource.name,self.name]
end
# Provide a common hook for setting @should, just like params.
def value=(value)
self.should = value
end
# This property will get automatically added to any type that responds
# to the methods 'exists?', 'create', and 'destroy'.
class Ensure < Puppet::Property
@name = :ensure
def self.defaultvalues
newvalue(:present) do
if @resource.provider and @resource.provider.respond_to?(:create)
@resource.provider.create
else
@resource.create
end
nil # return nil so the event is autogenerated
end
newvalue(:absent) do
if @resource.provider and @resource.provider.respond_to?(:destroy)
@resource.provider.destroy
else
@resource.destroy
end
nil # return nil so the event is autogenerated
end
defaultto do
if @resource.managed?
:present
else
nil
end
end
# This doc will probably get overridden
@doc ||= "The basic property that the object should be in."
end
def self.inherited(sub)
# Add in the two properties that everyone will have.
sub.class_eval do
end
end
def change_to_s(currentvalue, newvalue)
begin
if currentvalue == :absent or currentvalue.nil?
return "created"
elsif newvalue == :absent
return "removed"
else
return "%s changed '%s' to '%s'" %
[self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)]
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
raise Puppet::DevError, "Could not convert change %s to string: %s" %
[self.name, detail]
end
end
def retrieve
# XXX This is a problem -- whether the object exists or not often
# depends on the results of other properties, yet we're the first property
# to get checked, which means that those other properties do not have
# @is values set. This seems to be the source of quite a few bugs,
# although they're mostly logging bugs, not functional ones.
if prov = @resource.provider and prov.respond_to?(:exists?)
result = prov.exists?
elsif @resource.respond_to?(:exists?)
result = @resource.exists?
else
raise Puppet::DevError, "No ability to determine if %s exists" %
@resource.class.name
end
if result
return :present
else
return :absent
end
end
# If they're talking about the thing at all, they generally want to
# say it should exist.
#defaultto :present
defaultto do
if @resource.managed?
:present
else
nil
end
end
end
end
end
# $Id$