summaryrefslogtreecommitdiffstats
path: root/lib/puppet/parameter.rb
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2008-10-29 23:11:52 -0500
committerLuke Kanies <luke@madstop.com>2008-11-04 16:20:46 -0600
commitaa8d09149c167f1b2f1a714b2e55c89a4d9ac246 (patch)
treedb5231d2b0c8f9a130bbb21930b5caecc83f2c0b /lib/puppet/parameter.rb
parente5b503380c4be79bf7e8f7836867ed7f491dd25e (diff)
downloadpuppet-aa8d09149c167f1b2f1a714b2e55c89a4d9ac246.tar.gz
puppet-aa8d09149c167f1b2f1a714b2e55c89a4d9ac246.tar.xz
puppet-aa8d09149c167f1b2f1a714b2e55c89a4d9ac246.zip
Switched all value management in props/params to internal classes.
This is a significant refactor of some very murky code, and it's all much cleaner and more readable now. All of the 'newvalue' methods and any value-related code is in a ValueCollection class. This puts us in a good position to refactor the Property and Parameter classes more completely. Signed-off-by: Luke Kanies <luke@madstop.com>
Diffstat (limited to 'lib/puppet/parameter.rb')
-rw-r--r--lib/puppet/parameter.rb464
1 files changed, 253 insertions, 211 deletions
diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index 06dfe5b91..40bb32ff6 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -9,10 +9,222 @@ class Puppet::Parameter
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::MethodHelper
+
+ # A collection of values and regexes, used for specifying
+ # what values are allowed in a given parameter.
+ class ValueCollection
+ class Value
+ attr_reader :name, :options, :event
+ attr_accessor :block, :call, :method, :required_features
+
+ # Add an alias for this value.
+ def alias(name)
+ @aliases << convert(name)
+ end
+
+ # Return all aliases.
+ def aliases
+ @aliases.dup
+ end
+
+ # Store the event that our value generates, if it does so.
+ def event=(value)
+ @event = convert(value)
+ end
+
+ def initialize(name)
+ if name.is_a?(Regexp)
+ @name = name
+ else
+ # Convert to a string and then a symbol, so things like true/false
+ # still show up as symbols.
+ @name = convert(name)
+ end
+
+ @aliases = []
+
+ @call = :instead
+ end
+
+ # Does a provided value match our value?
+ def match?(value)
+ if regex?
+ return true if name =~ value.to_s
+ else
+ return true if name == convert(value)
+ return @aliases.include?(convert(value))
+ end
+ end
+
+ # Is our value a regex?
+ def regex?
+ @name.is_a?(Regexp)
+ end
+
+ private
+
+ # A standard way of converting all of our values, so we're always
+ # comparing apples to apples.
+ def convert(value)
+ if value == ''
+ # We can't intern an empty string, yay.
+ value
+ else
+ value.to_s.to_sym
+ end
+ end
+ end
+
+ def aliasvalue(name, other)
+ other = other.to_sym
+ unless value = match?(other)
+ raise Puppet::DevError, "Cannot alias nonexistent value %s" % other
+ end
+
+ value.alias(name)
+ end
+
+ # Return a doc string for all of the values in this parameter/property.
+ def doc
+ unless defined?(@doc)
+ @doc = ""
+ unless values.empty?
+ @doc += " Valid values are "
+ @doc += @strings.collect do |value|
+ if aliases = value.aliases and ! aliases.empty?
+ "``%s`` (also called ``%s``)" % [value.name, aliases.join(", ")]
+ else
+ "``%s``" % value.name
+ end
+ end.join(", ") + "."
+ end
+
+ unless regexes.empty?
+ @doc += " Values can match ``" + regexes.join("``, ``") + "``."
+ end
+ end
+
+ @doc
+ end
+
+ # Does this collection contain any value definitions?
+ def empty?
+ @values.empty?
+ end
+
+ def initialize
+ # We often look values up by name, so a hash makes more sense.
+ @values = {}
+
+ # However, we want to retain the ability to match values in order,
+ # but we always prefer directly equality (i.e., strings) over regex matches.
+ @regexes = []
+ @strings = []
+ end
+
+ # Can we match a given value?
+ def match?(test_value)
+ # First look for normal values
+ if value = @strings.find { |v| v.match?(test_value) }
+ return value
+ end
+
+ # Then look for a regex match
+ @regexes.find { |v| v.match?(test_value) }
+ end
+
+ # If the specified value is allowed, then munge appropriately.
+ def munge(value)
+ return value if empty?
+
+ if instance = match?(value)
+ if instance.regex?
+ return value
+ else
+ return instance.name
+ end
+ else
+ return value
+ end
+ 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:
+ # * <tt>:event</tt>: The event that should be returned when this value is set.
+ # * <tt>:call</tt>: 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 newvalue(name, options = {}, &block)
+ value = Value.new(name)
+ @values[value.name] = value
+ if value.regex?
+ @regexes << value
+ else
+ @strings << value
+ end
+
+ options.each { |opt, arg| value.send(opt.to_s + "=", arg) }
+ if block_given?
+ value.block = block
+ else
+ value.call = options[:call] || :none
+ end
+
+ if block_given? and ! value.regex?
+ value.method ||= "set_" + value.name.to_s
+ end
+
+ value
+ end
+
+ # Define one or more new values for our parameter.
+ def newvalues(*names)
+ names.each { |name| newvalue(name) }
+ end
+
+ def regexes
+ @regexes.collect { |r| r.name.inspect }
+ end
+
+ # Verify that the passed value is valid.
+ def validate(value)
+ return if empty?
+
+ unless @values.detect { |name, v| v.match?(value) }
+ str = "Invalid value %s. " % [value.inspect]
+
+ unless values.empty?
+ str += "Valid values are %s. " % values.join(", ")
+ end
+
+ unless regexes.empty?
+ str += "Valid values match %s." % regexes.join(", ")
+ end
+
+ raise ArgumentError, str
+ end
+ end
+
+ # Return a single value instance.
+ def value(name)
+ @values[name]
+ end
+
+ # Return the list of valid values.
+ def values
+ @strings.collect { |s| s.name }
+ end
+ end
+
class << self
include Puppet::Util
include Puppet::Util::Docs
- attr_reader :validater, :munger, :name, :default, :required_features
+ attr_reader :validater, :munger, :name, :default, :required_features, :value_collection
attr_accessor :metaparam
# Define the default value for a given parameter or parameter. This
@@ -36,36 +248,7 @@ class Puppet::Parameter
@doc ||= ""
unless defined? @addeddocvals
- unless values.empty?
- if @aliasvalues.empty?
- @doc += " Valid values are ``" +
- values.join("``, ``") + "``."
- else
- @doc += " Valid values are "
-
- @doc += values.collect do |value|
- ary = @aliasvalues.find do |name, val|
- val == value
- end
- if ary
- "``%s`` (also called ``%s``)" % [value, ary[0]]
- else
- "``#{value}``"
- end
- end.join(", ") + "."
- end
- end
-
- if defined? @parameterregexes and ! @parameterregexes.empty?
- regs = @parameterregexes
- if @parameterregexes.is_a? Hash
- regs = @parameterregexes.keys
- end
- unless regs.empty?
- @doc += " Values can also match ``" +
- regs.collect { |r| r.inspect }.join("``, ``") + "``."
- end
- end
+ @doc += value_collection.doc
if f = self.required_features
@doc += " Requires features %s." % f.flatten.collect { |f| f.to_s }.join(" ")
@@ -88,9 +271,7 @@ class Puppet::Parameter
end
def initvars
- @parametervalues = []
- @aliasvalues = {}
- @parameterregexes = []
+ @value_collection = ValueCollection.new
end
# This is how we munge the value. Basically, this is our
@@ -101,23 +282,6 @@ class Puppet::Parameter
# class's context, not the instance's, thus the two methods,
# instead of just one.
define_method(:unsafe_munge, &block)
-
- define_method(:munge) do |*args|
- begin
- ret = unsafe_munge(*args)
- rescue Puppet::Error => detail
- Puppet.debug "Reraising %s" % detail
- raise
- rescue => detail
- raise Puppet::DevError, "Munging failed for value %s in class %s: %s" %
- [args.inspect, self.name, detail], detail.backtrace
- end
-
- if self.shadow
- self.shadow.munge(*args)
- end
- ret
- end
end
# Mark whether we're the namevar.
@@ -140,6 +304,11 @@ class Puppet::Parameter
@required = true
end
+ # Specify features that are required for this parameter to work.
+ def required_features=(*args)
+ @required_features = args.flatten.collect { |a| a.to_s.downcase.intern }
+ end
+
# Is this parameter required? Defaults to false.
def required?
if defined? @required
@@ -151,88 +320,16 @@ class Puppet::Parameter
# Verify that we got a good value
def validate(&block)
- #@validater = block
define_method(:unsafe_validate, &block)
-
- define_method(:validate) do |*args|
- begin
- unsafe_validate(*args)
- rescue ArgumentError, Puppet::Error, TypeError
- raise
- rescue => detail
- raise Puppet::DevError,
- "Validate method failed for class %s: %s" %
- [self.name, detail], detail.backtrace
- end
- end
- end
-
- # Does the value match any of our regexes?
- def match?(value)
- value = value.to_s unless value.is_a? String
- @parameterregexes.find { |r|
- r = r[0] if r.is_a? Array # Properties use a hash here
- r =~ value
- }
end
# Define a new value for our parameter.
def newvalues(*names)
- names.each { |name|
- name = name.intern if name.is_a? String
-
- case name
- when Symbol
- if @parametervalues.include?(name)
- Puppet.warning "%s already has a value for %s" %
- [name, name]
- end
- @parametervalues << name
- when Regexp
- if @parameterregexes.include?(name)
- Puppet.warning "%s already has a value for %s" %
- [name, name]
- end
- @parameterregexes << name
- else
- raise ArgumentError, "Invalid value %s of type %s" %
- [name, name.class]
- end
- }
+ @value_collection.newvalues(*names)
end
def aliasvalue(name, other)
- other = symbolize(other)
- unless @parametervalues.include?(other)
- raise Puppet::DevError,
- "Cannot alias nonexistent value %s" % other
- end
-
- @aliasvalues[name] = other
- end
-
- def alias(name)
- @aliasvalues[name]
- end
-
- def regexes
- return @parameterregexes.dup
- end
-
- def required_features=(*args)
- @required_features = args.flatten.collect { |a| a.to_s.downcase.intern }
- end
-
- # Return the list of valid values.
- def values
- #[@aliasvalues.keys, @parametervalues.keys].flatten
- if @parametervalues.is_a? Array
- return @parametervalues.dup
- elsif @parametervalues.is_a? Hash
- return @parametervalues.keys
- else
- return []
- end
+ @value_collection.aliasvalue(name, other)
end
end
@@ -252,7 +349,6 @@ class Puppet::Parameter
attr_accessor :resource
# LAK 2007-05-09: Keep the @parent around for backward compatibility.
attr_accessor :parent
- attr_reader :shadow
def devfail(msg)
self.fail(Puppet::DevError, msg)
@@ -289,17 +385,12 @@ class Puppet::Parameter
raise Puppet::DevError, "No resource set for %s" % self.class.name
end
- if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name)
- setup_shadow(klass)
- end
-
set_options(options)
end
# Log a message using the resource's log level.
def log(msg)
unless @resource[:loglevel]
- p @resource
self.devfail "Parent %s has no loglevel" %
@resource.name
end
@@ -344,73 +435,45 @@ class Puppet::Parameter
end
# If the specified value is allowed, then munge appropriately.
- munge do |value|
- if self.class.values.empty? and self.class.regexes.empty?
- # This parameter isn't using defined values to do its work.
- return value
- end
-
- # We convert to a string and then a symbol so that things like
- # booleans work as we expect.
- intern = value.to_s.intern
-
- # If it's a valid value, always return it as a symbol.
- if self.class.values.include?(intern)
- retval = intern
- elsif other = self.class.alias(intern)
- retval = other
- elsif ary = self.class.match?(value)
- retval = value
- else
- # If it passed the validation but is not a registered value,
- # we just return it as is.
- retval = value
- end
+ # If the developer uses a 'munge' hook, this method will get overridden.
+ def unsafe_munge(value)
+ self.class.value_collection.munge(value)
+ end
- retval
+ # A wrapper around our munging that makes sure we raise useful exceptions.
+ def munge(value)
+ begin
+ ret = unsafe_munge(value)
+ rescue Puppet::Error => detail
+ Puppet.debug "Reraising %s" % detail
+ raise
+ rescue => detail
+ raise Puppet::DevError, "Munging failed for value %s in class %s: %s" % [value.inspect, self.name, detail], detail.backtrace
+ end
+ ret
end
# Verify that the passed value is valid.
- validate do |value|
- vals = self.class.values
- regs = self.class.regexes
-
- # this is true on properties
- regs = regs.keys if regs.is_a?(Hash)
-
- # This parameter isn't using defined values to do its work.
- return if vals.empty? and regs.empty?
-
- newval = value
- newval = value.to_s.intern unless value.is_a?(Symbol)
-
- name = newval
-
- unless vals.include?(newval) or name = self.class.alias(newval) or name = self.class.match?(value) # We match the string, not the symbol
- str = "Invalid '%s' value %s. " %
- [self.class.name, value.inspect]
-
- unless vals.empty?
- str += "Valid values are %s. " % vals.join(", ")
- end
-
- unless regs.empty?
- str += "Valid values match %s." % regs.collect { |r|
- r.to_s
- }.join(", ")
- end
+ # If the developer uses a 'validate' hook, this method will get overridden.
+ def unsafe_validate(value)
+ self.class.value_collection.validate(value)
+ end
- raise ArgumentError, str
+ # A protected validation method that only ever raises useful exceptions.
+ def validate(value)
+ begin
+ unsafe_validate(value)
+ rescue ArgumentError => detail
+ fail detail.to_s
+ rescue Puppet::Error, TypeError
+ raise
+ rescue => detail
+ raise Puppet::DevError, "Validate method failed for class %s: %s" % [self.name, detail], detail.backtrace
end
-
- # Now check for features.
- name = name[0] if name.is_a?(Array) # This is true for regexes.
- validate_features_per_value(name) if is_a?(Puppet::Property)
end
def remove
@resource = nil
- @shadow = nil
end
attr_reader :value
@@ -419,14 +482,9 @@ class Puppet::Parameter
# late-binding (e.g., users might not exist when the value is assigned
# but might when it is asked for).
def value=(value)
- if respond_to?(:validate)
- validate(value)
- end
+ validate(value)
- if respond_to?(:munge)
- value = munge(value)
- end
- @value = value
+ @value = munge(value)
end
def inspect
@@ -444,23 +502,7 @@ class Puppet::Parameter
@resource.provider || @resource
end
- # If there's a shadowing metaparam, instantiate it now.
- # This allows us to create a property or parameter with the
- # same name as a metaparameter, and the metaparam will only be
- # stored as a shadow.
- def setup_shadow(klass)
- @shadow = klass.new(:resource => self.resource)
- end
-
def to_s
s = "Parameter(%s)" % self.name
end
-
- # Make sure that we've got all of the required features for a given value.
- def validate_features_per_value(value)
- if features = self.class.value_option(value, :required_features)
- raise ArgumentError, "Provider must have features '%s' to set '%s' to '%s'" % [features, self.class.name, value] unless provider.satisfies?(features)
- end
- end
end
-