Skip to content

Commit 5b2ffbc

Browse files
author
luke
committed
Adding provider features. Woot!
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2313 980ebf18-57e1-0310-9a29-db15c13687c0
1 parent 80dac92 commit 5b2ffbc

File tree

7 files changed

+333
-22
lines changed

7 files changed

+333
-22
lines changed

CHANGELOG

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
0.22.2 (grover)
2+
Added the concept of provider features. Eventually these should be able
3+
to express the full range of provider functionality, but for now they can
4+
test a provider to see what methods it has set and determine what features it
5+
provides as a result. These features are integrated into the doc generation
6+
system so that you get feature documentation automatically.
7+
28
Switched apt/aptitide to using "apt-cache policy" instead of "apt-cache showpkg"
39
for determining the latest available version. (#487)
410

bin/puppetdoc

+30-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#
99
# = Usage
1010
#
11-
# puppetdoc [-h|--help] [-a|--arguments] [-t|--types]
11+
# puppetdoc [-h|--help] [-m|--mode <typedocs|configref>
1212
#
1313
# = Description
1414
#
@@ -19,14 +19,13 @@
1919
#
2020
# = Options
2121
#
22-
# arguments::
23-
# Print the documentation for arguments.
24-
#
2522
# help::
2623
# Print this help message
2724
#
28-
# types::
29-
# Print the argumenst for Puppet types. This is the default.
25+
# mode::
26+
# Print documentation of a given type. Valid optinos are 'typedocs', for
27+
# resource type documentation, and 'configref', for documentation on all
28+
# of the configuration parameters.
3029
#
3130
# = Example
3231
#
@@ -53,7 +52,7 @@ debug = false
5352

5453
$tab = " "
5554

56-
mode = :types
55+
mode = :typedocs
5756

5857
begin
5958
result.each { |opt,arg|
@@ -194,14 +193,20 @@ in your manifest, including defined components.
194193
string is assigned to the ``path`` parameter.
195194
196195
- *parameters* determine the specific configuration of the instance. They either
197-
directly modify the system (internally, these are called states) or they affect
196+
directly modify the system (internally, these are called properties) or they affect
198197
how the instance behaves (e.g., adding a search path for ``exec`` instances
199198
or determining recursion on ``file`` instances).
200199
201-
When required binaries are specified for providers, fully qualifed paths
202-
indicate that the binary must exist at that specific path and unqualified
203-
binaries indicate that Puppet will search for the binary using the shell
204-
path.
200+
- *providers* provide low-level functionality for a given resource type. This is
201+
usually in the form of calling out to external commands.
202+
203+
When required binaries are specified for providers, fully qualifed paths
204+
indicate that the binary must exist at that specific path and unqualified
205+
binaries indicate that Puppet will search for the binary using the shell
206+
path.
207+
208+
Resource types define features they can use, and providers can be tested to see
209+
which features they provide.
205210
206211
}
207212

@@ -219,22 +224,28 @@ path.
219224
<h2><a name='%s'>%s</a></h2>" % [name, name]
220225
puts scrub(type.doc) + "\n\n"
221226

227+
# Handle the feature docs.
228+
if featuredocs = type.featuredocs
229+
puts "<h3><a name='%s_features'>%s Features</a></h3>" % [name, name.to_s.capitalize]
230+
puts featuredocs
231+
end
232+
222233
docs = {}
223-
type.validstates.sort { |a,b|
234+
type.validproperties.sort { |a,b|
224235
a.to_s <=> b.to_s
225236
}.reject { |sname|
226-
state = type.statebyname(sname)
227-
state.nodoc
237+
property = type.propertybyname(sname)
238+
property.nodoc
228239
}.each { |sname|
229-
state = type.statebyname(sname)
240+
property = type.propertybyname(sname)
230241

231-
unless state
232-
raise "Could not retrieve state %s on type %s" % [sname, type.name]
242+
unless property
243+
raise "Could not retrieve property %s on type %s" % [sname, type.name]
233244
end
234245

235246
doc = nil
236247
str = nil
237-
unless doc = state.doc
248+
unless doc = property.doc
238249
$stderr.puts "No docs for %s[%s]" % [type, sname]
239250
next
240251
end

lib/puppet/metatype/providers.rb

+76
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
require 'puppet/util/provider_features'
12
class Puppet::Type
3+
# Add the feature handling module.
4+
extend Puppet::Util::ProviderFeatures
5+
26
attr_reader :provider
37

48
# the Type class attribute accessors
@@ -41,6 +45,74 @@ def self.defaultprovider
4145
return @defaultprovider
4246
end
4347

48+
# Define one or more features. Currently, features are just a list of
49+
# methods; if all methods are defined as instance methods on the provider,
50+
# then the provider has that feature, otherwise it does not.
51+
def self.dis_features(hash)
52+
@features ||= {}
53+
hash.each do |name, methods|
54+
name = symbolize(name)
55+
methods = methods.collect { |m| symbolize(m) }
56+
if @features.include?(name)
57+
raise Puppet::DevError, "Feature %s is already defined" % name
58+
end
59+
@features[name] = methods
60+
end
61+
end
62+
63+
# Generate a module that sets up the boolean methods to test for given
64+
# features.
65+
def self.dis_feature_module
66+
unless defined? @feature_module
67+
@features ||= {}
68+
@feature_module = ::Module.new
69+
const_set("FeatureModule", @feature_module)
70+
features = @features
71+
@feature_module.send(:define_method, :feature?) do |name|
72+
method = name.to_s + "?"
73+
if respond_to?(method) and send(method)
74+
return true
75+
else
76+
return false
77+
end
78+
end
79+
@feature_module.send(:define_method, :features) do
80+
return false unless defined?(features)
81+
features.keys.find_all { |n| feature?(n) }.sort { |a,b|
82+
a.to_s <=> b.to_s
83+
}
84+
end
85+
#if defined?(@features)
86+
@features.each do |name, methods|
87+
method = name.to_s + "?"
88+
@feature_module.send(:define_method, method) do
89+
set = nil
90+
methods.each do |m|
91+
if is_a?(Class)
92+
unless public_method_defined?(m)
93+
set = false
94+
break
95+
end
96+
else
97+
unless respond_to?(m)
98+
set = false
99+
break
100+
end
101+
end
102+
end
103+
104+
if set.nil?
105+
true
106+
else
107+
false
108+
end
109+
end
110+
end
111+
#end
112+
end
113+
@feature_module
114+
end
115+
44116
# Convert a hash, as provided by, um, a provider, into an instance of self.
45117
def self.hash2obj(hash)
46118
obj = nil
@@ -161,6 +233,10 @@ def self.provide(name, options = {}, &block)
161233
:attributes => options
162234
)
163235

236+
# Add the feature module to both the instances and classes.
237+
provider.send(:include, feature_module)
238+
provider.send(:extend, feature_module)
239+
164240
return provider
165241
end
166242

lib/puppet/provider.rb

+8
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ def self.to_s
212212
end
213213
end
214214

215+
dochook(:features) do
216+
if features().length > 0
217+
return " Supported features: " + features().collect do |f|
218+
"``#{f}``"
219+
end.join(", ") + "."
220+
end
221+
end
222+
215223
# Remove the reference to the model, so GC can clean up.
216224
def clear
217225
@model = nil

lib/puppet/type/package.rb

+24-3
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,23 @@ class PackageError < Puppet::Error; end
1717
1818
Puppet will automatically guess the packaging format that you are
1919
using based on the platform you are on, but you can override it
20-
using the ``type`` parameter; obviously, if you specify that you
21-
want to use ``rpm`` then the ``rpm`` tools must be available."
20+
using the ``provider`` parameter; each provider defines what it
21+
requires in order to function, and you must meet those requirements
22+
to use a given provider."
23+
24+
feature :installable, "The provider can install packages.",
25+
:methods => [:install]
26+
feature :uninstallable, "The provider can uninstall packages.",
27+
:methods => [:uninstall]
28+
feature :upgradeable, "The provider can upgrade to the latest version of a
29+
package. This feature is used by specifying ``latest`` as the
30+
desired value for the package.",
31+
:methods => [:update, :latest]
32+
feature :purgeable, "The provider can purge packages. This generally means
33+
that all traces of the package are removed, including
34+
existing configuration files. This feature is thus destructive
35+
and should be used with the utmost care.",
36+
:methods => [:purge]
2237

2338
ensurable do
2439
desc "What state the package should be in.
@@ -42,14 +57,20 @@ class PackageError < Puppet::Error; end
4257
end
4358

4459
newvalue(:purged, :event => :package_purged) do
60+
unless provider.purgeable?
61+
self.fail(
62+
"Package provider %s does not purging" %
63+
@parent[:provider]
64+
)
65+
end
4566
provider.purge
4667
end
4768

4869
# Alias the 'present' value.
4970
aliasvalue(:installed, :present)
5071

5172
newvalue(:latest) do
52-
unless provider.respond_to?(:latest)
73+
unless provider.upgradeable?
5374
self.fail(
5475
"Package provider %s does not support specifying 'latest'" %
5576
@parent[:provider]

lib/puppet/util/provider_features.rb

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Provides feature definitions.
2+
module Puppet::Util::ProviderFeatures
3+
class ProviderFeature
4+
require 'puppet/util/methodhelper'
5+
require 'puppet/util'
6+
include Puppet::Util
7+
include Puppet::Util::MethodHelper
8+
attr_accessor :name, :docs, :methods
9+
def initialize(name, docs, hash)
10+
self.name = symbolize(name)
11+
self.docs = docs
12+
hash = symbolize_options(hash)
13+
set_options(hash)
14+
end
15+
end
16+
17+
# Define one or more features. At a minimum, features require a name
18+
# and docs, and at this point they should also specify a list of methods
19+
# required to determine if the feature is present.
20+
def feature(name, docs, hash)
21+
@features ||= {}
22+
if @features.include?(name)
23+
raise Puppet::DevError, "Feature %s is already defined" % name
24+
end
25+
begin
26+
obj = ProviderFeature.new(name, docs, hash)
27+
@features[obj.name] = obj
28+
rescue ArgumentError => detail
29+
error = ArgumentError.new(
30+
"Could not create feature %s: %s" % [name, detail]
31+
)
32+
error.set_backtrace(detail.backtrace)
33+
raise error
34+
end
35+
end
36+
37+
# Return a hash of all feature documentation.
38+
def featuredocs
39+
str = ""
40+
@features ||= {}
41+
return nil if @features.empty?
42+
@features.each do |name, feature|
43+
doc = feature.docs.gsub(/\n\s+/, " ")
44+
str += " - **%s**: %s\n" % [name, doc]
45+
end
46+
str
47+
end
48+
49+
# Generate a module that sets up the boolean methods to test for given
50+
# features.
51+
def feature_module
52+
unless defined? @feature_module
53+
@features ||= {}
54+
@feature_module = ::Module.new
55+
const_set("FeatureModule", @feature_module)
56+
features = @features
57+
# Create a feature? method that can be passed a feature name and
58+
# determine if the feature is present.
59+
@feature_module.send(:define_method, :feature?) do |name|
60+
method = name.to_s + "?"
61+
if respond_to?(method) and send(method)
62+
return true
63+
else
64+
return false
65+
end
66+
end
67+
68+
# Create a method that will list all functional features.
69+
@feature_module.send(:define_method, :features) do
70+
return false unless defined?(features)
71+
features.keys.find_all { |n| feature?(n) }.sort { |a,b|
72+
a.to_s <=> b.to_s
73+
}
74+
end
75+
76+
# Create a boolean method for each feature so you can test them
77+
# individually as you might need.
78+
@features.each do |name, feature|
79+
method = name.to_s + "?"
80+
@feature_module.send(:define_method, method) do
81+
set = nil
82+
feature.methods.each do |m|
83+
if is_a?(Class)
84+
unless public_method_defined?(m)
85+
set = false
86+
break
87+
end
88+
else
89+
unless respond_to?(m)
90+
set = false
91+
break
92+
end
93+
end
94+
end
95+
96+
if set.nil?
97+
true
98+
else
99+
false
100+
end
101+
end
102+
end
103+
end
104+
@feature_module
105+
end
106+
end
107+
108+
# $Id$

0 commit comments

Comments
 (0)