summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG6
-rwxr-xr-xbin/puppetdoc49
-rw-r--r--lib/puppet/metatype/providers.rb76
-rw-r--r--lib/puppet/provider.rb8
-rw-r--r--lib/puppet/type/package.rb27
-rw-r--r--lib/puppet/util/provider_features.rb108
-rwxr-xr-xtest/ral/manager/provider.rb81
7 files changed, 333 insertions, 22 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 9ee894efc..880337ea6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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$