From 79889af9ae0d7ffbcb25a4bb20a74602d2d706e2 Mon Sep 17 00:00:00 2001 From: Henrik Lindberg Date: Thu, 13 Dec 2012 22:35:54 +0100 Subject: [PATCH] (#18023) Document user facing classes using yardoc and show api. This adds yardoc to user facing classes Type, Parameter, Property, Provider, Report, as well as classes used by these via inclusion or delegation and where documetation was most relevant to add at the source. This also includes puppet specific tags (@comment, and @dsl), puppet specific templates (shows DSL for elements marked with @dsl), as well as some general improvements to the default yard templates. As this is the first stab at providing more comprehensive code documentation, there are many unanswered questions in the documentation that are marked with @todo, or ??? to indicate uncertainty. More investigation is required to update these. Many methods are marked as being non api. As this documentation is not final, and only covers a portion of the puppet code base, the lack of a "private" tag being displayed should not yet be taken as authoritiative indication that it is api. --- .yardopts | 2 + lib/puppet/metatype/manager.rb | 55 +- lib/puppet/parameter.rb | 353 ++++++- lib/puppet/parameter/package_options.rb | 5 + lib/puppet/parameter/path.rb | 29 +- lib/puppet/parameter/value.rb | 36 +- lib/puppet/parameter/value_collection.rb | 102 +- lib/puppet/property.rb | 374 +++++-- lib/puppet/property/ensure.rb | 15 +- lib/puppet/property/keyvalue.rb | 26 +- lib/puppet/property/list.rb | 3 + lib/puppet/property/ordered_list.rb | 7 + lib/puppet/provider.rb | 328 ++++++- lib/puppet/provider/confiner.rb | 31 +- lib/puppet/reports.rb | 48 +- lib/puppet/transaction/report.rb | 135 ++- lib/puppet/type.rb | 912 +++++++++++++++--- lib/puppet/util/classgen.rb | 91 +- lib/puppet/util/execution.rb | 96 +- lib/puppet/util/provider_features.rb | 32 +- .../method_details/html/method_signature.erb | 25 + .../default/method_details/html/setup.rb | 37 + .../default/module/html/item_summary.erb | 47 + 23 files changed, 2331 insertions(+), 458 deletions(-) create mode 100644 yardoc/templates/default/method_details/html/method_signature.erb create mode 100644 yardoc/templates/default/method_details/html/setup.rb create mode 100644 yardoc/templates/default/module/html/item_summary.erb diff --git a/.yardopts b/.yardopts index a552a9bc425..4a8a9632367 100644 --- a/.yardopts +++ b/.yardopts @@ -7,5 +7,7 @@ --transitive-tag status --tag comment --hide-tag comment +--tag dsl:"DSL" --no-transitive-tag api +--template-path yardoc/templates lib/**/*.rb diff --git a/lib/puppet/metatype/manager.rb b/lib/puppet/metatype/manager.rb index 23da210c61d..727b89034b7 100644 --- a/lib/puppet/metatype/manager.rb +++ b/lib/puppet/metatype/manager.rb @@ -2,20 +2,29 @@ require 'puppet/util/classgen' require 'puppet/node/environment' -# Methods dealing with Type management. This module gets included into the -# Puppet::Type class, it's just split out here for clarity. +# This module defines methods dealing with Type management. +# This module gets included into the Puppet::Type class, it's just split out here for clarity. +# @api public +# module Puppet::MetaType module Manager include Puppet::Util::ClassGen - # remove all type instances; this is mostly only useful for testing + # An implementation specific method that removes all type instances during testing. + # @note Only use this method for testing purposes. + # @api private + # def allclear @types.each { |name, type| type.clear } end - # iterate across all of the subclasses of Type + # Iterates over all already loaded Type subclasses. + # @yield [t] a block receiving each type + # @yieldparam t [Puppet::Type] each defined type + # @yieldreturn [Object] the last returned object is also returned from this method + # @return [Object] the last returned value from the block. def eachtype @types.each do |name, type| # Only consider types that have names @@ -25,12 +34,31 @@ def eachtype end end - # Load all types. Only currently used for documentation. + # Loads all types. + # @note Should only be used for purposes such as generating documentation as this is potentially a very + # expensive operation. + # @return [void] + # def loadall typeloader.loadall end - # Define a new type. + # Defines a new type or redefines an existing type with the given name. + # A convenience method on the form `new` where name is the name of the type is also created. + # (If this generated method happens to clash with an existing method, a warning is issued and the original + # method is kept). + # + # @param name [String] the name of the type to create or redefine. + # @param options [Hash] options passed on to {Puppet::Util::ClassGen#genclass} as the option `:attributes` after + # first having removed any present `:parent` option. + # @option options [Puppet::Type] :parent the parent (super type) of this type. If nil, the default is + # Puppet::Type. This option is not passed on as an attribute to genclass. + # @yield [ ] a block evaluated in the context of the created class, thus allowing further detailing of + # that class. + # @return [Class] the created subclass + # @see Puppet::Util::ClassGen.genclass + # + # @dsl type # @api public def newtype(name, options = {}, &block) # Handle backward compatibility @@ -97,7 +125,9 @@ def newtype(name, options = {}, &block) klass end - # Remove an existing defined type. Largely used for testing. + # Removes an existing type. + # @note Only use this for testing. + # @api private def rmtype(name) # Then create the class. @@ -106,7 +136,11 @@ def rmtype(name) singleton_class.send(:remove_method, "new#{name}") if respond_to?("new#{name}") end - # Return a Type instance by name. + # Returns a Type instance by name. + # This will load the type if not already defined. + # @param [String, Symbol] name of the wanted Type + # @return [Puppet::Type, nil] the type or nil if the type was not defined and could not be loaded + # def type(name) @types ||= {} @@ -130,7 +164,10 @@ def type(name) return @types[name] end - # Create a loader for Puppet types. + # Creates a loader for Puppet types. + # Defaults to an instance of {Puppet::Util::Autoload} if no other auto loader has been set. + # @return [Puppet::Util::Autoload] the loader to use. + # @api private def typeloader unless defined?(@typeloader) @typeloader = Puppet::Util::Autoload.new(self, "puppet/type", :wrap => false) diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb index 3969374cde1..d515c5e247a 100644 --- a/lib/puppet/parameter.rb +++ b/lib/puppet/parameter.rb @@ -3,6 +3,22 @@ require 'puppet/util/logging' require 'puppet/util/docs' +# The Parameter class is the implementation of a resource's attributes of _parameter_ kind. +# The Parameter class is also the base class for {Puppet::Property}, and is used to describe meta-parameters +# (parameters that apply to all resource types). +# A Parameter (in contrast to a Property) has a single value where a property has both a current and a wanted value. +# The Parameter class methods are used to configure and create an instance of Parameter that represents +# one particular attribute data type; its valid value(s), and conversion to/from internal form. +# +# The intention is that a new parameter is created by using the DSL method {Puppet::Type.newparam}, or +# {Puppet::Type.newmetaparam} if the parameter should be applicable to all resource types. +# +# A Parameter that does not specify and valid values (via {newvalues}) accepts any value. +# +# @see Puppet::Type +# @see Puppet::Property +# @api public +# class Puppet::Parameter include Puppet::Util include Puppet::Util::Errors @@ -15,12 +31,71 @@ class Puppet::Parameter class << self include Puppet::Util include Puppet::Util::Docs - attr_reader :validater, :munger, :name, :default, :required_features, :value_collection + # Unused? + # @todo The term "validater" only appears in this location in the Puppet code base. There is `validate` + # which seems to works fine without this attribute declaration. + # @api private + # + attr_reader :validater + + # Unused? + # @todo The term "munger" only appears in this location in the Puppet code base. There is munge and unmunge + # and they seem to work perfectly fine without this attribute declaration. + # @api private + # + attr_reader :munger + + # @return [Symbol] The parameter name as given when it was created. + attr_reader :name + + # @return [Object] The default value of the parameter as determined by the {defaultto} method, or nil if no + # default has been set. + attr_reader :default + + # @comment This somewhat odd documentation construct is because the getter and setter are not + # orthogonal; the setter uses varargs and this confuses yard. To overcome the problem both the + # getter and the setter are documented here. If this issues is fixed, a todo will be displayed + # for the setter method, and the setter documentation can be moved there. + # Since the attribute is actually RW it should perhaps instead just be implemented as a setter + # and a getter method (and no attr_xxx declaration). + # + # @!attribute [rw] required_features + # @return [Array] The names of the _provider features_ required for this parameter to work. + # the returned names are always all lower case symbols. + # @overload required_features + # Returns the required _provider features_ as an array of lower case symbols + # @overload required_features=(*args) + # @param *args [Symbol] one or more names of required provider features + # Sets the required_provider_features_ from one or more values, or array. The given arguments + # are flattened, and internalized. + # @api public + # @dsl type + # + attr_reader :required_features + + # @return [Puppet::Parameter::ValueCollection] The set of valid values (or an empty set that accepts any value). + # @api private + # + attr_reader :value_collection + + # @return [Boolean] Flag indicating whether this parameter is a meta-parameter or not. attr_accessor :metaparam - # Define the default value for a given parameter or parameter. This - # means that 'nil' is an invalid default value. This defines - # the 'default' instance method. + # Defines how the `default` value of a parameter is computed. + # The computation of the parameter's default value is defined by providing a value or a block. + # A default of `nil` can not be used. + # @overload defaultto(value) + # Defines the default value with a literal value + # @param value [Object] the literal value to use as the default value + # @overload defaultto({|| ... }) + # Defines that the default value is produced by the given block. The given block + # should produce the default value. + # @raise [Puppet::DevError] if value is nil, and no block is given. + # @return [void] + # @see Parameter.default + # @dsl type + # @api public + # def defaultto(value = nil, &block) if block define_method(:default, &block) @@ -33,8 +108,12 @@ def defaultto(value = nil, &block) end end - # Return a documentation string. If there are valid values, - # then tack them onto the string. + # Produces a documentation string. + # If an enumeration of _valid values_ has been defined, it is appended to the documentation + # for this parameter specified with the {desc} method. + # @return [String] Returns a documentation string. + # @api public + # def doc @doc ||= "" @@ -50,21 +129,49 @@ def doc @doc end + # Removes the `default` method if defined. + # Has no effect if the default method is not defined. + # This method is intended to be used in a DSL scenario where a parameter inherits from a parameter + # with a default value that is not wanted in the derived parameter (otherwise, simply do not define + # a default value method). + # + # @return [void] + # @see desc + # @api public + # @dsl type + # def nodefault undef_method :default if public_method_defined? :default end - # Store documentation for this parameter. + # Sets the documentation for this parameter. + # @param str [String] The documentation string to set + # @return [String] the given `str` parameter + # @see doc + # @dsl type + # @api public + # def desc(str) @doc = str end + # Initializes the instance variables. + # Clears the internal value collection (set of allowed values). + # @return [void] + # @api private + # def initvars @value_collection = ValueCollection.new end - # This is how we munge the value. Basically, this is our - # opportunity to convert the value from one form into another. + # @overload munge {|| ... } + # Defines an optional method used to convert the parameter value from DSL/string form to an internal form. + # If a munge method is not defined, the DSL/string value is used as is. + # @note This adds a method with the name `unsafe_munge` in the created parameter class. Later this method is + # called in a context where exceptions will be rescued and handled. + # @dsl type + # @api public + # def munge(&block) # I need to wrap the unsafe version in begin/rescue parameterments, # but if I directly call the block then it gets bound to the @@ -73,55 +180,105 @@ def munge(&block) define_method(:unsafe_munge, &block) end - # Does the parameter support reverse munging? - # This will be called when something wants to access the parameter - # in a canonical form different to what the storage form is. + # @overload unmunge {|| ... } + # Defines an optional method used to convert the parameter value to DSL/string form from an internal form. + # If an `unmunge` method is not defined, the internal form is used. + # @see munge + # @note This adds a method with the name `unmunge` in the created parameter class. + # @dsl type + # @api public + # def unmunge(&block) define_method(:unmunge, &block) end - # Mark whether we're the namevar. + # Sets a marker indicating that this parameter is the _namevar_ (unique identifier) of the type + # where the parameter is contained. + # This also makes the parameter a required value. The marker can not be unset once it has been set. + # @return [void] + # @dsl type + # @api public + # def isnamevar @isnamevar = true @required = true end - # Is this parameter the namevar? Defaults to false. + # @return [Boolean] Returns whether this parameter is the _namevar_ or not. + # @api public + # def isnamevar? @isnamevar end - # This parameter is required. + # Sets a marker indicating that this parameter is required. + # Once set, it is not possible to make a parameter optional. + # @return [void] + # @dsl type + # @api public + # def isrequired @required = true end - # Specify features that are required for this parameter to work. + # @comment This method is not picked up by yard as it has a different signature than + # expected for an attribute (varargs). Instead, this method is documented as an overload + # of the attribute required_features. (Not ideal, but better than nothing). + # @todo If this text appears in documentation - see comment in source and makes corrections - it means + # that an issue in yardoc has been fixed. + # def required_features=(*args) @required_features = args.flatten.collect { |a| a.to_s.downcase.intern } end - # Is this parameter required? Defaults to false. + # Returns whether this parameter is required or not. + # A parameter is required if a call has been made to the DSL method {isrequired}. + # @return [Boolean] Returns whether this parameter is required or not. + # @api public + # def required? @required end - # Verify that we got a good value + # @overload validate {|| ... } + # Defines an optional method that is used to validate the parameter's value. + # Validation should raise appropriate exceptions, the return value of the given block is ignored. + # @return [void] + # @dsl type + # @api public + # def validate(&block) define_method(:unsafe_validate, &block) end - # Define a new value for our parameter. + # Defines valid values for the parameter (enumeration or regular expressions). + # The set of valid values for the parameter can be limited to a (mix of) literal values and + # regular expression patterns. + # @note Each call to this method adds to the set of valid values + # @param names [Symbol, Regexp] The set of valid literal values and/or patterns for the parameter. + # @return [void] + # @dsl type + # @api public + # def newvalues(*names) @value_collection.newvalues(*names) end + # Makes the given `name` an alias for the given `other` name. + # Or said differently, the valid value `other` can now also be referred to via the given `name`. + # Aliasing may affect how the parameter's value is serialized/stored (it may store the `other` value + # instead of the alias). + # @api public + # @dsl type + # def aliasvalue(name, other) @value_collection.aliasvalue(name, other) end end - # Just a simple method to proxy instance methods to class methods + # Creates instance (proxy) methods that delegates to a class method with the same name. + # @api private + # def self.proxymethods(*values) values.each { |val| define_method(val) do @@ -130,21 +287,45 @@ def self.proxymethods(*values) } end - # And then define one of these proxies for each method in our - # ParamHandler class. + # @!method required? + # (see required?) + # @!method isnamevar? + # (see isnamevar?) + # proxymethods("required?", "isnamevar?") + # @return [Puppet::Resource] A reference to the resource this parameter is an attribute of (the _associated resource_). attr_accessor :resource - # LAK 2007-05-09: Keep the @parent around for backward compatibility. + + # @comment LAK 2007-05-09: Keep the @parent around for backward compatibility. + # @return [Puppet::Parameter] A reference to the parameter's parent kept for backwards compatibility. + # @api private + # attr_accessor :parent + # @!method line() + # @return [Integer] Returns the result of calling the same method on the associated resource. + # @!method file + # @return [Integer] Returns the result of calling the same method on the associated resource. + # @!method version + # @return [Integer] Returns the result of calling the same method on the associated resource. + # [:line, :file, :version].each do |param| define_method(param) do resource.send(param) end end - # Basic parameter initialization. + # Initializes the parameter with a required resource reference and optional attribute settings. + # The option `:resource` must be specified or an exception is raised. Any additional options passed + # are used to initialize the attributes of this parameter by treating each key in the `options` hash as + # the name of the attribute to set, and the value as the value to set. + # @param options [Hash{Symbol => Object]] Options, where `resource` is required + # @option options [Puppet::Resource] :resource The resource this parameter holds a value for. Required. + # @raise [Puppet::DevError] If resource is not specified in the options hash. + # @api public + # @note A parameter should be created via the DSL method {Puppet::Type::newparam} + # def initialize(options = {}) options = symbolize_options(options) if resource = options[:resource] @@ -157,24 +338,35 @@ def initialize(options = {}) set_options(options) end + # Writes the given `msg` to the log with the loglevel indicated by the associated resource's + # `loglevel` parameter. + # @todo is loglevel a metaparameter? it is looked up with `resource[:loglevel]` + # @return [void] + # @api public def log(msg) send_log(resource[:loglevel], msg) end - # Is this parameter a metaparam? + # @return [Boolean] Returns whether this parameter is a meta-parameter or not. def metaparam? self.class.metaparam end - # each parameter class must define the name method, and parameter - # instances do not change that name this implicitly means that a given - # object can only have one parameter instance of a given parameter - # class + # @!attribute [r] name + # @return [Symbol] The parameter's name as given when it was created. + # @note Since a Parameter defines the name at the class level, each Parameter class must be + # unique within a type's inheritance chain. + # @comment each parameter class must define the name method, and parameter + # instances do not change that name this implicitly means that a given + # object can only have one parameter instance of a given parameter + # class def name self.class.name end - # for testing whether we should actually do anything + # @return [Boolean] Returns true if this parameter, the associated resource, or overall puppet mode is `noop`. + # @todo How is noop mode set for a parameter? Is this of value in DSL to inhibit a parameter? + # def noop @noop ||= false tmp = @noop || self.resource.noop || Puppet[:noop] || false @@ -182,8 +374,11 @@ def noop tmp end - # return the full path to us, for logging and rollback; not currently - # used + # @todo Original comment = _return the full path to us, for logging and rollback; not currently + # used_ This is difficult to figure out (if it is used or not as calls are certainly made to "pathbuilder" + # method is several places, not just sure if it is this implementation or not. + # + # @api private def pathbuilder if @resource return [@resource.pathbuilder, self.name] @@ -192,18 +387,31 @@ def pathbuilder end end - # If the specified value is allowed, then munge appropriately. - # If the developer uses a 'munge' hook, this method will get overridden. + # This is the default implementation of `munge` that simply produces the value (if it is valid). + # The DSL method {munge} should be used to define an overriding method if munging is required. + # + # @api private + # def unsafe_munge(value) self.class.value_collection.munge(value) end - # no unmunge by default + # Unmunges the value by transforming it from internal form to DSL form. + # This is the default implementation of `unmunge` that simply returns the value without processing. + # The DSL method {unmunge} should be used to define an overriding method if required. + # @return [Object] the unmunged value + # def unmunge(value) value end - # A wrapper around our munging that makes sure we raise useful exceptions. + # Munges the value to internal form. + # This implementation of `munge` provides exception handling around the specified munging of this parameter. + # @note This method should not be overridden. Use the DSL method {munge} to define a munging method + # if required. + # @param value [Object] the DSL value to munge + # @return [Object] the munged (internal) value + # def munge(value) begin ret = unsafe_munge(value) @@ -216,13 +424,27 @@ def munge(value) ret end - # Verify that the passed value is valid. - # If the developer uses a 'validate' hook, this method will get overridden. + # This is the default implementation of `validate` that may be overridden by the DSL method {validate}. + # If no valid values have been defined, the given value is accepted, else it is validated against + # the literal values (enumerator) and/or patterns defined by calling {newvalues}. + # + # @param value [Object] the value to check for validity + # @raise [ArgumentError] if the value is not valid + # @return [void] + # @api private + # def unsafe_validate(value) self.class.value_collection.validate(value) end + # Performs validation of the given value against the rules defined by this parameter. + # @return [void] + # @todo Better description of when the various exceptions are raised.ArgumentError is rescued and + # changed into Puppet::Error. + # @raise [ArgumentError, TypeError, Puppet::DevError, Puppet::Error] under various conditions # A protected validation method that only ever raises useful exceptions. + # @api public + # def validate(value) begin unsafe_validate(value) @@ -235,30 +457,54 @@ def validate(value) end end + # Sets the associated resource to nil. + # @todo Why - what is the intent/purpose of this? + # @return [nil] + # def remove @resource = nil end + # @return [Object] Gets the value of this parameter after performing any specified unmunging. def value unmunge(@value) unless @value.nil? end - # Store the value provided. All of the checking should possibly be - # late-binding (e.g., users might not exist when the value is assigned - # but might when it is asked for). + # Sets the given value as the value of this parameter. + # @todo This original comment _"All of the checking should possibly be + # late-binding (e.g., users might not exist when the value is assigned + # but might when it is asked for)."_ does not seem to be correct, the implementation + # calls both validate an munge on the given value, so no late binding. + # + # The given value is validated and then munged (if munging has been specified). The result is store + # as the value of this arameter. + # @return [Object] The given `value` after munging. + # @raise (see #validate) + # def value=(value) validate(value) @value = munge(value) end - # Retrieve the resource's provider. Some types don't have providers, in which - # case we return the resource object itself. + # @return [Puppet::Provider] Returns the provider of the associated resource. + # @todo The original comment says = _"Retrieve the resource's provider. + # Some types don't have providers, in which case we return the resource object itself."_ + # This does not seem to be true, the default implementation that sets this value may be + # {Puppet::Type.provider=} which always gets either the name of a provider or an instance of one. + # def provider @resource.provider end - # The properties need to return tags so that logs correctly collect them. + # @return [Array] Returns an array of the associated resource's symbolic tags (including the parameter itself). + # Returns an array of the associated resource's symbolic tags (including the parameter itself). + # At a minimun, the array contains the name of the parameter. If the associated resource + # has tags, these tags are also included in the array. + # @todo The original comment says = _"The properties need to return tags so that logs correctly + # collect them."_ what if anything of that is of interest to document. Should tags and their relationship + # to logs be described. This is a more general concept. + # def tags unless defined?(@tags) @tags = [] @@ -269,10 +515,29 @@ def tags @tags end + # @return [String] The name of the parameter in string form. def to_s name.to_s end + # Produces a String with the value formatted for display to a human. + # When the parameter value is a: + # + # * **single valued parameter value** the result is produced on the + # form `'value'` where _value_ is the string form of the parameter's value. + # + # * **Array** the list of values is enclosed in `[]`, and + # each produced value is separated by a comma. + # + # * **Hash** value is output with keys in sorted order enclosed in `{}` with each entry formatted + # on the form `'k' => v` where + # `k` is the key in string form and _v_ is the value of the key. Entries are comma separated. + # + # For both Array and Hash this method is called recursively to format contained values. + # @note this method does not protect against infinite structures. + # + # @return [String] The formatted value in string form. + # def self.format_value_for_display(value) if value.is_a? Array formatted_values = value.collect {|value| format_value_for_display(value)}.join(', ') diff --git a/lib/puppet/parameter/package_options.rb b/lib/puppet/parameter/package_options.rb index 7482cb0489d..60b3c06c370 100644 --- a/lib/puppet/parameter/package_options.rb +++ b/lib/puppet/parameter/package_options.rb @@ -1,5 +1,9 @@ require 'puppet/parameter' +# This specialized {Puppet::Parameter} handles munging of package options. +# Package options are passed as an array of key value pairs. Special munging is +# required as the keys and values needs to be quoted in a safe way. +# class Puppet::Parameter::PackageOptions < Puppet::Parameter def unsafe_munge(values) values = [values] unless values.is_a? Array @@ -20,6 +24,7 @@ def unsafe_munge(values) end end + # @api private def quote(value) value.include?(' ') ? %Q["#{value.gsub(/"/, '\"')}"] : value end diff --git a/lib/puppet/parameter/path.rb b/lib/puppet/parameter/path.rb index 11d0969dbd1..430aca1de33 100644 --- a/lib/puppet/parameter/path.rb +++ b/lib/puppet/parameter/path.rb @@ -1,6 +1,13 @@ require 'puppet/parameter' +# This specialized {Puppet::Parameter} handles validation and munging of paths. +# By default, a single path is accepted, and by calling {accept_arrays} it is possible to +# allow an array of paths. +# class Puppet::Parameter::Path < Puppet::Parameter + # Specifies whether multiple paths are accepted or not. + # @dsl type + # def self.accept_arrays(bool = true) @accept_arrays = !!bool end @@ -8,6 +15,13 @@ def self.arrays? @accept_arrays end + # Performs validation of the given paths. + # If the concrete parameter defines a validation method, it may call this method to perform + # path validation. + # @raise [Puppet::Error] if this property is configured for single paths and an array is given + # @raise [Puppet::Error] if a path is not an absolute path + # @return [Array] the given paths + # def validate_path(paths) if paths.is_a?(Array) and ! self.class.arrays? then fail "#{name} only accepts a single path, not an array of paths" @@ -18,13 +32,22 @@ def validate_path(paths) paths end - # This will be overridden if someone uses the validate option, which is why - # it just delegates to the other, useful, method. + # This is the default implementation of the `validate` method. + # It will be overridden if the validate option is used when defining the parameter. + # @return [void] + # def unsafe_validate(paths) validate_path(paths) end - # Likewise, this might be overridden, but by default... + # This is the default implementation of `munge`. + # If the concrete parameter defines a `munge` method, this default implementation will be overridden. + # This default implementation does not perform any munging, it just checks the one/many paths + # constraints. A derived implementation can perform this check as: + # `paths.is_a?(Array) and ! self.class.arrays?` and raise a {Puppet::Error}. + # @param [String, Array] one of multiple paths + # @return [String, Array] the given paths + # @raise [Puppet::Error] if the given paths does not comply with the on/many paths rule. def unsafe_munge(paths) if paths.is_a?(Array) and ! self.class.arrays? then fail "#{name} only accepts a single path, not an array of paths" diff --git a/lib/puppet/parameter/value.rb b/lib/puppet/parameter/value.rb index 12c389d2fa1..c45c40e5efc 100644 --- a/lib/puppet/parameter/value.rb +++ b/lib/puppet/parameter/value.rb @@ -1,25 +1,42 @@ require 'puppet/parameter/value_collection' -# An individual Value class. +# Describes an acceptable value for a parameter or property. +# An acceptable value is either specified as a literal value or a regular expression. +# @note this class should be used via the api methods in {Puppet::Parameter} and {Puppet::Property} +# @api private +# class Puppet::Parameter::Value attr_reader :name, :options, :event attr_accessor :block, :call, :method, :required_features - # Add an alias for this value. + # Adds an alias for this value. + # Makes the given _name_ be an alias for this acceptable value. + # @param name [Symbol] the additonal alias this value should be known as + # @api private + # def alias(name) @aliases << convert(name) end - # Return all aliases. + # @return [Array] Returns all aliases (or an empty array). + # @api private + # def aliases @aliases.dup end - # Store the event that our value generates, if it does so. + # Stores the event that our value generates, if it does so. + # @api private + # def event=(value) @event = convert(value) end + # Initializes the instance with a literal accepted value, or a regular expression. + # If anything else is passed, it is turned into a String, and then made into a Symbol. + # @param [Symbol, Regexp, Object] the value to accept, Symbol, a regular expression, or object to convert. + # @api private + # def initialize(name) if name.is_a?(Regexp) @name = name @@ -34,7 +51,10 @@ def initialize(name) @call = :instead end - # Does a provided value match our value? + # Checks if the given value matches the acceptance rules (literal value, regular expression, or one + # of the aliases. + # @api private + # def match?(value) if regex? return true if name =~ value.to_s @@ -43,7 +63,9 @@ def match?(value) end end - # Is our value a regex? + # @return [Boolean] whether the accepted value is a regular expression or not. + # @api private + # def regex? @name.is_a?(Regexp) end @@ -52,6 +74,8 @@ def regex? # A standard way of converting all of our values, so we're always # comparing apples to apples. + # @api private + # def convert(value) case value when Symbol, '' # can't intern an empty string diff --git a/lib/puppet/parameter/value_collection.rb b/lib/puppet/parameter/value_collection.rb index 619e0731d94..c048fbdaa74 100644 --- a/lib/puppet/parameter/value_collection.rb +++ b/lib/puppet/parameter/value_collection.rb @@ -1,9 +1,21 @@ require 'puppet/parameter/value' -# A collection of values and regexes, used for specifying -# what values are allowed in a given parameter. +# A collection of values and regular expressions, used for specifying allowed values +# in a given parameter. +# @note This class is considered part of the internal implementation of {Puppet::Parameter}, and +# {Puppet::Property} and the functionality provided by this class should be used via their interfaces. +# @comment This class probably have several problems when trying to use it with a combination of +# regular expressions and aliases as it finds an acceptable value holder vi "name" which may be +# a regular expression... +# +# @api private +# class Puppet::Parameter::ValueCollection + # Aliases the given existing _other_ value with the additional given _name_. + # @return [void] + # @api private + # def aliasvalue(name, other) other = other.to_sym unless value = match?(other) @@ -13,7 +25,10 @@ def aliasvalue(name, other) value.alias(name) end - # Return a doc string for all of the values in this parameter/property. + # Returns a doc string (enumerating the acceptable values) for all of the values in this parameter/property. + # @return [String] a documentation string. + # @api private + # def doc unless defined?(@doc) @doc = "" @@ -34,11 +49,15 @@ def doc @doc end - # Does this collection contain any value definitions? + # @return [Boolean] Returns whether the set of allowed values is empty or not. + # @api private + # def empty? @values.empty? end + # @api private + # def initialize # We often look values up by name, so a hash makes more sense. @values = {} @@ -49,7 +68,15 @@ def initialize @strings = [] end - # Can we match a given value? + # Checks if the given value is acceptable (matches one of the literal values or patterns) and returns + # the "matcher" that matched. + # Literal string matchers are tested first, if both a literal and a regexp match would match, the literal + # match wins. + # + # @param test_value [Object] the value to test if it complies with the configured rules + # @return [Puppet::Parameter::Value, nil] The instance of Puppet::Parameter::Value that matched the given value, or nil if there was no match. + # @api private + # def match?(test_value) # First look for normal values if value = @strings.find { |v| v.match?(test_value) } @@ -60,7 +87,14 @@ def match?(test_value) @regexes.find { |v| v.match?(test_value) } end - # If the specified value is allowed, then munge appropriately. + # Munges the value if it is valid, else produces the same value. + # @param value [Object] the value to munge + # @return [Object] the munged value, or the given value + # @todo This method does not seem to do any munging. It just returns the value if it matches the + # regexp, or the (most likely Symbolic) allowed value if it matches (which is more of a replacement + # of one instance with an equal one. Is the intent that this method should be specialized? + # @api private + # def munge(value) return value if empty? @@ -75,17 +109,25 @@ def munge(value) end end - # Define a new valid value for a property. You must provide the value itself, - # usually as a symbol, or a regex to match the value. + # Defines a new valid value for a {Puppet::Property}. + # A valid value is specified as a literal (typically a Symbol), but can also be + # specified with a regexp. + # + # @param name [Symbol, Regexp] a valid literal value, or a regexp that matches a value + # @param options [Hash] a hash with options + # @option options [Symbol] :event The event that should be emitted when this value is set. + # @todo Option :event original comment says "event should be returned...", is "returned" the correct word + # to use? + # @option options [Symbol] :call When to call any associated block. The default value is `:instead` which + # means that the block should be called instead of the provider. In earlier versions (before 20081031) it + # was possible to specify a value of `:before` or `:after` for the purpose of calling + # both the block and the provider. Use of these deprecated options will now raise an exception later + # in the process when the _is_ value is set (see Puppet::Property#set). + # @option options [Object] _any_ Any other option is treated as a call to a setter having the given + # option name (e.g. `:required_features` calls `required_features=` with the option's value as an + # argument). + # @api private # - # The first argument to the method is either the value itself or a regex. - # The second argument is an option hash; valid options are: - # * :event: The event that should be returned when this value is set. - # * :call: When to call any associated block. The default value - # is ``instead``, which means to call the value instead of calling the - # provider. You can also specify ``before`` or ``after``, which will - # call both the block and the provider, according to the order you specify - # (the ``first`` refers to when the block is called, not the provider). def newvalue(name, options = {}, &block) value = Puppet::Parameter::Value.new(name) @values[value.name] = value @@ -107,16 +149,27 @@ def newvalue(name, options = {}, &block) value end - # Define one or more new values for our parameter. + # Defines one or more valid values (literal or regexp) for a parameter or property. + # @return [void] + # @dsl type + # @api private + # def newvalues(*names) names.each { |name| newvalue(name) } end + # @return [Array] An array of the regular expressions in string form, configured as matching valid values. + # @api private + # def regexes @regexes.collect { |r| r.name.inspect } end - # Verify that the passed value is valid. + # Validates the given value against the set of valid literal values and regular expressions. + # @raise [ArgumentError] if the value is not accepted + # @return [void] + # @api private + # def validate(value) return if empty? @@ -131,12 +184,21 @@ def validate(value) end end - # Return a single value instance. + # Returns a valid value matcher (a literal or regular expression) + # @todo This looks odd, asking for an instance that matches a symbol, or a instance that has + # a regexp. What is the intention here? Marking as api private... + # + # @return [Puppet::Parameter::Value] a valid valud matcher + # @api private + # def value(name) @values[name] end - # Return the list of valid values. + # @return [Array] Returns a list of valid literal values. + # @see regexes + # @api private + # def values @strings.collect { |s| s.name } end diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb index 52e46fa27a1..083d68a9de9 100644 --- a/lib/puppet/property.rb +++ b/lib/puppet/property.rb @@ -4,27 +4,98 @@ require 'puppet' require 'puppet/parameter' +# The Property class is the implementation of a resource's attributes of _property_ kind. +# A Property is a specialized Resource Type Parameter that has both an 'is' (current) state, and +# a 'should' (wanted state). However, even if this is conceptually true, the current _is_ value is +# obtained by asking the associated provider for the value, and hence it is not actually part of a +# property's state, and only available when a provider has been selected and can obtain the value (i.e. when +# running on an agent). +# +# A Property (also in contrast to a parameter) is intended to describe a managed attribute of +# some system entity, such as the name or mode of a file. +# +# The current value _(is)_ is read and written with the methods {#retrieve} and {#set}, and the wanted +# value _(should)_ is read and written with the methods {#value} and {#value=} which delegate to +# {#should} and {#should=}, i.e. when a property is used like any other parameter, it is the _should_ value +# that is operated on. +# +# All resource type properties in the puppet system are derived from this class. +# +# The intention is that new parameters are created by using the DSL method {Puppet::Type.newproperty}. +# +# @abstract +# @note Properties of Types are expressed using subclasses of this class. Such a class describes one +# named property of a particular Type (as opposed to describing a type of property in general). This +# limits the use of one (concrete) property class instance to occur only once for a given type's inheritance +# chain. An instance of a Property class is the value holder of one instance of the resource type (e.g. the +# mode of a file resource instance). +# A Property class may server as the superclass _(parent)_ of another; e.g. a Size property that describes +# handling of measurements such as kb, mb, gb. If a type requires two different size measurements it requires +# one concrete class per such measure; e.g. MinSize (:parent => Size), and MaxSize (:parent => Size). +# +# @todo Describe meta-parameter shadowing. This concept can not be understood by just looking at the descriptions +# of the methods involved. +# +# @see Puppet::Type +# @see Puppet::Parameter +# +# @api public +# class Puppet::Property < Puppet::Parameter require 'puppet/property/ensure' - # Because 'should' uses an array, we have a special method for handling - # it. We also want to keep copies of the original values, so that - # they can be retrieved and compared later when merging. + # Returns the original wanted value(s) _(should)_ unprocessed by munging/unmunging. + # The original values are set by {#value=} or {#should=}. + # @return (see #should) + # attr_reader :shouldorig + # The noop mode for this property. + # By setting a property's noop mode to `true`, any management of this property is inhibited. Calculation + # and reporting still takes place, but if a change of the underlying managed entity's state + # should take place it will not be carried out. This noop + # setting overrides the overall `Puppet[:noop]` mode as well as the noop mode in the _associated resource_ + # attr_writer :noop class << self + # @todo Figure out what this is used for. Can not find any logic in the puppet code base that + # reads or writes this attribute. + # ??? Probably Unused attr_accessor :unmanaged + + # @return [Symbol] The name of the property as given when the property was created. + # attr_reader :name - # Return array matching info, defaulting to just matching - # the first value. + # @!attribute [rw] array_matching + # @comment note that $#46; is a period - char code require to not terminate sentence. + # The `is` vs. `should` array matching mode; `:first`, or `:all`. + # + # @comment there are two blank chars after the symbols to cause a break - do not remove these. + # * `:first` + # This is primarily used for single value properties. When matched against an array of values + # a match is true if the `is` value matches any of the values in the `should` array. When the `is` value + # is also an array, the matching is performed against the entire array as the `is` value. + # * `:all` + # : This is primarily used for multi-valued properties. When matched against an array of + # `should` values, the size of `is` and `should` must be the same, and all values in `is` must match + # a value in `should`. + # + # @note The semantics of these modes are implemented by the method {#insync?}. That method is the default + # implementation and it has a backwards compatible behavior that imposes additional constraints + # on what constitutes a positive match. A derived property may override that method. + # @return [Symbol] (:first) the mode in which matching is performed + # @see #insync? + # @dsl type + # @api public + # def array_matching @array_matching ||= :first end - # Set whether properties should match all values or just the first one. + # @comment This is documented as an attribute - see the {array_matching} method. + # def array_matching=(value) value = value.intern if value.is_a?(String) raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" unless [:first, :all].include?(value) @@ -32,33 +103,56 @@ def array_matching=(value) end end - # Look up a value's name, so we can find options and such. + # Looks up a value's name among valid values, to enable option lookup with result as a key. + # @param name [Object] the parameter value to match against valid values (names). + # @return {Symbol, Regexp} a value matching predicate + # @api private + # def self.value_name(name) if value = value_collection.match?(name) value.name end end - # Retrieve an option set when a value was defined. + # Returns the value of the given option (set when a valid value with the given "name" was defined). + # @param name [Symbol, Regexp] the valid value predicate as returned by {value_name} + # @param option [Symbol] the name of the wanted option + # @return [Object] value of the option + # @raise [NoMethodError] if the option is not supported + # @todo Guessing on result of passing a non supported option (it performs send(option)). + # @api private + # def self.value_option(name, option) if value = value_collection.value(name) value.send(option) end end - # Define a new valid value for a property. You must provide the value itself, - # usually as a symbol, or a regex to match the value. + # Defines a new valid value for this property. + # A valid value is specified as a literal (typically a Symbol), but can also be + # specified with a Regexp. # - # The first argument to the method is either the value itself or a regex. - # The second argument is an option hash; valid options are: - # * :method: The name of the method to define. Defaults to 'set_'. - # * :required_features: A list of features this value requires. - # * :event: The event that should be returned when this value is set. - # * :call: When to call any associated block. The default value - # is `instead`, which means to call the value instead of calling the - # provider. You can also specify `before` or `after`, which will - # call both the block and the provider, according to the order you specify - # (the `first` refers to when the block is called, not the provider). + # @param name [Symbol, Regexp] a valid literal value, or a regexp that matches a value + # @param options [Hash] a hash with options + # @option options [Symbol] :event The event that should be emitted when this value is set. + # @todo Option :event original comment says "event should be returned...", is "returned" the correct word + # to use? + # @option options [Symbol] :call When to call any associated block. The default value is `:instead` which + # means that the block should be called instead of the provider. In earlier versions (before 20081031) it + # was possible to specify a value of `:before` or `:after` for the purpose of calling + # both the block and the provider. Use of these deprecated options will now raise an exception later + # in the process when the _is_ value is set (see #set). + # @option options [Object] any Any other option is treated as a call to a setter having the given + # option name (e.g. `:required_features` calls `required_features=` with the option's value as an + # argument). + # @todo The original documentation states that the option `:method` will set the name of the generated + # setter method, but this is not implemented. Is the documentatin or the implementation in error? + # (The implementation is in Puppet::Parameter::ValueCollection#new_value). + # @todo verify that the use of :before and :after have been deprecated (or rather - never worked, and + # was never in use. (This means, that the option :call could be removed since calls are always :instead). + # + # @dsl type + # @api public def self.newvalue(name, options = {}, &block) value = value_collection.newvalue(name, options, &block) @@ -66,7 +160,12 @@ def self.newvalue(name, options = {}, &block) value end - # Call the provider method. + # Calls the provider setter method for this property with the given value as argument. + # @return [Object] what the provider returns when calling a setter for this property's name + # @raise [Puppet::Error] when the provider can not handle this property. + # @see #set + # @api private + # def call_provider(value) method = self.class.name.to_s + "=" unless provider.respond_to? method @@ -75,8 +174,19 @@ def call_provider(value) provider.send(method, value) end - # Call the dynamically-created method associated with our value, if - # there is one. + # Sets the value of this property to the given value by calling the dynamically created setter method associated with the "valid value" referenced by the given name. + # @param name [Symbol, Regexp] a valid value "name" as returned by {value_name} + # @param value [Object] the value to set as the value of the property + # @raise [Puppet::DevError] if there was no method to call + # @raise [Puppet::Error] if there were problems setting the value + # @raise [Puppet::ResourceError] if there was a problem setting the value and it was not raised + # as a Puppet::Error. The original exception is wrapped and logged. + # @todo The check for a valid value option called `:method` does not seem to be fully supported + # as it seems that this option is never consulted when the method is dynamically created. Needs to + # be investigated. (Bug, or documentation needs to be changed). + # @see #set + # @api private + # def call_valuemethod(name, value) if method = self.class.value_option(name, :method) and self.respond_to?(method) begin @@ -98,7 +208,11 @@ def call_valuemethod(name, value) end end - # How should a property change be printed as a string? + # Formats a message for a property change from the given `current_value` to the given `newvalue`. + # @return [String] a message describing the property change. + # @note If called with equal values, this is reported as a change. + # @raise [Puppet::DevError] if there were issues formatting the message + # def change_to_s(current_value, newvalue) begin if current_value == :absent @@ -117,7 +231,12 @@ def change_to_s(current_value, newvalue) end end - # Figure out which event to return. + # Produces the name of the event to use to describe a change of this property's value. + # The produced event name is either the event name configured for this property, or a generic + # event based on the name of the property with suffix `_changed`, or if the property is + # `:ensure`, the name of the resource type and one of the suffixes `_created`, `_removed`, or `_changed`. + # @return [String] the name of the event that describes the change + # def event_name value = self.should @@ -133,14 +252,35 @@ def event_name end).to_sym end - # Return a modified form of the resource event. + # Produces an event describing a change of this property. + # In addition to the event attributes set by the resource type, this method adds: + # + # * `:name` - the event_name + # * `:desired_value` - a.k.a _should_ or _wanted value_ + # * `:property` - reference to this property + # * `:source_description` - the _path_ (?? See todo) + # + # @todo What is the intent of this method? What is the meaning of the :source_description passed in the + # options to the created event? + # @return [Puppet::Transaction::Event] the created event + # @see Puppet::Type#event def event resource.event :name => event_name, :desired_value => should, :property => self, :source_description => path end + # @todo What is this? + # What is this used for? attr_reader :shadow - # initialize our property + # Initializes a Property the same way as a Parameter and handles the special case when a property is shadowing a meta-parameter. + # @todo There is some special initialization when a property is not a metaparameter but + # Puppet::Type.metaparamclass(for this class's name) is not nil - if that is the case a + # setup_shadow is performed for that class. + # + # @param hash [Hash] options passed to the super initializer {Puppet::Parameter#initialize} + # @note New properties of a type should be created via the DSL method {Puppet::Type.newproperty}. + # @see Puppet::Parameter#initialize description of Parameter initialize options. + # @api private def initialize(hash = {}) super @@ -149,14 +289,14 @@ def initialize(hash = {}) end end - # Determine whether the property is in-sync or not. If @should is - # not defined or is set to a non-true value, then we do not have - # a valid value for it and thus consider the property to be in-sync - # since we cannot fix it. Otherwise, we expect our should value - # to be an array, and if @is matches any of those values, then - # we consider it to be in-sync. + # Determines whether the property is in-sync or not in a way that is protected against missing value. + # @note If the wanted value _(should)_ is not defined or is set to a non-true value then this is + # a state that can not be fixed and the property is reported to be in sync. + # @return [Boolean] the protected result of `true` or the result of calling {#insync?}. + # + # @api private + # @note Do not override this method. # - # Don't override this method. def safe_insync?(is) # If there is no @should value, consider the property to be in sync. return true unless @should @@ -165,15 +305,34 @@ def safe_insync?(is) insync?(is) end + # Protects against override of the {#safe_insync?} method. + # @raise [RuntimeError] if the added method is `:safe_insync?` + # @api private + # def self.method_added(sym) raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync? end - # This method may be overridden by derived classes if necessary - # to provide extra logic to determine whether the property is in - # sync. In most cases, however, only `property_matches?` needs to be - # overridden to give the correct outcome - without reproducing all the array - # matching logic, etc, found here. + # Checks if the current _(is)_ value is in sync with the wanted _(should)_ value. + # The check if the two values are in sync is controlled by the result of {#match_all?} which + # specifies a match of `:first` or `:all`). The matching of the _is_ value against the entire _should_ value + # or each of the _should_ values (as controlled by {#match_all?} is performed by {#property_matches?}. + # + # A derived property typically only needs to override the {#property_matches?} method, but may also + # override this method if there is a need to have more control over the array matching logic. + # + # @note The array matching logic in this method contains backwards compatible logic that performs the + # comparison in `:all` mode by checking equality and equality of _is_ against _should_ converted to array of String, + # and that the lengths are equal, and in `:first` mode by checking if one of the _should_ values + # is included in the _is_ values. This means that the _is_ value needs to be carefully arranged to + # match the _should_. + # @todo The implementation should really do return is.zip(@should).all? {|a, b| property_matches?(a, b) } + # instead of using equality check and then check against an array with converted strings. + # @param is [Object] The current _(is)_ value to check if it is in sync with the wanted _(should)_ value(s) + # @return [Boolean] whether the values are in sync or not. + # @raise [Puppet::DevError] if wanted value _(should)_ is not an array. + # @api public + # def insync?(is) self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array) @@ -211,10 +370,16 @@ def insync?(is) end end - # Compare the current and desired value of a property in a property-specific - # way. Invoked by `insync?`; this should be overridden if your property - # has a different comparison type but does not actually differentiate the - # overall insync? logic. + # Checks if the given current and desired values are equal. + # This default implementation performs this check in a backwards compatible way where + # the equality of the two values is checked, and then the equality of current with desired + # converted to a string. + # + # A derived implementation may override this method to perform a property specific equality check. + # + # The intent of this method is to provide an equality check suitable for checking if the property + # value is in sync or not. It is typically called from {#insync?}. + # def property_matches?(current, desired) # This preserves the older Puppet behaviour of doing raw and string # equality comparisons for all equality. I am not clear this is globally @@ -222,15 +387,20 @@ def property_matches?(current, desired) current == desired or current == desired.to_s end - # because the @should and @is vars might be in weird formats, - # we need to set up a mechanism for pretty printing of the values - # default to just the values, but this way individual properties can - # override these methods + # Produces a pretty printing string for the given value. + # This default implementation simply returns the given argument. A derived implementation + # may perform property specific pretty printing when the _is_ and _should_ values are not + # already in suitable form. + # @return [String] a pretty printing string def is_to_s(currentvalue) currentvalue end - # Send a log message. + # Emits a log message at the log level specified for the associated resource. + # The log entry is associated with this property. + # @param msg [String] the message to log + # @return [void] + # def log(msg) Puppet::Util::Log.create( :level => resource[:loglevel], @@ -239,27 +409,36 @@ def log(msg) ) end - # Should we match all values, or just the first? + # @return [Boolean] whether the {array_matching} mode is set to `:all` or not def match_all? self.class.array_matching == :all end - # Execute our shadow's munge code, too, if we have one. + # (see Puppet::Parameter#munge) + # If this property is a meta-parameter shadow, the shadow's munge is also called. + # @todo Incomprehensible ! The concept of "meta-parameter-shadowing" needs to be explained. + # def munge(value) self.shadow.munge(value) if self.shadow super end - # each property class must define the name method, and property instances - # do not change that name - # this implicitly means that a given object can only have one property - # instance of a given property class + # @return [Symbol] the name of the property as stated when the property was created. + # @note A property class (just like a parameter class) describes one specific property and + # can only be used once within one type's inheritance chain. def name self.class.name end - # for testing whether we should actually do anything + # @return [Boolean] whether this property is in noop mode or not. + # Returns whether this property is in noop mode or not; if a difference between the + # _is_ and _should_ values should be acted on or not. + # The noop mode is a transitive setting. The mode is checked in this property, then in + # the _associated resource_ and finally in Puppet[:noop]. + # @todo This logic is different than Parameter#noop in that the resource noop mode overrides + # the property's mode - in parameter it is the other way around. Bug or feature? + # def noop # This is only here to make testing easier. if @resource.respond_to?(:noop?) @@ -273,14 +452,33 @@ def noop end end - # By default, call the method associated with the property name on our - # provider. In other words, if the property name is 'gid', we'll call - # 'provider.gid' to retrieve the current value. + # Retrieves the current value _(is)_ of this property from the provider. + # This implementation performs this operation by calling a provider method with the + # same name as this property (i.e. if the property name is 'gid', a call to the + # 'provider.gid' is expected to return the current value. + # @return [Object] what the provider returns as the current value of the property + # def retrieve provider.send(self.class.name) end - # Set our value, using the provider, an associated block, or both. + # Sets the current _(is)_ value of this property. + # The value is set using the provider's setter method for this property ({#call_provider}) if nothing + # else has been specified. If the _valid value_ for the given value defines a `:call` option with the + # value `:instead`, the + # value is set with {#call_valuemethod} which invokes a block specified for the valid value. + # + # @note In older versions (before 20081031) it was possible to specify the call types `:before` and `:after` + # which had the effect that both the provider method and the _valid value_ block were called. + # This is no longer supported. + # + # @param value [Object] the value to set as the value of this property + # @return [Object] returns what {#call_valuemethod} or {#call_provider} returns + # @raise [Puppet::Error] when the provider setter should be used but there is no provider set in the _associated + # resource_ + # @raise [Puppet::DevError] when a deprecated call form was specified (e.g. `:before` or `:after`). + # @api public + # def set(value) # Set a name for looking up associated options like the event. name = self.class.value_name(value) @@ -305,15 +503,29 @@ def set(value) end end - # If there's a shadowing metaparam, instantiate it now. - # This allows us to create a property or parameter with the - # same name as a metaparameter, and the metaparam will only be - # stored as a shadow. + # Sets up a shadow property for a shadowing meta-parameter. + # This construct allows the creation of a property with the + # same name as a meta-parameter. The metaparam will only be stored as a shadow. + # @param klass [Class] the class of the shadowed meta-parameter + # @return [Puppet::Parameter] an instance of the given class (a parameter or property) + # def setup_shadow(klass) @shadow = klass.new(:resource => self.resource) end - # Only return the first value + # Returns the wanted _(should)_ value of this property. + # If the _array matching mode_ {#match_all?} is true, an array of the wanted values in unmunged format + # is returned, else the first value in the array of wanted values in unmunged format is returned. + # @return [Array, Object, nil] Array of values if {#match_all?} else a single value, or nil if there are no + # wanted values. + # @raise [Puppet::DevError] if the wanted value is non nil and not an array + # + # @note This method will potentially return different values than the original values as they are + # converted via munging/unmunging. If the original values are wanted, call {#shouldorig}. + # + # @see #shouldorig + # @api public + # def should return nil unless defined?(@should) @@ -326,7 +538,14 @@ def should end end - # Set the should value. + # Sets the wanted _(should)_ value of this property. + # If the given value is not already an Array, it will be wrapped in one before being set. + # This method also sets the cached original _should_ values returned by {#shouldorig}. + # + # @param values [Array, Object] the value(s) to set as the wanted value(s) + # @raise [StandardError] when validation of a value fails (see {#validate}). + # @api public + # def should=(values) values = [values] unless values.is_a?(Array) @@ -336,23 +555,39 @@ def should=(values) @should = values.collect { |val| self.munge(val) } end + # Formats the given newvalue (following _should_ type conventions) for inclusion in a string describing a change. + # @return [String] Returns the given newvalue in string form with space separated entries if it is an array. + # @see #change_to_s + # def should_to_s(newvalue) [newvalue].flatten.join(" ") end + # Synchronizes the current value _(is)_ and the wanted value _(should)_ by calling {#set}. + # @raise [Puppet::DevError] if {#should} is nil + # @todo The implementation of this method is somewhat inefficient as it computes the should + # array twice. def sync devfail "Got a nil value for should" unless should set(should) end - # Verify that the passed value is valid. + # Asserts that the given value is valid. # If the developer uses a 'validate' hook, this method will get overridden. + # @raise [Exception] if the value is invalid, or value can not be handled. + # @return [void] + # @api private + # def unsafe_validate(value) super validate_features_per_value(value) end - # Make sure that we've got all of the required features for a given value. + # Asserts that all required provider features are present for the given property value. + # @raise [ArgumentError] if a required feature is not present + # @return [void] + # @api private + # def validate_features_per_value(value) if features = self.class.value_option(self.class.value_name(value), :required_features) features = Array(features) @@ -361,13 +596,12 @@ def validate_features_per_value(value) end end - # Just return any should value we might have. + # @return [Object, nil] Returns the wanted _(should)_ value of this property. def value self.should end - # Match the Parameter interface, but we really just use 'should' internally. - # Note that the should= method does all of the validation and such. + # (see #should=) def value=(value) self.should = value end diff --git a/lib/puppet/property/ensure.rb b/lib/puppet/property/ensure.rb index 8b97ddeab86..3c9f98d85a7 100644 --- a/lib/puppet/property/ensure.rb +++ b/lib/puppet/property/ensure.rb @@ -1,7 +1,12 @@ require 'puppet/property' -# This property will get automatically added to any type that responds +# This property is automatically added to any {Puppet::Type} that responds # to the methods 'exists?', 'create', and 'destroy'. +# +# Ensure defaults to having the wanted _(should)_ value `:present`. +# +# @api public +# class Puppet::Property::Ensure < Puppet::Property @name = :ensure @@ -58,6 +63,14 @@ def change_to_s(currentvalue, newvalue) end end + # Retrieves the _is_ value for the ensure property. + # The existence of the resource is checked by first consulting the provider (if it responds to + # `:exists`), and secondly the resource. A a value of `:present` or `:absent` is returned + # depending on if the managed entity exists or not. + # + # @return [Symbol] a value of `:present` or `:absent` depending on if it exists or not + # @raise [Puppet::DevError] if neither the provider nor the resource responds to `:exists` + # def retrieve # XXX This is a problem -- whether the object exists or not often # depends on the results of other properties, yet we're the first property diff --git a/lib/puppet/property/keyvalue.rb b/lib/puppet/property/keyvalue.rb index d66f16a326a..8184b77b3fc 100644 --- a/lib/puppet/property/keyvalue.rb +++ b/lib/puppet/property/keyvalue.rb @@ -1,16 +1,16 @@ -#This subclass of property manages string key value pairs. - -#In order to use this property: -# - the @should value must be an array of keyvalue pairs separated by the 'separator' -# - the retrieve method should return a hash with the keys as symbols -# IMPORTANT NOTE: In order for this property to work there must also be a 'membership' parameter -# The class that inherits from property should override that method with the symbol for the membership - - require 'puppet/property' module Puppet class Property + # This subclass of {Puppet::Property} manages string key value pairs. + # In order to use this property: + # + # * the _should_ value must be an array of key-value pairs separated by the 'separator' + # * the retrieve method should return a hash with the keys as symbols + # @note **IMPORTANT**: In order for this property to work there must also be a 'membership' parameter + # The class that inherits from property should override that method with the symbol for the membership + # @todo The node with an important message is not very clear. + # class KeyValue < Property def hash_to_key_value_s(hash) @@ -60,14 +60,19 @@ def should current.merge(members) end + # @return [String] Returns a default separator of "=" def separator "=" end + # @return [String] Returns a default delimiter of ";" def delimiter ";" end + # Retrieves the key-hash from the provider by invoking it's method named the same as this property. + # @return [Hash] the hash from the provider, or `:absent` + # def retrieve #ok, some 'convention' if the keyvalue property is named properties, provider should implement a properties method if key_hash = provider.send(name) and key_hash != :absent @@ -77,6 +82,9 @@ def retrieve end end + # Returns true if there is no _is_ value, else returns if _is_ is equal to _should_ using == as comparison. + # @return [Boolean] whether the property is in sync or not. + # def insync?(is) return true unless is diff --git a/lib/puppet/property/list.rb b/lib/puppet/property/list.rb index b86dc87f232..ed0566bddde 100644 --- a/lib/puppet/property/list.rb +++ b/lib/puppet/property/list.rb @@ -2,6 +2,9 @@ module Puppet class Property + # This subclass of {Puppet::Property} manages an unordered list of values. + # For an ordered list see {Puppet::Property::OrderedList}. + # class List < Property def should_to_s(should_value) diff --git a/lib/puppet/property/ordered_list.rb b/lib/puppet/property/ordered_list.rb index 7408b301916..65cd1695303 100644 --- a/lib/puppet/property/ordered_list.rb +++ b/lib/puppet/property/ordered_list.rb @@ -2,6 +2,13 @@ module Puppet class Property + # This subclass of {Puppet::Property} manages an ordered list of values. + # The maintained order is the order defined by the 'current' set of values (i.e. the + # original order is not disrupted). Any additions are added after the current values + # in their given order). + # + # For an unordered list see {Puppet::Property::List}. + # class OrderedList < List def add_should_with_current(should, current) diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index a8d2d3f9096..5edebb53efa 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -1,4 +1,40 @@ -# The container class for implementations. +# A Provider is an implementation of the actions that manage resources (of some type) on a system. +# This class is the base class for all implementation of a Puppet Provider. +# +# Concepts: +#-- +# * **Confinement** - confinement restricts providers to only be applicable under certain conditions. +# It is possible to confine a provider several different ways: +# * the included {#confine} method which provides filtering on fact, feature, existence of files, or a free form +# predicate. +# * the {commands} method that filters on the availability of given system commands. +# * **Property hash** - the important instance variable `@property_hash` contains all current state values +# for properties (it is lazily built). It is important that these values are managed appropriately in the +# methods {instances}, {prefetch}, and in methods that alters the current state (those that change the +# lifecycle (creates, destroys), or alters some value reflected backed by a property). +# * **Flush** - is a hook that is called once per resource when everything has been applied. The intent is +# that an implementation may defer modification of the current state typically done in property setters +# and instead record information that allows flush to perform the changes more efficiently. +# * **Execution Methods** - The execution methods provides access to execution of arbitrary commands. +# As a convenience execution methods are available on both the instance and the class of a provider since a +# lot of provider logic switch between these contexts fairly freely. +# * **System Entity/Resource** - this documentation uses the term "system entity" for system resources to make +# it clear if talking about a resource on the system being managed (e.g. a file in the file system) +# or about a description of such a resource (e.g. a Puppet Resource). +# * **Resource Type** - this is an instance of Type that describes a classification of instances of Resource (e.g. +# the `File` resource type describes all instances of `file` resources). +# (The term is used to contrast with "type" in general, and specifically to contrast with the implementation +# class of Resource or a specific Type). +# +# @note An instance of a Provider is associated with one resource. +# +# @note Class level methods are only called once to configure the provider (when the type is created), and not +# for each resource the provider is operating on. +# The instance methods are however called for each resource. +# +# +# @api public +# class Puppet::Provider include Puppet::Util include Puppet::Util::Errors @@ -16,53 +52,105 @@ class << self # Include the util module so we have access to things like 'which' include Puppet::Util, Puppet::Util::Docs include Puppet::Util::Logging + + # @return [String] The name of the provider attr_accessor :name - # The source parameter exists so that providers using the same - # source can specify this, so reading doesn't attempt to read the - # same package multiple times. + # + # @todo Original = _"The source parameter exists so that providers using the same + # source can specify this, so reading doesn't attempt to read the + # same package multiple times."_ This seems to be a package type specific attribute. Is this really + # used? + # + # @return [???] The source is WHAT? attr_writer :source - # LAK 2007-05-09: Keep the model stuff around for backward compatibility + # @todo Original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_ + # Is this really needed? The comment about backwards compatibility was made in 2007. + # + # @return [???] A model kept for backwards compatibility. + # @api private + # @deprecated This attribute is available for backwards compatibility reasons. attr_reader :model + + # @todo What is this type? A reference to a Puppet::Type ? + # @return [Puppet::Type] the resource type (that this provider is ... WHAT?) + # attr_accessor :resource_type + + # @!attribute [r] doc + # The (full) documentation for this provider class. The documentation for the provider class itself + # should be set with the DSL method {desc=}. Setting the documentation with with {doc=} has the same effect + # as setting it with {desc=} (only the class documentation part is set). In essence this means that + # there is no getter for the class documentation part (since the getter returns the full + # documentation when there are additional contributors). + # + # @return [String] Returns the full documentation for the provider. + # @see Puppet::Utils::Docs + # @comment This is puzzling ... a write only doc attribute??? The generated setter never seems to be + # used, instead the instance variable @doc is set in the `desc` method. This seems wrong. It is instead + # documented as a read only attribute (to get the full documentation). Also see doc below for + # desc. + # @!attribute [w] desc + # Sets the documentation of this provider class. (The full documentation is read via the + # {doc} attribute). + # + # @dsl type + # + # attr_writer :doc + end - # LAK 2007-05-09: Keep the model stuff around for backward compatibility + # @todo original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_, why is it + # both here (instance) and at class level? Is this a different model? + # @return [???] model is WHAT? attr_reader :model + + # @return [???] This resource is what? Is an instance of a provider attached to one particular Puppet::Resource? + # attr_accessor :resource - # Provide access to execution of arbitrary commands in providers. Execution methods are - # available on both the instance and the class of a provider because it seems that a lot of - # providers switch between these contexts fairly freely. - # - # @see Puppet::Util::Execution for how to use these methods + # Convenience methods - see class method with the same name. + # @see execute + # @return (see execute) def execute(*args) Puppet::Util::Execution.execute(*args) end + # (see Puppet::Util::Execution.execute) def self.execute(*args) Puppet::Util::Execution.execute(*args) end + # Convenience methods - see class method with the same name. + # @see execpipe + # @return (see execpipe) def execpipe(*args, &block) Puppet::Util::Execution.execpipe(*args, &block) end + # (see Puppet::Util::Execution.execpipe) def self.execpipe(*args, &block) Puppet::Util::Execution.execpipe(*args, &block) end + # Convenience methods - see class method with the same name. + # @see execfail + # @return (see execfail) def execfail(*args) Puppet::Util::Execution.execfail(*args) end + # (see Puppet::Util::Execution.execfail) def self.execfail(*args) Puppet::Util::Execution.execfail(*args) end - ######### + # Returns the absolute path to the executable for the command referenced by the given name. + # @raise [Puppet::DevError] if the name does not reference an existing command. + # @return [String] the absolute path to the found executable for the command + # @see which def self.command(name) name = name.intern @@ -77,22 +165,33 @@ def self.command(name) which(command) end - # Define commands that are not optional. + # Confines this provider to be suitable only on hosts where the given commands are present. + # Also see {Puppet::Provider::Confiner#confine} for other types of confinement of a provider by use of other types of + # predicates. + # + # @note It is preferred if the commands are not entered with absolute paths as this allows puppet + # to search for them using the PATH variable. # - # @param [Hash{String => String}] command_specs Named commands that the provider will + # @param command_specs [Hash{String => String}] Map of name to command that the provider will # be executing on the system. Each command is specified with a name and the path of the executable. - # (@see #has_command) + # @return [void] + # @see optional_commands + # def self.commands(command_specs) command_specs.each do |name, path| has_command(name, path) end end - # Define commands that are optional. - # + # Defines optional commands. + # Since Puppet 2.7.8 this is typically not needed as evaluation of provider suitability + # is lazy (when a resource is evaluated) and the absence of commands + # that will be present after other resources have been applied no longer needs to be specified as + # optional. # @param [Hash{String => String}] command_specs Named commands that the provider will # be executing on the system. Each command is specified with a name and the path of the executable. # (@see #has_command) + # @see commands def self.optional_commands(hash) hash.each do |name, target| has_command(name, target) do @@ -101,27 +200,31 @@ def self.optional_commands(hash) end end - # Define a single command - # - # A method will be generated on the provider that allows easy execution of the command. The generated - # method can take arguments that will be passed through to the executable as the command line arguments - # when it is run. + # Creates a convenience method for invocation of a command. # - # has_command(:echo, "/bin/echo") - # def some_method - # echo("arg 1", "arg 2") - # end + # This generates a Provider method that allows easy execution of the command. The generated + # method may take arguments that will be passed through to the executable as the command line arguments + # when it is invoked. # - # # or + # @example Use it like this: + # has_command(:echo, "/bin/echo") + # def some_method + # echo("arg 1", "arg 2") + # end + # @comment the . . . below is intentional to avoid the three dots to become an illegible ellipsis char. + # @example . . . or like this + # has_command(:echo, "/bin/echo") do + # is_optional + # environment :HOME => "/var/tmp", :PWD => "/tmp" + # end # - # has_command(:echo, "/bin/echo") do - # is_optional - # environment :HOME => "/var/tmp", :PWD => "/tmp" - # end + # @param name [Symbol] The name of the command (will become the name of the generated method that executes the command) + # @param path [String] The path to the executable for the command + # @yield [ ] A block that configures the command (see {Puppet::Provider::Command}) + # @comment a yield [ ] produces {|| ...} in the signature, do not remove the space. + # @note the name ´has_command´ looks odd in an API context, but makes more sense when seen in the internal + # DSL context where a Provider is declaratively defined. # - # @param [Symbol] name Name of the command (will be the name of the generated method to call the command) - # @param [String] path The path to the executable for the command - # @yield A block that configures the command (@see Puppet::Provider::Command) def self.has_command(name, path, &block) name = name.intern command = CommandDefiner.define(name, path, self, &block) @@ -134,6 +237,8 @@ def self.has_command(name, path, &block) end end + # Internal helper class when creating commands - undocumented. + # @api private class CommandDefiner private_class_method :new @@ -168,13 +273,15 @@ def command end end - # Is the provided feature a declared feature? + # @return [Boolean] Return whether the given feature has been declared or not. def self.declared_feature?(name) defined?(@declared_features) and @declared_features.include?(name) end - # Does this implementation match all of the default requirements? If - # defaults are empty, we return false. + # @return [Boolean] Returns whether this implementation satisfies all of the default requirements or not. + # Returns false If defaults are empty. + # @see Provider.defaultfor + # def self.default? return false if @defaults.empty? if @defaults.find do |fact, values| @@ -198,31 +305,79 @@ def self.default? end end - # Store how to determine defaults. + # Sets a facts filter that determine which of several suitable providers should be picked by default. + # This selection only kicks in if there is more than one suitable provider. + # To filter on multiple facts the given hash may contain more than one fact name/value entry. + # The filter picks the provider if all the fact/value entries match the current set of facts. (In case + # there are still more than one provider after this filtering, the first found is picked). + # @param hash [Hash<{String => Object}>] hash of fact name to fact value. + # @return [void] + # def self.defaultfor(hash) hash.each do |d,v| @defaults[d] = v end end + # @return [Integer] Returns a numeric specificity for this provider based on how many requirements it has + # and number of _ancestors_. The higher the number the more specific the provider. + # The number of requirements is based on the number of defaults set up with {Provider.defaultfor}. + # + # The _ancestors_ is the Ruby Module::ancestors method and the number of classes returned is used + # to boost the score. The intent is that if two providers are equal, but one is more "derived" than the other + # (i.e. includes more classes), it should win because it is more specific). + # @note Because of how this value is + # calculated there could be surprising side effects if a provider included an excessive amount of classes. + # def self.specificity + # This strange piece of logic attempts to figure out how many parent providers there + # are to increase the score. What is will actually do is count all classes that Ruby Module::ancestors + # returns (which can be other classes than those the parent chain) - in a way, an odd measure of the + # complexity of a provider). (@defaults.length * 100) + ancestors.select { |a| a.is_a? Class }.length end + # Initializes defaults and commands (i.e. clears them). + # @return [void] def self.initvars @defaults = {} @commands = {} end - # The method for returning a list of provider instances. Note that it returns providers, preferably with values already - # filled in, not resources. + # Returns a list of system resources (entities) this provider may/can manage. + # This is a query mechanism that lists entities that the provider may manage on a given system. It is + # is directly used in query services, but is also the foundation for other services; prefetching, and + # purging. + # + # As an example, a package provider lists all installed packages. (In contrast, the File provider does + # not list all files on the file-system as that would make execution incredibly slow). An implementation + # of this method should be made if it is possible to quickly (with a single system call) provide all + # instances. + # + # An implementation of this method should only cache the values of properties + # if they are discovered as part of the process for finding existing resources. + # Resource properties that require additional commands (than those used to determine existence/identity) + # should be implemented in their respective getter method. (This is important from a performance perspective; + # it may be expensive to compute, as well as wasteful as all discovered resources may perhaps not be managed). + # + # An implementation may return an empty list (naturally with the effect that it is not possible to query + # for manageable entities). + # + # By implementing this method, it is possible to use the `resources´ resource type to specify purging + # of all non managed entities. + # + # @note The returned instances are instance of some subclass of Provider, not resources. + # @return [Array] a list of providers referencing the system entities + # @abstract this method must be implemented by a subclass and this super method should never be called as it raises an exception. + # @raise [Puppet::DevError] Error indicating that the method should have been implemented by subclass. + # @see prefetch def self.instances raise Puppet::DevError, "Provider #{self.name} has not defined the 'instances' class method" end - # Create the methods for a given command. - # - # @deprecated Use {#commands}, {#optional_commands}, or {#has_command} instead. This was not meant to be part of a public API + # Creates the methods for a given command. + # @api private + # @deprecated Use {commands}, {optional_commands}, or {has_command} instead. This was not meant to be part of a public API def self.make_command_methods(name) Puppet.deprecation_warning "Provider.make_command_methods is deprecated; use Provider.commands or Provider.optional_commands instead for creating command methods" @@ -245,9 +400,19 @@ def self.make_command_methods(name) end end - # Create getter/setter methods for each property our resource type supports. - # They all get stored in @property_hash. This method is useful - # for those providers that use prefetch and flush. + # Creates getter- and setter- methods for each property supported by the resource type. + # Call this method to generate simple accessors for all properties supported by the + # resource type. These simple accessors lookup and sets values in the property hash. + # The generated methods may be overridden by more advanced implementations if something + # else than a straight forward getter/setter pair of methods is required. + # (i.e. define such overriding methods after this method has been called) + # + # An implementor of a provider that makes use of `prefetch` and `flush` can use this method since it uses + # the internal `@property_hash` variable to store values. An implementation would then update the system + # state on a call to `flush` based on the current values in the `@property_hash`. + # + # @return [void] + # def self.mk_resource_methods [resource_type.validproperties, resource_type.parameters].flatten.each do |attr| attr = attr.intern @@ -264,6 +429,10 @@ def self.mk_resource_methods self.initvars + # This method is used to generate a method for a command. + # @return [void] + # @api private + # def self.create_class_and_instance_method(name, &block) unless singleton_class.method_defined?(name) meta_def(name, &block) @@ -277,12 +446,20 @@ def self.create_class_and_instance_method(name, &block) end private_class_method :create_class_and_instance_method - # Retrieve the data source. Defaults to the provider name. + # @return [String] Returns the data source, which is the provider name if no other source has been set. + # @todo Unclear what "the source" is used for? def self.source @source ||= self.name end - # Does this provider support the specified parameter? + # Returns true if the given attribute/parameter is supported by the provider. + # The check is made that the parameter is a valid parameter for the resource type, and then + # if all its required features (if any) are supported by the provider. + # + # @param param [Class, Puppet::Parameter] the parameter class, or a parameter instance + # @return [Boolean] Returns whether this provider supports the given parameter or not. + # @raise [Puppet::DevError] if the given parameter is not valid for the resource type + # def self.supports_parameter?(param) if param.is_a?(Class) klass = param @@ -331,22 +508,34 @@ def self.supports_parameter?(param) end end - # Remove the reference to the resource, so GC can clean up. + # Clears this provider instance to allow GC to clean up. def clear @resource = nil @model = nil end - # Retrieve a named command. + # (see command) def command(name) self.class.command(name) end - # Get a parameter value. + # Returns the value of a parameter value, or `:absent` if it is not defined. + # @param param [Puppet::Parameter] the parameter to obtain the value of + # @return [Object] the value of the parameter or `:absent` if not defined. + # def get(param) @property_hash[param.intern] || :absent end + # Creates a new provider that is optionally initialized from a resource or a hash of properties. + # If no argument is specified, a new non specific provider is initialized. If a resource is given + # it is remembered for further operations. If a hash is used it becomes the internal `@property_hash` + # structure of the provider - this hash holds the current state property values of system entities + # as they are being discovered by querying or other operations (typically getters). + # + # @todo The use of a hash as a parameter needs a better exaplanation; why is this done? What is the intent? + # @param resource [Puppet::Resource, Hash] optional resource or hash + # def initialize(resource = nil) if resource.is_a?(Hash) # We don't use a duplicate here, because some providers (ParsedFile, at least) @@ -362,6 +551,10 @@ def initialize(resource = nil) end end + # Returns the name of the resource this provider is operating on. + # @return [String] the name of the resource instance (e.g. the file path of a File). + # @raise [Puppet::DevError] if no resource is set, or no name defined. + # def name if n = @property_hash[:name] return n @@ -372,24 +565,53 @@ def name end end - # Set passed params as the current values. + # Sets the given parameters values as the current values for those parameters. + # Other parameters are unchanged. + # @param [Array(other) # We can only have ordering against other providers. return nil unless other.is_a? Puppet::Provider # Otherwise, order by the providers class name. return self.class.name <=> other.class.name end + + # @comment Document prefetch here as it does not exist anywhere else (called from transaction if implemented) + # @!method self.prefetch(resource_hash) + # @abstract A subclass may implement this - it is not implemented in the Provider class + # This method may be implemented by a provider in order to pre-fetch resource properties. + # If implemented it should set the provider instance of the managed resources to a provider with the + # fetched state (i.e. what is returned from the {instances} method). + # @param resources_hash [Hash<{String => Puppet::Resource}>] map from name to resource of resources to prefetch + # @return [void] + + # @comment Document flush here as it does not exist anywhere (called from transaction if implemented) + # @!method flush() + # @abstract A subclass may implement this - it is not implemented in the Provider class + # This method may be implemented by a provider in order to flush properties that has not been individually + # applied to the managed entity's current state. + # @return [void] + end diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/provider/confiner.rb index 6e1fb23ab17..63499e78cfa 100644 --- a/lib/puppet/provider/confiner.rb +++ b/lib/puppet/provider/confiner.rb @@ -1,15 +1,44 @@ require 'puppet/provider/confine_collection' +# The Confiner module contains methods for managing a Provider's confinement (suitability under given +# conditions). The intent is to include this module in an object where confinement management is wanted. +# It lazily adds an instance variable `@confine_collection` to the object where it is included. +# module Puppet::Provider::Confiner + # Confines a provider to be suitable only under the given conditions. + # The hash describes a confine using mapping from symbols to values or predicate code. + # + # * _fact_name_ => value of fact (or array of facts) + # * `:exists` => the path to an existing file + # * `:true` => a predicate code block returning true + # * `:false` => a predicate code block returning false + # * `:feature` => name of system feature that must be present + # + # @example + # confine :operatingsystem => [:redhat, :fedora] + # confine :true { ... } + # + # @param hash [Hash<{Symbol => Object}>] hash of confines + # @return [void] + # @api public + # def confine(hash) confine_collection.confine(hash) end + # @return [Puppet::Provider::ConfineCollection] the collection of confines + # @api private + # def confine_collection @confine_collection ||= Puppet::Provider::ConfineCollection.new(self.to_s) end - # Check whether this implementation is suitable for our platform. + # Checks whether this implementation is suitable for the current platform (or returns a summary + # of all confines if short == false). + # @return [Boolean. Hash] Returns whether the confines are all valid (if short == true), or a hash of all confines + # if short == false. + # @api public + # def suitable?(short = true) return(short ? confine_collection.valid? : confine_collection.summary) end diff --git a/lib/puppet/reports.rb b/lib/puppet/reports.rb index 69a3196e81e..bf89ad5106c 100755 --- a/lib/puppet/reports.rb +++ b/lib/puppet/reports.rb @@ -1,6 +1,32 @@ require 'puppet/util/instance_loader' -# A simple mechanism for loading and returning reports. +# This class is an implementation of a simple mechanism for loading and returning reports. +# The intent is that a user registers a report by calling {register_report} and providing a code +# block that performs setup, and defines a method called `process`. The setup, and the `process` method +# are called in the context where `self` is an instance of {Puppet::Transaction::Report} which provides the actual +# data for the report via its methods. +# +# @example Minimal scaffolding for a report... +# Puppet::Reports::.register_report(:myreport) do +# # do setup here +# def process +# if self.status == 'failed' +# msg = "failed puppet run for #{self.host} #{self.status}" +# . . . +# else +# . . . +# end +# end +# end +# +# Required configuration: +# -- +# * A .rb file that defines a new report should be placed in the master's directory `lib/puppet/reports` +# * The `puppet.conf` file must have `report = true` in the `[agent]` section +# +# @see Puppet::Transaction::Report +# @api public +# class Puppet::Reports extend Puppet::Util::ClassGen extend Puppet::Util::InstanceLoader @@ -9,10 +35,22 @@ class Puppet::Reports instance_load :report, 'puppet/reports' class << self + # @api private attr_reader :hooks end - # Add a new report type. + # Adds a new report type. + # The block should contain setup, and define a method with the name `process`. The `process` method is + # called when the report is executed; the `process` method has access to report data via methods available + # in its context where `self` is an instance of {Puppet::Transaction::Report}. + # + # For an example, see the overview of this class. + # + # @param name [Symbol] the name of the report (do not use whitespace in the name). + # @param options [Hash] a hash of options + # @option options [Boolean] :useyaml whether yaml should be used or not + # @todo Uncertain what the options :useyaml really does; "whether yaml should be used or not", used where/how? + # def self.register_report(name, options = {}, &block) name = name.intern @@ -25,7 +63,8 @@ def self.register_report(name, options = {}, &block) end end - # Collect the docs for all of our reports. + # Collects the docs for all reports. + # @api private def self.reportdocs docs = "" @@ -41,7 +80,8 @@ def self.reportdocs docs end - # List each of the reports. + # Lists each of the reports. + # @api private def self.reports instance_loader(:report).loadall loaded_instances(:report) diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index 4fe5062daa8..260a068b21c 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -1,34 +1,119 @@ require 'puppet' require 'puppet/indirector' -# A class for reporting what happens on each client. Reports consist of -# two types of data: Logs and Metrics. Logs are the output that each -# change produces, and Metrics are all of the numerical data involved -# in the transaction. +# This class is used to report what happens on a client. +# There are two types of data in a report; _Logs_ and _Metrics_. +# +# * **Logs** - are the output that each change produces. +# * **Metrics** - are all of the numerical data involved in the transaction. +# +# Use {Puppet::Reports} class to create a new custom report type. This class is indirectly used +# as a source of data to report in such a registered report. +# +# ##Metrics +# There are three types of metrics in each report, and each type of metric has one or more values. +# +# * Time: Keeps track of how long things took. +# * Total: Total time for the configuration run +# * File: +# * Exec: +# * User: +# * Group: +# * Config Retrieval: How long the configuration took to retrieve +# * Service: +# * Package: +# * Resources: Keeps track of the following stats: +# * Total: The total number of resources being managed +# * Skipped: How many resources were skipped, because of either tagging or scheduling restrictions +# * Scheduled: How many resources met any scheduling restrictions +# * Out of Sync: How many resources were out of sync +# * Applied: How many resources were attempted to be fixed +# * Failed: How many resources were not successfully fixed +# * Restarted: How many resources were restarted because their dependencies changed +# * Failed Restarts: How many resources could not be restarted +# * Changes: The total number of changes in the transaction. +# +# @api public class Puppet::Transaction::Report extend Puppet::Indirector indirects :report, :terminus_class => :processor - attr_accessor :configuration_version, :host, :environment - attr_reader :resource_statuses, :logs, :metrics, :time, :kind, :status, - :puppet_version, :report_format - - # This is necessary since Marshall doesn't know how to - # dump hash with default proc (see below @records) + # The version of the configuration + # @todo Uncertain what this is? + # @return [???] the configuration version + attr_accessor :configuration_version + + # The host name for which the report is generated + # @return [String] the host name + attr_accessor :host + + # The name of the environment the host is in + # @return [String] the environment name + attr_accessor :environment + + # A hash with a map from resource to status + # @return [Hash<{String => String}>] Resource name to status string. + # @todo Uncertain if the types in the hash are correct... + attr_reader :resource_statuses + + # A list of log messages. + # @return [Array] logged messages + attr_reader :logs + + # A hash of metric name to metric value. + # @return [Hash<{String => Object}>] A map of metric name to value. + # @todo Uncertain if all values are numbers - now marked as Object. + # + attr_reader :metrics + + # The time when the report data was generated. + # @return [Time] A time object indicating when the report data was generated + # + attr_reader :time + + # The 'kind' of report is the name of operation that triggered the report to be produced. + # Typically "apply". + # @return [String] the kind of operation that triggered the generation of the report. + # + attr_reader :kind + + # The status of the client run is an enumeration: 'failed', 'changed' or 'unchanged' + # @return [String] the status of the run - one of the values 'failed', 'changed', or 'unchanged' + # + attr_reader :status + + # @return [String] The Puppet version in String form. + # @see Puppet::version() + # + attr_reader :puppet_version + + # @return [Integer] (3) a report format version number + # @todo Unclear what this is - a version? + # + attr_reader :report_format + + # This is necessary since Marshal doesn't know how to + # dump hash with default proc (see below "@records") ? + # @todo there is no "@records" to see below, uncertain what this is for. + # @api private + # def self.default_format :yaml end + # @api private def <<(msg) @logs << msg self end + # @api private def add_times(name, value) @external_times[name] = value end + # @api private def add_metric(name, hash) metric = Puppet::Util::Metric.new(name) @@ -40,10 +125,12 @@ def add_metric(name, hash) metric end + # @api private def add_resource_status(status) @resource_statuses[status.resource] = status end + # @api private def compute_status(resource_metrics, change_metric) if (resource_metrics["failed"] || 0) > 0 'failed' @@ -54,10 +141,12 @@ def compute_status(resource_metrics, change_metric) end end + # @api private def prune_internal_data resource_statuses.delete_if {|name,res| res.resource_type == 'Whit'} end + # @api private def finalize_report prune_internal_data @@ -69,6 +158,7 @@ def finalize_report @status = compute_status(resource_metrics, change_metric) end + # @api private def initialize(kind, configuration_version=nil, environment=nil) @metrics = {} @logs = [] @@ -84,11 +174,18 @@ def initialize(kind, configuration_version=nil, environment=nil) @status = 'failed' # assume failed until the report is finalized end + # @return [String] the host name + # @api public + # def name host end # Provide a human readable textual summary of this report. + # @note This is intended for debugging purposes + # @return [String] A string with a textual summary of this report. + # @api public + # def summary report = raw_summary @@ -115,7 +212,10 @@ def summary ret end - # Provide a raw hash summary of this report. + # Provides a raw hash summary of this report. + # @return [Hash<{String => Object}>] A hash with metrics key to value map + # @api public + # def raw_summary report = { "version" => { "config" => configuration_version, "puppet" => Puppet.version } } @@ -131,9 +231,16 @@ def raw_summary report end - # Based on the contents of this report's metrics, compute a single number - # that represents the report. The resulting number is a bitmask where + # Computes a single number that represents the report's status. + # The computation is based on the contents of this report's metrics. + # The resulting number is a bitmask where # individual bits represent the presence of different metrics. + # + # * 0x2 set if there are changes + # * 0x4 set if there are failures + # @return [Integer] A bitmask where 0x2 is set if there are changes, and 0x4 is set of there are failures. + # @api public + # def exit_status status = 0 status |= 2 if @metrics["changes"]["total"] > 0 @@ -141,6 +248,8 @@ def exit_status status end + # @api private + # def to_yaml_properties instance_variables - [:@external_times] end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index e537ddbb588..ccab27cb717 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -15,8 +15,65 @@ module Puppet -# The base class for all Puppet types +# The base class for all Puppet types. +# +# A type describes: +#-- +# * **Attributes** - properties, parameters, and meta-parameters are different types of attributes of a type. +# * **Properties** - these are the properties of the managed resource (attributes of the entity being managed; like +# a file's owner, group and mode). A property describes two states; the 'is' (current state) and the 'should' (wanted +# state). +# * **Ensurable** - a set of traits that control the lifecycle (create, remove, etc.) of a managed entity. +# There is a default set of operations associated with being _ensurable_, but this can be changed. +# * **Name/Identity** - one property is the name/identity of a resource, the _namevar_ that uniquely identifies +# one instance of a type from all others. +# * **Parameters** - additional attributes of the type (that does not directly related to an instance of the managed +# resource; if an operation is recursive or not, where to look for things, etc.). A Parameter (in contrast to Property) +# has one current value where a Property has two (current-state and wanted-state). +# * **Meta-Parameters** - parameters that are available across all types. A meta-parameter typically has +# additional semantics; like the `require` meta-parameter. A new type typically does not add new meta-parameters, +# but you need to be aware of their existence so you do not inadvertently shadow an existing meta-parameters. +# * **Parent** - a type can have a super type (that it inherits from). +# * **Validation** - If not just a basic data type, or an enumeration of symbolic values, it is possible to provide +# validation logic for a type, properties and parameters. +# * **Munging** - munging/unmunging is the process of turning a value in external representation (as used +# by a provider) into an internal representation and vice versa. A Type supports adding custom logic for these. +# * **Auto Requirements** - a type can specify automatic relationships to resources to ensure that if they are being +# managed, they will be processed before this type. +# * **Providers** - a provider is an implementation of a type's behavior - the management of a resource in the +# system being managed. A provider is often platform specific and is selected at runtime based on +# criteria/predicates specified in the configured providers. See {Puppet::Provider} for details. +# * **Device Support** - A type has some support for being applied to a device; i.e. something that is managed +# by running logic external to the device itself. There are several methods that deals with type +# applicability for these special cases such as {apply_to_device}. +# +# Additional Concepts: +# -- +# * **Resource-type** - A _resource type_ is a term used to denote the type of a resource; internally a resource +# is really an instance of a Ruby class i.e. {Puppet::Resource} which defines its behavior as "resource data". +# Conceptually however, a resource is an instance of a subclass of Type (e.g. File), where such a class describes +# its interface (what can be said/what is known about a resource of this type), +# * **Managed Entity** - This is not a term in general use, but is used here when there is a need to make +# a distinction between a resource (a description of what/how something should be managed), and what it is +# managing (a file in the file system). The term _managed entity_ is a reference to the "file in the file system" +# * **Isomorphism** - the quality of being _isomorphic_ means that two resource instances with the same name +# refers to the same managed entity. Or put differently; _an isomorphic name is the identity of a resource_. +# As an example, `exec` resources (that executes some command) have the command (i.e. the command line string) as +# their name, and these resources are said to be non-isomorphic. +# +# @note The Type class deals with multiple concerns; some methods provide an internal DSL for convenient definition +# of types, other methods deal with various aspects while running; wiring up a resource (expressed in Puppet DSL +# or Ruby DSL) with its _resource type_ (i.e. an instance of Type) to enable validation, transformation of values +# (munge/unmunge), etc. Lastly, Type is also responsible for dealing with Providers; the concrete implementations +# of the behavior that constitutes how a particular Type behaves on a particular type of system (e.g. how +# commands are executed on a flavor of Linux, on Windows, etc.). This means that as you are reading through the +# documentation of this class, you will be switching between these concepts, as well as switching between +# the conceptual level "a resource is an instance of a resource-type" and the actual implementation classes +# (Type, Resource, Provider, and various utility and helper classes). +# # @api public +# +# class Type include Puppet::Util include Puppet::Util::Errors @@ -24,9 +81,15 @@ class Type include Puppet::Util::Logging include Puppet::Util::Tagging - ############################### # Comparing type instances. include Comparable + + # Compares this type against the given _other_ (type) and returns -1, 0, or +1 depending on the order. + # @param other [Object] the object to compare against (produces nil, if not kind of Type} + # @return [-1, 0, +1, nil] produces -1 if this type is before the given _other_ type, 0 if equals, and 1 if after. + # Returns nil, if the given _other_ is not a kind of Type. + # @see Comparable + # def <=>(other) # We only order against other types, not arbitrary objects. return nil unless other.is_a? Puppet::Type @@ -35,22 +98,30 @@ def <=>(other) self.ref <=> other.ref end - ############################### # Code related to resource type attributes. class << self include Puppet::Util::ClassGen include Puppet::Util::Warnings + + # @return [Array] The list of declared properties for the resource type. + # The returned lists contains instances if Puppet::Property or its subclasses. attr_reader :properties end - # All parameters, in the appropriate order. The key_attributes come first, then - # the provider, then the properties, and finally the params and metaparams - # in the order they were specified in the files. + # Returns all the attribute names of the type in the appropriate order. + # The {key_attributes} come first, then the {provider}, then the {properties}, and finally + # the {parameters} and {metaparams}, + # all in the order they were specified in the respective files. + # @return [Array] all type attribute names in a defined order. + # def self.allattrs key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams end - # Find the class associated with any given attribute. + # Returns the class associated with the given attribute name. + # @param name [String] the name of the attribute to obtain the class for + # @return [Class, nil] the class for the given attribute, or nil if the name does not refer to an existing attribute + # def self.attrclass(name) @attrclasses ||= {} @@ -66,8 +137,11 @@ def self.attrclass(name) @attrclasses[name] end - # What type of parameter are we dealing with? Cache the results, because - # this method gets called so many times. + # Returns the attribute type (`:property`, `;param`, `:meta`). + # @comment What type of parameter are we dealing with? Cache the results, because + # this method gets called so many times. + # @return [Symbol] a symbol describing the type of attribute (`:property`, `;param`, `:meta`) + # def self.attrtype(attr) @attrtypes ||= {} unless @attrtypes.include?(attr) @@ -80,13 +154,35 @@ def self.attrtype(attr) @attrtypes[attr] end - + + # Provides iteration over meta-parameters. + # @yieldparam p [Puppet::Parameter] each meta parameter + # @return [void] + # 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. + # Creates a new `ensure` property with configured default values or with configuration by an optional block. + # This method is a convenience method for creating a property `ensure` with default accepted values. + # If no block is specified, the new `ensure` property will accept the default symbolic + # values `:present`, and `:absent` - see {Puppet::Property::Ensure}. + # If something else is wanted, pass a block and make calls to {Puppet::Property.newvalue} from this block + # to define each possible value. If a block is passed, the defaults are not automatically added to the set of + # valid values. + # + # @note This method will be automatically called without a block if the type implements the methods + # specified by {ensurable?}. It is recommended to always call this method and not rely on this automatic + # specification to clearly state that the type is ensurable. + # + # @overload ensurable() + # @overload ensurable({|| ... }) + # @yield [ ] A block evaluated in scope of the new Parameter + # @yieldreturn [void] + # @return [void] + # @dsl type + # @api public + # def self.ensurable(&block) if block_given? self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block) @@ -97,7 +193,12 @@ def self.ensurable(&block) end end - # Should we add the 'ensure' property to this class? + # Returns true if the type implements the default behavior expected by being _ensurable_ "by default". + # A type is _ensurable_ by default if it responds to `:exists`, `:create`, and `:destroy`. + # If a type implements these methods and have not already specified that it is _ensurable_, it will be + # made so with the defaults specified in {ensurable}. + # @return [Boolean] whether the type is _ensurable_ or not. + # def self.ensurable? # If the class has all three of these methods defined, then it's # ensurable. @@ -106,34 +207,60 @@ def self.ensurable? } end - # These `apply_to` methods are horrible. They should really be implemented - # as part of the usual system of constraints that apply to a type and - # provider pair, but were implemented as a separate shadow system. + # @comment These `apply_to` methods are horrible. They should really be implemented + # as part of the usual system of constraints that apply to a type and + # provider pair, but were implemented as a separate shadow system. + # + # @comment We should rip them out in favour of a real constraint pattern around the + # target device - whatever that looks like - and not have this additional + # magic here. --daniel 2012-03-08 + # + # Makes this type applicable to `:device`. + # @return [Symbol] Returns `:device` + # @api private # - # We should rip them out in favour of a real constraint pattern around the - # target device - whatever that looks like - and not have this additional - # magic here. --daniel 2012-03-08 def self.apply_to_device @apply_to = :device end + # Makes this type applicable to `:host`. + # @return [Symbol] Returns `:host` + # @api private + # def self.apply_to_host @apply_to = :host end + # Makes this type applicable to `:both` (i.e. `:host` and `:device`). + # @return [Symbol] Returns `:both` + # @api private + # def self.apply_to_all @apply_to = :both end + # Makes this type apply to `:host` if not already applied to something else. + # @return [Symbol] a `:device`, `:host`, or `:both` enumeration + # @api private def self.apply_to @apply_to ||= :host end + # Returns true if this type is applicable to the given target. + # @param target [Symbol] should be :device, :host or :target, if anything else, :host is enforced + # @return [Boolean] true + # @api private + # def self.can_apply_to(target) [ target == :device ? :device : :host, :both ].include?(apply_to) end - # Deal with any options passed into parameters. + # Processes the options for a named parameter. + # @param name [String] the name of a parameter + # @param options [Hash] a hash of options + # @option options [Boolean] :boolean if option set to true, an access method on the form _name_? is added for the param + # @return [void] + # def self.handle_param_options(name, options) # If it's a boolean parameter, create a method to test the value easily if options[:boolean] @@ -146,28 +273,59 @@ def self.handle_param_options(name, options) end end - # Is the parameter in question a meta-parameter? + # Is the given parameter a meta-parameter? + # @return [Boolean] true if the given parameter is a meta-parameter. + # def self.metaparam?(param) @@metaparamhash.include?(param.intern) end - # Find the metaparameter class associated with a given metaparameter name. - # Must accept a `nil` name, and return nil. + # Returns the meta-parameter class associated with the given meta-parameter name. + # Accepts a `nil` name, and return nil. + # @param name [String, nil] the name of a meta-parameter + # @return [Class,nil] the class for the given meta-parameter, or `nil` if no such meta-parameter exists, (or if + # the given meta-parameter name is `nil`. + # def self.metaparamclass(name) return nil if name.nil? @@metaparamhash[name.intern] end + # Returns all meta-parameter names. + # @return [Array] all meta-parameter names + # def self.metaparams @@metaparams.collect { |param| param.name } end + # Returns the documentation for a given meta-parameter of this type. + # @todo the type for the param metaparam + # @param metaparam [??? Puppet::Parameter] the meta-parameter to get documentation for. + # @return [String] the documentation associated with the given meta-parameter, or nil of not such documentation + # exists. + # @raises [?] if the given metaparam is not a meta-parameter in this type + # 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. + # Creates a new meta-parameter. + # This creates a new meta-parameter that is added to all types. + # @param name [Symbol] the name of the parameter + # @param options [Hash] a hash with options. + # @option options [Class] :parent (Puppet::Parameter) the super class of this parameter + # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class + # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given + # block is evaluated. + # @option options [Boolean] :boolean (false) specifies if this is a boolean parameter + # @option options [Boolean] :namevar (false) specifies if this parameter is the namevar + # @option options [Symbol, Array] :required_features specifies required provider features by name + # @return [Class] the created parameter + # @yield [ ] a required block that is evaluated in the scope of the new meta-parameter + # @api public + # @dsl type + # @todo Verify that this description is ok + # def self.newmetaparam(name, options = {}, &block) @@metaparams ||= [] @@metaparamhash ||= {} @@ -193,6 +351,11 @@ def self.newmetaparam(name, options = {}, &block) param end + # Returns parameters that act as a key. + # All parameters that return true from #isnamevar? or is named `:name` are included in the returned result. + # @todo would like a better explanation + # @return Array + # def self.key_attribute_parameters @key_attribute_parameters ||= ( params = @parameters.find_all { |param| @@ -201,11 +364,43 @@ def self.key_attribute_parameters ) end + # Returns cached {key_attribute_parameters} names + # @todo what is a 'key_attribute' ? + # @return [Array] cached key_attribute names + # def self.key_attributes # This is a cache miss around 0.05 percent of the time. --daniel 2012-07-17 @key_attributes_cache ||= key_attribute_parameters.collect { |p| p.name } end + # Returns a mapping from the title string to setting of attribute value(s). + # This default implementation provides a mapping of title to the one and only _namevar_ present + # in the type's definition. + # @note Advanced: some logic requires this mapping to be done differently, using a different + # validation/pattern, breaking up the title + # into several parts assigning each to an individual attribute, or even use a composite identity where + # all namevars are seen as part of the unique identity (such computation is done by the {#uniqueness} method. + # These advanced options are rarely used (only one of the built in puppet types use this, and then only + # a small part of the available functionality), and the support for these advanced mappings is not + # implemented in a straight forward way. For these reasons, this method has been marked as private). + # + # @raise [Puppet::DevError] if there is no title pattern and there are two or more key attributes + # @return [Array>>>, nil] a structure with a regexp and the first key_attribute ??? + # @comment This wonderful piece of logic creates a structure used by Resource.parse_title which + # has the capability to assign parts of the title to one or more attributes; It looks like an implementation + # of a composite identity key (all parts of the key_attributes array are in the key). This can also + # be seen in the method uniqueness_key. + # The implementation in this method simply assigns the title to the one and only namevar (which is name + # or a variable marked as namevar). + # If there are multiple namevars (any in addition to :name?) then this method MUST be implemented + # as it raises an exception if there is more than 1. Note that in puppet, it is only File that uses this + # to create a different pattern for assigning to the :path attribute + # This requires further digging. + # The entire construct is somewhat strange, since resource checks if the method "title_patterns" is + # implemented (it seems it always is) - why take this more expensive regexp mathching route for all + # other types? + # @api private + # def self.title_patterns case key_attributes.length when 0; [] @@ -216,12 +411,29 @@ def self.title_patterns end end + # Produces a _uniqueness_key_ + # @todo Explain what a uniqueness_key is + # @return [Object] an object that is a _uniqueness_key_ for this object + # def uniqueness_key self.class.key_attributes.sort_by { |attribute_name| attribute_name.to_s }.map{ |attribute_name| self[attribute_name] } end - # Create a new parameter. Requires a block and a name, stores it in the - # @parameters array, and does some basic checking on it. + # Creates a new parameter. + # @param name [Symbol] the name of the parameter + # @param options [Hash] a hash with options. + # @option options [Class] :parent (Puppet::Parameter) the super class of this parameter + # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class + # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given + # block is evaluated. + # @option options [Boolean] :boolean (false) specifies if this is a boolean parameter + # @option options [Boolean] :namevar (false) specifies if this parameter is the namevar + # @option options [Symbol, Array] :required_features specifies required provider features by name + # @return [Class] the created parameter + # @yield [ ] a required block that is evaluated in the scope of the new parameter + # @api public + # @dsl type + # def self.newparam(name, options = {}, &block) options[:attributes] ||= {} @@ -245,12 +457,24 @@ def self.newparam(name, options = {}, &block) param 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: - # * :parent: The parent class for the property. Defaults to Puppet::Property. - # * :retrieve: The method to call on the provider or @parent object (if - # the provider is not set) to retrieve the current value. + # Creates a new property. + # @param name [Symbol] the name of the property + # @param options [Hash] a hash with options. + # @option options [Symbol] :array_matching (:first) specifies how the current state is matched against + # the wanted state. Use `:first` if the property is single valued, and (`:all`) otherwise. + # @option options [Class] :parent (Puppet::Property) the super class of this property + # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class + # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given + # block is evaluated. + # @option options [Boolean] :boolean (false) specifies if this is a boolean parameter + # @option options [Symbol] :retrieve the method to call on the provider (or `parent` if `provider` is not set) + # to retrieve the current value of this property. + # @option options [Symbol, Array] :required_features specifies required provider features by name + # @return [Class] the created property + # @yield [ ] a required block that is evaluated in the scope of the new property + # @api public + # @dsl type + # def self.newproperty(name, options = {}, &block) name = name.intern @@ -298,22 +522,25 @@ def self.paramdoc(param) @paramhash[param].doc end - # Return the parameter names + # @return [Array] Returns 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. + # @return [Puppet::Parameter] Returns the parameter class associated with the given parameter name. def self.paramclass(name) @paramhash[name] end - # Return the property class associated with a name + # @return [Puppet::Property] Returns the property class ??? associated with the given property name def self.propertybyname(name) @validproperties[name] end + # Returns whether or not the given name is the name of a property, parameter or meta-parameter + # @return [Boolean] true if the given attribute name is the name of an existing property, parameter or meta-parameter + # def self.validattr?(name) name = name.intern return true if name == :name @@ -326,37 +553,43 @@ def self.validattr?(name) @validattrs[name] end - # does the name reflect a valid property? + # @return [Boolean] Returns true if the given name is the name of an existing property def self.validproperty?(name) name = name.intern @validproperties.include?(name) && @validproperties[name] end - # Return the list of validproperties + # @return [Array, {}] Returns a list of valid property names, or an empty hash if there are none. + # @todo An empty hash is returned if there are no defined parameters (not an empty array). This looks like + # a bug. + # def self.validproperties return {} unless defined?(@parameters) @validproperties.keys end - # does the name reflect a valid parameter? + # @return [Boolean] Returns true if the given name is the name of an existing parameter def self.validparameter?(name) raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters) !!(@paramhash.include?(name) or @@metaparamhash.include?(name)) end - # This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource. + # (see validattr?) + # @note see comment in code - how should this be documented? Are some of the other query methods deprecated? + # (or should be). + # @comment This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource. def self.valid_parameter?(name) validattr?(name) end - # Are we deleting this resource? + # @return [Boolean] Returns true if the wanted state of the resoure is that it should be absent (i.e. to be deleted). 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 + # Creates a new property value holder for the resource if it is valid and does not already exist + # @return [Boolean] 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) @@ -365,20 +598,24 @@ def add_property_parameter(prop_name) false end - # - # The name_var is the key_attribute in the case that there is only one. - # + # @return [Symbol, Boolean] Returns the name of the namevar if there is only one or false otherwise. + # @comment This is really convoluted and part of the support for multiple namevars (?). + # If there is only one namevar, the produced value is naturally this namevar, but if there are several? + # The logic caches the name of the namevar if it is a single name, but otherwise always + # calls key_attributes, and then caches the first if there was only one, otherwise it returns + # false and caches this (which is then subsequently returned as a cache hit). + # def name_var return @name_var_cache unless @name_var_cache.nil? key_attributes = self.class.key_attributes @name_var_cache = (key_attributes.length == 1) && key_attributes.first 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)'. + # Gets the 'is' (current state) value of a parameter or property by name. + # To explicitly get the 'is' value use `o.is(:name)`, and to get the 'should' value + # use `o.should(:name)` + # @param name [String] the name of the attribute to obtain the 'is' value for. + # @return [Object] current value of the given attribute def [](name) name = name.intern fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name) @@ -396,9 +633,9 @@ def [](name) 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. + # Sets the 'should' (wanted state) value of a property, or the value of a parameter. + # @return + # @raise [Puppet::Error] if the setting of the value fails, or if the given name is nil. def []=(name,value) name = name.intern @@ -425,8 +662,16 @@ def []=(name,value) nil end - # remove a property from the object; useful in testing or in cleanup + # Removes a property from the object; useful in testing or in cleanup # when an error has been encountered + # @todo Incomprehensible - the comment says "Remove a property", the code refers to @parameters, and + # the method parameter is called "attr" - What is it, property, parameter, both (i.e an attribute) or what? + # @todo Don't know what the attr is (name or Property/Parameter?). Guessing it is a String name... + # @todo Is it possible to delete a meta-parameter? + # @todo What does delete mean? Is it deleted from the type or is its value state 'is'/'should' deleted? + # @param attr [String] the attribute to delete from this object. WHAT IS THE TYPE? + # @raise [Puppet::DecError] when an attempt is made to delete an attribute that does not exists. + # def delete(attr) attr = attr.intern if @parameters.has_key?(attr) @@ -436,7 +681,11 @@ def delete(attr) end end - # iterate across the existing properties + # Iterates over the existing properties. + # @todo what does this mean? As opposed to iterating over the "non existing properties" ??? Is it an + # iteration over those properties that have state? CONFUSING. + # @yieldparam property [Puppet::Property] each property + # @return [void] def eachproperty # properties is a private method properties.each { |property| @@ -444,21 +693,38 @@ def eachproperty } end - # Create a transaction event. Called by Transaction or by - # a property. + # Creates a transaction event. + # Called by Transaction or by a property. + # Merges the given options with the options `:resource`, `:file`, `:line`, and `:tags`, initialized from + # values in this object. For possible options to pass (if any ????) see {Puppet::Transaction::Event}. + # @todo Needs a better explanation "Why should I care who is calling this method?", What do I need to know + # about events and how they work? Where can I read about them? + # @param options [Hash] options merged with a fixed set of options defined by this method, passed on to {Puppet::Transaction::Event}. + # @return [Puppet::Transaction::Event] the created event def event(options = {}) Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options)) end - # retrieve the 'should' value for a specified property + # @return [Object, nil] Returns the 'should' (wanted state) value for a specified property, or nil if the + # given attribute name is not a property (i.e. if it is a parameter, meta-parameter, or does not exist). def should(name) name = name.intern (prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil end - # Create the actual attribute instance. Requires either the attribute - # name or class as the first argument, then an optional hash of + # Creates an instance to represent/manage the given attribute. + # Requires either the attribute name or class as the first argument, then an optional hash of # attributes to set during initialization. + # @todo The original comment is just wrong - the method does not accept a hash of options + # @todo Detective work required; this method interacts with provider to ask if it supports a parameter of + # the given class. it then returns the parameter if it exists, otherwise creates a parameter + # with its :resource => self. + # @overload newattr(name) + # @param name [String] Unclear what name is (probably a symbol) - Needs investigation. + # @overload newattr(klass) + # @param klass [Class] a class supported as an attribute class - Needs clarification what that means. + # @return [???] Probably returns a new instance of the class - Needs investigation. + # def newattr(name) if name.is_a?(Class) klass = name @@ -480,31 +746,46 @@ def newattr(name) @parameters[name] = klass.new(:resource => self) end - # return the value of a parameter + # Returns the value of this object's parameter given by name + # @param name [String] the name of the parameter + # @return [Object] the value def parameter(name) @parameters[name.to_sym] end + # Returns a shallow copy of this object's hash of parameters. + # @todo Add that this is not only "parameters", but also "properties" and "meta-parameters" ? + # Changes to the contained parameters will have an effect on the parameters of this type, but changes to + # the returned hash does not. + # @return [Hash{String => Puppet:???Parameter}] a new hash being a shallow copy of the parameters map name to parameter def parameters @parameters.dup end - # Is the named property defined? + # @return [Boolean] Returns whether the property given by name is defined or not. + # @todo what does it mean to be defined? def propertydefined?(name) name = name.intern unless name.is_a? Symbol @parameters.include?(name) end - # Return an actual property instance by name; to return the value, use 'resource[param]' - # LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method, - # this one should probably go away at some point. + # Returns a {Puppet::Property} instance by name. + # To return the value, use 'resource[param]' + # @todo LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method, + # this one should probably go away at some point. - Does this mean it should be deprecated ? + # @return [Puppet::Property] the property with the given name, or nil if not a property or does not exist. def property(name) (obj = @parameters[name.intern] and obj.is_a?(Puppet::Property)) ? obj : nil 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. + # @todo comment says "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." + # @todo Needs a better explanation, and investigation about the claim an array can be passed (it is passed + # to self.class.attrclass to produce a class on which a check is made if it has a method class :default (does + # not seem to support an array... + # @return [void] + # def set_default(attr) return unless klass = self.class.attrclass(attr) return unless klass.method_defined?(:default) @@ -519,7 +800,14 @@ def set_default(attr) end end - # Convert our object to a hash. This just includes properties. + # @todo the comment says: "Convert our object to a hash. This just includes properties." + # @todo this is confused, again it is the @parameters instance variable that is consulted, and + # each value is copied - does it contain "properties" and "parameters" or both? Does it contain + # meta-parameters? + # + # @return [Hash{ ??? => ??? }] a hash of WHAT?. The hash is a shallow copy, any changes to the + # objects returned in this hash will be reflected in the original resource having these attributes. + # def to_hash rethash = {} @@ -530,31 +818,47 @@ def to_hash rethash end + # @return [String] the name of this object's class + # @todo Would that be "file" for the "File" resource type? of "File" or something else? + # def type self.class.name end - # Return a specific value for an attribute. + # @todo Comment says "Return a specific value for an attribute.", as opposed to what "An upspecific value"??? + # @todo is this the 'is' or the 'should' value? + # @todo why is the return restricted to things that respond to :value? (Only non structural basic data types + # supported? + # + # @return [Object, nil] the value of the attribute having the given name, or nil if the given name is not + # an attribute, or the referenced attribute does not respond to `:value`. def value(name) name = name.intern (obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil end + # @todo What is this used for? Needs a better explanation. + # @return [???] the version of the catalog or 0 if there is no catalog. def version return 0 unless catalog catalog.version end - # Return all of the property objects, in the order specified in the - # class. + # @return [Array] Returns all of the property objects, in the order specified in the + # class. + # @todo "what does the 'order specified in the class' mean? The order the properties where added in the + # ruby file adding a new type with new properties? + # def properties self.class.properties.collect { |prop| @parameters[prop.name] }.compact end - # Is this type's name isomorphic with the object? That is, if the - # name conflicts, does it necessarily mean that the objects conflict? + # Returns true if the type's notion of name is the identity of a resource. + # See the overview of this class for a longer explanation of the concept _isomorphism_. # Defaults to true. + # + # @return [Boolan] true, if this type's name is isomorphic with the object def self.isomorphic? if defined?(@isomorphic) return @isomorphic @@ -563,14 +867,18 @@ def self.isomorphic? end end + # @todo check that this gets documentation (it is at the class level as well as instance). + # (see isomorphic?) 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 + # Returns true if the instance is 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. + # @note An object that is managed always stays managed, but an object that is not managed + # may become managed later in its lifecycle. + # @return [Boolean] true if the object is managed 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, @@ -593,12 +901,25 @@ def managed? ############################### # Code related to the container behaviour. + + # Returns true if the search should be done in depth-first order. + # This implementation always returns false. + # @todo What is this used for? + # + # @return [Boolean] true if the search should be done in depth first order. + # def depthfirst? false end - # Remove an object. The argument determines whether the object's - # subscriptions get eliminated, too. + # Removes this object (FROM WHERE?) + # @todo removes if from where? + # @overload remove(rmdeps) + # @deprecated Use remove() + # @param rmdeps [Boolean] intended to indicate that all subscriptions should also be removed, ignored. + # @overload remove() + # @return [void] + # def remove(rmdeps = true) # This is hackish (mmm, cut and paste), but it works for now, and it's # better than warnings. @@ -619,19 +940,32 @@ def remove(rmdeps = true) ############################### # Code related to evaluating the resources. + + # Returns the ancestors - WHAT? + # This implementation always returns an empty list. + # @todo WHAT IS THIS ? + # @return [Array] returns a list of ancestors. def ancestors [] end - # Flush the provider, if it supports it. This is called by the - # transaction. + # Flushes the provider if supported by the provider, else no action. + # This is called by the transaction. + # @todo What does Flushing the provider mean? Why is it interesting to know that this is + # called by the transaction? (It is not explained anywhere what a transaction is). + # + # @return [???, nil] WHAT DOES IT RETURN? GUESS IS VOID def flush self.provider.flush if self.provider and self.provider.respond_to?(:flush) 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 + # Returns true if all contained objects are in sync. + # @todo "contained in what?" in the given "in" parameter? + # + # @todo deal with the comment _"FIXME I don't think this is used on the type instances any more, + # it's really only used for testing"_ + # @return [Boolean] true if in sync, false otherwise. + # def insync?(is) insync = true @@ -665,7 +999,12 @@ def insync?(is) insync end - # retrieve the current value of all contained properties + # Retrieves the current value of all contained properties. + # Parameters and meta-parameters are not included in the result. + # @todo As oposed to all non contained properties? How is this different than any of the other + # methods that also "gets" properties/parameters/etc. ? + # @return [Array] array of all property values (mix of types) + # @raise [fail???] if there is a provider and it is not suitable for the host this is evaluated for. def retrieve fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable? @@ -692,15 +1031,22 @@ def retrieve result end + # ??? + # @todo what does this do? It seems to create a new Resource based on the result of calling #retrieve + # and if that is a Hash, else this method produces nil. + # @return [Puppet::Resource, nil] a new Resource, or nil, if this object did not produce a Hash as the + # result from #retrieve + # def retrieve_resource resource = retrieve resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash resource end - # Get a hash of the current properties. Returns a hash with - # the actual property instance as the key and the current value - # as the, um, value. + # Returns a hash of the current properties and their values. + # If a resource is absent, it's value is the symbol `:absent` + # @return [Hash{Puppet::Property => Object}] mapping of property instance to its value + # def currentpropvalues # It's important to use the 'properties' method here, as it follows the order # in which they're defined in the class. It also guarantees that 'ensure' @@ -722,7 +1068,8 @@ def currentpropvalues end end - # Are we running in noop mode? + # Returns the `noop` run mode status of this. + # @return [Boolean] true if running in noop mode. def noop? # If we're not a host_config, we're almost certainly part of # Settings, and we want to ignore 'noop' @@ -735,11 +1082,15 @@ def noop? end end + # (see #noop?) def noop noop? end - # Retrieve all known instances. Either requires providers or must be overridden. + # Retrieves all known instances. + # @todo Retrieves them from where? Known to whom? + # Either requires providers or must be overridden. + # @raise [Puppet::DevError] when there are no providers and the implementation has not overridded this method. def self.instances raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty? @@ -769,7 +1120,10 @@ def self.instances end.flatten.compact end - # Return a list of one suitable provider per source, with the default provider first. + # Returns a list of one suitable provider per source, with the default provider first. + # @todo Needs better explanation; what does "source" mean in this context? + # @return [Array] list of providers + # def self.providers_by_source # Put the default provider first (can be nil), then the rest of the suitable providers. sources = [] @@ -782,7 +1136,11 @@ def self.providers_by_source end.compact end - # Convert a simple hash into a Resource instance. + # Converts a simple hash into a Resource instance. + # @todo as opposed to a complex hash? Other raised exceptions? + # @param [Hash{Symbol, String => Object}] resource attribute to value map to initialize the created resource from + # @return [Puppet::Resource] the resource created from the hash + # @raise [Puppet::Error] if a title is missing in the given hash def self.hash2resource(hash) hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result } @@ -807,7 +1165,10 @@ def self.hash2resource(hash) resource end - # Create the path for logging and such. + # Creates the path for logging and such. + # @todo "and such?", what? + # @api private + # def pathbuilder if p = parent [p.pathbuilder, self.ref].flatten @@ -817,7 +1178,7 @@ def pathbuilder end ############################### - # Add all of the meta parameters. + # Add all of the meta-parameters. newmetaparam(:noop) do desc "Boolean flag indicating whether work should actually be done." @@ -1012,6 +1373,10 @@ def properties_to_audit(list) end end + # RelationshipMetaparam is an implementation supporting the meta-parameters `:require`, `:subscribe`, + # `:notify`, and `:before`. + # + # class RelationshipMetaparam < Puppet::Parameter class << self attr_accessor :direction, :events, :callback, :subclasses @@ -1023,6 +1388,7 @@ def self.inherited(sub) @subclasses << sub end + # @return [Array] turns attribute value(s) into list of resources def munge(references) references = [references] unless references.is_a?(Array) references.collect do |ref| @@ -1034,6 +1400,10 @@ def munge(references) end end + # Checks each reference to assert that what it references exists in the catalog. + # + # @raise [???fail] if the referenced resource can not be found + # @return [void] def validate_relationship @value.each do |ref| unless @resource.catalog.resource(ref.to_s) @@ -1043,14 +1413,19 @@ def validate_relationship 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 + # Creates edges for all relationships. + # The `:in` relationships are specified by the event-receivers, and `:out` + # relationships are specified by the event generator. + # @todo references to "event-receivers" and "event generator" means in this context - are those just + # the resources at the two ends of the relationship? + # This way 'source' and 'target' are consistent terms in both edges + # and events, i.e. 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. + # @return [Array] + # @raise [???fail] when a reference can not be resolved + # def to_edges @value.collect do |reference| reference.catalog = resource.catalog @@ -1089,6 +1464,8 @@ def to_edges end end + # @todo document this, have no clue what this does... it retuns "RelationshipMetaparam.subclasses" + # def self.relationship_params RelationshipMetaparam.subclasses end @@ -1239,15 +1616,33 @@ class { 'foo': stage => 'pre' } # Add the feature handling module. extend Puppet::Util::ProviderFeatures + # The provider that has been selected for the instance of the resource type. + # @return [Puppet::Provider,nil] the selected provider or nil, if none has been selected + # attr_reader :provider # the Type class attribute accessors class << self + # The loader of providers to use when loading providers from disk. + # Although it looks like this attribute provides a way to operate with different loaders of + # providers that is not the case; the attribute is written when a new type is created, + # and should not be changed thereafter. + # @api private + # attr_accessor :providerloader + + # @todo Don't know if this is a name, or a reference to a Provider instance (now marked up as an instance + # of Provider. + # @return [Puppet::Provider, nil] The default provider for this type, or nil if non is defines + # attr_writer :defaultprovider end - # Find the default provider. + # The default provider, or the most suitable provider if no default provider was set. + # @note a warning will be issued if no default provider has been configured and a search for the most + # suitable provider returns more than one equally suitable provider. + # @return [Puppet::Provider, nil] the default or most suitable provider, or nil if no provider was found + # def self.defaultprovider return @defaultprovider if @defaultprovider @@ -1270,16 +1665,26 @@ def self.defaultprovider @defaultprovider = defaults.shift unless defaults.empty? end + # @return [Hash{??? => Puppet::Provider}] Returns a hash of WHAT EXACTLY for the given type + # @todo what goes into this hash? def self.provider_hash_by_type(type) @provider_hashes ||= {} @provider_hashes[type] ||= {} end + # @return [Hash{ ??? => Puppet::Provider}] Returns a hash of WHAT EXACTLY for this type. + # @see provider_hash_by_type method to get the same for some other type def self.provider_hash Puppet::Type.provider_hash_by_type(self.name) end - # Retrieve a provider by name. + # Returns the provider having the given name. + # This will load a provider if it is not already loaded. The returned provider is the first found provider + # having the given name, where "first found" semantics is defined by the {providerloader} in use. + # + # @param name [String] the name of the provider to get + # @return [Puppet::Provider, nil] the found provider, or nil if no provider of the given name was found + # def self.provider(name) name = name.intern @@ -1288,19 +1693,41 @@ def self.provider(name) provider_hash[name] end - # Just list all of the providers. + # Returns a list of loaded providers by name. + # This method will not load/search for available providers. + # @return [Array] list of loaded provider names + # def self.providers provider_hash.keys end + # Returns true if the given name is a reference to a provider and if this is a suitable provider for + # this type. + # @todo How does the provider know if it is suitable for the type? Is it just suitable for the platform/ + # environment where this method is executing? + # @param name [String] the name of the provider for which validity is checked + # @return [Boolean] true if the given name references a provider that is suitable + # def self.validprovider?(name) name = name.intern (provider_hash.has_key?(name) && provider_hash[name].suitable?) end - # Create a new provider of a type. This method must be called - # directly on the type that it's implementing. + # Creates a new provider of a type. + # This method must be called directly on the type that it's implementing. + # @todo Fix Confusing Explanations! + # Is this a new provider of a Type (metatype), or a provider of an instance of Type (a resource), or + # a Provider (the implementation of a Type's behavior). CONFUSED. It calls magically named methods like + # "providify" ... + # @param name [String, Symbol] the name of the WHAT? provider? type? + # @param options [Hash{Symbol => Object}] a hash of options, used by this method, and passed on to {#genclass}, (see + # it for additional options to pass). + # @option options [Puppet::Provider] :parent the parent provider (what is this?) + # @option options [Puppet::Type] :resource_type the resource type, defaults to this type if unspecified + # @return [Puppet::Provider] a provider ??? + # @raise [Puppet::DevError] when the parent provider could not be found. + # def self.provide(name, options = {}, &block) name = name.intern @@ -1342,8 +1769,9 @@ def self.provide(name, options = {}, &block) provider end - # Make sure we have a :provider parameter defined. Only gets called if there - # are providers. + # Ensures there is a `:provider` parameter defined. + # Should only be called if there are providers. + # @return [void] def self.providify return if @paramhash.has_key? :provider @@ -1361,10 +1789,14 @@ def self.providify # This is so we can refer back to the type to get a list of # providers for documentation. class << self + # The reference to a parent type for the parameter `:provider` used to get a list of + # providers for documentation purposes. + # attr_accessor :parenttype end - # We need to add documentation for each provider. + # Provides the ability to add documentation to a provider. + # def self.doc # Since we're mixing @doc with text from other sources, we must normalize # its indentation with scrub. But we don't need to manually scrub the @@ -1376,6 +1808,8 @@ def self.doc }.join end + # @todo this does what? where and how? + # @returns [String] the name of the provider defaultto { prov = @resource.class.defaultprovider prov.name if prov @@ -1404,6 +1838,9 @@ def self.doc end.parenttype = self end + # @todo this needs a better explanation + # Removes the implementation class of a given provider. + # @return [Object] returns what {Puppet::Util::ClassGen#rmclass} returns def self.unprovide(name) if @defaultprovider and @defaultprovider.name == name @defaultprovider = nil @@ -1412,7 +1849,12 @@ def self.unprovide(name) rmclass(name, :hash => provider_hash, :prefix => "Provider") end - # Return an array of all of the suitable providers. + # Returns a list of suitable providers for the given type. + # A call to this method will load all providers if not already loaded and ask each if it is + # suitable - those that are are included in the result. + # @note This method also does some special processing which rejects a provider named `:fake` (for testing purposes). + # @return [Array] Returns an array of all suitable providers. + # def self.suitableprovider providerloader.loadall if provider_hash.empty? provider_hash.find_all { |name, provider| @@ -1422,6 +1864,9 @@ def self.suitableprovider }.reject { |p| p.name == :fake } # For testing end + # @return [Boolean] Returns true if this is something else than a `:provider`, or if it + # is a provider and it is suitable, or if there is a default provider. Otherwise, false is returned. + # def suitable? # If we don't use providers, then we consider it suitable. return true unless self.class.paramclass(:provider) @@ -1440,6 +1885,16 @@ def suitable? false end + # Sets the provider to the given provider/name. + # @overload provider=(name) + # Sets the provider to the result of resolving the name to an instance of Provider. + # @param name [String] the name of the provider + # @overload provider=(provider) + # Sets the provider to the given instances of Provider. + # @param provider [Puppet::Provider] the provider to set + # @return [Puppet::Provider] the provider set + # @raise [ArgumentError] if the provider could not be found/resolved. + # def provider=(name) if name.is_a?(Puppet::Provider) @provider = name @@ -1454,15 +1909,30 @@ def provider=(name) ############################### # 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. + + # Adds a block producing a single name (or list of names) of the given resource type name to autorequire. + # @example Autorequire the files File['foo', 'bar'] + # autorequire( 'file', {|| ['foo', 'bar'] }) + # + # @todo original = _"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."_ + # @param name [String] the name of a type of which one or several resources should be autorequired e.g. "file" + # @yield [ ] a block returning list of names of given type to auto require + # @yieldreturn [String, Array] one or several resource names for the named type + # @return [void] + # @dsl type + # @api public + # def self.autorequire(name, &block) @autorequires ||= {} @autorequires[name] = block end - # Yield each of those autorequires in turn, yo. + # Provides iteration over added auto-requirements (see {autorequire}). + # @yieldparam type [String] the name of the type to autoriquire an instance of + # @yieldparam block [Proc] a block producing one or several dependencies to auto require (see {autorequire}). + # @yieldreturn [void] + # @return [void] def self.eachautorequire @autorequires ||= {} @autorequires.each { |type, block| @@ -1470,8 +1940,13 @@ def self.eachautorequire } end - # Figure out of there are any objects we can automatically add as - # dependencies. + # Adds dependencies to the catalog from added autorequirements. + # See {autorequire} for how to add an auto-requirement. + # @todo needs details - see the param rel_catalog, and type of this param + # @param rel_catalog [Puppet::Catalog, nil] the catalog to add dependencies to. Defaults to the + # catalog (TODO: what is the type of the catalog). + # @raise [Puppet::DevError] if there is no catalog + # def autorequire(rel_catalog = nil) rel_catalog ||= catalog raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog @@ -1502,7 +1977,12 @@ def autorequire(rel_catalog = nil) reqs end - # Build the dependencies associated with an individual object. + # Builds the dependencies associated with an individual object. + # @todo Which object is the "individual object", as opposed to "object as a group?" or should it simply + # be "this object" as in "this resource" ? + # @todo Does this method "build dependencies" or "build what it depends on" ... CONFUSING + # + # @return [Array] list of WHAT? resources? edges? def builddepends # Handle the requires self.class.relationship_params.collect do |klass| @@ -1512,21 +1992,40 @@ def builddepends end.flatten.reject { |r| r.nil? } end - # Define the initial list of tags. + # Sets the initial list of tags... + # @todo The initial list of tags, that ... that what? + # @return [void] ??? def tags=(list) tag(self.class.name) tag(*list) end - # Types (which map to resources in the languages) are entirely composed of - # attribute value pairs. Generally, Puppet calls any of these things an - # 'attribute', but these attributes always take one of three specific - # forms: parameters, metaparams, or properties. - - # In naming methods, I have tried to consistently name the method so - # that it is clear whether it operates on all attributes (thus has 'attr' in - # the method name, or whether it operates on a specific type of attributes. + # @comment - these two comments were floating around here, and turned up as documentation + # for the attribute "title", much to my surprise and amusement. Clearly these comments + # are orphaned ... I think they can just be removed as what they say should be covered + # by the now added yardoc. (Yo! to quote some of the other actual awsome specific comments applicable + # to objects called from elsewhere, or not. ;-) + # + # @comment Types (which map to resources in the languages) are entirely composed of + # attribute value pairs. Generally, Puppet calls any of these things an + # 'attribute', but these attributes always take one of three specific + # forms: parameters, metaparams, or properties. + + # @comment In naming methods, I have tried to consistently name the method so + # that it is clear whether it operates on all attributes (thus has 'attr' in + # the method name, or whether it operates on a specific type of attributes. + + + # The title attribute of WHAT ??? + # @todo Figure out what this is the title attribute of (it appears on line 1926 currently). + # @return [String] the title attr_writer :title + + # The noop attribute of WHAT ??? does WHAT??? + # @todo Figure out what this is the noop attribute of (it appears on line 1931 currently). + # @return [???] the noop WHAT ??? (mode? if so of what, or noop for an instance of the type, or for all + # instances of a type, or for what??? + # attr_writer :noop include Enumerable @@ -1535,9 +2034,14 @@ def tags=(list) public - # the Type class attribute accessors + # The Type class attribute accessors class << self + # @return [String] the name of the resource type; e.g., "File" + # attr_reader :name + + # @return [Boolean] true if the type should send itself a refresh event on change. + # attr_accessor :self_refresh include Enumerable, Puppet::Util::ClassGen include Puppet::MetaType::Manager @@ -1546,7 +2050,9 @@ class << self include Puppet::Util::Logging end - # all of the variables that must be initialized for each subclass + # Initializes all of the variables that must be initialized for each subclass. + # @todo Does the explanation make sense? + # @return [void] def self.initvars # all of the instances of this class @objects = Hash.new @@ -1574,6 +2080,11 @@ def self.initvars end + # Returns the name of this type (if specified) or the parent type #to_s. + # The returned name is on the form "Puppet::Type::", where the first letter of name is + # capitalized. + # @return [String] the fully qualified name Puppet::Type:: where the first letter of name is captialized + # def self.to_s if defined?(@name) "Puppet::Type::#{@name.to_s.capitalize}" @@ -1582,26 +2093,42 @@ def self.to_s end end - # Create a block to validate that our object is set up entirely. This will - # be run before the object is operated on. + # Creates a `validate` method that is used to validate a resource before it is operated on. + # The validation should raise exceptions if the validation finds errors. (It is not recommended to + # issue warnings as this typically just ends up in a logfile - you should fail if a validation fails). + # The easiest way to raise an appropriate exception is to call the method {Puppet::Util::Errors.fail} with + # the message as an argument. + # + # @yield [ ] a required block called with self set to the instance of a Type class representing a resource. + # @return [void] + # @dsl type + # @api public + # def self.validate(&block) define_method(:validate, &block) #@validate = block end - # Origin information. - attr_accessor :file, :line + # @return [String] The file from which this type originates from + attr_accessor :file + + # @return [Integer] The line in {#file} from which this type originates from + attr_accessor :line - # The catalog that this resource is stored in. + # @todo what does this mean "this resource" (sounds like this if for an instance of the type, not the meta Type), + # but not sure if this is about the catalog where the meta Type is included) + # @return [??? TODO] The catalog that this resource is stored in. attr_accessor :catalog - # is the resource exported + # @return [Boolean] Flag indicating if this type is exported attr_accessor :exported - # is the resource virtual (it should not :-)) + # @return [Boolean] Flag indicating if the type is virtual (it should not be). attr_accessor :virtual - # create a log at specified level + # Creates a log entry with the given message at the log level specified by the parameter `loglevel` + # @return [void] + # def log(msg) Puppet::Util::Log.create( @@ -1619,9 +2146,24 @@ def log(msg) public + # @return [Hash] hash of parameters originally defined + # @api private attr_reader :original_parameters - # initialize the type instance + # Creates an instance of Type from a hash or a {Puppet::Resource}. + # @todo Unclear if this is a new Type or a new instance of a given type (the initialization ends + # with calling validate - which seems like validation of an instance of a given type, not a new + # meta type. + # + # @todo Explain what the Hash and Resource are. There seems to be two different types of + # resources; one that causes the title to be set to resource.title, and one that + # causes the title to be resource.ref ("for components") - what is a component? + # + # @overaload initialize(hsh) + # @param hsh [Hash] + # @overload initialize(resource) + # @param resource [Puppet:Resource] + # def initialize(resource) resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource) @@ -1658,12 +2200,32 @@ def initialize(resource) private - # Set our resource's name. + # Sets the name of the resource from a hash containing a mapping of `name_var` to value. + # Sets the value of the property/parameter appointed by the `name_var` (if it is defined). The value set is + # given by the corresponding entry in the given hash - e.g. if name_var appoints the name `:path` the value + # of `:path` is set to the value at the key `:path` in the given hash. As a side effect this key/value is then + # removed from the given hash. + # + # @note This method mutates the given hash by removing the entry with a key equal to the value + # returned from name_var! + # @param hash [Hash] a hash of what + # @return [void] def set_name(hash) self[name_var] = hash.delete(name_var) if name_var end - # Set all of the parameters from a hash, in the appropriate order. + # Sets parameters from the given hash. + # Values are set in _attribute order_ i.e. higher priority attributes before others, otherwise in + # the order they were specified (as opposed to just setting them in the order they happen to appear in + # when iterating over the given hash). + # + # Attributes that are not included in the given hash are set to their default value. + # + # @todo Is this description accurate? Is "ensure" an example of such a higher priority attribute? + # @return [void] + # @raise [Puppet::DevError] when impossible to set the value due to some problem + # @raise [ArgumentError, TypeError, Puppet::Error] when faulty arguments have been passed + # def set_parameters(hash) # Use the order provided by allattrs, but add in any # extra attributes from the resource so we get failures @@ -1694,7 +2256,14 @@ def set_parameters(hash) public - # Set up all of our autorequires. + # Finishes any outstanding processing. + # This method should be called as a final step in setup, + # to allow the parameters that have associated auto-require needs to be processed. + # + # @todo what is the expected sequence here - who is responsible for calling this? When? + # Is the returned type correct? + # @return [Array] the validated list/set of attributes + # def finish # Make sure all of our relationships are valid. Again, must be done # when the entire catalog is instantiated. @@ -1705,13 +2274,22 @@ def finish end.flatten.reject { |r| r.nil? } end - # For now, leave the 'name' method functioning like it used to. Once 'title' - # works everywhere, I'll switch it. + # @comment For now, leave the 'name' method functioning like it used to. Once 'title' + # works everywhere, I'll switch it. + # Returns the resource's name + # @todo There is a comment in source that this is not quite the same as ':title' and that a switch should + # be made... + # @return [String] the name of a resource def name self[:name] end - # Look up our parent in the catalog, if we have one. + # Returns the parent of this in the catalog. + # In case of an erroneous catalog where multiple parents have been produced, the first found (non deterministic) + # parent is returned. + # @return [???, nil] WHAT (which types can be the parent of a resource in a catalog?), or nil if there + # is no catalog. + # def parent return nil unless catalog @@ -1727,24 +2305,36 @@ def parent @parent end - # Return the "type[name]" style reference. + # Returns a reference to this as a string in "Type[name]" format. + # @return [String] a reference to this object on the form 'Type[name]' + # def ref # memoizing this is worthwhile ~ 3 percent of calls are the "first time # around" in an average run of Puppet. --daniel 2012-07-17 @ref ||= "#{self.class.name.to_s.capitalize}[#{self.title}]" end + # (see self_refresh) + # @todo check that meaningful yardoc is produced - this method delegates to "self.class.self_refresh" + # @return [Boolean] - ??? returns true when ... what? + # def self_refresh? self.class.self_refresh end - # Mark that we're purging. + # Marks the object as "being purged". + # This method is used by transactions to forbid deletion when there are dependencies. + # @todo what does this mean; "mark that we are purging" (purging what from where). How to use/when? + # Is this internal API in transactions? + # @see purging? def purging @purging = true end - # Is this resource being purged? Used by transactions to forbid - # deletion when there are dependencies. + # Returns whether this resource is being purged or not. + # This method is used by transactions to forbid deletion when there are dependencies. + # @return [Boolean] the current "purging" state + # def purging? if defined?(@purging) @purging @@ -1753,8 +2343,15 @@ def purging? end end - # Retrieve the title of an object. If no title was set separately, - # then use the object's name. + # Returns the title of this object, or it's name if title was not explicetly set. + # If the title is not already set, it will be computed by looking up the {#name_var} and using + # that value as the title. + # @todo it is somewhat confusing that if the name_var is a valid parameter, it is assumed to + # be the name_var called :name, but if it is a property, it uses the name_var. + # It is further confusing as Type in some respects supports multiple namevars. + # + # @return [String] Returns the title of this object, or it's name if title was not explicetly set. + # @raise [??? devfail] if title is not set, and name_var can not be found. def title unless @title if self.class.validparameter?(name_var) @@ -1769,11 +2366,16 @@ def title @title end - # convert to a string + # Produces a reference to this in reference format. + # @see #ref + # def to_s self.ref end + # @todo What to resource? Which one of the resource forms is prroduced? returned here? + # @return [??? Resource] a resource that WHAT??? + # def to_resource resource = self.retrieve_resource resource.tag(*self.tags) @@ -1790,13 +2392,21 @@ def to_resource resource end + # @return [Boolean] Returns whether the resource is virtual or not def virtual?; !!@virtual; end + # @return [Boolean] Returns whether the resource is exported or not def exported?; !!@exported; end + # @return [Boolean] Returns whether the resource is applicable to `:device` + # @todo Explain what this means + # @api private def appliable_to_device? self.class.can_apply_to(:device) end + # @return [Boolean] Returns whether the resource is applicable to `:host` + # @todo Explain what this means + # @api private def appliable_to_host? self.class.can_apply_to(:host) end diff --git a/lib/puppet/util/classgen.rb b/lib/puppet/util/classgen.rb index 7993e695beb..e03bf2ab707 100644 --- a/lib/puppet/util/classgen.rb +++ b/lib/puppet/util/classgen.rb @@ -3,51 +3,66 @@ class ConstantAlreadyDefined < Error; end class SubclassAlreadyDefined < Error; end end +# This is a utility module for generating classes. +# @api public +# module Puppet::Util::ClassGen include Puppet::Util::MethodHelper include Puppet::Util - # Create a new subclass. Valid options are: - # * :array: An array of existing classes. If specified, the new - # class is added to this array. - # * :attributes: A hash of attributes to set before the block is - # evaluated. - # * :block: The block to evaluate in the context of the class. - # You can also just pass the block normally, but it will still be evaluated - # with class_eval. - # * :constant: What to set the constant as. Defaults to the - # capitalized name. - # * :hash: A hash of existing classes. If specified, the new - # class is added to this hash, and it is also used for overwrite tests. - # * :overwrite: Whether to overwrite an existing class. - # * :parent: The parent class for the generated class. Defaults to - # self. - # * :prefix: The constant prefix. Default to nothing; if specified, - # the capitalized name is appended and the result is set as the constant. + # Create a new class. + # @param name [String] the name of the generated class + # @param options [Hash] a hash of options + # @option options [Array] :array if specified, the generated class is appended to this array + # @option options [Hash<{String => Object}>] :attributes a hash that is applied to the generated class + # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given + # block is evaluated. + # @option options [Proc] :block a block to evaluate in the context of the class (this block can be provided + # this way, or as a normal yield block). + # @option options [String] :constant (name with first letter capitalized) what to set the constant that references + # the generated class to. + # @option options [Hash] :hash a hash of existing classes that this class is appended to (name => class). + # This hash must be specified if the `:overwrite` option is set to `true`. + # @option options [Boolean] :overwrite whether an overwrite of an existing class should be allowed (requires also + # defining the `:hash` with existing classes as the test is based on the content of this hash). + # @option options [Class] :parent (self) the parent class of the generated class. + # @option options [String] ('') :prefix the constant prefix to prepend to the constant name referencing the + # generated class. + # @return [Class] the generated class + # def genclass(name, options = {}, &block) genthing(name, Class, options, block) end - # Create a new module. Valid options are: - # * :array: An array of existing classes. If specified, the new - # class is added to this array. - # * :attributes: A hash of attributes to set before the block is - # evaluated. - # * :block: The block to evaluate in the context of the class. - # You can also just pass the block normally, but it will still be evaluated - # with class_eval. - # * :constant: What to set the constant as. Defaults to the - # capitalized name. - # * :hash: A hash of existing classes. If specified, the new - # class is added to this hash, and it is also used for overwrite tests. - # * :overwrite: Whether to overwrite an existing class. - # * :prefix: The constant prefix. Default to nothing; if specified, + # Creates a new module. + # @param name [String] the name of the generated module + # @param optinos [Hash] hash with options + # @option options [Array] :array if specified, the generated class is appended to this array + # @option options [Hash<{String => Object}>] :attributes a hash that is applied to the generated class + # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given + # block is evaluated. + # @option options [Proc] :block a block to evaluate in the context of the class (this block can be provided + # this way, or as a normal yield block). + # @option options [String] :constant (name with first letter capitalized) what to set the constant that references + # the generated class to. + # @option options [Hash] :hash a hash of existing classes that this class is appended to (name => class). + # This hash must be specified if the `:overwrite` option is set to `true`. + # @option options [Boolean] :overwrite whether an overwrite of an existing class should be allowed (requires also + # defining the `:hash` with existing classes as the test is based on the content of this hash). # the capitalized name is appended and the result is set as the constant. + # @option options [String] ('') :prefix the constant prefix to prepend to the constant name referencing the + # generated class. + # @return [Module] the generated Module def genmodule(name, options = {}, &block) genthing(name, Module, options, block) end - # Remove an existing class + # Removes an existing class. + # @param name [String] the name of the class to remove + # @param options [Hash] options + # @option options [Hash] :hash a hash of existing classes from which the class to be removed is also removed + # @return [Boolean] whether the class was removed or not + # def rmclass(name, options) options = symbolize_options(options) const = genconst_string(name, options) @@ -68,7 +83,8 @@ def rmclass(name, options) private - # Generate the constant to create or remove. + # Generates the constant to create or remove. + # @api private def genconst_string(name, options) unless const = options[:constant] prefix = options[:prefix] || "" @@ -80,6 +96,7 @@ def genconst_string(name, options) # This does the actual work of creating our class or module. It's just a # slightly abstract version of genclass. + # @api private def genthing(name, type, options, block) options = symbolize_options(options) @@ -128,6 +145,8 @@ class << self # of which class hierarchy it polls for nested namespaces # # See http://redmine.ruby-lang.org/issues/show/1915 + # @api private + # def is_constant_defined?(const) if ::RUBY_VERSION =~ /1.9/ const_defined?(const, false) @@ -137,6 +156,8 @@ def is_constant_defined?(const) end # Handle the setting and/or removing of the associated constant. + # @api private + # def handleclassconst(klass, name, options) const = genconst_string(name, options) @@ -155,6 +176,8 @@ def handleclassconst(klass, name, options) end # Perform the initializations on the class. + # @api private + # def initclass(klass, options) klass.initvars if klass.respond_to? :initvars @@ -178,11 +201,13 @@ def initclass(klass, options) end # Convert our name to a constant. + # @api private def name2const(name) name.to_s.capitalize end # Store the class in the appropriate places. + # @api private def storeclass(klass, klassname, options) if hash = options[:hash] if hash.include? klassname and ! options[:overwrite] diff --git a/lib/puppet/util/execution.rb b/lib/puppet/util/execution.rb index c0baec0f349..5f008930319 100644 --- a/lib/puppet/util/execution.rb +++ b/lib/puppet/util/execution.rb @@ -6,16 +6,31 @@ module Puppet class ExecutionFailure < Puppet::Error end +# This module defines methods for execution of system commands. It is intented for inclusion +# in classes that needs to execute system commands. +# @api public module Util::Execution - # Execute the provided command with STDIN connected to a pipe, yielding the - # pipe object. That allows data to be fed to that subprocess. + # Executes the provided command with STDIN connected to a pipe, yielding the + # pipe object. + # This allows data to be fed to the subprocess. # # The command can be a simple string, which is executed as-is, or an Array, - # which is treated as a set of command arguments to pass through.# + # which is treated as a set of command arguments to pass through. # # In all cases this is passed directly to the shell, and STDOUT and STDERR # are connected together during execution. + # @param command [String, Array] the command to execute as one string, or as parts in an array. + # the parts of the array are joined with one separating space between each entry when converting to + # the command line string to execute. + # @param failonfail [Boolean] (true) if the execution should fail with Exception on failure or not. + # @yield [pipe] to a block executing a subprocess + # @yieldparam pipe [IO] the opened pipe + # @yieldreturn [String] the output to return + # @raise [ExecutionFailure] if the executed chiled process did not exit with status == 0 and `failonfail` is + # `true`. + # @return [String] a string with the output from the subprocess executed by the given block + # def self.execpipe(command, failonfail = true) if respond_to? :debug debug "Executing '#{command}'" @@ -44,6 +59,9 @@ def self.execpipe(command, failonfail = true) output end + # Wraps execution of {execute} with mapping of exception to given exception (and output as argument). + # @raise [exception] under same conditions as {execute}, but raises the given `exception` with the output as argument + # @return (see execute) def self.execfail(command, exception) output = execute(command) return output @@ -51,35 +69,37 @@ def self.execfail(command, exception) raise exception, output end + # Default empty options for {execute} + NoOptionsSpecified = {} - # Execute the desired command, and return the status and output. + # Executes the desired command, and return the status and output. # def execute(command, options) - # [command] an Array or String representing the command to execute. If it is + # @param command [Array, String] the command to execute. If it is # an Array the first element should be the executable and the rest of the # elements should be the individual arguments to that executable. - # [options] a Hash optionally containing any of the following keys: - # :failonfail (see below) -- if this value is set to true, then this method will raise an error if the - # command is not executed successfully. - # :uid (default nil) -- the user id of the user that the process should be run as - # :gid (default nil) -- the group id of the group that the process should be run as - # :combine (see below) -- sets whether or not to combine stdout/stderr in the output - # :stdinfile (default nil) -- sets a file that can be used for stdin. Passing a string for stdin is not currently - # supported. - # :squelch (default false) -- if true, ignore stdout / stderr completely - # :override_locale (default true) -- by default (and if this option is set to true), we will temporarily override - # the user/system locale to "C" (via environment variables LANG and LC_*) while we are executing the command. - # This ensures that the output of the command will be formatted consistently, making it predictable for parsing. - # Passing in a value of false for this option will allow the command to be executed using the user/system locale. - # :custom_environment (default {}) -- a hash of key/value pairs to set as environment variables for the duration - # of the command + # @param options [Hash] a Hash of options + # @option options [Boolean] :failonfail if this value is set to true, then this method will raise an error if the + # command is not executed successfully. + # @option options [?] :uid (nil) the user id of the user that the process should be run as + # @option options [?] :gid (nil) the group id of the group that the process should be run as + # @option options [Boolean] :combine sets whether or not to combine stdout/stderr in the output + # @option options [String] :stdinfile (nil) sets a file that can be used for stdin. Passing a string for stdin is not currently + # supported. + # @option options [Boolean] :squelch (true) if true, ignore stdout / stderr completely. + # @option options [Boolean] :override_locale (true) by default (and if this option is set to true), we will temporarily override + # the user/system locale to "C" (via environment variables LANG and LC_*) while we are executing the command. + # This ensures that the output of the command will be formatted consistently, making it predictable for parsing. + # Passing in a value of false for this option will allow the command to be executed using the user/system locale. + # @option options [Hash<{String => String}>] :custom_environment ({}) a hash of key/value pairs to set as environment variables for the duration + # of the command. + # @return [String] output as specified by options + # @note Unfortunately, the default behavior for failonfail and combine (since + # 0.22.4 and 0.24.7, respectively) depend on whether options are specified + # or not. If specified, then failonfail and combine default to false (even + # when the options specified are neither failonfail nor combine). If no + # options are specified, then failonfail and combine default to true. + # @comment See commits efe9a833c and d32d7f30 # - # Unfortunately, the default behavior for failonfail and combine (since - # 0.22.4 and 0.24.7, respectively) depend on whether options are specified - # or not. If specified, then failonfail and combine default to false (even - # when the options specified are neither failonfail nor combine). If no - # options are specified, then failonfail and combine default to true. See - # commits efe9a833c and d32d7f30 - NoOptionsSpecified = {} def self.execute(command, options = NoOptionsSpecified) # specifying these here rather than in the method signature to allow callers to pass in a partial # set of overrides without affecting the default values for options that they don't pass in @@ -147,9 +167,10 @@ def self.execute(command, options = NoOptionsSpecified) output end - # get the path to the ruby executable (available via Config object, even if - # it's not in the PATH... so this is slightly safer than just using - # Puppet::Util.which) + # Returns the path to the ruby executable (available via Config object, even if + # it's not in the PATH... so this is slightly safer than just using Puppet::Util.which) + # @return [String] the path to the Ruby executable + # def self.ruby_path() File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']). @@ -162,7 +183,10 @@ class << self end - # this is private method, see call to private_class_method after method definition + # This is private method. + # @comment see call to private_class_method after method definition + # @api private + # def self.execute_posix(command, options, stdin, stdout, stderr) child_pid = Puppet::Util.safe_posix_fork(stdin, stdout, stderr) do @@ -208,7 +232,10 @@ def self.execute_posix(command, options, stdin, stdout, stderr) private_class_method :execute_posix - # this is private method, see call to private_class_method after method definition + # This is private method. + # @comment see call to private_class_method after method definition + # @api private + # def self.execute_windows(command, options, stdin, stdout, stderr) command = command.map do |part| part.include?(' ') ? %Q["#{part.gsub(/"/, '\"')}"] : part @@ -222,7 +249,10 @@ def self.execute_windows(command, options, stdin, stdout, stderr) private_class_method :execute_windows - # this is private method, see call to private_class_method after method definition + # This is private method. + # @comment see call to private_class_method after method definition + # @api private + # def self.wait_for_output(stdout) # Make sure the file's actually been written. This is basically a race # condition, and is probably a horrible way to handle it, but, well, oh diff --git a/lib/puppet/util/provider_features.rb b/lib/puppet/util/provider_features.rb index 7d61ae55cc9..f2f7a10285e 100644 --- a/lib/puppet/util/provider_features.rb +++ b/lib/puppet/util/provider_features.rb @@ -2,10 +2,15 @@ require 'puppet/util/methodhelper' require 'puppet/util/docs' require 'puppet/util' +# This module models provider features and handles checking whether the features +# are present. +# @todo Unclear what is api and what is private in this module. +# module Puppet::Util::ProviderFeatures include Puppet::Util::Docs - # The class that models the features and handles checking whether the features + # This class models provider features and handles checking whether the features # are present. + # @todo Unclear what is api and what is private in this class class ProviderFeature include Puppet::Util include Puppet::Util::MethodHelper @@ -13,6 +18,9 @@ class ProviderFeature attr_accessor :name, :docs, :methods # Are all of the requirements met? + # Requirements are checked by checking if feature predicate methods have been generated - see {#methods_available?}. + # @param obj [Object, Class] the object or class to check if requirements are met + # @return [Boolean] whether all requirements for this feature are met or not. def available?(obj) if self.methods return !!methods_available?(obj) @@ -33,7 +41,9 @@ def initialize(name, docs, hash) private - # Are all of the required methods available? + # Checks whether all feature predicate methods are available. + # @param obj [Object, Class] the object or class to check if feature predicates are available or not. + # @return [Boolean] Returns whether all of the required methods are available or not in the given object. def methods_available?(obj) methods.each do |m| if obj.is_a?(Class) @@ -46,9 +56,11 @@ def methods_available?(obj) end end - # Define one or more features. At a minimum, features require a name + # Defines one feature. + # At a minimum, a feature requires a name # and docs, and at this point they should also specify a list of methods # required to determine if the feature is present. + # @todo How methods that determine if the feature is present are specified. def feature(name, docs, hash = {}) @features ||= {} raise(Puppet::DevError, "Feature #{name} is already defined") if @features.include?(name) @@ -64,7 +76,7 @@ def feature(name, docs, hash = {}) end end - # Return a hash of all feature documentation. + # @return [String] Returns a string with documentation covering all features. def featuredocs str = "" @features ||= {} @@ -94,14 +106,14 @@ def featuredocs str end - # Return a list of features. + # @return [Array] Returns a list of features. def features @features ||= {} @features.keys end - # Generate a module that sets up the boolean methods to test for given - # features. + # Generates a module that sets up the boolean predicate methods to test for given features. + # def feature_module unless defined?(@feature_module) @features ||= {} @@ -158,7 +170,11 @@ def feature_module @feature_module end - # Return the actual provider feature instance. Really only used for testing. + # @return [ProviderFeature] Returns a provider feature instance by name. + # @param name [String] the name of the feature to return + # @note Should only be used for testing. + # @api private + # def provider_feature(name) return nil unless defined?(@features) diff --git a/yardoc/templates/default/method_details/html/method_signature.erb b/yardoc/templates/default/method_details/html/method_signature.erb new file mode 100644 index 00000000000..5416782d6c9 --- /dev/null +++ b/yardoc/templates/default/method_details/html/method_signature.erb @@ -0,0 +1,25 @@ +

+ <% if object.tags(:overload).size == 1 %> + <%= signature(object.tag(:overload), false) + format_method_detail_extras(object) %> + <% elsif object.tags(:overload).size > 1 %> + <% object.tags(:overload).each do |overload| %> + <%= signature(overload, false) + format_method_detail_extras(object) %> + <% end %> + <% else %> + <%= signature(object, false) + format_method_detail_extras(object)%> + <% end %> + + <% if object.aliases.size > 0 %> + Also known as: + <%= object.aliases.map {|o| + "" + h(o.name.to_s) + "" }.join(", ") %> + + <% end %> + + <% if owner != object.namespace %> + + Originally defined in <%= object.namespace.type %> + <%= linkify object, owner.relative_path(object.namespace) %> + + <% end %> +

\ No newline at end of file diff --git a/yardoc/templates/default/method_details/html/setup.rb b/yardoc/templates/default/method_details/html/setup.rb new file mode 100644 index 00000000000..3de2542b91a --- /dev/null +++ b/yardoc/templates/default/method_details/html/setup.rb @@ -0,0 +1,37 @@ +def init + super +end + +def format_method_detail_extras(object) + result = "" + if object + if object.respond_to?(:visibility) + if object.visibility != :public + result << "#{object.visibility}" + end + end + if object.has_tag?(:abstract) + result << 'abstract' + end + if object.has_tag?(:deprecated) + result << 'deprecated' + end + if object.respond_to?(:visibility) + if object.has_tag?(:api) && object.tag(:api).text == 'private' && object.visibility != :private + result << 'private' + end + else + if object.has_tag?(:api) && object.tag(:api).text == 'private' + result << 'private' + end + end + if object.has_tag?(:dsl) + result << 'DSL' + end + end + # separate the extras with one space + if result != "" + result = " " + result + end + result +end diff --git a/yardoc/templates/default/module/html/item_summary.erb b/yardoc/templates/default/module/html/item_summary.erb new file mode 100644 index 00000000000..a42c45f4bea --- /dev/null +++ b/yardoc/templates/default/module/html/item_summary.erb @@ -0,0 +1,47 @@ +<% +# This is a copy of the original erb file with the same name. It adds "DSL" marker. +# It also removes the double output of "private" when being both api private and private +%> +
  • + + <% if @item.tags(:overload).size == 1 %> + <%= signature(@item.tag(:overload), true, !@item.attr_info) %> + <% else %> + <%= signature(@item, true, false, !@item.attr_info) %> + <% end %> + + <% if @item.aliases.size > 0 %> + (also: <%= @item.aliases.map {|o| h(o.name(true)) }.join(", ") %>) + <% end %> + + <% if object != @item.namespace %> + + <%= @item.namespace.type == :class ? 'inherited' : (@item.scope == :class ? 'extended' : 'included') %> + from <%= linkify @item, object.relative_path(@item.namespace) %> + + <% end %> + <% if @item.constructor? %> + constructor + <% end %> + <% if @item.has_tag?(:dsl) %> + DSL + <% end %> + <% if rw = @item.attr_info %> + <% if !run_verifier([rw[:read]].compact).empty? && run_verifier([rw[:write]].compact).empty? %> + readonly + <% end %> + <% if !run_verifier([rw[:write]].compact).empty? && run_verifier([rw[:read]].compact).empty? %> + writeonly + <% end %> + <% end %> + <% if @item.visibility != :public %><%= @item.visibility %><% end %> + <% if @item.has_tag?(:abstract) %>abstract<% end %> + <% if @item.has_tag?(:deprecated) %>deprecated<% end %> + <% if @item.has_tag?(:api) && @item.tag(:api).text == 'private'&& @item.visibility != :private %>private<% end %> + + <% if @item.has_tag?(:deprecated) %> + Deprecated. <%= htmlify_line @item.tag(:deprecated).text %> + <% else %> + <%= htmlify_line docstring_summary(@item) %> + <% end %> +