Adding provider features. Woot!
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2313 980ebf18-57e1-0310-9a29-db15c13687c0
This commit is contained in:
Родитель
80dac92b3a
Коммит
5b2ffbcb5d
|
@ -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$
|
||||
|
|
Загрузка…
Ссылка в новой задаче