summaryrefslogtreecommitdiffstats
path: root/lib/puppet/parameter.rb
blob: 1dc00130afd14bb5350e8ed33f71aa0978fdfdcf (plain)
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
module Puppet
    class Parameter < Puppet::Element
        class << self
            attr_reader :validater, :munger, :name, :default
            attr_accessor :ismetaparameter, :element

            # Define the default value for a given parameter or parameter.  This
            # means that 'nil' is an invalid default value.  This defines
            # the 'default' instance method.
            def defaultto(value = nil, &block)
                if block
                    define_method(:default, &block)
                else
                    if value.nil?
                        raise Puppet::DevError,
                            "Either a default value or block must be provided"
                    end
                    define_method(:default) do value end
                end
            end

            # Return a documentation string.  If there are valid values,
            # then tack them onto the string.
            def doc
                @doc ||= ""

                unless defined? @addeddocvals
                    unless values.empty?
                        if @aliasvalues.empty?
                            @doc += "  Valid values are ``" +
                                values.join("``, ``") + "``."
                        else
                            @doc += "  Valid values are "

                            @doc += values.collect do |value|
                                ary = @aliasvalues.find do |name, val|
                                    val == value
                                end
                                if ary
                                    "``%s`` (also called ``%s``)" % [value, ary[0]]
                                else
                                    "``#{value}``"
                                end
                            end.join(", ") + "."
                        end
                    end

                    if defined? @parameterregexes and ! @parameterregexes.empty?
                        regs = @parameterregexes
                        if @parameterregexes.is_a? Hash
                            regs = @parameterregexes.keys
                        end
                        unless regs.empty?
                            @doc += "  Values can also match ``" +
                                regs.join("``, ``") + "``."
                        end
                    end
                    @addeddocvals = true
                end

                @doc
            end

            def nodefault
                if public_method_defined? :default
                    undef_method :default
                end
            end

            # Store documentation for this parameter.
            def desc(str)
                @doc = str
            end

            def initvars
                @parametervalues = []
                @aliasvalues = {}
                @parameterregexes = []
            end

            # This is how we munge the value.  Basically, this is our
            # opportunity to convert the value from one form into another.
            def munge(&block)
                # I need to wrap the unsafe version in begin/rescue parameterments,
                # but if I directly call the block then it gets bound to the
                # class's context, not the instance's, thus the two methods,
                # instead of just one.
                define_method(:unsafe_munge, &block)

                define_method(:munge) do |*args|
                    begin
                        unsafe_munge(*args)
                    rescue Puppet::Error => detail
                        Puppet.debug "Reraising %s" % detail
                        raise
                    rescue => detail
                        raise Puppet::DevError, "Munging failed for value %s in class %s: %s" %
                            [args.inspect, self.name, detail], detail.backtrace
                    end
                end
                #@munger = block
            end

            # Mark whether we're the namevar.
            def isnamevar
                @isnamevar = true
                @required = true
            end

            # Is this parameter the namevar?  Defaults to false.
            def isnamevar?
                if defined? @isnamevar
                    return @isnamevar
                else
                    return false
                end
            end

            # This parameter is required.
            def isrequired
                @required = true
            end

            # Is this parameter required?  Defaults to false.
            def required?
                if defined? @required
                    return @required
                else
                    return false
                end
            end

            # Verify that we got a good value
            def validate(&block)
                #@validater = block
                define_method(:unsafe_validate, &block)

                define_method(:validate) do |*args|
                    begin
                        unsafe_validate(*args)
                    rescue ArgumentError, Puppet::Error, TypeError
                        raise
                    rescue => detail
                        raise Puppet::DevError,
                            "Validate method failed for class %s: %s" %
                            [self.name, detail], detail.backtrace
                    end
                end
            end

            # Does the value match any of our regexes?
            def match?(value)
                value = value.to_s unless value.is_a? String
                @parameterregexes.find { |r|
                    r = r[0] if r.is_a? Array # States use a hash here
                    r =~ value
                }
            end

            # Define a new value for our parameter.
            def newvalues(*names)
                names.each { |name|
                    name = name.intern if name.is_a? String

                    case name
                    when Symbol
                        if @parametervalues.include?(name)
                            Puppet.warning "%s already has a value for %s" %
                                [name, name]
                        end
                        @parametervalues << name
                    when Regexp
                        if @parameterregexes.include?(name)
                            Puppet.warning "%s already has a value for %s" %
                                [name, name]
                        end
                        @parameterregexes << name
                    else
                        raise ArgumentError, "Invalid value %s of type %s" %
                            [name, name.class]
                    end
                }
            end

            def aliasvalue(name, other)
                unless @parametervalues.include?(other)
                    raise Puppet::DevError,
                        "Cannot alias nonexistent value %s" % other
                end

                @aliasvalues[name] = other
            end

            def alias(name)
                @aliasvalues[name]
            end

            def regexes
                return @parameterregexes.dup
            end

            # Return the list of valid values.
            def values
                #[@aliasvalues.keys, @parametervalues.keys].flatten
                if @parametervalues.is_a? Array
                    return @parametervalues.dup
                elsif @parametervalues.is_a? Hash
                    return @parametervalues.keys
                else
                    return []
                end
            end
        end

        # Just a simple method to proxy instance methods to class methods
        def self.proxymethods(*values)
            values.each { |val|
                define_method(val) do
                    self.class.send(val)
                end
            }
        end

        # And then define one of these proxies for each method in our
        # ParamHandler class.
        proxymethods("required?", "isnamevar?")

        attr_accessor :parent

        def devfail(msg)
            self.fail(Puppet::DevError, msg)
        end

        def fail(*args)
            type = nil
            if args[0].is_a?(Class)
                type = args.shift
            else
                type = Puppet::Error
            end

            error = type.new(args.join(" "))

            if defined? @parent and @parent and @parent.line
                error.line = @parent.line
            end

            if defined? @parent and @parent and @parent.file
                error.file = @parent.file
            end

            raise error
        end

        # Log a message using the parent's log level.
        def log(msg)
            unless @parent[:loglevel]
                p @parent
                self.devfail "Parent %s has no loglevel" %
                    @parent.name
            end
            Puppet::Log.create(
                :level => @parent[:loglevel],
                :message => msg,
                :source => self
            )
        end

        # each parameter class must define the name() method, and parameter
        # instances do not change that name this implicitly means that a given
        # object can only have one parameter instance of a given parameter
        # class
        def name
            return self.class.name
        end

        # for testing whether we should actually do anything
        def noop
            unless defined? @noop
                @noop = false
            end
            tmp = @noop || self.parent.noop || Puppet[:noop] || false
            #debug "noop is %s" % tmp
            return tmp
        end

        # return the full path to us, for logging and rollback; not currently
        # used
        def path
            return [@parent.path, self.name].join("/")
        end

        # If the specified value is allowed, then munge appropriately.
        munge do |value|
            if self.class.values.empty? and self.class.regexes.empty?
                # This parameter isn't using defined values to do its work.
                return value
            end

            # We convert to a string and then a symbol so that things like
            # booleans work as we expect.
            intern = value.to_s.intern

            # If it's a valid value, always return it as a symbol.
            if self.class.values.include?(intern)
                retval = intern
            elsif other = self.class.alias(intern)
                retval = other
            elsif ary = self.class.match?(value)
                retval = value
            else
                # If it passed the validation but is not a registered value,
                # we just return it as is.
                retval = value
            end

            retval
        end

        # Verify that the passed value is valid.
        validate do |value|
            vals = self.class.values
            regs = self.class.regexes

            if regs.is_a? Hash # this is true on states
                regs = regs.keys
            end
            if vals.empty? and regs.empty?
                # This parameter isn't using defined values to do its work.
                return 
            end
            newval = value
            unless value.is_a?(Symbol)
                newval = value.to_s.intern
            end

            unless vals.include?(newval) or
                self.class.alias(newval) or
                self.class.match?(value) # We match the string, not the symbol
                    str = "Invalid '%s' value %s. " %
                        [self.class.name, value.inspect]

                    unless vals.empty?
                        str += "Valid values are %s. " % vals.join(", ")
                    end

                    unless regs.empty?
                        str += "Valid values match %s." % regs.collect { |r|
                            r.to_s
                        }.join(", ")
                    end

                    raise ArgumentError, str
            end
        end

        def remove
            @parent = nil
        end

        # This should only be called for parameters, but go ahead and make
        # it possible to call for states, too.
        def value
            if self.is_a?(Puppet::State)
                # We should return the 'is' value if there's not 'should'
                # value.  This might be bad, though, because the 'should'
                # method knows whether to return an array or not and that info
                # is not exposed, and the 'is' value could be a symbol.  I
                # can't seem to create a test in which this is a problem, but
                # that doesn't mean it's not one.
                if self.should
                    return self.should
                else
                    return self.is
                end
            else
                if defined? @value
                    return @value
                else
                    return nil
                end
            end
        end

        # Store the value provided.  All of the checking should possibly be
        # late-binding (e.g., users might not exist when the value is assigned
        # but might when it is asked for).
        def value=(value)
            # If we're a parameter, just hand the processing off to the should
            # method.
            if self.is_a?(Puppet::State)
                return self.should = value
            end
            if respond_to?(:validate)
                validate(value)
            end

            if respond_to?(:munge)
                value = munge(value)
            end
            @value = value
        end

        def inspect
            s = "Parameter(%s = %s" % [self.name, self.value || "nil"]
            if defined? @parent
                s += ", @parent = %s)" % @parent
            else
                s += ")"
            end
        end

        def to_s
            s = "Parameter(%s)" % self.name
        end
    end
end

# $Id$