jobmanager 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/FAQ.txt +151 -0
  2. data/History.txt +2 -0
  3. data/LICENSE +20 -0
  4. data/Manifest.txt +42 -0
  5. data/README.txt +395 -0
  6. data/Rakefile +17 -0
  7. data/bin/jobmanager +20 -0
  8. data/examples/email_settings.rb +14 -0
  9. data/examples/example.rb +23 -0
  10. data/examples/jobmanager.yaml +66 -0
  11. data/examples/mysql_backup.rb +18 -0
  12. data/lib/jobmanager.rb +1 -0
  13. data/lib/jobmanager/application.rb +455 -0
  14. data/lib/jobmanager/applicationconfig.rb +89 -0
  15. data/lib/jobmanager/applicationlogger.rb +306 -0
  16. data/lib/jobmanager/applicationoptionparser.rb +163 -0
  17. data/lib/jobmanager/system.rb +240 -0
  18. data/lib/jobmanager/teestream.rb +78 -0
  19. data/lib/jobmanager/util.rb +25 -0
  20. data/test/configs/all_values.yaml +33 -0
  21. data/test/configs/bad_email_condition.yaml +7 -0
  22. data/test/configs/bad_no_central_log_file.yaml +7 -0
  23. data/test/configs/bad_number_of_job_logs.yaml +7 -0
  24. data/test/configs/bad_timeout_zero.yaml +7 -0
  25. data/test/configs/email_settings.rb +16 -0
  26. data/test/configs/incomplete_1.yaml +23 -0
  27. data/test/configs/jobmanager_1.yaml +9 -0
  28. data/test/configs/jobmanager_2.yaml +11 -0
  29. data/test/configs/jobmanager_3.yaml +13 -0
  30. data/test/configs/jobmanager_4_bad_central_log_file.yaml +9 -0
  31. data/test/configs/jobmanager_4_bad_email_settings_file.yaml +9 -0
  32. data/test/configs/jobmanager_4_bad_job_logs_directory_1.yaml +9 -0
  33. data/test/configs/jobmanager_4_bad_job_logs_directory_2.yaml +9 -0
  34. data/test/configs/minimum_plus_email_settings.yaml +9 -0
  35. data/test/configs/minimum_required.yaml +5 -0
  36. data/test/helpers.rb +7 -0
  37. data/test/mock_syslog.rb +68 -0
  38. data/test/test_applicationlogger.rb +145 -0
  39. data/test/test_config.rb +208 -0
  40. data/test/test_jobmanager.rb +443 -0
  41. data/test/test_system.rb +206 -0
  42. data/test/test_teestream.rb +55 -0
  43. metadata +172 -0
@@ -0,0 +1,17 @@
1
+ # -*- ruby -*-
2
+ require 'rubygems'
3
+ require 'hoe'
4
+
5
+ Hoe.new('jobmanager', "1.0.0") do |p|
6
+ p.remote_rdoc_dir = ''
7
+ p.developer('DesigningPatterns', 'technical.inquiries@designingpatterns.com')
8
+
9
+ p.extra_deps << ['simpleemail']
10
+ p.extra_deps << ['configtoolkit', '>= 2.2']
11
+ p.extra_deps << ['relative']
12
+ p.extra_deps << ['logrotate']
13
+ p.extra_deps << ['SyslogLogger']
14
+ p.extra_deps << ['sys-host']
15
+ end
16
+
17
+ # vim: syntax=Ruby
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'relative'
5
+ require 'jobmanager'
6
+
7
+ PROGRAM_NAME = File.basename($0)
8
+
9
+ begin
10
+ env_config_file = ENV["JOBMANAGER_CONFIG_FILE"]
11
+ config_file = env_config_file ? env_config_file : "/etc/jobmanager.yaml"
12
+
13
+ jobmanager = JobManager::Application.new(PROGRAM_NAME, ARGV, config_file)
14
+ jobmanager.run()
15
+ rescue => error
16
+ # All relevant errors have already been printed.
17
+ exit(1)
18
+ end
19
+
20
+
@@ -0,0 +1,14 @@
1
+ ActionMailer::Base.delivery_method = :smtp
2
+
3
+ ActionMailer::Base.smtp_settings = {
4
+ :address => 'smtp.gmail.com',
5
+ :port => 587,
6
+ :domain => "gmail.com",
7
+ :authentication => "login",
8
+ :user_name => "sender_address@gmail.com",
9
+ :password => "password",
10
+ :tls => :auto
11
+ }
12
+
13
+ SimpleEmail::Email.default_from = "Sender Address <sender_address@gmail.com>"
14
+ SimpleEmail::Email.default_to = "Receiver Address <receiver_address@gmail.com>"
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # These example programs use the 'relative' gem in order to express
5
+ # paths relative to __FILE__ cleanly, allowing them to be run from any
6
+ # directory. In particular, they use
7
+ # File.expand_path_relative_to_caller in order to refer to
8
+ # configuration files within the examples directory.
9
+ #
10
+ require 'rubygems'
11
+ require 'relative'
12
+
13
+ CONFIGURATION_FILE = File.expand_path_relative_to_caller("jobmanager.yaml")
14
+
15
+ ENV["JOBMANAGER_CONFIG_FILE"] = CONFIGURATION_FILE
16
+
17
+ command = "jobmanager --job_name ls_usr \"ls /usr\""
18
+ print "Command: #{command}\n"
19
+
20
+ output = %x[ #{command} ]
21
+
22
+ print "Output:\n"
23
+ print output
@@ -0,0 +1,66 @@
1
+ ################################################################################
2
+ # The jobmanager configuration file.
3
+ #
4
+ # For a full description of each of the fields, and their default values,
5
+ # see the CONFIGURATION FILE section in README.txt.
6
+ ################################################################################
7
+
8
+ # The number of log files to be kept per job.
9
+ number_of_job_logs: 5
10
+
11
+ # The directory in which the job logs will be kept, to be interpreted
12
+ # by ERB. Allowed variables: user_name.
13
+ job_logs_directory: /tmp/logs
14
+
15
+ # jobmanager logs operational information (when jobs are launched,
16
+ # exit, etc). This can be done via syslog or directly to a log file.
17
+ # Possible values are: syslog, file.
18
+ central_log_mode: file
19
+
20
+ # If the central_log_mode is set to "file", the log file must be
21
+ # specified. This parameter will be interpreted by ERB. Allowed
22
+ # variables: user_name.
23
+ central_log_file: /tmp/logs/jobmanager.log
24
+
25
+ # The path to search for the command that jobmanager is invoked with.
26
+ #command_path: /usr/bin:/usr/designingpatterns/bin
27
+
28
+ # Whether jobmanager should print debug trace to the central log file.
29
+ debug: false
30
+
31
+ # The condition upon which condition results should be emailed.
32
+ # Possible values are: always, never, on_failure.
33
+ email_condition: always
34
+
35
+ # The configuration file for the simpleemail gem. This parameter will
36
+ # be interpreted by ERB. Allowed variables: user_name. Note: If a
37
+ # relative path is specified, it will be considered to be relative to
38
+ # the location of this configuration file.
39
+ email_settings_file: email_settings.rb
40
+
41
+ # The email subject, to be interpreted by ERB.
42
+ # Allowed variables: job_name, command, host_name, results, user_name.
43
+ email_subject: "jobmanager results for <%=job_name%> on <%=host_name%> : <%=result%>"
44
+
45
+ # The extension of the rotated job log file. If true, the extension
46
+ # is a date_time. Otherwise, it is a simple count (.1, .2, etc.).
47
+ date_time_extension: true
48
+
49
+ # If date_time_extension is true, this field will be used as the
50
+ # format for the date/time extension of the rotated job log file
51
+ # name.
52
+ date_time_extension_format: '%F_%T'
53
+
54
+ # Whether the rotated job log file should be zipped.
55
+ zip_rotated_log_file: false
56
+
57
+ # The timeout (in seconds). If not present, no timeout will be used.
58
+ timeout: 1800
59
+
60
+ # The sender of emails sent from jobmanager. This can also be
61
+ # specified in the email_settings_file (attribute default_from).
62
+ #email_from_address: "Sender <sender@gmail.com>"
63
+
64
+ # The receiver of emails sent from jobmanager. This can also be
65
+ # specified in the email_settings_file (attributes default_to).
66
+ #email_to_address: "Receiver <receiver@gmail.com>"
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ print "=================================================\n"
4
+ print "Backing up Database\n"
5
+ print "=================================================\n"
6
+
7
+ database = ARGV[0]
8
+
9
+ command = "mysqldump #{database} > /tmp/#{database}.mysql"
10
+ print command, "\n"
11
+
12
+ if (system("#{command}"))
13
+ print"\nSuccess!\n"
14
+ else
15
+ print "\nFailure!\n"
16
+ exit(1)
17
+ end
18
+
@@ -0,0 +1 @@
1
+ require 'jobmanager/application'
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'erb'
4
+ require 'ostruct'
5
+ require 'stringio'
6
+
7
+ require 'rubygems'
8
+ require 'logrotate'
9
+ require 'relative'
10
+ require 'sys/host'
11
+ require 'configtoolkit'
12
+ require 'configtoolkit/yamlreader'
13
+ require 'configtoolkit/hashreader'
14
+ require 'configtoolkit/overridereader'
15
+
16
+ require 'jobmanager/util'
17
+
18
+ # The simpleemail gem will bring in a bunch of ActionMailer warnings.
19
+ # Disable this temporarily.
20
+ JobManager.disable_warnings { require 'simpleemail' }
21
+
22
+ require 'jobmanager/applicationconfig'
23
+ require 'jobmanager/applicationoptionparser'
24
+ require 'jobmanager/system'
25
+ require 'jobmanager/teestream'
26
+ require 'jobmanager/applicationlogger'
27
+
28
+ module JobManager
29
+
30
+ class Application
31
+
32
+ # Inside the Application class, two classes of exceptions are
33
+ # raised (Error and ComposedError). This is done so that when an
34
+ # exception is caught, the exception handling code can discern
35
+ # between an exception that this Application has raised and an
36
+ # unexpected exception (raised in code other than this class).
37
+ # The exception handling code can than log this information.
38
+
39
+ # A simple exception class used in this class.
40
+ class Error < StandardError
41
+ end
42
+
43
+ # A simple exception class that repackages an existing exception.
44
+ # This class often catches exceptions raised by other libraries,
45
+ # and repackages them, adding additional trace (via the msg
46
+ # parameter).
47
+ class ComposedError < Error
48
+ def initialize(msg, contained_exception, backtrace = caller())
49
+ @msg = msg
50
+ @contained_exception = contained_exception
51
+
52
+ super(msg)
53
+ set_backtrace(backtrace)
54
+ end
55
+
56
+ def contained_exception()
57
+ return @contained_exception
58
+ end
59
+ end
60
+
61
+ #
62
+ # ====Description:
63
+ # This method creates an Application instance. It also reads in
64
+ # the configuration file and the command line options.
65
+ #
66
+ # ====Parameters:
67
+ # [program_name]
68
+ # The name of the program.
69
+ # [argv]
70
+ # A list of command line arguments.
71
+ # [config_file]
72
+ # The jobmanager configuration file name.
73
+ # [override_terminal_shell]
74
+ # Whether to override this method's internal determination of
75
+ # whether the calling process has been invoked by a shell
76
+ # attached to a terminal. If the value is nil, this method
77
+ # will make its own determination. This is purely for
78
+ # debugging purposes.
79
+ #
80
+ def initialize(program_name,
81
+ argv,
82
+ config_file,
83
+ override_terminal_shell = nil)
84
+
85
+ begin
86
+ @mgr_logger = nil
87
+ @email_ready = false
88
+ @program_name = program_name
89
+ @argv = argv
90
+ @config_file = config_file
91
+ @user_name = Etc.getpwuid(Process::Sys.getuid).name()
92
+
93
+ @terminal_shell = STDIN.tty? || STDOUT.tty? || STDERR.tty?
94
+ if (override_terminal_shell != nil)
95
+ @terminal_shell = override_terminal_shell
96
+ end
97
+
98
+ # If any of this process's standard iostreams are connected to
99
+ # a terminal, this process has been invoked directly or
100
+ # indirectly by the command line shell. As a result, let's log
101
+ # central log messages to STDOUT.
102
+ if (@terminal_shell)
103
+ @mgr_logger = ApplicationIOLogger.new(STDOUT,
104
+ @user_name)
105
+
106
+ str = "\n"
107
+ str << "*NOTE* "
108
+ str << "jobmanager has been invoked interactively. Ignoring the "
109
+ str << "central_log_* configuration parameters, and logging all output to "
110
+ str << "the terminal.\n\n"
111
+
112
+ print str
113
+
114
+ else
115
+ # None of this process's standard iostreams are connected to
116
+ # a terminal. This process has most likely been invoked by
117
+ # cron. Log all central log messages to syslog. The user
118
+ # may have specified in the configuration file for all
119
+ # central log messages to be directed to a file. However,
120
+ # until we successfully process the configuration file, we
121
+ # do not know the log mode they have chosen. Thus,
122
+ # temporarily log central log messages to syslog until we
123
+ # have read this configuration parameter. No messages will
124
+ # actually be written to syslog unless an error condition
125
+ # occurs (such as not being able to read or parse the
126
+ # configuration file itself!)
127
+ @mgr_logger = ApplicationSyslogLogger.new(@program_name,
128
+ @user_name)
129
+ end
130
+
131
+ # @mgr_logger is now valid! We can print to @mgr_logger in
132
+ # rescue statements going forwards.
133
+ # This method populates @config with the configuration from
134
+ # the configuration file and the command line parameters.
135
+ gather_options(@program_name, @argv, @config_file)
136
+
137
+ # This is done so that if any methods called by this
138
+ # application call any of the standard print commands (print,
139
+ # puts, etc) directly, these messages will be printed to
140
+ # @mgr_logger.
141
+ $stderr = $stdout = @mgr_logger
142
+
143
+ # Print the config in debug mode.
144
+ if (@mgr_logger.level <= Logger::DEBUG)
145
+ @mgr_logger.debug("Configuration:\n #{@config}\n")
146
+ end
147
+ rescue => e
148
+ handle_exception(e)
149
+ # Re-raise the exception to notify bin/jobmanager to exit!
150
+ raise e
151
+ end
152
+ end
153
+
154
+ def handle_exception(e)
155
+ if (@mgr_logger)
156
+ case e
157
+ when ComposedError
158
+ @mgr_logger.record_tagged_exception(e.message, e.contained_exception)
159
+ when Error
160
+ @mgr_logger.record_exception(e)
161
+ else
162
+ @mgr_logger.record_tagged_exception("Unexpected Error", e)
163
+ end
164
+
165
+ begin
166
+ if (@email_ready) then email_results(false) end
167
+ rescue => e
168
+ @mgr_logger.record_tagged_exception("Error emailing results", e)
169
+ end
170
+
171
+ @mgr_logger.close()
172
+ @mgr_logger = nil
173
+ end
174
+
175
+ $stderr = STDERR
176
+ $stdout = STDOUT
177
+ end
178
+
179
+ #
180
+ # ====Description:
181
+ # This method runs the jobmanager application.
182
+ #
183
+ def run()
184
+ success = false
185
+
186
+ begin
187
+ FileUtils.mkdir_p(@config.job_logs_directory)
188
+ rescue => e
189
+ raise ComposedError.new("Error creating job_logs_directory", e)
190
+ end
191
+
192
+ job_log_file = File.join(@config.job_logs_directory, "#{@config.job_name}.log")
193
+ @mgr_logger.debug("Opening job log file #{job_log_file}.")
194
+
195
+ begin
196
+ job_log_file_stream = File.open(job_log_file, "w")
197
+ rescue => e
198
+ raise ComposedError.new("Error opening job log file", e)
199
+ end
200
+
201
+ #
202
+ # The output of the job that will be invoked will be sent back
203
+ # to this process via a pipe (inside invoke_command). This
204
+ # process will use the job's output for 2 purposes. It will be
205
+ # forwarded to a job log file and the output will be used later
206
+ # when emailing results. invoke_command takes only 1
207
+ # job_output_stream. We could pass a string stream to
208
+ # invoke_command, and after write the contents to the job's log
209
+ # file, and also include it in the emailed results. However,
210
+ # this is less than optimal, as the job output will not be
211
+ # written to the log file in real time; it will not be written
212
+ # until after the job has completed. For long jobs, this can
213
+ # leave the job owner with no way of determining the status of
214
+ # the job while it is running. Instead, we construct a
215
+ # TeeStream which is a stream that forwards the messages to a
216
+ # list of streams. In this case, we will initialize the
217
+ # TeeStream to forward strings to the job log file stream and a
218
+ # string stream (which will be later used for emailing results).
219
+ #
220
+ begin
221
+ job_tee_stream = TeeStream.new(:file => job_log_file_stream,
222
+ :stringio => StringIO.new())
223
+
224
+ optional_args = {}
225
+ if @config.timeout then optional_args[:timeout] = @config.timeout end
226
+ optional_args[:command_path] = @config.command_path
227
+
228
+ # invoke the command - fork and exec the process, and wait til it finishes to reap the status.
229
+ success = System::invoke_command(@config.command,
230
+ job_tee_stream,
231
+ @mgr_logger,
232
+ optional_args)
233
+
234
+ job_output = job_tee_stream.get_stream(:stringio).string()
235
+ job_tee_stream.close()
236
+
237
+ rescue => e
238
+ @mgr_logger.record_tagged_exception("Error running job", e)
239
+ end
240
+
241
+ # Rotate the job log file.
242
+ begin
243
+ if (File.file?(job_log_file))
244
+
245
+ before_entries = Dir.glob("#{@config.job_logs_directory}/#{@config.job_name}*")
246
+
247
+ logrotate_options = {
248
+ :count => @config.number_of_job_logs,
249
+ :date_time_ext => @config.date_time_extension,
250
+ :date_time_format => @config.date_time_extension_format,
251
+ :gzip => @config.zip_rotated_log_file
252
+ }
253
+
254
+ LogRotate.rotate_file(job_log_file, logrotate_options)
255
+
256
+ rotated_file = (Dir.glob("#{@config.job_logs_directory}/#{@config.job_name}*") - before_entries)[0]
257
+ @mgr_logger.info("Job log rotated to #{rotated_file}.")
258
+ end
259
+ rescue => e
260
+ success = false
261
+ @mgr_logger.record_tagged_exception("Error rotating file #{job_log_file}", e)
262
+ end
263
+
264
+ # Email the results!
265
+ begin
266
+ if (@config.email_condition == :always ||
267
+ (@config.email_condition == :on_failure && !success))
268
+
269
+ email_results(success, job_output)
270
+ end
271
+ rescue => e
272
+ @mgr_logger.record_tagged_exception("Error emailing results", e)
273
+ end
274
+
275
+ rescue => e
276
+ handle_exception(e)
277
+ raise e
278
+
279
+ ensure
280
+ if (@mgr_logger) then
281
+ @mgr_logger.close()
282
+ @mgr_logger = nil
283
+ end
284
+
285
+ $stderr = STDERR
286
+ $stdout = STDOUT
287
+ end
288
+
289
+ private
290
+
291
+ #
292
+ # ====Description:
293
+ # This method reads the configuration file and the command line
294
+ # options into a configuration class (ApplicationConfig).
295
+ #
296
+ def gather_options(program_name, argv, config_file)
297
+ yaml_reader = nil
298
+ begin
299
+ yaml_reader = ConfigToolkit::YAMLReader.new(config_file)
300
+ rescue => e
301
+ raise ComposedError.new("Error loading #{config_file}", e)
302
+ end
303
+
304
+ command_parameters_hash = nil
305
+ begin
306
+ command_parameters_hash = JobManager::ApplicationOptionParser.parse(program_name, argv)
307
+ hash_reader = ConfigToolkit::HashReader.new(command_parameters_hash)
308
+ rescue => e
309
+ raise ComposedError.new("Error Parsing Command Line", e)
310
+ end
311
+
312
+ @config = nil
313
+ begin
314
+ # Union together the configuration file and the command line options.
315
+ override_reader = ConfigToolkit::OverrideReader.new(yaml_reader, hash_reader)
316
+ @config = JobManager::ApplicationConfig.load(override_reader)
317
+ rescue => e
318
+ raise ComposedError.new("Error validating configuration", e)
319
+ end
320
+
321
+ # if jobmanager is configured to email results, process email
322
+ # related configuration parameters.
323
+ if (@config.email_condition != :never)
324
+
325
+ # Do not email on error, email is not yet setup!
326
+ @config.email_settings_file = evaluate_configuration_parameter(@config, :email_settings_file)
327
+
328
+ # if the email_settings_file is a relative path, convert it to
329
+ # an absolute path (or a relative path relative to the
330
+ # process's current working directory). Assume that the
331
+ # email_settings_file is specified relative to the jobmanager
332
+ # configuration file's directory.
333
+ if (value = @config.email_settings_file)
334
+ if (!value.absolute?)
335
+ value = value.expand_path(File.dirname(config_file))
336
+ end
337
+
338
+ if (!value.file? || !value.readable?)
339
+ raise Error.new("email_settings_file (#{value}) does not exist or is not readable.\n")
340
+ end
341
+
342
+ @config.email_settings_file = value
343
+ end
344
+
345
+ # Note: From/To email addresses can be specified in either the
346
+ # jobmanager configuration file or the simpleemail gem
347
+ # configuration file.
348
+ begin
349
+ SimpleEmail::Email.email_settings_file = @config.email_settings_file
350
+ if (@config.email_from_address)
351
+ SimpleEmail::Email.default_from = @config.email_from_address
352
+ end
353
+ if (@config.email_to_address)
354
+ SimpleEmail::Email.default_to = @config.email_to_address
355
+ end
356
+ rescue => e
357
+ raise ComposedError.new("Error evaluating email settings file (#{@config.email_settings_file}, ", e)
358
+ end
359
+
360
+ @email_ready = true
361
+ end
362
+
363
+ if (@config.central_log_mode == :file && !@terminal_shell)
364
+ # Note: Earlier we set up @mgr_logger to log via syslog
365
+ # regardless of the central_log_mode configuration parameter
366
+ # (we hadn't read the configuration file at that point!).
367
+ # Close the syslog logger, and set @mgr_logger up to log
368
+ # directly to a file.
369
+
370
+ # Evaluate the string parameters that allow ERB syntax.
371
+ @config.central_log_file = evaluate_configuration_parameter(@config, :central_log_file)
372
+
373
+ begin
374
+ new_logger = ApplicationFileLogger.new(@config.central_log_file,
375
+ @user_name)
376
+
377
+ old_logger = @mgr_logger
378
+ @mgr_logger = new_logger
379
+ old_logger.close
380
+
381
+ rescue => e
382
+ raise ComposedError.new("Error Initializing Central Log", e)
383
+ end
384
+ end
385
+
386
+ @mgr_logger.job_name = @config.job_name
387
+ @mgr_logger.level = @config.debug ? Logger::DEBUG : Logger::INFO
388
+
389
+ @config.job_logs_directory = evaluate_configuration_parameter(@config, :job_logs_directory)
390
+ end
391
+
392
+ #
393
+ # ====Description:
394
+ # This method emails the results of the job to the configured email address.
395
+ #
396
+ def email_results(success,
397
+ job_output = nil)
398
+
399
+ email_banner = "---------------------------------------------------------\n"
400
+
401
+ mgr_output = @mgr_logger.string()
402
+
403
+ # Evaluate the email_subject configuration parameter string with
404
+ # ERB. Resolve the following variables to their values listed
405
+ # below.
406
+ result = success ? "Success" : "Failure"
407
+ host_name = Sys::Host.hostname
408
+ job_name = @config.job_name
409
+ command = @config.command
410
+ user_name = @user_name
411
+
412
+ subject = nil
413
+ begin
414
+ subject = ERB.new(@config.email_subject).result(binding)
415
+ rescue => e
416
+ @mgr_logger.record_tagged_exception("Error evaluating email subject with ERB", e)
417
+ subject = "Invalid Subject - Check Logs!"
418
+ end
419
+
420
+ body =""
421
+ body += email_banner
422
+ body += "jobmanager Output:\n"
423
+ body += email_banner
424
+ body += "#{mgr_output}\n"
425
+
426
+ if (job_output && job_output.length > 0)
427
+ body += email_banner
428
+ body += "Job Output:\n"
429
+ body += email_banner
430
+ body += "#{job_output}\n"
431
+ end
432
+
433
+ ActionMailer::Base.logger = @mgr_logger
434
+ SimpleEmail::Email.send_email(nil, nil, subject, body)
435
+ end
436
+
437
+ def evaluate_configuration_parameter(config, parameter)
438
+ # Evaluate the parameter string with ERB, replacing the
439
+ # user_name variable reference with the user name that invoked
440
+ # this application.
441
+ evaluated_parameter = nil
442
+
443
+ begin
444
+ user_name = @user_name
445
+ evaluated_parameter = ERB.new(config.send(parameter)).result(binding)
446
+ rescue => e
447
+ raise ComposedError.new("Error evaluating #{parameter} with ERB", e)
448
+ end
449
+
450
+ return evaluated_parameter
451
+ end
452
+
453
+ end
454
+
455
+ end