1
require 'puppet/util/docs'
2
require 'puppet/indirector/envelope'
3
require 'puppet/indirector/request'
5
# The class that connects functional classes with their different collection
6
# back-ends. Each indirection has a set of associated terminus classes,
7
# each of which is a subclass of Puppet::Indirector::Terminus.
8
class Puppet::Indirector::Indirection
9
include Puppet::Util::Docs
13
# Clear all cached termini from all indirections.
15
@@indirections.each { |ind| ind.clear_cache }
18
# Find an indirection by name. This is provided so that Terminus classes
19
# can specifically hook up with the indirections they are associated with.
20
def self.instance(name)
21
@@indirections.find { |i| i.name == name }
24
# Return a list of all known indirections. Used to generate the
27
@@indirections.collect { |i| i.name }
30
# Find an indirected model by name. This is provided so that Terminus classes
31
# can specifically hook up with the indirections they are associated with.
33
return nil unless match = @@indirections.find { |i| i.name == name }
37
attr_accessor :name, :model
39
# Create and return our cache terminus.
41
raise(Puppet::DevError, "Tried to cache when no cache class was set") unless cache_class
45
# Should we use a cache?
47
cache_class ? true : false
50
attr_reader :cache_class
51
# Define a terminus class to be used for caching.
52
def cache_class=(class_name)
53
validate_terminus_class(class_name)
54
@cache_class = class_name
57
# Clear our cached list of termini, and reset the cache name
58
# so it's looked up again.
59
# This is only used for testing.
64
# This is only used for testing.
66
@@indirections.delete(self) if @@indirections.include?(self)
69
# Set the time-to-live for instances created through this indirection.
71
raise ArgumentError, "Indirection TTL must be an integer" unless value.is_a?(Fixnum)
75
# Default to the runinterval for the ttl.
78
@ttl = Puppet[:runinterval].to_i
83
# Calculate the expiration date for a returned instance.
88
# Generate the full doc string.
92
if defined? @doc and @doc
93
text += scrub(@doc) + "\n\n"
96
if s = terminus_setting()
97
text += "* **Terminus Setting**: %s" % terminus_setting
103
def initialize(model, name, options = {})
109
@terminus_class = nil
111
raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name }
112
@@indirections << self
114
if mod = options[:extend]
116
options.delete(:extend)
119
# This is currently only used for cache_class and terminus_class.
120
options.each do |name, value|
122
send(name.to_s + "=", value)
124
raise ArgumentError, "%s is not a valid Indirection parameter" % name
129
# Set up our request object.
130
def request(method, key, arguments = nil)
131
Puppet::Indirector::Request.new(self.name, method, key, arguments)
134
# Return the singleton terminus for this indirection.
135
def terminus(terminus_name = nil)
136
# Get the name of the terminus.
137
unless terminus_name ||= terminus_class
138
raise Puppet::DevError, "No terminus specified for %s; cannot redirect" % self.name
141
return @termini[terminus_name] ||= make_terminus(terminus_name)
144
# This can be used to select the terminus class.
145
attr_accessor :terminus_setting
147
# Determine the terminus class.
149
unless @terminus_class
150
if setting = self.terminus_setting
151
self.terminus_class = Puppet.settings[setting].to_sym
153
raise Puppet::DevError, "No terminus class nor terminus setting was provided for indirection %s" % self.name
159
# Specify the terminus class to use.
160
def terminus_class=(klass)
161
validate_terminus_class(klass)
162
@terminus_class = klass
165
# This is used by terminus_class= and cache=.
166
def validate_terminus_class(terminus_class)
167
unless terminus_class and terminus_class.to_s != ""
168
raise ArgumentError, "Invalid terminus name %s" % terminus_class.inspect
170
unless Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class)
171
raise ArgumentError, "Could not find terminus %s for indirection %s" % [terminus_class, self.name]
175
# Expire a cached object, if one is cached. Note that we don't actually
176
# remove it, we expire it and write it back out to disk. This way people
177
# can still use the expired object if they want.
178
def expire(key, *args)
179
request = request(:expire, key, *args)
181
return nil unless cache?
183
return nil unless instance = cache.find(request(:find, key, *args))
185
Puppet.info "Expiring the %s cache of %s" % [self.name, instance.name]
187
# Set an expiration date in the past
188
instance.expiration = Time.now - 60
190
cache.save(request(:save, instance, *args))
193
# Search for an instance in the appropriate terminus, caching the
194
# results if caching is configured..
196
request = request(:find, key, *args)
197
terminus = prepare(request)
199
# See if our instance is in the cache and up to date.
200
if cache? and cached = cache.find(request)
202
Puppet.info "Not using expired %s for %s from cache; expired at %s" % [self.name, request.key, cached.expiration]
204
Puppet.debug "Using cached %s for %s" % [self.name, request.key]
209
# Otherwise, return the result from the terminus, caching if appropriate.
210
if result = terminus.find(request)
211
result.expiration ||= self.expiration
213
Puppet.info "Caching %s for %s" % [self.name, request.key]
214
cache.save request(:save, result, *args)
223
# Remove something via the terminus.
224
def destroy(key, *args)
225
request = request(:destroy, key, *args)
226
terminus = prepare(request)
228
result = terminus.destroy(request)
230
if cache? and cached = cache.find(request(:find, key, *args))
231
# Reuse the existing request, since it's equivalent.
232
cache.destroy(request)
238
# Search for more than one instance. Should always return an array.
239
def search(key, *args)
240
request = request(:search, key, *args)
241
terminus = prepare(request)
243
if result = terminus.search(request)
244
raise Puppet::DevError, "Search results from terminus %s are not an array" % terminus.name unless result.is_a?(Array)
245
result.each do |instance|
246
instance.expiration ||= self.expiration
252
# Save the instance in the appropriate terminus. This method is
253
# normally an instance method on the indirected class.
254
def save(instance, *args)
255
request = request(:save, instance, *args)
256
terminus = prepare(request)
258
# If caching is enabled, save our document there
259
cache.save(request) if cache?
260
terminus.save(request)
265
# Check authorization if there's a hook available; fail if there is one
266
# and it returns false.
267
def check_authorization(request, terminus)
268
# At this point, we're assuming authorization makes no sense without
269
# client information.
270
return unless request.node
272
# This is only to authorize via a terminus-specific authorization hook.
273
return unless terminus.respond_to?(:authorized?)
275
unless terminus.authorized?(request)
276
raise ArgumentError, "Not authorized to call %s on %s with %s" % [request.method, request.key, request.options.inspect]
280
# Setup a request, pick the appropriate terminus, check the request's authorization, and return it.
283
if respond_to?(:select_terminus)
284
terminus_name = select_terminus(request)
286
terminus_name = terminus_class
289
check_authorization(request, terminus(terminus_name))
291
return terminus(terminus_name)
294
# Create a new terminus instance.
295
def make_terminus(terminus_class)
296
# Load our terminus class.
297
unless klass = Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class)
298
raise ArgumentError, "Could not find terminus %s for indirection %s" % [terminus_class, self.name]