File Coverage

blib/lib/TaskForest/Options.pm
Criterion Covered Total %
statement 203 239 84.9
branch 148 206 71.8
condition 6 6 100.0
subroutine 12 13 92.3
pod 3 3 100.0
total 372 467 79.6


line stmt bran cond sub pod time code
1             ################################################################################
2             #
3             # $Id: Options.pm 288 2010-03-21 01:08:33Z aijaz $
4             #
5             ################################################################################
6              
7             =head1 NAME
8              
9             TaskForest::Options - Get options from command line and/or environment
10              
11             =head1 SYNOPSIS
12              
13             use TaskForest::Options;
14              
15             my $o = &TaskForest::Options::getOptions();
16             # the above command will die if required options are not present
17              
18             =head1 DOCUMENTATION
19              
20             If you're just looking to use the taskforest application, the only
21             documentation you need to read is that for TaskForest. You can do this
22             either of the two ways:
23              
24             perldoc TaskForest
25              
26             OR
27              
28             man TaskForest
29              
30             =head1 DESCRIPTION
31              
32             This is a convenience class that gets the required and optional
33             command line parameters, and uses environment variables if command
34             line parameters are not specified.
35              
36             =head1 METHODS
37              
38             =cut
39              
40             package TaskForest::Options;
41 93     93   109765 use strict;
  93         188  
  93         3538  
42 93     93   571 use warnings;
  93         174  
  93         2585  
43 93     93   226800 use Getopt::Long;
  93         1829886  
  93         672  
44 93     93   19223 use Data::Dumper;
  93         243  
  93         6063  
45 93     93   557 use Carp;
  93         177  
  93         6425  
46 93     93   230194 use Config::General qw(ParseConfig);
  93         5270282  
  93         10837  
47 93     93   172520 use Log::Log4perl qw(:levels);
  93         7379224  
  93         785  
48 93     93   13418 use Sys::Hostname;
  93         224  
  93         8105  
49              
50             BEGIN {
51 93     93   597 use vars qw($VERSION);
  93         221  
  93         5150  
52 93     93   521842 $VERSION = '1.30';
53             }
54              
55              
56             # This is the main data structure that stores the options
57             our $options = {};
58              
59             # This is a list of all options that are accepted, optional and
60             # required. The values of the hash are the parameters passed to
61             # Getopts::Long if the corresponding option stores a value, or undef
62             # if the parameter represents a boolean value
63             #
64             my %all_options = (
65             end_time => 's',
66             wait_time => 's',
67             log_dir => 's',
68             once_only => undef,
69             collapse => undef,
70             job_dir => 's',
71             family_dir => 's',
72             run_wrapper => 's',
73             email => 's',
74             verbose => undef,
75             help => undef,
76             config_file => 's',
77             chained => undef,
78             log_threshold => 's',
79             log => undef,
80             log_file => 's',
81             err_file => 's',
82             log_status => undef,
83             ignore_regex => 's@',
84             default_time_zone => 's',
85             date => 's',
86             calendar_dir => 's',
87             instructions_dir => 's',
88             smtp_server => 's',
89             smtp_port => 's',
90             smtp_sender => 's',
91             mail_from => 's',
92             mail_reply_to => 's',
93             mail_return_path => 's',
94             smtp_timeout => 's',
95             num_retries => 's',
96             retry_sleep => 's',
97             retry_email => 's',
98             no_retry_email => undef,
99             retry_success_email => 's',
100             no_retry_success_email => undef,
101             );
102              
103             # These are the required options. The absence of any one of these will
104             # cause the program to die.
105             #
106             my @required_options = qw (run_wrapper log_dir job_dir family_dir);
107              
108             my $command_line_read = 0;
109              
110             my $command_line_options = undef;
111              
112              
113             # ------------------------------------------------------------------------------
114             =pod
115              
116             =over 4
117              
118             =item getConfig()
119              
120             Usage : my $config = &TaskForest::Options::getConfig($file)
121             Purpose : This method reads a config file
122             Returns : A hash ref of the options specified in the config file
123             Argument : The name of the config file
124             Throws : Nothing
125              
126             =back
127              
128             =cut
129              
130             # ------------------------------------------------------------------------------
131             sub getConfig {
132 116     116 1 249 my $config_file = shift;
133 116         1422 my %config = ParseConfig(-ConfigFile => $config_file, -LowerCaseNames => 1, -CComments => 0);
134 116         877685 return \%config;
135             }
136              
137             # ------------------------------------------------------------------------------
138             =pod
139              
140             =over 4
141              
142             =item getOptions()
143              
144             Usage : my $options = &TaskForest::Options::getOptions
145             Purpose : This method returns a list of all the options passed
146             in.
147             Returns : A hash ref of the options
148             Argument : None
149             Throws : "The following required options are missing"
150             Various exceptions if the parameters passed in are of
151             the wrong format.
152              
153             =back
154              
155             =cut
156              
157             # ------------------------------------------------------------------------------
158             sub getOptions {
159 456     456 1 26861 my $reread = shift;
160              
161             # If the options hash is already populated, just return it
162 456 100 100     10612 if ((not defined $reread) and keys(%$options)) {
163 274         1442 return $options;
164             }
165              
166 182         464 my $new_options = {};
167              
168 182 100       724 if (! defined($command_line_options)) {
169             # never do this more than once
170 90         223 $command_line_options = {};
171 90 100       1986 GetOptions($command_line_options, map { if ($all_options{$_}) { "$_=$all_options{$_}"} else { $_ } } (keys %all_options));
  3240         8877  
  2430         8731  
  810         1666  
172             }
173              
174             # As options are first retrieved, they're considered tainted and
175             # stored in this hash. Upon untainting they're stored in $options.
176             #
177 182         209100 my $tainted_options = {};
178              
179             # Every option starts of as undef
180 182         2537 foreach my $option (keys %all_options) { $tainted_options->{$option} = undef; }
  6552         10325  
181             # handle multiple value options specially
182             #$tainted_options->{ignore_regex} = [];
183              
184             # Then it gets the command line value
185 182         1536 foreach my $option (keys %all_options) { $tainted_options->{$option} = $command_line_options->{$option}; }
  6552         9583  
186              
187             # Then it gets the environment variable value, if necessary
188 182 50       2383 foreach my $option (keys %all_options) { $tainted_options->{$option} = $ENV{"TF_".uc($option)} unless defined $tainted_options->{$option} }
  6552         51035  
189              
190             # Then it gets the config file value if necessary
191 182         1456 my $config;
192 182 100       1321 if ($tainted_options->{config_file}) {
193 116         453 my $config_file = $tainted_options->{config_file};
194 116         507 $config_file =~ s/;//g;
195 116         491 $config = getConfig($config_file);
196             }
197 182 100       1638 foreach my $option (keys %all_options) { $tainted_options->{$option} = $config->{$option} unless defined $tainted_options->{$option} }
  6552         16975  
198 182 50       1595 $tainted_options->{token} = $config->{token} unless defined $tainted_options->{token};
199            
200              
201             # Finally, pick a default value if necessary
202 182 100       1202 $tainted_options->{wait_time} = 60 unless defined $tainted_options->{wait_time};
203 182 100       740 $tainted_options->{end_time} = '2355' unless defined $tainted_options->{end_time};
204 182 100       762 $tainted_options->{once_only} = 0 unless defined $tainted_options->{once_only};
205 182 100       1965 $tainted_options->{verbose} = 0 unless defined $tainted_options->{verbose};
206 182 100       835 $tainted_options->{collapse} = 0 unless defined $tainted_options->{collapse};
207 182 100       723 $tainted_options->{chained} = 0 unless defined $tainted_options->{chained};
208 182 100       1344 $tainted_options->{log} = 0 unless defined $tainted_options->{log};
209 182 100       722 $tainted_options->{log_threshold} = 'info' unless defined $tainted_options->{log_threshold};
210 182 100       684 $tainted_options->{log_file} = "stdout" unless defined $tainted_options->{log_file};
211 182 100       830 $tainted_options->{err_file} = "stderr" unless defined $tainted_options->{err_file};
212 182 100       692 $tainted_options->{log_status} = 0 unless defined $tainted_options->{log_status};
213 182 100       742 $tainted_options->{ignore_regex} = [] unless defined $tainted_options->{ignore_regex};
214 182 50       1110 $tainted_options->{default_time_zone} = 'America/Chicago' unless defined $tainted_options->{default_time_zone};
215 182 50       793 $tainted_options->{date} = '' unless defined $tainted_options->{date};
216 182 100       1727 $tainted_options->{token} = {} unless defined $tainted_options->{token};
217 182 100       877 $tainted_options->{smtp_server} = '' unless defined $tainted_options->{smtp_server};
218 182 100       617 $tainted_options->{smtp_port} = 25 unless defined $tainted_options->{smtp_port};
219 182 100       821 $tainted_options->{smtp_timeout} = 60 unless defined $tainted_options->{smtp_timeout};
220 182 50       788 $tainted_options->{smtp_sender} = '' unless defined $tainted_options->{smtp_sender};
221 182 100       651 $tainted_options->{mail_from} = '' unless defined $tainted_options->{mail_from};
222 182 100       985 $tainted_options->{mail_reply_to} = '' unless defined $tainted_options->{mail_reply_to};
223 182 100       636 $tainted_options->{mail_return_path} = '' unless defined $tainted_options->{mail_return_path};
224 182 50       755 $tainted_options->{num_retries} = 0 unless defined $tainted_options->{num_retries};
225 182 50       860 $tainted_options->{retry_sleep} = 60 unless defined $tainted_options->{retry_sleep};
226 182 100       669 $tainted_options->{email} = '' unless defined $tainted_options->{email};
227 182 100       762 $tainted_options->{instructions_dir} = '' unless defined $tainted_options->{instructions_dir};
228 182 100       619 $tainted_options->{retry_email} = '' unless defined $tainted_options->{retry_email};
229 182 100       667 $tainted_options->{no_retry_email} = 0 unless defined $tainted_options->{no_retry_email};
230 182 100       625 $tainted_options->{retry_success_email} = '' unless defined $tainted_options->{retry_success_email};
231 182 100       625 $tainted_options->{no_retry_success_email} = 0 unless defined $tainted_options->{no_retry_success_email};
232              
233             # show help
234 182 50       764 if ($tainted_options->{help}) {
235 0         0 showHelp();
236 0         0 exit 0;
237             }
238              
239             # Make sure all required options are present
240 182         538 my @missing = ();
241 182         893 foreach my $req (@required_options) {
242 728 100       2802 unless ($tainted_options->{$req}) {
243 10         23 push (@missing, $req);
244             }
245             }
246 182 100       799 if (@missing) {
247 4         722 croak "The following required options are missing: ", join(", ", @missing);
248             }
249            
250             # Untaint each option
251             #
252             # The booleans are set to 1 or 0
253             #
254 178 100       755 if ($tainted_options->{once_only}) { $new_options->{once_only} = 1; } else { $new_options->{once_only} = 0; }
  167         511  
  11         37  
255 178 100       643 if ($tainted_options->{collapse}) { $new_options->{collapse} = 1; } else { $new_options->{collapse} = 0; }
  116         316  
  62         306  
256 178 50       578 if ($tainted_options->{verbose}) { $new_options->{verbose} = 1; } else { $new_options->{verbose} = 0; }
  0         0  
  178         439  
257 178 100       1099 if ($tainted_options->{chained}) { $new_options->{chained} = 1; } else { $new_options->{chained} = 0; }
  116         282  
  62         181  
258 178 100       671 if ($tainted_options->{log}) { $new_options->{log} = 1; } else { $new_options->{log} = 0; }
  128         294  
  50         136  
259              
260             # The non-booleans are scanned with regexes with the matches being
261             # put into $new_options
262             #
263 178 100       871 if ( ($tainted_options->{email})) {
264 116 50       861 if ($tainted_options->{email} =~ m!^([a-z0-9\-_:\.\@\+]+)!i) { $new_options->{email} = $1; } else { croak "Bad email"; }
  116         457  
  0         0  
265             }
266 178 50       876 if (defined ($tainted_options->{run_wrapper})) {
267 178 50       1207 if ($tainted_options->{run_wrapper} =~ m!^([a-z0-9/_:\\\.\-]+)!i) { $new_options->{run_wrapper} = $1; } else { croak "Bad run_wrapper"; }
  178         1083  
  0         0  
268             }
269 178 50       949 if (defined ($tainted_options->{family_dir})) {
270 178 50       1056 if ($tainted_options->{family_dir} =~ m!^([a-z0-9/_:\\\.\-]+)!i) { $new_options->{family_dir} = $1; } else { croak "Bad family_dir"; }
  178         712  
  0         0  
271             }
272 178 50       963 if (defined ($tainted_options->{job_dir})) {
273 178 50       1201 if ($tainted_options->{job_dir} =~ m!^([a-z0-9/_:\\\.\-]+)!i) { $new_options->{job_dir} = $1; } else { croak "Bad job_dir"; }
  178         768  
  0         0  
274             }
275 178 50       675 if (defined ($tainted_options->{log_dir})) {
276 178 50       1187 if ($tainted_options->{log_dir} =~ m!^([a-z0-9/_:\\\.\-]+)!i) { $new_options->{log_dir} = $1; } else { croak "Bad log_dir"; }
  178         730  
  0         0  
277             }
278 178 50       703 if (defined ($tainted_options->{end_time})) {
279 178 50       1445 if ($tainted_options->{end_time} =~ /(\d{2}:?\d{2})/) { $new_options->{end_time} = $1; } else { croak "Bad end_time"; }
  178         648  
  0         0  
280             }
281 178         1446 $new_options->{end_time} =~ s/://g;
282 178 50       967 if (defined ($tainted_options->{wait_time})) {
283 178 50       1233 if ($tainted_options->{wait_time} =~ /^(\d+)$/) { $new_options->{wait_time} = $1; } else { croak "Bad wait_time"; }
  178         941  
  0         0  
284             }
285 178 50       677 if (defined ($tainted_options->{log_threshold})) {
286 178 50       1092 if ($tainted_options->{log_threshold} =~ m!^([a-z0-9_:\.\@]+)!i) { $new_options->{log_threshold} = $1; } else { croak "Bad log_threshold"; }
  178         669  
  0         0  
287             }
288 178 50       726 if (defined ($tainted_options->{log_file})) {
289 178 50       1031 if ($tainted_options->{log_file} =~ m!^([a-z0-9_:\.\-]+)!i) { $new_options->{log_file} = $1; } else { croak "Bad log_file"; }
  178         553  
  0         0  
290             }
291 178 50       724 if (defined ($tainted_options->{err_file})) {
292 178 50       955 if ($tainted_options->{err_file} =~ m!^([a-z0-9_:\.\-]+)!i) { $new_options->{err_file} = $1; } else { croak "Bad err_file"; }
  178         534  
  0         0  
293             }
294 178 50       646 if (defined ($tainted_options->{ignore_regex})) {
295 178         709 $new_options->{ignore_regex} = $tainted_options->{ignore_regex};
296             }
297 178 50       654 if (defined ($tainted_options->{default_time_zone})) {
298 178 50       951 if ($tainted_options->{default_time_zone} =~ m!^([a-z0-9\/\_]+)!i) { $new_options->{default_time_zone} = $1; } else { croak "Bad default_time_zone"; }
  178         754  
  0         0  
299             }
300 178 50       740 if ($tainted_options->{date}) {
301 0 0       0 if ($tainted_options->{date} =~ m!^(\d{8})$!i) { $new_options->{date} = $1; } else { croak "Bad date"; }
  0         0  
  0         0  
302             }
303 178 50       606 if ($tainted_options->{token}) {
304 178         335 foreach my $r (keys %{$tainted_options->{token}}) {
  178         4197  
305 580 50       2276 if ($r =~ /^([a-z0-9_\.\-]+)/i) {
306 580         1175 my $token_name = $1;
307 580         1358 $new_options->{token}->{$token_name} = {};
308 580         2099 my $num_tokens = $tainted_options->{token}->{$token_name}->{number} * 1;
309 580         3658 $new_options->{token}->{$token_name}->{number} = $num_tokens;
310             }
311             else {
312 0         0 croak ("Bad token name: $r. A token name can only contain the characters [a-zA-Z0-9_]");
313             }
314             }
315             }
316 178 100       882 if (defined ($tainted_options->{calendar_dir})) {
317 116 50       648 if ($tainted_options->{calendar_dir} =~ m!^([a-z0-9/_:\\\.\-]*)!i) { $new_options->{calendar_dir} = $1; } else { croak "Bad calendar_dir"; }
  116         386  
  0         0  
318             }
319 178 100       593 if ( ($tainted_options->{instructions_dir})) {
320 116 50       1032 if ($tainted_options->{instructions_dir} =~ m!^([a-z0-9/_:\\\.\-]*)!i) { $new_options->{instructions_dir} = $1; } else { croak "Bad instructions_dir"; }
  116         388  
  0         0  
321             }
322 178 100       671 if ( ($tainted_options->{smtp_server})) {
323 116 50       529 if ($tainted_options->{smtp_server} =~ m!^([a-z0-9_\-\.]*)!i) { $new_options->{smtp_server} = $1; } else { croak "Bad smtp server"; }
  116         401  
  0         0  
324             }
325 178 50       622 if ( ($tainted_options->{smtp_sender})) {
326 0 0       0 if ($tainted_options->{smtp_sender} =~ m!^([a-z0-9_:\-\.\@\+]*)!i) { $new_options->{smtp_sender} = $1; } else { croak "Bad smtp sender"; }
  0         0  
  0         0  
327             }
328 178 100       601 if ( ($tainted_options->{mail_from})) {
329 116 50       692 if ($tainted_options->{mail_from} =~ m!^([a-z0-9_:\-\.\@\+]*)!i) { $new_options->{mail_from} = $1; } else { croak "Bad mail from"; }
  116         376  
  0         0  
330             }
331 178 100       741 if ( ($tainted_options->{mail_reply_to})) {
332 116 50       629 if ($tainted_options->{mail_reply_to} =~ m!^([a-z0-9_:\-\.\@\+]*)!i) { $new_options->{mail_reply_to} = $1; } else { croak "Bad reply to"; }
  116         464  
  0         0  
333             }
334 178 100       633 if ( ($tainted_options->{mail_return_path})) {
335 116 50       652 if ($tainted_options->{mail_return_path} =~ m!^([a-z0-9_:\-\.\@\+]*)!i) { $new_options->{mail_return_path} = $1; } else { croak "Bad return path"; }
  116         354  
  0         0  
336             }
337 178 50       4352 if (defined ($tainted_options->{smtp_port})) {
338 178 50       971 if ($tainted_options->{smtp_port} =~ m!^(\d+)!i) { $new_options->{smtp_port} = $1; } else { croak "Bad smtp port"; }
  178         571  
  0         0  
339             }
340 178 50       634 if (defined ($tainted_options->{smtp_timeout})) {
341 178 50       945 if ($tainted_options->{smtp_timeout} =~ m!^(\d+)!i) { $new_options->{smtp_timeout} = $1; } else { croak "Bad smtp timeout"; }
  178         609  
  0         0  
342             }
343 178 50       602 if (defined ($tainted_options->{num_retries})) {
344 178 50       923 if ($tainted_options->{num_retries} =~ m!^(\d+)!i) { $new_options->{num_retries} = $1; } else { croak "Bad smtp timeout"; }
  178         1966  
  0         0  
345             }
346 178 50       1231 if (defined ($tainted_options->{retry_sleep})) {
347 178 50       1160 if ($tainted_options->{retry_sleep} =~ m!^(\d+)!i) { $new_options->{retry_sleep} = $1; } else { croak "Bad smtp timeout"; }
  178         521  
  0         0  
348             }
349 178 100       9730 if ( ($tainted_options->{retry_email})) {
350 116 50       537 if ($tainted_options->{retry_email} =~ m!^([a-z0-9\-_:\.\@\+]*)!i) { $new_options->{retry_email} = $1; } else { croak "Bad retry_email"; }
  116         391  
  0         0  
351             }
352 178 50       801 if ($tainted_options->{no_retry_email}) { $new_options->{no_retry_email} = 1; } else { $new_options->{no_retry_email} = 0; }
  0         0  
  178         480  
353 178 100       804 if ( ($tainted_options->{retry_success_email})) {
354 116 50       556 if ($tainted_options->{retry_success_email} =~ m!^([a-z0-9\-_:\.\@\+]*)!i) { $new_options->{retry_success_email} = $1; } else { croak "Bad retry_success_email"; }
  116         368  
  0         0  
355             }
356 178 50       524 if ($tainted_options->{no_retry_success_email}) { $new_options->{no_retry_success_email} = 1; } else { $new_options->{no_retry_success_email} = 0; }
  0         0  
  178         851  
357              
358              
359 178 100       742 if (%$options) {
360             # if options have changed, let the user know
361 88         1026 my %all_keys = map { $_ => 1 } (keys (%$options), keys (%$new_options));
  5112         8157  
362 88         1141 foreach (keys %all_keys) {
363 2556 100 100     10956 if (($new_options->{$_} ne $options->{$_}) and (!ref($new_options->{$_}))) {
364 3         33 print "Option $_ has changed from $options->{$_} to $new_options->{$_}\n";
365             }
366             }
367             }
368 178         571 $options = $new_options;
369              
370 178         1927 $ENV{HOSTNAME} = hostname;
371              
372 178 50       2653 print "options is ", Dumper($options) if $options->{verbose};
373              
374            
375 178         3484 return $options;
376             }
377              
378              
379             # ------------------------------------------------------------------------------
380             =pod
381              
382             =over 4
383              
384             =item showHelp()
385              
386             Usage : showHelp()
387             Purpose : This method prints help text
388             Returns : Nothing
389             Argument : None
390             Throws : Nothing
391              
392             =back
393              
394             =cut
395              
396             # ------------------------------------------------------------------------------
397             sub showHelp {
398 0     0 1   print qq^
399             USAGE
400             To run the main taskforest dependency checker, do one of the following:
401              
402             export TF_JOB_DIR=/foo/jobs
403             export TF_LOG_DIR=/foo/logs
404             export TF_FAMILY_DIR=/foo/families
405             export TF_RUN_WRAPPER=/foo/bin/run
406             taskforest
407              
408             OR
409              
410             taskforest --run_wrapper=/foo/bin/run \
411             --log_dir=/foo/logs \
412             --job_dir=/foo/jobs \
413             --family_dir=/foo/families
414              
415             OR
416              
417             taskforest --config_file=taskforest.cfg
418              
419             All jobs will run as the user who invoked taskforest.
420              
421             To get the status of all currently running and recently run jobs,
422             enter the following command:
423              
424             status --collapse
425              
426             For more detailed documentation, enter:
427              
428             man TaskForest
429              
430             or
431              
432             perldoc TaskForest
433            
434             ^;
435             }
436              
437             1