1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
# Provides feature definitions.
require_relative '../../puppet/util/docs'
require_relative '../../puppet/util'
# This module models provider features and handles checking whether the features
# are present.
# @todo Unclear what is api and what is private in this module.
#
module Puppet::Util::ProviderFeatures
include Puppet::Util::Docs
# This class models provider features and handles checking whether the features
# are present.
# @todo Unclear what is api and what is private in this class
class ProviderFeature
include Puppet::Util
include Puppet::Util::Docs
attr_accessor :name, :docs, :methods
# Are all of the requirements met?
# Requirements are checked by checking if feature predicate methods have been generated - see {#methods_available?}.
# @param obj [Object, Class] the object or class to check if requirements are met
# @return [Boolean] whether all requirements for this feature are met or not.
def available?(obj)
if self.methods
return !!methods_available?(obj)
else
# In this case, the provider has to declare support for this
# feature, and that's been checked before we ever get to the
# method checks.
return false
end
end
def initialize(name, docs, methods: nil)
self.name = name.intern
self.docs = docs
@methods = methods
end
private
# Checks whether all feature predicate methods are available.
# @param obj [Object, Class] the object or class to check if feature predicates are available or not.
# @return [Boolean] Returns whether all of the required methods are available or not in the given object.
def methods_available?(obj)
methods.each do |m|
if obj.is_a?(Class)
return false unless obj.public_method_defined?(m)
else
return false unless obj.respond_to?(m)
end
end
true
end
end
# Defines one feature.
# At a minimum, a feature requires a name
# and docs, and at this point they should also specify a list of methods
# required to determine if the feature is present.
# @todo How methods that determine if the feature is present are specified.
def feature(name, docs, hash = {})
@features ||= {}
raise Puppet::DevError, _("Feature %{name} is already defined") % { name: name } if @features.include?(name)
begin
obj = ProviderFeature.new(name, docs, **hash)
@features[obj.name] = obj
rescue ArgumentError => detail
error = ArgumentError.new(
_("Could not create feature %{name}: %{detail}") % { name: name, detail: detail }
)
error.set_backtrace(detail.backtrace)
raise error
end
end
# @return [String] Returns a string with documentation covering all features.
def featuredocs
str = ""
@features ||= {}
return nil if @features.empty?
names = @features.keys.sort_by(&:to_s)
names.each do |name|
doc = @features[name].docs.gsub(/\n\s+/, " ")
str << "- *#{name}*: #{doc}\n"
end
if providers.length > 0
headers = ["Provider", names].flatten
data = {}
providers.each do |provname|
data[provname] = []
prov = provider(provname)
names.each do |name|
if prov.feature?(name)
data[provname] << "*X*"
else
data[provname] << ""
end
end
end
str << doctable(headers, data)
end
str
end
# @return [Array<String>] Returns a list of features.
def features
@features ||= {}
@features.keys
end
# Generates a module that sets up the boolean predicate 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 + "?"
return !!(respond_to?(method) and send(method))
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_by(&:to_s)
end
# Create a method that will determine if a provided list of
# features are satisfied by the curred provider.
@feature_module.send(:define_method, :satisfies?) do |*needed|
ret = true
needed.flatten.each do |feature|
unless feature?(feature)
ret = false
break
end
end
ret
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
(is_a?(Class) ? declared_feature?(name) : self.class.declared_feature?(name)) or feature.available?(self)
end
end
# Allow the provider to declare that it has a given feature.
@feature_module.send(:define_method, :has_features) do |*names|
@declared_features ||= []
names.each do |name|
@declared_features << name.intern
end
end
# Aaah, grammatical correctness
@feature_module.send(:alias_method, :has_feature, :has_features)
end
@feature_module
end
# @return [ProviderFeature] Returns a provider feature instance by name.
# @param name [String] the name of the feature to return
# @note Should only be used for testing.
# @api private
#
def provider_feature(name)
return nil unless defined?(@features)
@features[name]
end
end
|