diff options
| -rw-r--r-- | CHANGELOG | 6 | ||||
| -rwxr-xr-x | bin/puppetdoc | 49 | ||||
| -rw-r--r-- | lib/puppet/metatype/providers.rb | 76 | ||||
| -rw-r--r-- | lib/puppet/provider.rb | 8 | ||||
| -rw-r--r-- | lib/puppet/type/package.rb | 27 | ||||
| -rw-r--r-- | lib/puppet/util/provider_features.rb | 108 | ||||
| -rwxr-xr-x | test/ral/manager/provider.rb | 81 |
7 files changed, 333 insertions, 22 deletions
@@ -1,4 +1,10 @@ 0.22.2 (grover) + Added the concept of provider features. Eventually these should be able + to express the full range of provider functionality, but for now they can + test a provider to see what methods it has set and determine what features it + provides as a result. These features are integrated into the doc generation + system so that you get feature documentation automatically. + Switched apt/aptitide to using "apt-cache policy" instead of "apt-cache showpkg" for determining the latest available version. (#487) diff --git a/bin/puppetdoc b/bin/puppetdoc index 8da37b35f..8e9e1b360 100755 --- a/bin/puppetdoc +++ b/bin/puppetdoc @@ -8,7 +8,7 @@ # # = Usage # -# puppetdoc [-h|--help] [-a|--arguments] [-t|--types] +# puppetdoc [-h|--help] [-m|--mode <typedocs|configref> # # = Description # @@ -19,14 +19,13 @@ # # = Options # -# arguments:: -# Print the documentation for arguments. -# # help:: # Print this help message # -# types:: -# Print the argumenst for Puppet types. This is the default. +# mode:: +# Print documentation of a given type. Valid optinos are 'typedocs', for +# resource type documentation, and 'configref', for documentation on all +# of the configuration parameters. # # = Example # @@ -53,7 +52,7 @@ debug = false $tab = " " -mode = :types +mode = :typedocs begin result.each { |opt,arg| @@ -194,14 +193,20 @@ in your manifest, including defined components. string is assigned to the ``path`` parameter. - *parameters* determine the specific configuration of the instance. They either - directly modify the system (internally, these are called states) or they affect + directly modify the system (internally, these are called properties) or they affect how the instance behaves (e.g., adding a search path for ``exec`` instances or determining recursion on ``file`` instances). -When required binaries are specified for providers, fully qualifed paths -indicate that the binary must exist at that specific path and unqualified -binaries indicate that Puppet will search for the binary using the shell -path. +- *providers* provide low-level functionality for a given resource type. This is + usually in the form of calling out to external commands. + + When required binaries are specified for providers, fully qualifed paths + indicate that the binary must exist at that specific path and unqualified + binaries indicate that Puppet will search for the binary using the shell + path. + + Resource types define features they can use, and providers can be tested to see + which features they provide. } @@ -219,22 +224,28 @@ path. <h2><a name='%s'>%s</a></h2>" % [name, name] puts scrub(type.doc) + "\n\n" + # Handle the feature docs. + if featuredocs = type.featuredocs + puts "<h3><a name='%s_features'>%s Features</a></h3>" % [name, name.to_s.capitalize] + puts featuredocs + end + docs = {} - type.validstates.sort { |a,b| + type.validproperties.sort { |a,b| a.to_s <=> b.to_s }.reject { |sname| - state = type.statebyname(sname) - state.nodoc + property = type.propertybyname(sname) + property.nodoc }.each { |sname| - state = type.statebyname(sname) + property = type.propertybyname(sname) - unless state - raise "Could not retrieve state %s on type %s" % [sname, type.name] + unless property + raise "Could not retrieve property %s on type %s" % [sname, type.name] end doc = nil str = nil - unless doc = state.doc + unless doc = property.doc $stderr.puts "No docs for %s[%s]" % [type, sname] next end diff --git a/lib/puppet/metatype/providers.rb b/lib/puppet/metatype/providers.rb index ef7f73c6c..c078ced24 100644 --- a/lib/puppet/metatype/providers.rb +++ b/lib/puppet/metatype/providers.rb @@ -1,4 +1,8 @@ +require 'puppet/util/provider_features' class Puppet::Type + # Add the feature handling module. + extend Puppet::Util::ProviderFeatures + attr_reader :provider # the Type class attribute accessors @@ -41,6 +45,74 @@ class Puppet::Type return @defaultprovider end + # Define one or more features. Currently, features are just a list of + # methods; if all methods are defined as instance methods on the provider, + # then the provider has that feature, otherwise it does not. + def self.dis_features(hash) + @features ||= {} + hash.each do |name, methods| + name = symbolize(name) + methods = methods.collect { |m| symbolize(m) } + if @features.include?(name) + raise Puppet::DevError, "Feature %s is already defined" % name + end + @features[name] = methods + end + end + + # Generate a module that sets up the boolean methods to test for given + # features. + def self.dis_feature_module + unless defined? @feature_module + @features ||= {} + @feature_module = ::Module.new + const_set("FeatureModule", @feature_module) + features = @features + @feature_module.send(:define_method, :feature?) do |name| + method = name.to_s + "?" + if respond_to?(method) and send(method) + return true + else + return false + end + end + @feature_module.send(:define_method, :features) do + return false unless defined?(features) + features.keys.find_all { |n| feature?(n) }.sort { |a,b| + a.to_s <=> b.to_s + } + end + #if defined?(@features) + @features.each do |name, methods| + method = name.to_s + "?" + @feature_module.send(:define_method, method) do + set = nil + methods.each do |m| + if is_a?(Class) + unless public_method_defined?(m) + set = false + break + end + else + unless respond_to?(m) + set = false + break + end + end + end + + if set.nil? + true + else + false + end + end + end + #end + end + @feature_module + end + # Convert a hash, as provided by, um, a provider, into an instance of self. def self.hash2obj(hash) obj = nil @@ -161,6 +233,10 @@ class Puppet::Type :attributes => options ) + # Add the feature module to both the instances and classes. + provider.send(:include, feature_module) + provider.send(:extend, feature_module) + return provider end diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index 23c921ac0..f5e45ba6d 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -212,6 +212,14 @@ class Puppet::Provider end end + dochook(:features) do + if features().length > 0 + return " Supported features: " + features().collect do |f| + "``#{f}``" + end.join(", ") + "." + end + end + # Remove the reference to the model, so GC can clean up. def clear @model = nil diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index 107acc20d..14ce4a941 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -17,8 +17,23 @@ module Puppet Puppet will automatically guess the packaging format that you are using based on the platform you are on, but you can override it - using the ``type`` parameter; obviously, if you specify that you - want to use ``rpm`` then the ``rpm`` tools must be available." + using the ``provider`` parameter; each provider defines what it + requires in order to function, and you must meet those requirements + to use a given provider." + + feature :installable, "The provider can install packages.", + :methods => [:install] + feature :uninstallable, "The provider can uninstall packages.", + :methods => [:uninstall] + feature :upgradeable, "The provider can upgrade to the latest version of a + package. This feature is used by specifying ``latest`` as the + desired value for the package.", + :methods => [:update, :latest] + feature :purgeable, "The provider can purge packages. This generally means + that all traces of the package are removed, including + existing configuration files. This feature is thus destructive + and should be used with the utmost care.", + :methods => [:purge] ensurable do desc "What state the package should be in. @@ -42,6 +57,12 @@ module Puppet end newvalue(:purged, :event => :package_purged) do + unless provider.purgeable? + self.fail( + "Package provider %s does not purging" % + @parent[:provider] + ) + end provider.purge end @@ -49,7 +70,7 @@ module Puppet aliasvalue(:installed, :present) newvalue(:latest) do - unless provider.respond_to?(:latest) + unless provider.upgradeable? self.fail( "Package provider %s does not support specifying 'latest'" % @parent[:provider] diff --git a/lib/puppet/util/provider_features.rb b/lib/puppet/util/provider_features.rb new file mode 100644 index 000000000..c378ff805 --- /dev/null +++ b/lib/puppet/util/provider_features.rb @@ -0,0 +1,108 @@ +# Provides feature definitions. +module Puppet::Util::ProviderFeatures + class ProviderFeature + require 'puppet/util/methodhelper' + require 'puppet/util' + include Puppet::Util + include Puppet::Util::MethodHelper + attr_accessor :name, :docs, :methods + def initialize(name, docs, hash) + self.name = symbolize(name) + self.docs = docs + hash = symbolize_options(hash) + set_options(hash) + end + end + + # Define one or more features. At a minimum, features require a name + # and docs, and at this point they should also specify a list of methods + # required to determine if the feature is present. + def feature(name, docs, hash) + @features ||= {} + if @features.include?(name) + raise Puppet::DevError, "Feature %s is already defined" % name + end + begin + obj = ProviderFeature.new(name, docs, hash) + @features[obj.name] = obj + rescue ArgumentError => detail + error = ArgumentError.new( + "Could not create feature %s: %s" % [name, detail] + ) + error.set_backtrace(detail.backtrace) + raise error + end + end + + # Return a hash of all feature documentation. + def featuredocs + str = "" + @features ||= {} + return nil if @features.empty? + @features.each do |name, feature| + doc = feature.docs.gsub(/\n\s+/, " ") + str += " - **%s**: %s\n" % [name, doc] + end + str + end + + # Generate a module that sets up the boolean methods to test for given + # features. + def feature_module + unless defined? @feature_module + @features ||= {} + @feature_module = ::Module.new + const_set("FeatureModule", @feature_module) + features = @features + # Create a feature? method that can be passed a feature name and + # determine if the feature is present. + @feature_module.send(:define_method, :feature?) do |name| + method = name.to_s + "?" + if respond_to?(method) and send(method) + return true + else + return false + end + end + + # Create a method that will list all functional features. + @feature_module.send(:define_method, :features) do + return false unless defined?(features) + features.keys.find_all { |n| feature?(n) }.sort { |a,b| + a.to_s <=> b.to_s + } + end + + # Create a boolean method for each feature so you can test them + # individually as you might need. + @features.each do |name, feature| + method = name.to_s + "?" + @feature_module.send(:define_method, method) do + set = nil + feature.methods.each do |m| + if is_a?(Class) + unless public_method_defined?(m) + set = false + break + end + else + unless respond_to?(m) + set = false + break + end + end + end + + if set.nil? + true + else + false + end + end + end + end + @feature_module + end +end + +# $Id$ diff --git a/test/ral/manager/provider.rb b/test/ral/manager/provider.rb index 6075697d6..870c3134a 100755 --- a/test/ral/manager/provider.rb +++ b/test/ral/manager/provider.rb @@ -47,6 +47,87 @@ class TestTypeProviders < Test::Unit::TestCase assert_equal(should, type.allattrs.reject { |p| ! should.include?(p) }, "Providify did not reorder parameters") end + + def test_features + type = Puppet::Type.newtype(:feature_test) do + newparam(:name) {} + ensurable + end + cleanup { Puppet::Type.rmtype(:feature_test) } + + features = {:numeric => [:one, :two], :alpha => [:a, :b]} + + features.each do |name, methods| + assert_nothing_raised("Could not define features") do + type.feature(name, "boo", :methods => methods) + end + end + + providers = {:numbers => features[:numeric], :letters => features[:alpha]} + providers[:both] = features[:numeric] + features[:alpha] + providers[:mixed] = [:one, :b] + providers[:neither] = [:something, :else] + + providers.each do |name, methods| + assert_nothing_raised("Could not create provider %s" % name) do + type.provide(name) do + methods.each do |name| + define_method(name) {} + end + end + end + end + + model = type.create(:name => "foo") + {:numbers => [:numeric], :letters => [:alpha], :both => [:numeric, :alpha], + :mixed => [], :neither => []}.each do |name, should| + should.sort! { |a,b| a.to_s <=> b.to_s } + provider = type.provider(name) + assert(provider, "Could not find provider %s" % name) + assert_equal(should, provider.features, + "Provider %s has incorrect features" % name) + + inst = provider.new(model) + # Make sure the boolean methods work on both the provider and + # instance. + features.keys.each do |feature| + method = feature.to_s + "?" + assert(inst.respond_to?(method), + "No boolean instance method for %s on %s" % + [name, feature]) + assert(provider.respond_to?(method), + "No boolean class method for %s on %s" % [name, feature]) + + if should.include?(feature) + assert(provider.feature?(feature), + "class missing feature? %s" % feature) + assert(inst.feature?(feature), + "instance missing feature? %s" % feature) + assert(provider.send(method), + "class missing feature %s" % feature) + assert(inst.send(method), + "instance missing feature %s" % feature) + else + assert(! provider.feature?(feature), + "class has feature? %s" % feature) + assert(! inst.feature?(feature), + "instance has feature? %s" % feature) + assert(! provider.send(method), + "class has feature %s" % feature) + assert(! inst.send(method), + "instance has feature %s" % feature) + end + end + + end + + Puppet[:trace] = true + Puppet::Type.loadall + Puppet::Type.eachtype do |type| + assert(type.respond_to?(:feature), + "No features method defined for %s" % type.name) + end + end end # $Id$ |
