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

« back to all changes in this revision

Viewing changes to lib/puppet/property.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
 # The virtual base class for properties, which are the self-contained building
 
2
# blocks for actually doing work on the system.
 
3
 
 
4
require 'puppet'
 
5
require 'puppet/parameter'
 
6
 
 
7
module Puppet
 
8
class Property < Puppet::Parameter
 
9
 
 
10
    # Because 'should' uses an array, we have a special method for handling
 
11
    # it.  We also want to keep copies of the original values, so that
 
12
    # they can be retrieved and compared later when merging.
 
13
    attr_reader :shouldorig
 
14
 
 
15
    attr_writer :noop
 
16
 
 
17
    class << self
 
18
        attr_accessor :unmanaged
 
19
        attr_reader :name
 
20
 
 
21
        # Return array matching info, defaulting to just matching
 
22
        # the first value.
 
23
        def array_matching
 
24
            unless defined?(@array_matching)
 
25
                @array_matching = :first
 
26
            end
 
27
            @array_matching
 
28
        end
 
29
 
 
30
        # Set whether properties should match all values or just the first one.
 
31
        def array_matching=(value)
 
32
            value = value.intern if value.is_a?(String)
 
33
            unless [:first, :all].include?(value)
 
34
                raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'"
 
35
            end
 
36
            @array_matching = value
 
37
        end
 
38
 
 
39
        def checkable
 
40
            @checkable = true
 
41
        end
 
42
 
 
43
        def uncheckable
 
44
            @checkable = false
 
45
        end
 
46
 
 
47
        def checkable?
 
48
            if defined? @checkable
 
49
                return @checkable
 
50
            else
 
51
                return true
 
52
            end
 
53
        end
 
54
    end
 
55
 
 
56
    # Look up a value's name, so we can find options and such.
 
57
    def self.value_name(value)
 
58
        if value != '' and name = symbolize(value) and @parametervalues.include?(name)
 
59
            return name
 
60
        elsif ary = self.match?(value)
 
61
            return ary[0]
 
62
        else
 
63
            return nil
 
64
        end
 
65
    end
 
66
 
 
67
    # Retrieve an option set when a value was defined.
 
68
    def self.value_option(name, option)
 
69
        option = option.to_sym
 
70
        if hash = @parameteroptions[name]
 
71
            hash[option]
 
72
        else
 
73
            nil
 
74
        end
 
75
    end
 
76
 
 
77
    # Create the value management variables.
 
78
    def self.initvars
 
79
        @parametervalues = {}
 
80
        @aliasvalues = {}
 
81
        @parameterregexes = {}
 
82
        @parameteroptions = {}
 
83
    end
 
84
 
 
85
    # Define a new valid value for a property.  You must provide the value itself,
 
86
    # usually as a symbol, or a regex to match the value.
 
87
    #
 
88
    # The first argument to the method is either the value itself or a regex.
 
89
    # The second argument is an option hash; valid options are:
 
90
    # * <tt>:event</tt>: The event that should be returned when this value is set.
 
91
    # * <tt>:call</tt>: When to call any associated block.  The default value
 
92
    #   is ``instead``, which means to call the value instead of calling the
 
93
    #   provider.  You can also specify ``before`` or ``after``, which will 
 
94
    #   call both the block and the provider, according to the order you specify
 
95
    #   (the ``first`` refers to when the block is called, not the provider).
 
96
    def self.newvalue(name, options = {}, &block)
 
97
        name = name.intern if name.is_a? String
 
98
 
 
99
        @parameteroptions[name] = {}
 
100
        paramopts = @parameteroptions[name]
 
101
 
 
102
        # Symbolize everything
 
103
        options.each do |opt, val|
 
104
            paramopts[symbolize(opt)] = symbolize(val)
 
105
        end
 
106
 
 
107
        # By default, call the block instead of the provider.
 
108
        if block_given?
 
109
            paramopts[:call] ||= :instead
 
110
        else
 
111
            paramopts[:call] ||= :none
 
112
        end
 
113
        # If there was no block given, we still want to store the information
 
114
        # for validation, but we won't be defining a method
 
115
        block ||= true
 
116
 
 
117
        case name
 
118
        when Symbol
 
119
            if @parametervalues.include?(name)
 
120
                Puppet.warning "%s reassigning value %s" % [self.name, name]
 
121
            end
 
122
            @parametervalues[name] = block
 
123
 
 
124
            if block_given?
 
125
                method = "set_" + name.to_s
 
126
                settor = paramopts[:settor] || (self.name.to_s + "=")
 
127
                define_method(method, &block)
 
128
                paramopts[:method] = method
 
129
            end
 
130
        when Regexp
 
131
            # The regexes are handled in parameter.rb.  This value is used
 
132
            # for validation.
 
133
            @parameterregexes[name] = block
 
134
 
 
135
            # This is used for looking up the block for execution.
 
136
            if block_given?
 
137
                paramopts[:block] = block
 
138
            end
 
139
        else
 
140
            raise ArgumentError, "Invalid value %s of type %s" %
 
141
                [name, name.class]
 
142
        end
 
143
    end
 
144
 
 
145
    # Call the provider method.
 
146
    def call_provider(value)
 
147
        begin
 
148
            provider.send(self.class.name.to_s + "=", value)
 
149
        rescue NoMethodError
 
150
            self.fail "The %s provider can not handle attribute %s" %
 
151
                [provider.class.name, self.class.name]
 
152
        end
 
153
    end
 
154
 
 
155
    # Call the dynamically-created method associated with our value, if
 
156
    # there is one.
 
157
    def call_valuemethod(name, value)
 
158
        event = nil
 
159
        if method = self.class.value_option(name, :method) and self.respond_to?(method)
 
160
            #self.debug "setting %s (currently %s)" % [value, self.retrieve]
 
161
 
 
162
            begin
 
163
                event = self.send(method)
 
164
            rescue Puppet::Error
 
165
                raise
 
166
            rescue => detail
 
167
                if Puppet[:trace]
 
168
                    puts detail.backtrace
 
169
                end
 
170
                error = Puppet::Error.new("Could not set %s on %s: %s" %
 
171
                    [value, self.class.name, detail], @resource.line, @resource.file)
 
172
                error.set_backtrace detail.backtrace
 
173
                raise error
 
174
            end
 
175
        elsif block = self.class.value_option(name, :block)
 
176
            # FIXME It'd be better here to define a method, so that
 
177
            # the blocks could return values.
 
178
            # If the regex was defined with no associated block, then just pass
 
179
            # through and the correct event will be passed back.
 
180
            event = self.instance_eval(&block)
 
181
        end
 
182
        return event, name
 
183
    end
 
184
 
 
185
    # How should a property change be printed as a string?
 
186
    def change_to_s(currentvalue, newvalue)
 
187
        begin
 
188
            if currentvalue == :absent
 
189
                return "defined '%s' as '%s'" %
 
190
                    [self.name, self.should_to_s(newvalue)]
 
191
            elsif newvalue == :absent or newvalue == [:absent]
 
192
                return "undefined %s from '%s'" %
 
193
                    [self.name, self.is_to_s(currentvalue)]
 
194
            else
 
195
                return "%s changed '%s' to '%s'" %
 
196
                    [self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)]
 
197
            end
 
198
        rescue Puppet::Error, Puppet::DevError
 
199
            raise
 
200
        rescue => detail
 
201
            raise Puppet::DevError, "Could not convert change %s to string: %s" %
 
202
                [self.name, detail]
 
203
        end
 
204
    end
 
205
 
 
206
    # Figure out which event to return.
 
207
    def event(name, event = nil)
 
208
        if value_event = self.class.value_option(name, :event)
 
209
            return value_event 
 
210
        end
 
211
 
 
212
        if event and event.is_a?(Symbol)
 
213
            if event == :nochange
 
214
                return nil
 
215
            else
 
216
                return event
 
217
            end
 
218
        end
 
219
 
 
220
        if self.class.name == :ensure
 
221
            event = case self.should
 
222
            when :present: (@resource.class.name.to_s + "_created").intern
 
223
            when :absent: (@resource.class.name.to_s + "_removed").intern
 
224
            else
 
225
                (@resource.class.name.to_s + "_changed").intern
 
226
            end
 
227
        else
 
228
            event = (@resource.class.name.to_s + "_changed").intern
 
229
        end
 
230
 
 
231
        return event
 
232
    end
 
233
    
 
234
    # initialize our property
 
235
    def initialize(hash = {})
 
236
        super
 
237
    end
 
238
 
 
239
    def inspect
 
240
        str = "Property('%s', " % self.name
 
241
 
 
242
        if defined? @should and @should
 
243
            str += "@should = '%s')" % @should.join(", ")
 
244
        else
 
245
            str += "@should = nil)"
 
246
        end
 
247
    end
 
248
 
 
249
    # Determine whether the property is in-sync or not.  If @should is
 
250
    # not defined or is set to a non-true value, then we do not have
 
251
    # a valid value for it and thus consider the property to be in-sync
 
252
    # since we cannot fix it.  Otherwise, we expect our should value
 
253
    # to be an array, and if @is matches any of those values, then
 
254
    # we consider it to be in-sync.
 
255
    def insync?(is)
 
256
        #debug "%s value is '%s', should be '%s'" %
 
257
        #    [self,self.is.inspect,self.should.inspect]
 
258
        unless defined? @should and @should
 
259
            return true
 
260
        end
 
261
 
 
262
        unless @should.is_a?(Array)
 
263
            self.devfail "%s's should is not array" % self.class.name
 
264
        end
 
265
 
 
266
        # an empty array is analogous to no should values
 
267
        if @should.empty?
 
268
            return true
 
269
        end
 
270
 
 
271
        # Look for a matching value
 
272
        if match_all?
 
273
            return (is == @should or is == @should.collect { |v| v.to_s })
 
274
        else
 
275
            @should.each { |val|
 
276
                if is == val or is == val.to_s
 
277
                    return true
 
278
                end
 
279
            }
 
280
        end
 
281
 
 
282
        # otherwise, return false
 
283
        return false
 
284
    end
 
285
 
 
286
    # because the @should and @is vars might be in weird formats,
 
287
    # we need to set up a mechanism for pretty printing of the values
 
288
    # default to just the values, but this way individual properties can
 
289
    # override these methods
 
290
    def is_to_s(currentvalue)
 
291
        currentvalue
 
292
    end
 
293
 
 
294
    # Send a log message.
 
295
    def log(msg)
 
296
        unless @resource[:loglevel]
 
297
            self.devfail "Parent %s has no loglevel" % @resource.name
 
298
        end
 
299
        Puppet::Util::Log.create(
 
300
            :level => @resource[:loglevel],
 
301
            :message => msg,
 
302
            :source => self
 
303
        )
 
304
    end
 
305
 
 
306
    # Should we match all values, or just the first?
 
307
    def match_all?
 
308
        self.class.array_matching == :all
 
309
    end
 
310
 
 
311
    # each property class must define the name() method, and property instances
 
312
    # do not change that name
 
313
    # this implicitly means that a given object can only have one property
 
314
    # instance of a given property class
 
315
    def name
 
316
        return self.class.name
 
317
    end
 
318
 
 
319
    # for testing whether we should actually do anything
 
320
    def noop
 
321
        # This is only here to make testing easier.
 
322
        if @resource.respond_to?(:noop?)
 
323
            @resource.noop?
 
324
        else
 
325
            if defined?(@noop)
 
326
                @noop
 
327
            else
 
328
                Puppet[:noop]
 
329
            end
 
330
        end
 
331
    end
 
332
 
 
333
    # By default, call the method associated with the property name on our
 
334
    # provider.  In other words, if the property name is 'gid', we'll call
 
335
    # 'provider.gid' to retrieve the current value.
 
336
    def retrieve
 
337
        provider.send(self.class.name)
 
338
    end
 
339
 
 
340
    # Set our value, using the provider, an associated block, or both.
 
341
    def set(value)
 
342
        # Set a name for looking up associated options like the event.
 
343
        name = self.class.value_name(value)
 
344
 
 
345
        call = self.class.value_option(name, :call)
 
346
 
 
347
        # If we're supposed to call the block first or instead, call it now
 
348
        if call == :before or call == :instead
 
349
            event, tmp = call_valuemethod(name, value) 
 
350
        end
 
351
        unless call == :instead
 
352
            if @resource.provider
 
353
                call_provider(value)
 
354
            else
 
355
                # They haven't provided a block, and our parent does not have
 
356
                # a provider, so we have no idea how to handle this.
 
357
                self.fail "%s cannot handle values of type %s" %
 
358
                    [self.class.name, value.inspect]
 
359
            end
 
360
        end
 
361
        if call == :after
 
362
            event, tmp = call_valuemethod(name, value) 
 
363
        end
 
364
 
 
365
        return event(name, event)
 
366
    end
 
367
 
 
368
    # Only return the first value
 
369
    def should
 
370
        if defined? @should
 
371
            unless @should.is_a?(Array)
 
372
                self.devfail "should for %s on %s is not an array" %
 
373
                    [self.class.name, @resource.name]
 
374
            end
 
375
            if match_all?
 
376
                return @should
 
377
            else
 
378
                return @should[0]
 
379
            end
 
380
        else
 
381
            return nil
 
382
        end
 
383
    end
 
384
 
 
385
    # Set the should value.
 
386
    def should=(values)
 
387
        unless values.is_a?(Array)
 
388
            values = [values]
 
389
        end
 
390
 
 
391
        @shouldorig = values
 
392
 
 
393
        if self.respond_to?(:validate)
 
394
            values.each { |val|
 
395
                validate(val)
 
396
            }
 
397
        end
 
398
        if self.respond_to?(:munge)
 
399
            @should = values.collect { |val|
 
400
                self.munge(val)
 
401
            }
 
402
        else
 
403
            @should = values
 
404
        end
 
405
    end
 
406
 
 
407
    def should_to_s(newvalue)
 
408
        newvalue = [newvalue] unless newvalue.is_a? Array
 
409
        if defined? newvalue
 
410
            newvalue.join(" ")
 
411
        else
 
412
            return nil
 
413
        end
 
414
    end
 
415
 
 
416
    # The default 'sync' method only selects among a list of registered # values.
 
417
    def sync
 
418
        self.devfail("No values defined for %s" % self.class.name) unless self.class.values
 
419
 
 
420
        if value = self.should
 
421
            set(value)
 
422
        else
 
423
            self.devfail "Got a nil value for should"
 
424
        end
 
425
    end
 
426
 
 
427
    # The properties need to return tags so that logs correctly collect them.
 
428
    def tags
 
429
        unless defined? @tags
 
430
            @tags = []
 
431
            # This might not be true in testing
 
432
            if @resource.respond_to? :tags
 
433
                @tags = @resource.tags
 
434
            end
 
435
            @tags << self.name
 
436
        end
 
437
        @tags
 
438
    end
 
439
 
 
440
    def to_s
 
441
        return "%s(%s)" % [@resource.name,self.name]
 
442
    end
 
443
 
 
444
    # Provide a common hook for setting @should, just like params.
 
445
    def value=(value)
 
446
        self.should = value
 
447
    end
 
448
 
 
449
    # This property will get automatically added to any type that responds
 
450
    # to the methods 'exists?', 'create', and 'destroy'.
 
451
    class Ensure < Puppet::Property
 
452
        @name = :ensure
 
453
 
 
454
        def self.defaultvalues
 
455
            newvalue(:present) do
 
456
                if @resource.provider and @resource.provider.respond_to?(:create)
 
457
                    @resource.provider.create
 
458
                else
 
459
                    @resource.create
 
460
                end
 
461
                nil # return nil so the event is autogenerated
 
462
            end
 
463
 
 
464
            newvalue(:absent) do
 
465
                if @resource.provider and @resource.provider.respond_to?(:destroy)
 
466
                    @resource.provider.destroy
 
467
                else
 
468
                    @resource.destroy
 
469
                end
 
470
                nil # return nil so the event is autogenerated
 
471
            end
 
472
 
 
473
            defaultto do
 
474
                if @resource.managed?
 
475
                    :present
 
476
                else
 
477
                    nil
 
478
                end
 
479
            end
 
480
 
 
481
            # This doc will probably get overridden
 
482
            @doc ||= "The basic property that the object should be in."
 
483
        end
 
484
 
 
485
        def self.inherited(sub)
 
486
            # Add in the two properties that everyone will have.
 
487
            sub.class_eval do
 
488
            end
 
489
        end
 
490
 
 
491
        def change_to_s(currentvalue, newvalue)
 
492
            begin
 
493
                if currentvalue == :absent or currentvalue.nil?
 
494
                    return "created"
 
495
                elsif newvalue == :absent
 
496
                    return "removed"
 
497
                else
 
498
                    return "%s changed '%s' to '%s'" %
 
499
                        [self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)]
 
500
                end
 
501
            rescue Puppet::Error, Puppet::DevError
 
502
                raise
 
503
            rescue => detail
 
504
                raise Puppet::DevError, "Could not convert change %s to string: %s" %
 
505
                    [self.name, detail]
 
506
            end
 
507
        end
 
508
 
 
509
        def retrieve
 
510
            # XXX This is a problem -- whether the object exists or not often
 
511
            # depends on the results of other properties, yet we're the first property
 
512
            # to get checked, which means that those other properties do not have
 
513
            # @is values set.  This seems to be the source of quite a few bugs,
 
514
            # although they're mostly logging bugs, not functional ones.
 
515
            if prov = @resource.provider and prov.respond_to?(:exists?)
 
516
                result = prov.exists?
 
517
            elsif @resource.respond_to?(:exists?)
 
518
                result = @resource.exists?
 
519
            else
 
520
                raise Puppet::DevError, "No ability to determine if %s exists" %
 
521
                    @resource.class.name
 
522
            end
 
523
            if result
 
524
                return :present
 
525
            else
 
526
                return :absent
 
527
            end
 
528
        end
 
529
 
 
530
        # If they're talking about the thing at all, they generally want to
 
531
        # say it should exist.
 
532
        #defaultto :present
 
533
        defaultto do
 
534
            if @resource.managed?
 
535
                :present
 
536
            else
 
537
                nil
 
538
            end
 
539
        end
 
540
    end
 
541
end
 
542
end
 
543