jobmanager 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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