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
|
require 'puppet/util/autoload'
require 'puppet/parser/scope'
module Puppet::Parser
module Functions
# A module for managing parser functions. Each specified function
# becomes an instance method on the Scope class.
class << self
include Puppet::Util
end
def self.autoloader
unless defined? @autoloader
@autoloader = Puppet::Util::Autoload.new(self,
"puppet/parser/functions",
:wrap => false
)
end
@autoloader
end
# Create a new function type.
def self.newfunction(name, options = {}, &block)
@functions ||= {}
name = symbolize(name)
if @functions.include? name
raise Puppet::DevError, "Function %s already defined" % name
end
# We want to use a separate, hidden module, because we don't want
# people to be able to call them directly.
unless defined? FCollection
eval("module FCollection; end")
end
ftype = options[:type] || :statement
unless ftype == :statement or ftype == :rvalue
raise Puppet::DevError, "Invalid statement type %s" % ftype.inspect
end
fname = "function_" + name.to_s
Puppet::Parser::Scope.send(:define_method, fname, &block)
# Someday we'll support specifying an arity, but for now, nope
#@functions[name] = {:arity => arity, :type => ftype}
@functions[name] = {:type => ftype, :name => fname}
if options[:doc]
@functions[name][:doc] = options[:doc]
end
end
# Determine if a given name is a function
def self.function(name)
name = symbolize(name)
unless @functions.include? name
autoloader.load(name)
end
if @functions.include? name
return @functions[name][:name]
else
return false
end
end
def self.functiondocs
autoloader.loadall
ret = ""
@functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash|
#ret += "%s\n%s\n" % [name, hash[:type]]
ret += "%s\n%s\n" % [name, "-" * name.to_s.length]
if hash[:doc]
ret += hash[:doc].gsub(/\n\s*/, ' ')
else
ret += "Undocumented.\n"
end
ret += "\n\n- **Type**: %s\n\n" % hash[:type]
end
return ret
end
def self.functions
@functions.keys
end
# Determine if a given function returns a value or not.
def self.rvalue?(name)
name = symbolize(name)
if @functions.include? name
case @functions[name][:type]
when :statement: return false
when :rvalue: return true
end
else
return false
end
end
# Include the specified classes
newfunction(:include, :doc => "Evaluate one or more classes.") do |vals|
vals = [vals] unless vals.is_a?(Array)
# The 'false' disables lazy evaluation.
klasses = compiler.evaluate_classes(vals, self, false)
missing = vals.find_all do |klass|
! klasses.include?(klass)
end
unless missing.empty?
# Throw an error if we didn't evaluate all of the classes.
str = "Could not find class"
if missing.length > 1
str += "es"
end
str += " " + missing.join(", ")
if n = namespaces and ! n.empty? and n != [""]
str += " in namespaces %s" % @namespaces.join(", ")
end
self.fail Puppet::ParseError, str
end
end
# Tag the current scope with each passed name
newfunction(:tag, :doc => "Add the specified tags to the containing class
or definition. All contained objects will then acquire that tag, also.
") do |vals|
self.resource.tag(*vals)
end
# Test whether a given tag is set. This functions as a big OR -- if any of the
# specified tags are unset, we return false.
newfunction(:tagged, :type => :rvalue, :doc => "A boolean function that
tells you whether the current container is tagged with the specified tags.
The tags are ANDed, so that all of the specified tags must be included for
the function to return true.") do |vals|
configtags = compiler.catalog.tags
resourcetags = resource.tags
retval = true
vals.each do |val|
unless configtags.include?(val) or resourcetags.include?(val)
retval = false
break
end
end
return retval
end
# Test whether a given class or definition is defined
newfunction(:defined, :type => :rvalue, :doc => "Determine whether a given
type is defined, either as a native type or a defined type, or whether a class is defined.
This is useful for checking whether a class is defined and only including it if it is.
This function can also test whether a resource has been defined, using resource references
(e.g., ``if defined(File['/tmp/myfile']) { ... }``). This function is unfortunately
dependent on the parse order of the configuration when testing whether a resource is defined.") do |vals|
result = false
vals.each do |val|
case val
when String:
# For some reason, it doesn't want me to return from here.
if Puppet::Type.type(val) or finddefine(val) or findclass(val)
result = true
break
end
when Puppet::Parser::Resource::Reference:
if findresource(val.to_s)
result = true
break
end
else
raise ArgumentError, "Invalid argument of type %s to 'defined'" % val.class
end
end
result
end
newfunction(:fail, :doc => "Fail with a parse error.") do |vals|
vals = vals.collect { |s| s.to_s }.join(" ") if vals.is_a? Array
raise Puppet::ParseError, vals.to_s
end
# Runs a newfunction to create a function for each of the log levels
Puppet::Util::Log.levels.each do |level|
newfunction(level, :doc => "Log a message on the server at level
#{level.to_s}.") do |vals|
send(level, vals.join(" "))
end
end
newfunction(:template, :type => :rvalue, :doc => "Evaluate a template and
return its value. See `the templating docs </trac/puppet/wiki/PuppetTemplating>`_
for more information. Note that if multiple templates are specified, their
output is all concatenated and returned as the output of the function.
") do |vals|
require 'erb'
vals.collect do |file|
# Use a wrapper, so the template can't get access to the full
# Scope object.
debug "Retrieving template %s" % file
wrapper = Puppet::Parser::TemplateWrapper.new(self, file)
begin
wrapper.result()
rescue => detail
raise Puppet::ParseError,
"Failed to parse template %s: %s" %
[file, detail]
end
end.join("")
end
# This is just syntactic sugar for a collection, although it will generally
# be a good bit faster.
newfunction(:realize, :doc => "Make a virtual object real. This is useful
when you want to know the name of the virtual object and don't want to
bother with a full collection. It is slightly faster than a collection,
and, of course, is a bit shorter. You must pass the object using a
reference; e.g.: ``realize User[luke]``." ) do |vals|
coll = Puppet::Parser::Collector.new(self, :nomatter, nil, nil, :virtual)
vals = [vals] unless vals.is_a?(Array)
coll.resources = vals
compiler.add_collection(coll)
end
newfunction(:search, :doc => "Add another namespace for this class to search.
This allows you to create classes with sets of definitions and add
those classes to another class's search path.") do |vals|
vals.each do |val|
add_namespace(val)
end
end
newfunction(:file, :type => :rvalue,
:doc => "Return the contents of a file. Multiple files
can be passed, and the first file that exists will be read in.") do |vals|
ret = nil
vals.each do |file|
unless file =~ /^#{File::SEPARATOR}/
raise Puppet::ParseError, "Files must be fully qualified"
end
if FileTest.exists?(file)
ret = File.read(file)
break
end
end
if ret
ret
else
raise Puppet::ParseError, "Could not find any files from %s" %
vals.join(", ")
end
end
newfunction(:generate, :type => :rvalue,
:doc => "Calls an external command and returns the results of the
command. Any arguments are passed to the external command as
arguments. If the generator does not exit with return code of 0,
the generator is considered to have failed and a parse error is
thrown. Generators can only have file separators, alphanumerics, dashes,
and periods in them. This function will attempt to protect you from
malicious generator calls (e.g., those with '..' in them), but it can
never be entirely safe. No subshell is used to execute
generators, so all shell metacharacters are passed directly to
the generator.") do |args|
unless args[0] =~ /^#{File::SEPARATOR}/
raise Puppet::ParseError, "Generators must be fully qualified"
end
unless args[0] =~ /^[-#{File::SEPARATOR}\w.]+$/
raise Puppet::ParseError,
"Generators can only contain alphanumerics, file separators, and dashes"
end
if args[0] =~ /\.\./
raise Puppet::ParseError,
"Can not use generators with '..' in them."
end
begin
output = Puppet::Util.execute(args)
rescue Puppet::ExecutionFailure => detail
raise Puppet::ParseError, "Failed to execute generator %s: %s" %
[args[0], detail]
end
output
end
end
end
|