~ubuntu-branches/ubuntu/oneiric/puppet/oneiric-security

« back to all changes in this revision

Viewing changes to lib/puppet/indirector/indirection.rb

  • Committer: Bazaar Package Importer
  • Author(s): Micah Anderson
  • Date: 2008-07-26 15:43:45 UTC
  • mto: (3.1.1 lenny) (1.3.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20080726154345-1fmgo76b4l72ulvc
ImportĀ upstreamĀ versionĀ 0.24.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'puppet/util/docs'
 
2
require 'puppet/indirector/envelope'
 
3
require 'puppet/indirector/request'
 
4
 
 
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
 
10
 
 
11
    @@indirections = []
 
12
 
 
13
    # Clear all cached termini from all indirections.
 
14
    def self.clear_cache
 
15
        @@indirections.each { |ind| ind.clear_cache }
 
16
    end
 
17
 
 
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 }
 
22
    end
 
23
 
 
24
    # Return a list of all known indirections.  Used to generate the
 
25
    # reference.
 
26
    def self.instances
 
27
        @@indirections.collect { |i| i.name }
 
28
    end
 
29
    
 
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.
 
32
    def self.model(name)
 
33
        return nil unless match = @@indirections.find { |i| i.name == name }
 
34
        match.model
 
35
    end
 
36
    
 
37
    attr_accessor :name, :model
 
38
 
 
39
    # Create and return our cache terminus.
 
40
    def cache
 
41
        raise(Puppet::DevError, "Tried to cache when no cache class was set") unless cache_class
 
42
        terminus(cache_class)
 
43
    end
 
44
 
 
45
    # Should we use a cache?
 
46
    def cache?
 
47
        cache_class ? true : false
 
48
    end
 
49
 
 
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
 
55
    end
 
56
 
 
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.
 
60
    def clear_cache
 
61
        @termini.clear
 
62
    end
 
63
 
 
64
    # This is only used for testing.
 
65
    def delete
 
66
        @@indirections.delete(self) if @@indirections.include?(self)
 
67
    end
 
68
 
 
69
    # Set the time-to-live for instances created through this indirection.
 
70
    def ttl=(value)
 
71
        raise ArgumentError, "Indirection TTL must be an integer" unless value.is_a?(Fixnum)
 
72
        @ttl = value
 
73
    end
 
74
 
 
75
    # Default to the runinterval for the ttl.
 
76
    def ttl
 
77
        unless defined?(@ttl)
 
78
            @ttl = Puppet[:runinterval].to_i
 
79
        end
 
80
        @ttl
 
81
    end
 
82
 
 
83
    # Calculate the expiration date for a returned instance.
 
84
    def expiration
 
85
        Time.now + ttl
 
86
    end
 
87
 
 
88
    # Generate the full doc string.
 
89
    def doc
 
90
        text = ""
 
91
 
 
92
        if defined? @doc and @doc
 
93
            text += scrub(@doc) + "\n\n"
 
94
        end
 
95
 
 
96
        if s = terminus_setting()
 
97
            text += "* **Terminus Setting**: %s" % terminus_setting
 
98
        end
 
99
 
 
100
        text
 
101
    end
 
102
 
 
103
    def initialize(model, name, options = {})
 
104
        @model = model
 
105
        @name = name
 
106
 
 
107
        @termini = {}
 
108
        @cache_class = nil
 
109
        @terminus_class = nil
 
110
 
 
111
        raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name }
 
112
        @@indirections << self
 
113
 
 
114
        if mod = options[:extend]
 
115
            extend(mod)
 
116
            options.delete(:extend)
 
117
        end
 
118
 
 
119
        # This is currently only used for cache_class and terminus_class.
 
120
        options.each do |name, value|
 
121
            begin
 
122
                send(name.to_s + "=", value)
 
123
            rescue NoMethodError
 
124
                raise ArgumentError, "%s is not a valid Indirection parameter" % name
 
125
            end
 
126
        end
 
127
    end
 
128
 
 
129
    # Set up our request object.
 
130
    def request(method, key, arguments = nil)
 
131
        Puppet::Indirector::Request.new(self.name, method, key, arguments)
 
132
    end
 
133
 
 
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
 
139
        end
 
140
        
 
141
        return @termini[terminus_name] ||= make_terminus(terminus_name)
 
142
    end
 
143
 
 
144
    # This can be used to select the terminus class.
 
145
    attr_accessor :terminus_setting
 
146
 
 
147
    # Determine the terminus class.
 
148
    def terminus_class
 
149
        unless @terminus_class
 
150
            if setting = self.terminus_setting
 
151
                self.terminus_class = Puppet.settings[setting].to_sym
 
152
            else
 
153
                raise Puppet::DevError, "No terminus class nor terminus setting was provided for indirection %s" % self.name
 
154
            end
 
155
        end
 
156
        @terminus_class
 
157
    end
 
158
 
 
159
    # Specify the terminus class to use.
 
160
    def terminus_class=(klass)
 
161
        validate_terminus_class(klass)
 
162
        @terminus_class = klass
 
163
    end
 
164
 
 
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
 
169
        end
 
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]
 
172
        end
 
173
    end
 
174
 
 
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)
 
180
 
 
181
        return nil unless cache?
 
182
 
 
183
        return nil unless instance = cache.find(request(:find, key, *args))
 
184
 
 
185
        Puppet.info "Expiring the %s cache of %s" % [self.name, instance.name]
 
186
 
 
187
        # Set an expiration date in the past
 
188
        instance.expiration = Time.now - 60
 
189
 
 
190
        cache.save(request(:save, instance, *args))
 
191
    end
 
192
 
 
193
    # Search for an instance in the appropriate terminus, caching the
 
194
    # results if caching is configured..
 
195
    def find(key, *args)
 
196
        request = request(:find, key, *args)
 
197
        terminus = prepare(request)
 
198
 
 
199
        # See if our instance is in the cache and up to date.
 
200
        if cache? and cached = cache.find(request)
 
201
            if cached.expired?
 
202
                Puppet.info "Not using expired %s for %s from cache; expired at %s" % [self.name, request.key, cached.expiration]
 
203
            else
 
204
                Puppet.debug "Using cached %s for %s" % [self.name, request.key]
 
205
                return cached
 
206
            end
 
207
        end
 
208
 
 
209
        # Otherwise, return the result from the terminus, caching if appropriate.
 
210
        if result = terminus.find(request)
 
211
            result.expiration ||= self.expiration
 
212
            if cache?
 
213
                Puppet.info "Caching %s for %s" % [self.name, request.key]
 
214
                cache.save request(:save, result, *args)
 
215
            end
 
216
 
 
217
            return result
 
218
        end
 
219
 
 
220
        return nil
 
221
    end
 
222
 
 
223
    # Remove something via the terminus.
 
224
    def destroy(key, *args)
 
225
        request = request(:destroy, key, *args)
 
226
        terminus = prepare(request)
 
227
 
 
228
        result = terminus.destroy(request)
 
229
 
 
230
        if cache? and cached = cache.find(request(:find, key, *args))
 
231
            # Reuse the existing request, since it's equivalent.
 
232
            cache.destroy(request)
 
233
        end
 
234
 
 
235
        result
 
236
    end
 
237
 
 
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)
 
242
 
 
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
 
247
            end
 
248
            return result
 
249
        end
 
250
    end
 
251
 
 
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)
 
257
 
 
258
        # If caching is enabled, save our document there
 
259
        cache.save(request) if cache?
 
260
        terminus.save(request)
 
261
    end
 
262
 
 
263
    private
 
264
 
 
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
 
271
 
 
272
        # This is only to authorize via a terminus-specific authorization hook.
 
273
        return unless terminus.respond_to?(:authorized?)
 
274
 
 
275
        unless terminus.authorized?(request)
 
276
            raise ArgumentError, "Not authorized to call %s on %s with %s" % [request.method, request.key, request.options.inspect]
 
277
        end
 
278
    end
 
279
 
 
280
    # Setup a request, pick the appropriate terminus, check the request's authorization, and return it.
 
281
    def prepare(request)
 
282
        # Pick our terminus.
 
283
        if respond_to?(:select_terminus)
 
284
            terminus_name = select_terminus(request)
 
285
        else
 
286
            terminus_name = terminus_class
 
287
        end
 
288
 
 
289
        check_authorization(request, terminus(terminus_name))
 
290
 
 
291
        return terminus(terminus_name)
 
292
    end
 
293
 
 
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]
 
299
        end
 
300
        return klass.new
 
301
    end
 
302
end