diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb index 7e9a5c804..1dc00130a 100644 --- a/lib/puppet/parameter.rb +++ b/lib/puppet/parameter.rb @@ -1,419 +1,419 @@ module Puppet class Parameter < Puppet::Element class << self attr_reader :validater, :munger, :name, :default attr_accessor :ismetaparameter, :element # 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 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.join("``, ``") + "``." end 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 @parametervalues = [] @aliasvalues = {} @parameterregexes = [] 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) define_method(:munge) do |*args| begin unsafe_munge(*args) rescue Puppet::Error => detail Puppet.debug "Reraising %s" % detail raise rescue => detail - raise Puppet::DevError, "Munging failed for class %s: %s" % - [self.name, detail], detail.backtrace + raise Puppet::DevError, "Munging failed for value %s in class %s: %s" % + [args.inspect, self.name, detail], detail.backtrace end end #@munger = 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 # 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) #@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 # States 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 } end def aliasvalue(name, 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 # 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 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 :parent def devfail(msg) self.fail(Puppet::DevError, msg) 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? @parent and @parent and @parent.line error.line = @parent.line end if defined? @parent and @parent and @parent.file error.file = @parent.file end raise error end # Log a message using the parent's log level. def log(msg) unless @parent[:loglevel] p @parent self.devfail "Parent %s has no loglevel" % @parent.name end Puppet::Log.create( :level => @parent[:loglevel], :message => msg, :source => self ) 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.parent.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 path return [@parent.path, self.name].join("/") 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 retval end # Verify that the passed value is valid. validate do |value| vals = self.class.values regs = self.class.regexes if regs.is_a? Hash # this is true on states regs = regs.keys end if vals.empty? and regs.empty? # This parameter isn't using defined values to do its work. return end newval = value unless value.is_a?(Symbol) newval = value.to_s.intern end unless vals.include?(newval) or self.class.alias(newval) or 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 raise ArgumentError, str end end def remove @parent = nil end # This should only be called for parameters, but go ahead and make # it possible to call for states, too. def value if self.is_a?(Puppet::State) # We should return the 'is' value if there's not 'should' # value. This might be bad, though, because the 'should' # method knows whether to return an array or not and that info # is not exposed, and the 'is' value could be a symbol. I # can't seem to create a test in which this is a problem, but # that doesn't mean it's not one. if self.should return self.should else return self.is end else if defined? @value return @value else return nil end end 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) # If we're a parameter, just hand the processing off to the should # method. if self.is_a?(Puppet::State) return self.should = value end if respond_to?(:validate) validate(value) end if respond_to?(:munge) value = munge(value) end @value = value end def inspect s = "Parameter(%s = %s" % [self.name, self.value || "nil"] if defined? @parent s += ", @parent = %s)" % @parent else s += ")" end end def to_s s = "Parameter(%s)" % self.name end end end # $Id$ diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index c10ec462d..2c99e3343 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -1,412 +1,405 @@ require 'puppet' require 'puppet/log' require 'puppet/element' require 'puppet/event' require 'puppet/metric' require 'puppet/type/state' require 'puppet/parameter' require 'puppet/util' require 'puppet/autoload' require 'puppet/metatype/manager' # see the bottom of the file for the rest of the inclusions module Puppet class Type < Puppet::Element # Nearly all of the code in this class is stored in files in the # metatype/ directory. This is a temporary measure until I get a chance # to refactor this class entirely. There's still more simplification to # do, but this works for now. require 'puppet/metatype/attributes' require 'puppet/metatype/closure' require 'puppet/metatype/container' require 'puppet/metatype/evaluation' require 'puppet/metatype/instances' require 'puppet/metatype/metaparams' require 'puppet/metatype/providers' require 'puppet/metatype/relationships' require 'puppet/metatype/schedules' require 'puppet/metatype/tags' # Types (which map to elements 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 states. # 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_accessor :file, :line attr_reader :parent attr_writer :title include Enumerable # class methods dealing with Type management public # the Type class attribute accessors class << self attr_reader :name include Enumerable, Puppet::Util::ClassGen include Puppet::MetaType::Manager 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 @providers = Hash.new @defaults = {} unless defined? @parameters @parameters = [] end @validstates = {} @states = [] @parameters = [] @paramhash = {} @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 unless defined? @states @states = [] 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 # iterate across all children, and then iterate across states # we do children first so we're sure that all dependent objects # are checked first # we ignore parameters here, because they only modify how work gets # done, they don't ever actually result in work specifically def each # we want to return the states in the order that each type # specifies it, because it may (as in the case of File#create) # be important if self.class.depthfirst? @children.each { |child| yield child } end self.eachstate { |state| yield state } unless self.class.depthfirst? @children.each { |child| yield child } end end # Recurse deeply through the tree, but only yield types, not states. def delve(&block) self.each do |obj| if obj.is_a? Puppet::Type obj.delve(&block) end end block.call(self) end # create a log at specified level def log(msg) Puppet::Log.create( :level => @metaparams[:loglevel].value, :message => msg, :source => self ) end # instance methods related to instance intrinsics # e.g., initialize() and name() public def initvars @children = [] @evalcount = 0 @tags = [] # callbacks are per object and event @callbacks = Hash.new { |chash, key| chash[key] = {} } # states and parameters are treated equivalently from the outside: # as name-value pairs (using [] and []=) # internally, however, parameters are merely a hash, while states # point to State objects # further, the lists of valid states and parameters are defined # at the class level unless defined? @states @states = Hash.new(false) end unless defined? @parameters @parameters = Hash.new(false) end unless defined? @metaparams @metaparams = Hash.new(false) end # set defalts @noop = false # keeping stats for the total number of changes, and how many were # completely sync'ed # this isn't really sufficient either, because it adds lots of special # cases such as failed changes # it also doesn't distinguish between changes from the current transaction # vs. changes over the process lifetime @totalchanges = 0 @syncedchanges = 0 @failedchanges = 0 @inited = true end # initialize the type instance def initialize(hash) unless defined? @inited self.initvars end namevar = self.class.namevar orighash = hash # If we got passed a transportable object, we just pull a bunch of info # directly from it. This is the main object instantiation mechanism. if hash.is_a?(Puppet::TransObject) #self[:name] = hash[:name] [:file, :line, :tags].each { |getter| if hash.respond_to?(getter) setter = getter.to_s + "=" if val = hash.send(getter) self.send(setter, val) end end } # XXX This will need to change when transobjects change to titles. @title = hash.name hash = hash.to_hash elsif hash[:title] # XXX This should never happen @title = hash[:title] hash.delete(:title) end # Before anything else, set our parent if it was included if hash.include?(:parent) @parent = hash[:parent] hash.delete(:parent) end # Munge up the namevar stuff so we only have one value. hash = self.argclean(hash) # Let's do the name first, because some things need to happen once # we have the name but before anything else attrs = self.class.allattrs if hash.include?(namevar) #self.send(namevar.to_s + "=", hash[namevar]) self[namevar] = hash[namevar] hash.delete(namevar) if attrs.include?(namevar) attrs.delete(namevar) else self.devfail "My namevar isn\'t a valid attribute...?" end else self.devfail "I was not passed a namevar" end # If the name and title differ, set up an alias if self.name != self.title if obj = self.class[self.name] if self.class.isomorphic? raise Puppet::Error, "%s already exists with name %s" % [obj.title, self.name] end else self.class.alias(self.name, self) end end # This is all of our attributes except the namevar. attrs.each { |attr| if hash.include?(attr) begin self[attr] = hash[attr] rescue ArgumentError, Puppet::Error, TypeError raise rescue => detail self.devfail( "Could not set %s on %s: %s" % [attr, self.class.name, detail] ) end hash.delete attr end } # Set all default values. self.setdefaults if hash.length > 0 self.debug hash.inspect self.fail("Class %s does not accept argument(s) %s" % [self.class.name, hash.keys.join(" ")]) end if self.respond_to?(:validate) self.validate end end # Set up all of our autorequires. def finish # Scheduling has to be done when the whole config is instantiated, so # that file order doesn't matter in finding them. self.schedule end # Return a cached value def cached(name) Puppet::Storage.cache(self)[name] #@cache[name] ||= nil end # Cache a value def cache(name, value) Puppet::Storage.cache(self)[name] = value #@cache[name] = value end # def set(name, value) # send(name.to_s + "=", value) # end # # def get(name) # send(name) # 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 # Return the "type[name]" style reference. def ref "%s[%s]" % [self.class.name, self.title] 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 namevar = self.class.namevar if self.class.validparameter?(namevar) @title = self[:name] elsif self.class.validstate?(namevar) @title = self.should(namevar) else self.devfail "Could not find namevar %s for %s" % [namevar, self.class.name] end end return @title end # convert to a string def to_s self.title end # Convert to a transportable object def to_trans(ret = true) # Retrieve the object, if they tell use to. if ret retrieve() end trans = TransObject.new(self.title, self.class.name) states().each do |state| trans[state.name] = state.is end @parameters.each do |name, param| # Avoid adding each instance name as both the name and the namevar next if param.class.isnamevar? and param.value == self.title trans[name] = param.value end @metaparams.each do |name, param| trans[name] = param.value end trans.tags = self.tags # FIXME I'm currently ignoring 'parent' and 'path' return trans end end # Puppet::Type end require 'puppet/statechange' require 'puppet/provider' require 'puppet/type/component' -require 'puppet/type/cron' -require 'puppet/type/exec' -require 'puppet/type/group' -require 'puppet/type/package' require 'puppet/type/pfile' require 'puppet/type/pfilebucket' -require 'puppet/type/schedule' -require 'puppet/type/service' -require 'puppet/type/user' require 'puppet/type/tidy' -require 'puppet/type/parsedtype' -require 'puppet/type/notify' + + # $Id$