require 'puppet/util/methodhelper' require 'puppet/util/log_paths' require 'puppet/util/logging' require 'puppet/util/docs' require 'puppet/util/cacher' class Puppet::Parameter include Puppet::Util include Puppet::Util::Errors include Puppet::Util::LogPaths include Puppet::Util::Logging include Puppet::Util::MethodHelper include Puppet::Util::Cacher # 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: # * :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 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, :value_collection attr_accessor :metaparam # Define the default value for a given parameter or parameter. This # means that 'nil' is an invalid default value. This defines # the 'default' instance method. def defaultto(value = nil, &block) if block define_method(:default, &block) else if value.nil? raise Puppet::DevError, "Either a default value or block must be provided" end define_method(:default) do value end end end # Return a documentation string. If there are valid values, # then tack them onto the string. def doc @doc ||= "" unless defined? @addeddocvals @doc += value_collection.doc if f = self.required_features @doc += " Requires features %s." % f.flatten.collect { |f| f.to_s }.join(" ") end @addeddocvals = true end @doc end def nodefault if public_method_defined? :default undef_method :default end end # Store documentation for this parameter. def desc(str) @doc = str end def initvars @value_collection = ValueCollection.new end # This is how we munge the value. Basically, this is our # opportunity to convert the value from one form into another. def munge(&block) # I need to wrap the unsafe version in begin/rescue parameterments, # but if I directly call the block then it gets bound to the # class's context, not the instance's, thus the two methods, # instead of just one. define_method(:unsafe_munge, &block) end # Does the parameter supports reverse munge? # This will be called when something wants to access the parameter # in a canonical form different to what the storage form is. def unmunge(&block) define_method(:unmunge, &block) end # Mark whether we're the namevar. def isnamevar @isnamevar = true @required = true end # Is this parameter the namevar? Defaults to false. def isnamevar? if defined? @isnamevar return @isnamevar else return false end end # This parameter is required. def isrequired @required = true end # Specify features that are required for this parameter to work. def required_features=(*args) @required_features = args.flatten.collect { |a| a.to_s.downcase.intern } end # Is this parameter required? Defaults to false. def required? if defined? @required return @required else return false end end # Verify that we got a good value def validate(&block) define_method(:unsafe_validate, &block) end # Define a new value for our parameter. def newvalues(*names) @value_collection.newvalues(*names) end def aliasvalue(name, other) @value_collection.aliasvalue(name, other) end end # Just a simple method to proxy instance methods to class methods def self.proxymethods(*values) values.each { |val| define_method(val) do self.class.send(val) end } end # And then define one of these proxies for each method in our # ParamHandler class. proxymethods("required?", "isnamevar?") attr_accessor :resource # LAK 2007-05-09: Keep the @parent around for backward compatibility. attr_accessor :parent def devfail(msg) self.fail(Puppet::DevError, msg) end def expirer resource.catalog end def fail(*args) type = nil if args[0].is_a?(Class) type = args.shift else type = Puppet::Error end error = type.new(args.join(" ")) if defined? @resource and @resource and @resource.line error.line = @resource.line end if defined? @resource and @resource and @resource.file error.file = @resource.file end raise error end # Basic parameter initialization. def initialize(options = {}) options = symbolize_options(options) if resource = options[:resource] self.resource = resource options.delete(:resource) else raise Puppet::DevError, "No resource set for %s" % self.class.name end set_options(options) end # Log a message using the resource's log level. 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 # Is this parameter a metaparam? def metaparam? self.class.metaparam end # each parameter class must define the name() method, and parameter # instances do not change that name this implicitly means that a given # object can only have one parameter instance of a given parameter # class def name return self.class.name end # for testing whether we should actually do anything def noop unless defined? @noop @noop = false end tmp = @noop || self.resource.noop || Puppet[:noop] || false #debug "noop is %s" % tmp return tmp end # return the full path to us, for logging and rollback; not currently # used def pathbuilder if defined? @resource and @resource return [@resource.pathbuilder, self.name] else return [self.name] end end # If the specified value is allowed, then munge appropriately. # If the developer uses a 'munge' hook, this method will get overridden. def unsafe_munge(value) self.class.value_collection.munge(value) end # no unmunge by default def unmunge(value) value end # A wrapper around our munging that makes sure we raise useful exceptions. def munge(value) begin ret = unsafe_munge(value) rescue Puppet::Error => detail Puppet.debug "Reraising %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. # If the developer uses a 'validate' hook, this method will get overridden. def unsafe_validate(value) self.class.value_collection.validate(value) end # A protected validation method that only ever raises useful exceptions. def validate(value) begin unsafe_validate(value) rescue ArgumentError => detail fail detail.to_s rescue Puppet::Error, TypeError raise rescue => detail raise Puppet::DevError, "Validate method failed for class %s: %s" % [self.name, detail], detail.backtrace end end def remove @resource = nil end def value unmunge(@value) end # Store the value provided. All of the checking should possibly be # late-binding (e.g., users might not exist when the value is assigned # but might when it is asked for). def value=(value) validate(value) @value = munge(value) end # Retrieve the resource's provider. Some types don't have providers, in which # case we return the resource object itself. def provider @resource.provider end def to_s s = "Parameter(%s)" % self.name end end