Adding provider features. Woot!

git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2313 980ebf18-57e1-0310-9a29-db15c13687c0
This commit is contained in:
luke 2007-03-19 08:15:36 +00:00
Родитель 80dac92b3a
Коммит 5b2ffbcb5d
7 изменённых файлов: 333 добавлений и 22 удалений

Просмотреть файл

@ -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)

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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]

Просмотреть файл

@ -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$

Просмотреть файл

@ -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$