diff options
author | Luke Kanies <luke@madstop.com> | 2008-09-15 23:10:13 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-09-15 23:10:13 -0500 |
commit | dd4f65478c23eaeb56dcef588f96bfdd31557080 (patch) | |
tree | 70e02972da490a7a9ca3f3bb347e314b4007b1d1 | |
parent | 923fd89a2a5d52a6ec9abeb02f6edc01c721fd91 (diff) | |
download | puppet-dd4f65478c23eaeb56dcef588f96bfdd31557080.tar.gz puppet-dd4f65478c23eaeb56dcef588f96bfdd31557080.tar.xz puppet-dd4f65478c23eaeb56dcef588f96bfdd31557080.zip |
Fixing #1576 - moving all of the Puppet::Type code back into type.rb.
Signed-off-by: Luke Kanies <luke@madstop.com>
-rw-r--r-- | lib/puppet/metatype/attributes.rb | 685 | ||||
-rw-r--r-- | lib/puppet/metatype/closure.rb | 49 | ||||
-rw-r--r-- | lib/puppet/metatype/container.rb | 50 | ||||
-rw-r--r-- | lib/puppet/metatype/evaluation.rb | 163 | ||||
-rw-r--r-- | lib/puppet/metatype/instances.rb | 305 | ||||
-rw-r--r-- | lib/puppet/metatype/metaparams.rb | 424 | ||||
-rw-r--r-- | lib/puppet/metatype/providers.rb | 247 | ||||
-rw-r--r-- | lib/puppet/metatype/relationships.rb | 115 | ||||
-rw-r--r-- | lib/puppet/metatype/schedules.rb | 33 | ||||
-rw-r--r-- | lib/puppet/metatype/tags.rb | 38 | ||||
-rw-r--r-- | lib/puppet/type.rb | 2114 | ||||
-rwxr-xr-x | spec/unit/type/noop_metaparam.rb | 2 |
12 files changed, 2101 insertions, 2124 deletions
diff --git a/lib/puppet/metatype/attributes.rb b/lib/puppet/metatype/attributes.rb deleted file mode 100644 index 3f48f22ff..000000000 --- a/lib/puppet/metatype/attributes.rb +++ /dev/null @@ -1,685 +0,0 @@ -require 'puppet' -require 'puppet/type' - -class Puppet::Type - 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 namevar comes first, - # then the properties, then the params and metaparams in the order they - # were specified in the files. - def self.allattrs - # now get all of the arguments, in a specific order - # Cache this, since it gets called so many times - namevar = self.namevar - - order = [namevar] - if self.parameters.include?(:provider) - order << :provider - end - order << [self.properties.collect { |property| property.name }, - self.parameters - [:provider], - self.metaparams].flatten.reject { |param| - # we don't want our namevar in there multiple times - param == namevar - } - - order.flatten! - - return order - 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 - else - raise Puppet::DevError, - "Invalid attribute '%s' for class '%s'" % - [attr, self.name] - end - end - - @attrtypes[attr] - end - - # Copy an existing class parameter. This allows other types to avoid - # duplicating a parameter definition, and is mostly used by subclasses - # of the File class. - def self.copyparam(klass, name) - param = klass.attrclass(name) - - unless param - raise Puppet::DevError, "Class %s has no param %s" % [klass, name] - end - @parameters << param - @parameters.each { |p| @paramhash[name] = p } - - if param.isnamevar? - @namevar = param.name - end - end - - # A similar function but one that yields the class and type. - # This is mainly so that setdefaults doesn't call quite so many functions. - def self.eachattr(*ary) - if ary.empty? - ary = nil - end - - # We have to do this in a specific order, so that defaults are - # created in that order (e.g., providers should be set up before - # anything else). - allattrs.each do |name| - next unless ary.nil? or ary.include?(name) - if obj = @properties.find { |p| p.name == name } - yield obj, :property - elsif obj = @parameters.find { |p| p.name == name } - yield obj, :param - elsif obj = @@metaparams.find { |p| p.name == name } - yield obj, :meta - else - raise Puppet::DevError, "Could not find parameter %s" % name - end - end - 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 - - # If this param handles relationships, store that information - 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 - - # Find the namevar - def self.namevar - unless defined? @namevar - params = @parameters.find_all { |param| - param.isnamevar? or param.name == :name - } - - if params.length > 1 - raise Puppet::DevError, "Found multiple namevars for %s" % self.name - elsif params.length == 1 - @namevar = params[0].name - else - raise Puppet::DevError, "No namevar for %s" % self.name - end - end - @namevar - 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] - - # These might be enabled later. -# define_method(name) do -# @parameters[name].value -# end -# -# define_method(name.to_s + "=") do |value| -# newparam(param, value) -# end - - if param.isnamevar? - @namevar = param.name - end - - 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: - # * <tt>:parent</tt>: The parent class for the property. Defaults to Puppet::Property. - # * <tt>:retrieve</tt>: 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 - -# define_method(name) do -# @parameters[name].should -# end -# -# define_method(name.to_s + "=") do |value| -# newproperty(name, :should => value) -# 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 - - # fix any namevar => param translations - def argclean(oldhash) - # This duplication is here because it might be a transobject. - hash = oldhash.dup.to_hash - - if hash.include?(:resource) - hash.delete(:resource) - end - namevar = self.class.namevar - - # Do a simple translation for those cases where they've passed :name - # but that's not our namevar - if hash.include? :name and namevar != :name - if hash.include? namevar - raise ArgumentError, "Cannot provide both name and %s" % namevar - end - hash[namevar] = hash[:name] - hash.delete(:name) - end - - # Make sure we have a name, one way or another - unless hash.include? namevar - if defined? @title and @title - hash[namevar] = @title - else - raise Puppet::Error, "Was not passed a namevar or title" - end - end - - return hash - 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 - - # 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) - raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect]) - end - - if name == :name - name = self.class.namevar - 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) - raise TypeError.new("Invalid parameter %s" % [name]) - end - - if name == :name - name = self.class.namevar - 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 - - # 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) - unless name.is_a? Symbol - name = name.intern - end - return @parameters[name].value - 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 type by name; to return the value, use 'inst[name]' - # FIXME this method should go away - def property(name) - if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property) - return obj - else - return nil - end - end - -# def set(name, value) -# send(name.to_s + "=", value) -# end -# -# def get(name) -# send(name) -# 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 setdefaults(*ary) - #self.class.eachattr(*ary) { |klass, type| - self.class.eachattr(*ary) { |klass, type| - # not many attributes will have defaults defined, so we short-circuit - # those away - next unless klass.method_defined?(:default) - next if @parameters[klass.name] - - next unless obj = self.newattr(klass) - - # We have to check for nil values, not "truth", so we allow defaults - # to false. - value = obj.default and ! value.nil? - if ! value.nil? - obj.value = value - else - @parameters.delete(obj.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 - - # 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 - - # Meta-parameter methods: These methods deal with the results - # of specifying metaparameters - - private - - # Return all of the property objects, in the order specified in the - # class. - def properties - #debug "%s has %s properties" % [self,@parameters.length] - props = self.class.properties.collect { |prop| - @parameters[prop.name] - }.find_all { |p| - ! p.nil? - }.each do |prop| - unless prop.is_a?(Puppet::Property) - raise Puppet::DevError, "got a non-property %s(%s)" % - [prop.class, prop.class.name] - end - end - - props - end -end - diff --git a/lib/puppet/metatype/closure.rb b/lib/puppet/metatype/closure.rb deleted file mode 100644 index 673a2359d..000000000 --- a/lib/puppet/metatype/closure.rb +++ /dev/null @@ -1,49 +0,0 @@ -class Puppet::Type - attr_writer :implicit - - # 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 implicit? - if defined? @implicit and @implicit - return true - else - return false - 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 -end diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb deleted file mode 100644 index 2bbe3f546..000000000 --- a/lib/puppet/metatype/container.rb +++ /dev/null @@ -1,50 +0,0 @@ -class Puppet::Type - - # 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 - - # Add a hook for testing for recursion. - def parentof?(child) - if (self == child) - debug "parent is equal to child" - return true - elsif defined? @parent and @parent.parentof?(child) - debug "My parent is parent of child" - return true - else - return false - end - 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 - self.class.delete(self) - - @parent = nil - - # Remove the reference to the provider. - if self.provider - @provider.clear - @provider = nil - end - end -end - diff --git a/lib/puppet/metatype/evaluation.rb b/lib/puppet/metatype/evaluation.rb deleted file mode 100644 index 18bbb812f..000000000 --- a/lib/puppet/metatype/evaluation.rb +++ /dev/null @@ -1,163 +0,0 @@ -class Puppet::Type - # This method is responsible for collecting property changes we always - # descend into the children before we evaluate our current properties. - # This returns any changes resulting from testing, thus 'collect' rather - # than 'each'. - def evaluate - if self.provider.is_a?(Puppet::Provider) - unless provider.class.suitable? - raise Puppet::Error, "Provider %s is not functional on this platform" % provider.class.name - end - end - #Puppet.err "Evaluating %s" % self.path.join(":") - unless defined? @evalcount - self.err "No evalcount defined on '%s' of type '%s'" % - [self.title,self.class] - @evalcount = 0 - end - @evalcount += 1 - - if p = self.provider and p.respond_to?(:prefetch) - p.prefetch - end - - # this only operates on properties, not properties + children - # it's important that we call retrieve() on the type instance, - # not directly on the property, because it allows the type to override - # the method, like pfile does - currentvalues = self.retrieve - - changes = propertychanges(currentvalues).flatten - - # now record how many changes we've resulted in - if changes.length > 0 - self.debug "%s change(s)" % - [changes.length] - end - - # If we're in noop mode, we don't want to store the checked time, - # because it will result in the resource not getting scheduled if - # someone were to apply the catalog in non-noop mode. - # We're going to go ahead and record that we checked if there were - # no changes, since it's unlikely it will affect the scheduling. - noop = noop? - if ! noop or (noop && changes.length == 0) - self.cache(:checked, Time.now) - end - return changes.flatten - end - - # 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 - return currentpropvalues - end - - # get a hash of the current properties. - def currentpropvalues(override_value = nil) - # it's important to use the method here, as it follows the order - # in which they're defined in the object - return properties().inject({}) { | prophash, property| - prophash[property] = override_value.nil? ? - property.retrieve : - override_value - prophash - } - end - - # Are we running in noop mode? - def noop? - if defined?(@noop) - @noop - else - Puppet[:noop] - end - end - - def noop - noop? - end - - # Retrieve the changes associated with all of the properties. - def propertychanges(currentvalues) - # If we are changing the existence of the object, then none of - # the other properties matter. - changes = [] - ensureparam = @parameters[:ensure] - - # This allows resource types to have 'ensure' be a parameter, which allows them to - # just pass the parameter on to other generated resources. - ensureparam = nil unless ensureparam.is_a?(Puppet::Property) - if ensureparam && !currentvalues.include?(ensureparam) - raise Puppet::DevError, "Parameter ensure defined but missing from current values" - end - - if ensureparam and ! ensureparam.insync?(currentvalues[ensureparam]) - changes << Puppet::Transaction::Change.new(ensureparam, currentvalues[ensureparam]) - # Else, if the 'ensure' property is correctly absent, then do - # nothing - elsif ensureparam and currentvalues[ensureparam] == :absent - return [] - else - changes = properties().find_all { |property| - currentvalues[property] ||= :absent - ! property.insync?(currentvalues[property]) - }.collect { |property| - Puppet::Transaction::Change.new(property, currentvalues[property]) - } - end - - if Puppet[:debug] and changes.length > 0 - self.debug("Changing " + changes.collect { |ch| ch.property.name }.join(",")) - end - - changes - end -end - diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb deleted file mode 100644 index 3f44413f8..000000000 --- a/lib/puppet/metatype/instances.rb +++ /dev/null @@ -1,305 +0,0 @@ -require 'puppet/transportable' - -class Puppet::Type - # Make 'new' private, so people have to use create instead. - class << self - private :new - end - - # retrieve a named instance of the current type - def self.[](name) - @objects[name] || @aliases[name] - end - - # add an instance by name to the class list of instances - def self.[]=(name,object) - 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) - 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 - 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) - # Don't modify the original hash; instead, create a duplicate and modify it. - # We have to dup and use the ! so that it stays a TransObject if it is - # one. - hash = args.dup - symbolizehash!(hash) - - # If we're the base class, then pass the info on appropriately - if self == Puppet::Type - type = nil - if hash.is_a? Puppet::TransObject - type = hash.type - else - # If we're using the type to determine object type, then delete it - if type = hash[:type] - hash.delete(:type) - end - end - - # If they've specified a type and called on the base, then - # delegate to the subclass. - if type - if typeklass = self.type(type) - return typeklass.create(hash) - else - raise Puppet::Error, "Unknown type %s" % type - end - else - raise Puppet::Error, "No type found for %s" % hash.inspect - end - end - - # Handle this new object being implicit - implicit = hash[:implicit] || false - if hash.include?(:implicit) - hash.delete(:implicit) - end - - name = nil - unless hash.is_a? Puppet::TransObject - hash = self.hash2trans(hash) - end - - # XXX This will have to change when transobjects change to using titles - title = hash.name - - # if the object already exists - if self.isomorphic? and retobj = self[title] - # if only one of our objects is implicit, then it's easy to see - # who wins -- the non-implicit one. - if retobj.implicit? and ! implicit - Puppet.notice "Removing implicit %s" % retobj.title - # Remove all of the objects, but do not remove their subscriptions. - retobj.remove(false) - - # now pass through and create the new object - elsif implicit - Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title] - return nil - else - raise Puppet::Error, "%s is already being managed" % retobj.ref - end - end - - # create it anew - # if there's a failure, destroy the object if it got that far, but raise - # the error. - begin - obj = new(hash) - rescue => detail - Puppet.err "Could not create %s: %s" % [title, detail.to_s] - if obj - obj.remove(true) - elsif obj = self[title] - obj.remove(true) - end - raise - end - - if implicit - obj.implicit = true - end - - # Store the object by title - self[obj.title] = obj - - return obj - end - - # remove a specified object - def self.delete(resource) - 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 - 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) - return @objects.has_key?(name) - end - - # Convert a hash to a TransObject. - def self.hash2trans(hash) - title = nil - if hash.include? :title - title = hash[:title] - hash.delete(:title) - elsif hash.include? self.namevar - title = hash[self.namevar] - hash.delete(self.namevar) - - if hash.include? :name - raise ArgumentError, "Cannot provide both name and %s to %s" % - [self.namevar, self.name] - end - elsif hash[:name] - title = hash[:name] - hash.delete :name - end - - if catalog = hash[:catalog] - hash.delete(:catalog) - end - - raise(Puppet::Error, "You must specify a title for objects of type %s" % self.to_s) unless title - - if hash.include? :type - unless self.validattr? :type - hash.delete :type - end - end - - # okay, now make a transobject out of hash - begin - trans = Puppet::TransObject.new(title, self.name.to_s) - trans.catalog = catalog if catalog - hash.each { |param, value| - trans[param] = value - } - rescue => detail - raise Puppet::Error, "Could not create %s: %s" % - [name, detail] - end - - return trans - end - - # Retrieve all known instances. Either requires providers or must be overridden. - def self.instances - unless defined?(@providers) and ! @providers.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| - # First try to get the resource if it already exists - # Skip instances that map to a managed resource with a different provider - next if resource = self[instance.name] and resource.provider.class != instance.class - - # 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 - - if resource - resource.provider = instance - resource - else - create(:name => instance.name, :provider => instance, :check => :all) - end - 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 - - # Create the path for logging and such. - def pathbuilder - if p = parent - [p.pathbuilder, self.ref].flatten - else - [self.ref] - end - end -end - diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb deleted file mode 100644 index df96733c2..000000000 --- a/lib/puppet/metatype/metaparams.rb +++ /dev/null @@ -1,424 +0,0 @@ -require 'puppet' -require 'puppet/type' - -class Puppet::Type - # Add all of the meta parameters. - #newmetaparam(:onerror) do - # desc "How to handle errors -- roll back innermost - # transaction, roll back entire transaction, ignore, etc. Currently - # non-functional." - #end - - 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 "Propertys 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 - - # We've got four relationship metaparameters, so this method is used - # to reduce code duplication between them. - def munge_relationship(param, values) - # We need to support values passed in as an array or as a - # resource reference. - result = [] - - # 'values' could be an array or a reference. If it's an array, - # it could be an array of references or an array of arrays. - if values.is_a?(Puppet::Type) - result << [values.class.name, values.title] - else - unless values.is_a?(Array) - devfail "Relationships must be resource references" - end - if values[0].is_a?(String) or values[0].is_a?(Symbol) - # we're a type/title array reference - values[0] = symbolize(values[0]) - result << values - else - # we're an array of stuff - values.each do |value| - if value.is_a?(Puppet::Type) - result << [value.class.name, value.title] - elsif value.is_a?(Array) - value[0] = symbolize(value[0]) - result << value - else - devfail "Invalid relationship %s" % value.inspect - end - end - end - end - - if existing = self[param] - result = existing + result - end - - result - 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 - - @resource.info "Adding aliases %s" % aliases.collect { |a| a.inspect }.join(", ") - - 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 - - # LAK:FIXME Old-school, add the alias to the class. - @resource.class.alias(other, @resource) - - # 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:: - - puppetd --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(rels) - @resource.munge_relationship(self.class.name, rels) - end - - def validate_relationship - @value.each do |value| - unless @resource.catalog.resource(*value) - description = self.class.direction == :in ? "dependency" : "dependent" - fail Puppet::Error, "Could not find %s %s[%s] for %s" % - [description, value[0].to_s.capitalize, value[1], 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 |value| - # we just have a name and a type, and we need to convert it - # to an object... - tname, name = value - reference = Puppet::ResourceReference.new(tname, name) - - # Either of the two retrieval attempts could have returned - # nil. - unless object = 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 = object - target = @resource - else - source = @resource - target = object - end - - if method = self.class.callback - subargs = { - :event => self.class.events, - :callback => method - } - self.debug("subscribes to %s" % [object.ref]) - else - # If there's no callback, there's no point in even adding - # a label. - subargs = nil - self.debug("requires %s" % [object.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 -end # Puppet::Type - diff --git a/lib/puppet/metatype/providers.rb b/lib/puppet/metatype/providers.rb deleted file mode 100644 index 6308f7e54..000000000 --- a/lib/puppet/metatype/providers.rb +++ /dev/null @@ -1,247 +0,0 @@ -require 'puppet/provider' -require 'puppet/util/provider_features' -class Puppet::Type - # 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.defaultnum }.max - defaults = defaults.find_all { |provider| provider.defaultnum == 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 - - # Convert a hash, as provided by, um, a provider, into an instance of self. - def self.hash2obj(hash) - obj = nil - - namevar = self.namevar - unless hash.include?(namevar) and hash[namevar] - raise Puppet::DevError, "Hash was not passed with namevar" - end - - # if the obj already exists with that name... - if obj = self[hash[namevar]] - # We're assuming here that objects with the same name - # are the same object, which *should* be the case, assuming - # we've set up our naming stuff correctly everywhere. - - # Mark found objects as present - hash.each { |param, value| - if property = obj.property(param) - elsif val = obj[param] - obj[param] = val - else - # There is a value on disk, but it should go away - obj[param] = :absent - end - } - else - # create a new obj, since no existing one seems to - # match - obj = self.create(namevar => hash[namevar]) - - # We can't just pass the hash in at object creation time, - # because it sets the should value, not the is value. - hash.delete(namevar) - hash.each { |param, value| - obj[param] = value unless obj.add_property_parameter(param) - } - end - - return obj - 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 @providers.has_key?(name) - @providerloader.load(name) - end - return @providers[name] - end - - # Just list all of the providers. - def self.providers - @providers.keys - end - - def self.validprovider?(name) - name = Puppet::Util.symbolize(name) - - return (@providers.has_key?(name) && @providers[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 = @providers[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 => @providers, - :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 @providers.has_key? name - rmclass(name, - :hash => @providers, - :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 @providers.empty? - providerloader.loadall - end - @providers.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 -end diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb deleted file mode 100644 index 4fb78ae56..000000000 --- a/lib/puppet/metatype/relationships.rb +++ /dev/null @@ -1,115 +0,0 @@ -class Puppet::Type - # 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 - 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) - - # 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 = typeobj[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 - - # Does this resource have a relationship with the other? We have to - # check each object for both directions of relationship. - def requires?(other) - them = [other.class.name, other.title] - me = [self.class.name, self.title] - self.class.relationship_params.each do |param| - case param.direction - when :in: return true if v = self[param.name] and v.include?(them) - when :out: return true if v = other[param.name] and v.include?(me) - end - end - return false - end - - # we've received an event - # we only support local events right now, so we can pass actual - # objects around, including the transaction object - # the assumption here is that container objects will pass received - # methods on to contained objects - # i.e., we don't trigger our children, our refresh() method calls - # refresh() on our children - def trigger(event, source) - trans = event.transaction - if @callbacks.include?(source) - [:ALL_EVENTS, event.event].each { |eventname| - if method = @callbacks[source][eventname] - if trans.triggered?(self, method) > 0 - next - end - if self.respond_to?(method) - self.send(method) - end - - trans.triggered(self, method) - end - } - end - end - - # Unsubscribe from a given object, possibly with a specific event. - def unsubscribe(object, event = nil) - # First look through our own relationship params - [:require, :subscribe].each do |param| - if values = self[param] - newvals = values.reject { |d| - d == [object.class.name, object.title] - } - if newvals.length != values.length - self.delete(param) - self[param] = newvals - end - end - end - end -end - diff --git a/lib/puppet/metatype/schedules.rb b/lib/puppet/metatype/schedules.rb deleted file mode 100644 index 96ebce0ab..000000000 --- a/lib/puppet/metatype/schedules.rb +++ /dev/null @@ -1,33 +0,0 @@ -class Puppet::Type - # Look up the schedule and set it appropriately. This is done after - # the instantiation phase, so that the schedule can be anywhere in the - # file. - def schedule - unless defined? @schedule - if name = self[:schedule] - if sched = Puppet.type(:schedule)[name] - @schedule = sched - else - self.fail "Could not find schedule %s" % name - end - else - @schedule = nil - end - end - @schedule - end - - # Check whether we are scheduled to run right now or not. - def scheduled? - return true if Puppet[:ignoreschedules] - return true unless schedule = self.schedule - - # 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?(self.cached(:checked).to_i) - end -end - diff --git a/lib/puppet/metatype/tags.rb b/lib/puppet/metatype/tags.rb deleted file mode 100644 index 1d96306dd..000000000 --- a/lib/puppet/metatype/tags.rb +++ /dev/null @@ -1,38 +0,0 @@ -class Puppet::Type - attr_reader :tags - - # Add a new tag. - def tag(tag) - tag = tag.intern if tag.is_a? String - unless @tags.include? tag - @tags << tag - end - end - - # Define the initial list of tags. - def tags=(list) - list = [list] unless list.is_a? Array - - @tags = list.collect do |t| - case t - when String: t.intern - when Symbol: t - else - self.warning "Ignoring tag %s of type %s" % [tag.inspect, tag.class] - end - end - - @tags << self.class.name unless @tags.include?(self.class.name) - end - - # Figure out of any of the specified tags apply to this object. This is an - # OR operation. - def tagged?(tags) - tags = [tags] unless tags.is_a? Array - - tags = tags.collect { |t| t.intern } - - return tags.find { |tag| @tags.include? tag } - end -end - diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 45dd7f5b5..c7a866e2c 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -20,20 +20,2106 @@ class Type include Puppet::Util::LogPaths include Puppet::Util::Logging - # 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' + ############################### + # 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 namevar comes first, + # then the properties, then the params and metaparams in the order they + # were specified in the files. + def self.allattrs + # now get all of the arguments, in a specific order + # Cache this, since it gets called so many times + namevar = self.namevar + + order = [namevar] + if self.parameters.include?(:provider) + order << :provider + end + order << [self.properties.collect { |property| property.name }, + self.parameters - [:provider], + self.metaparams].flatten.reject { |param| + # we don't want our namevar in there multiple times + param == namevar + } + + order.flatten! + + return order + 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 + else + raise Puppet::DevError, + "Invalid attribute '%s' for class '%s'" % + [attr, self.name] + end + end + + @attrtypes[attr] + end + + # Copy an existing class parameter. This allows other types to avoid + # duplicating a parameter definition, and is mostly used by subclasses + # of the File class. + def self.copyparam(klass, name) + param = klass.attrclass(name) + + unless param + raise Puppet::DevError, "Class %s has no param %s" % [klass, name] + end + @parameters << param + @parameters.each { |p| @paramhash[name] = p } + + if param.isnamevar? + @namevar = param.name + end + end + + # A similar function but one that yields the class and type. + # This is mainly so that setdefaults doesn't call quite so many functions. + def self.eachattr(*ary) + if ary.empty? + ary = nil + end + + # We have to do this in a specific order, so that defaults are + # created in that order (e.g., providers should be set up before + # anything else). + allattrs.each do |name| + next unless ary.nil? or ary.include?(name) + if obj = @properties.find { |p| p.name == name } + yield obj, :property + elsif obj = @parameters.find { |p| p.name == name } + yield obj, :param + elsif obj = @@metaparams.find { |p| p.name == name } + yield obj, :meta + else + raise Puppet::DevError, "Could not find parameter %s" % name + end + end + 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 + + # If this param handles relationships, store that information + 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 + + # Find the namevar + def self.namevar + unless defined? @namevar + params = @parameters.find_all { |param| + param.isnamevar? or param.name == :name + } + + if params.length > 1 + raise Puppet::DevError, "Found multiple namevars for %s" % self.name + elsif params.length == 1 + @namevar = params[0].name + else + raise Puppet::DevError, "No namevar for %s" % self.name + end + end + @namevar + 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] + + # These might be enabled later. +# define_method(name) do +# @parameters[name].value +# end +# +# define_method(name.to_s + "=") do |value| +# newparam(param, value) +# end + + if param.isnamevar? + @namevar = param.name + end + + 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: + # * <tt>:parent</tt>: The parent class for the property. Defaults to Puppet::Property. + # * <tt>:retrieve</tt>: 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 + +# define_method(name) do +# @parameters[name].should +# end +# +# define_method(name.to_s + "=") do |value| +# newproperty(name, :should => value) +# 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 + + # fix any namevar => param translations + def argclean(oldhash) + # This duplication is here because it might be a transobject. + hash = oldhash.dup.to_hash + + if hash.include?(:resource) + hash.delete(:resource) + end + namevar = self.class.namevar + + # Do a simple translation for those cases where they've passed :name + # but that's not our namevar + if hash.include? :name and namevar != :name + if hash.include? namevar + raise ArgumentError, "Cannot provide both name and %s" % namevar + end + hash[namevar] = hash[:name] + hash.delete(:name) + end + + # Make sure we have a name, one way or another + unless hash.include? namevar + if defined? @title and @title + hash[namevar] = @title + else + raise Puppet::Error, "Was not passed a namevar or title" + end + end + + return hash + 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 + + # 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) + raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect]) + end + + if name == :name + name = self.class.namevar + 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) + raise TypeError.new("Invalid parameter %s" % [name]) + end + + if name == :name + name = self.class.namevar + 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 + + # 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) + unless name.is_a? Symbol + name = name.intern + end + return @parameters[name].value + 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 type by name; to return the value, use 'inst[name]' + # FIXME this method should go away + def property(name) + if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property) + return obj + else + return nil + end + end + +# def set(name, value) +# send(name.to_s + "=", value) +# end +# +# def get(name) +# send(name) +# 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 setdefaults(*ary) + #self.class.eachattr(*ary) { |klass, type| + self.class.eachattr(*ary) { |klass, type| + # not many attributes will have defaults defined, so we short-circuit + # those away + next unless klass.method_defined?(:default) + next if @parameters[klass.name] + + next unless obj = self.newattr(klass) + + # We have to check for nil values, not "truth", so we allow defaults + # to false. + value = obj.default and ! value.nil? + if ! value.nil? + obj.value = value + else + @parameters.delete(obj.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 + + # 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 + + # Meta-parameter methods: These methods deal with the results + # of specifying metaparameters + + private + + # Return all of the property objects, in the order specified in the + # class. + def properties + #debug "%s has %s properties" % [self,@parameters.length] + props = self.class.properties.collect { |prop| + @parameters[prop.name] + }.find_all { |p| + ! p.nil? + }.each do |prop| + unless prop.is_a?(Puppet::Property) + raise Puppet::DevError, "got a non-property %s(%s)" % + [prop.class, prop.class.name] + end + end + + props + end + + public + + ############################### + # Code related to the closure-like behaviour of the resource classes. + attr_writer :implicit + + # 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 implicit? + if defined? @implicit and @implicit + return true + else + return false + 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. + def self.depthfirst? + if defined? @depthfirst + return @depthfirst + else + return false + end + end + + def depthfirst? + self.class.depthfirst? + end + + # Add a hook for testing for recursion. + def parentof?(child) + if (self == child) + debug "parent is equal to child" + return true + elsif defined? @parent and @parent.parentof?(child) + debug "My parent is parent of child" + return true + else + return false + end + 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 + self.class.delete(self) + + @parent = nil + + # Remove the reference to the provider. + if self.provider + @provider.clear + @provider = nil + end + end + + ############################### + # Code related to evaluating the resources. + + # This method is responsible for collecting property changes we always + # descend into the children before we evaluate our current properties. + # This returns any changes resulting from testing, thus 'collect' rather + # than 'each'. + def evaluate + if self.provider.is_a?(Puppet::Provider) + unless provider.class.suitable? + raise Puppet::Error, "Provider %s is not functional on this platform" % provider.class.name + end + end + #Puppet.err "Evaluating %s" % self.path.join(":") + unless defined? @evalcount + self.err "No evalcount defined on '%s' of type '%s'" % + [self.title,self.class] + @evalcount = 0 + end + @evalcount += 1 + + if p = self.provider and p.respond_to?(:prefetch) + p.prefetch + end + + # this only operates on properties, not properties + children + # it's important that we call retrieve() on the type instance, + # not directly on the property, because it allows the type to override + # the method, like pfile does + currentvalues = self.retrieve + + changes = propertychanges(currentvalues).flatten + + # now record how many changes we've resulted in + if changes.length > 0 + self.debug "%s change(s)" % + [changes.length] + end + + # If we're in noop mode, we don't want to store the checked time, + # because it will result in the resource not getting scheduled if + # someone were to apply the catalog in non-noop mode. + # We're going to go ahead and record that we checked if there were + # no changes, since it's unlikely it will affect the scheduling. + noop = noop? + if ! noop or (noop && changes.length == 0) + self.cache(:checked, Time.now) + end + return changes.flatten + end + + # 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 + return currentpropvalues + end + + # get a hash of the current properties. + def currentpropvalues(override_value = nil) + # it's important to use the method here, as it follows the order + # in which they're defined in the object + return properties().inject({}) { | prophash, property| + prophash[property] = override_value.nil? ? + property.retrieve : + override_value + prophash + } + end + + # Are we running in noop mode? + def noop? + if defined?(@noop) + @noop + else + Puppet[:noop] + end + end + + def noop + noop? + end + + # Retrieve the changes associated with all of the properties. + def propertychanges(currentvalues) + # If we are changing the existence of the object, then none of + # the other properties matter. + changes = [] + ensureparam = @parameters[:ensure] + + # This allows resource types to have 'ensure' be a parameter, which allows them to + # just pass the parameter on to other generated resources. + ensureparam = nil unless ensureparam.is_a?(Puppet::Property) + if ensureparam && !currentvalues.include?(ensureparam) + raise Puppet::DevError, "Parameter ensure defined but missing from current values" + end + + if ensureparam and ! ensureparam.insync?(currentvalues[ensureparam]) + changes << Puppet::Transaction::Change.new(ensureparam, currentvalues[ensureparam]) + # Else, if the 'ensure' property is correctly absent, then do + # nothing + elsif ensureparam and currentvalues[ensureparam] == :absent + return [] + else + changes = properties().find_all { |property| + currentvalues[property] ||= :absent + ! property.insync?(currentvalues[property]) + }.collect { |property| + Puppet::Transaction::Change.new(property, currentvalues[property]) + } + end + + if Puppet[:debug] and changes.length > 0 + self.debug("Changing " + changes.collect { |ch| ch.property.name }.join(",")) + end + + changes + end + + ############################### + # Code related to managing resource instances. + require 'puppet/transportable' + + # Make 'new' private, so people have to use create instead. + class << self + private :new + end + + # retrieve a named instance of the current type + def self.[](name) + @objects[name] || @aliases[name] + end + + # add an instance by name to the class list of instances + def self.[]=(name,object) + 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) + 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 + 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) + # Don't modify the original hash; instead, create a duplicate and modify it. + # We have to dup and use the ! so that it stays a TransObject if it is + # one. + hash = args.dup + symbolizehash!(hash) + + # If we're the base class, then pass the info on appropriately + if self == Puppet::Type + type = nil + if hash.is_a? Puppet::TransObject + type = hash.type + else + # If we're using the type to determine object type, then delete it + if type = hash[:type] + hash.delete(:type) + end + end + + # If they've specified a type and called on the base, then + # delegate to the subclass. + if type + if typeklass = self.type(type) + return typeklass.create(hash) + else + raise Puppet::Error, "Unknown type %s" % type + end + else + raise Puppet::Error, "No type found for %s" % hash.inspect + end + end + + # Handle this new object being implicit + implicit = hash[:implicit] || false + if hash.include?(:implicit) + hash.delete(:implicit) + end + + name = nil + unless hash.is_a? Puppet::TransObject + hash = self.hash2trans(hash) + end + + # XXX This will have to change when transobjects change to using titles + title = hash.name + + # if the object already exists + if self.isomorphic? and retobj = self[title] + # if only one of our objects is implicit, then it's easy to see + # who wins -- the non-implicit one. + if retobj.implicit? and ! implicit + Puppet.notice "Removing implicit %s" % retobj.title + # Remove all of the objects, but do not remove their subscriptions. + retobj.remove(false) + + # now pass through and create the new object + elsif implicit + Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title] + return nil + else + raise Puppet::Error, "%s is already being managed" % retobj.ref + end + end + + # create it anew + # if there's a failure, destroy the object if it got that far, but raise + # the error. + begin + obj = new(hash) + rescue => detail + Puppet.err "Could not create %s: %s" % [title, detail.to_s] + if obj + obj.remove(true) + elsif obj = self[title] + obj.remove(true) + end + raise + end + + if implicit + obj.implicit = true + end + + # Store the object by title + self[obj.title] = obj + + return obj + end + + # remove a specified object + def self.delete(resource) + 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 + 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) + return @objects.has_key?(name) + end + + # Convert a hash to a TransObject. + def self.hash2trans(hash) + title = nil + if hash.include? :title + title = hash[:title] + hash.delete(:title) + elsif hash.include? self.namevar + title = hash[self.namevar] + hash.delete(self.namevar) + + if hash.include? :name + raise ArgumentError, "Cannot provide both name and %s to %s" % + [self.namevar, self.name] + end + elsif hash[:name] + title = hash[:name] + hash.delete :name + end + + if catalog = hash[:catalog] + hash.delete(:catalog) + end + + raise(Puppet::Error, "You must specify a title for objects of type %s" % self.to_s) unless title + + if hash.include? :type + unless self.validattr? :type + hash.delete :type + end + end + + # okay, now make a transobject out of hash + begin + trans = Puppet::TransObject.new(title, self.name.to_s) + trans.catalog = catalog if catalog + hash.each { |param, value| + trans[param] = value + } + rescue => detail + raise Puppet::Error, "Could not create %s: %s" % + [name, detail] + end + + return trans + end + + # Retrieve all known instances. Either requires providers or must be overridden. + def self.instances + unless defined?(@providers) and ! @providers.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| + # First try to get the resource if it already exists + # Skip instances that map to a managed resource with a different provider + next if resource = self[instance.name] and resource.provider.class != instance.class + + # 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 + + if resource + resource.provider = instance + resource + else + create(:name => instance.name, :provider => instance, :check => :all) + end + 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 + + # 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 "Propertys 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 + + # We've got four relationship metaparameters, so this method is used + # to reduce code duplication between them. + def munge_relationship(param, values) + # We need to support values passed in as an array or as a + # resource reference. + result = [] + + # 'values' could be an array or a reference. If it's an array, + # it could be an array of references or an array of arrays. + if values.is_a?(Puppet::Type) + result << [values.class.name, values.title] + else + unless values.is_a?(Array) + devfail "Relationships must be resource references" + end + if values[0].is_a?(String) or values[0].is_a?(Symbol) + # we're a type/title array reference + values[0] = symbolize(values[0]) + result << values + else + # we're an array of stuff + values.each do |value| + if value.is_a?(Puppet::Type) + result << [value.class.name, value.title] + elsif value.is_a?(Array) + value[0] = symbolize(value[0]) + result << value + else + devfail "Invalid relationship %s" % value.inspect + end + end + end + end + + if existing = self[param] + result = existing + result + end + + result + 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 + + @resource.info "Adding aliases %s" % aliases.collect { |a| a.inspect }.join(", ") + + 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 + + # LAK:FIXME Old-school, add the alias to the class. + @resource.class.alias(other, @resource) + + # 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:: + + puppetd --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(rels) + @resource.munge_relationship(self.class.name, rels) + end + + def validate_relationship + @value.each do |value| + unless @resource.catalog.resource(*value) + description = self.class.direction == :in ? "dependency" : "dependent" + fail Puppet::Error, "Could not find %s %s[%s] for %s" % + [description, value[0].to_s.capitalize, value[1], 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 |value| + # we just have a name and a type, and we need to convert it + # to an object... + tname, name = value + reference = Puppet::ResourceReference.new(tname, name) + + # Either of the two retrieval attempts could have returned + # nil. + unless object = 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 = object + target = @resource + else + source = @resource + target = object + end + + if method = self.class.callback + subargs = { + :event => self.class.events, + :callback => method + } + self.debug("subscribes to %s" % [object.ref]) + else + # If there's no callback, there's no point in even adding + # a label. + subargs = nil + self.debug("requires %s" % [object.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 + + ############################### + # 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.defaultnum }.max + defaults = defaults.find_all { |provider| provider.defaultnum == 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 + + # Convert a hash, as provided by, um, a provider, into an instance of self. + def self.hash2obj(hash) + obj = nil + + namevar = self.namevar + unless hash.include?(namevar) and hash[namevar] + raise Puppet::DevError, "Hash was not passed with namevar" + end + + # if the obj already exists with that name... + if obj = self[hash[namevar]] + # We're assuming here that objects with the same name + # are the same object, which *should* be the case, assuming + # we've set up our naming stuff correctly everywhere. + + # Mark found objects as present + hash.each { |param, value| + if property = obj.property(param) + elsif val = obj[param] + obj[param] = val + else + # There is a value on disk, but it should go away + obj[param] = :absent + end + } + else + # create a new obj, since no existing one seems to + # match + obj = self.create(namevar => hash[namevar]) + + # We can't just pass the hash in at object creation time, + # because it sets the should value, not the is value. + hash.delete(namevar) + hash.each { |param, value| + obj[param] = value unless obj.add_property_parameter(param) + } + end + + return obj + 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 @providers.has_key?(name) + @providerloader.load(name) + end + return @providers[name] + end + + # Just list all of the providers. + def self.providers + @providers.keys + end + + def self.validprovider?(name) + name = Puppet::Util.symbolize(name) + + return (@providers.has_key?(name) && @providers[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 = @providers[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 => @providers, + :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 @providers.has_key? name + rmclass(name, + :hash => @providers, + :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 @providers.empty? + providerloader.loadall + end + @providers.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 + 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) + + # 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 = typeobj[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 + + # Does this resource have a relationship with the other? We have to + # check each object for both directions of relationship. + def requires?(other) + them = [other.class.name, other.title] + me = [self.class.name, self.title] + self.class.relationship_params.each do |param| + case param.direction + when :in: return true if v = self[param.name] and v.include?(them) + when :out: return true if v = other[param.name] and v.include?(me) + end + end + return false + end + + # we've received an event + # we only support local events right now, so we can pass actual + # objects around, including the transaction object + # the assumption here is that container objects will pass received + # methods on to contained objects + # i.e., we don't trigger our children, our refresh() method calls + # refresh() on our children + def trigger(event, source) + trans = event.transaction + if @callbacks.include?(source) + [:ALL_EVENTS, event.event].each { |eventname| + if method = @callbacks[source][eventname] + if trans.triggered?(self, method) > 0 + next + end + if self.respond_to?(method) + self.send(method) + end + + trans.triggered(self, method) + end + } + end + end + + # Unsubscribe from a given object, possibly with a specific event. + def unsubscribe(object, event = nil) + # First look through our own relationship params + [:require, :subscribe].each do |param| + if values = self[param] + newvals = values.reject { |d| + d == [object.class.name, object.title] + } + if newvals.length != values.length + self.delete(param) + self[param] = newvals + end + end + end + end + + ############################### + # All of the scheduling code. + + # Look up the schedule and set it appropriately. This is done after + # the instantiation phase, so that the schedule can be anywhere in the + # file. + def schedule + unless defined? @schedule + if name = self[:schedule] + if sched = Puppet.type(:schedule)[name] + @schedule = sched + else + self.fail "Could not find schedule %s" % name + end + else + @schedule = nil + end + end + @schedule + end + + # Check whether we are scheduled to run right now or not. + def scheduled? + return true if Puppet[:ignoreschedules] + return true unless schedule = self.schedule + + # 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?(self.cached(:checked).to_i) + end + + ############################### + # All of the tagging code. + attr_reader :tags + + # Add a new tag. + def tag(tag) + tag = tag.intern if tag.is_a? String + unless @tags.include? tag + @tags << tag + end + end + + # Define the initial list of tags. + def tags=(list) + list = [list] unless list.is_a? Array + + @tags = list.collect do |t| + case t + when String: t.intern + when Symbol: t + else + self.warning "Ignoring tag %s of type %s" % [tag.inspect, tag.class] + end + end + + @tags << self.class.name unless @tags.include?(self.class.name) + end + + # Figure out of any of the specified tags apply to this object. This is an + # OR operation. + def tagged?(tags) + tags = [tags] unless tags.is_a? Array + + tags = tags.collect { |t| t.intern } + + return tags.find { |tag| @tags.include? tag } + end # Types (which map to resources in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an diff --git a/spec/unit/type/noop_metaparam.rb b/spec/unit/type/noop_metaparam.rb index 2a3e0160d..8510a1b6b 100755 --- a/spec/unit/type/noop_metaparam.rb +++ b/spec/unit/type/noop_metaparam.rb @@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' -require 'puppet/metatype/metaparams' +require 'puppet/type' describe Puppet::Type.type(:file).attrclass(:noop) do before do |