File Coverage

blib/lib/Nagios/Plugin/Getopt.pm
Criterion Covered Total %
statement 207 211 98.1
branch 76 86 88.3
condition 24 38 63.1
subroutine 29 29 100.0
pod 1 3 33.3
total 337 367 91.8


line stmt bran cond sub pod time code
1             #
2             # Nagios::Plugin::Getopt - OO perl module providing standardised argument
3             # processing for nagios plugins
4             #
5              
6             package Nagios::Plugin::Getopt;
7              
8 6     6   140520 use strict;
  6         14  
  6         274  
9 6     6   37 use File::Basename;
  6         11  
  6         793  
10 6     6   9913 use Getopt::Long qw(:config no_ignore_case bundling);
  6         121153  
  6         44  
11 6     6   1584 use Carp;
  6         15  
  6         612  
12 6     6   4266 use Params::Validate qw(:all);
  6         55971  
  6         1425  
13 6     6   49 use base qw(Class::Accessor);
  6         14  
  6         4601  
14              
15 6     6   15538 use Nagios::Plugin::Functions;
  6         17  
  6         599  
16 6     6   4053 use Nagios::Plugin::Config;
  6         16  
  6         244  
17 6     6   49 use vars qw($VERSION);
  6         14  
  6         26027  
18             $VERSION = $Nagios::Plugin::Functions::VERSION;
19              
20             # Standard defaults
21             my %DEFAULT = (
22             timeout => 15,
23             verbose => 0,
24             license =>
25             "This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
26             It may be used, redistributed and/or modified under the terms of the GNU
27             General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).",
28             );
29             # Standard arguments
30             my @ARGS = ({
31             spec => 'usage|?',
32             help => "-?, --usage\n Print usage information",
33             }, {
34             spec => 'help|h',
35             help => "-h, --help\n Print detailed help screen",
36             }, {
37             spec => 'version|V',
38             help => "-V, --version\n Print version information",
39             }, {
40             spec => 'extra-opts:s@',
41             help => "--extra-opts=[section][\@file]\n Read options from an ini file. See http://nagiosplugins.org/extra-opts\n for usage and examples.",
42             }, {
43             spec => 'timeout|t=i',
44             help => "-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)",
45             default => $DEFAULT{timeout},
46             }, {
47             spec => 'verbose|v+',
48             help => "-v, --verbose\n Show details for command-line debugging (can repeat up to 3 times)",
49             default => $DEFAULT{verbose},
50             },
51             );
52             # Standard arguments we traditionally display last in the help output
53             my %DEFER_ARGS = map { $_ => 1 } qw(timeout verbose);
54              
55             # -------------------------------------------------------------------------
56             # Private methods
57              
58             sub _die
59             {
60 13     13   55 my $self = shift;
61 13         25 my ($msg) = @_;
62 13 100       65 $msg .= "\n" unless substr($msg, -1) eq "\n";
63 13         66 Nagios::Plugin::Functions::_nagios_exit(3, $msg);
64             }
65              
66             # Return the given attribute, if set, including a final newline
67             sub _attr
68             {
69 24     24   37 my $self = shift;
70 24         41 my ($item, $extra) = @_;
71 24 100       53 $extra = '' unless defined $extra;
72 24 100       73 return '' unless $self->{_attr}->{$item};
73 19         134 $self->{_attr}->{$item} . "\n" . $extra;
74             }
75              
76             # Turn argument spec into help-style output
77             sub _spec_to_help
78             {
79 26     26   40 my ($self, $spec, $label) = @_;
80              
81 26         79 my ($opts, $type) = split /=/, $spec, 2;
82 26         29 my (@short, @long);
83 26         53 for (split /\|/, $opts) {
84 44 100       82 if (length $_ == 1) {
85 16         35 push @short, "-$_";
86             } else {
87 28         60 push @long, "--$_";
88             }
89             }
90              
91 26         51 my $help = join(', ', @short, @long);
92 26 100       40 if ($type) {
    50          
93 24 100       37 if ($label) {
94 10         13 $help .= '=' . $label;
95             }
96             else {
97 14 100       792 $help .= $type eq 'i' ? '=INTEGER' : '=STRING';
98             }
99             }
100             elsif ($label) {
101 0         0 carp "Label specified, but there's no type in spec '$spec'";
102             }
103 26         29 $help .= "\n ";
104 26         92 return $help;
105             }
106              
107             # Options output for plugin -h
108             sub _options
109             {
110 8     8   16 my $self = shift;
111              
112 8         16 my @args = ();
113 8         10 my @defer = ();
114 8         11 for (@{$self->{_args}}) {
  8         22  
115 72 100       137 if (exists $DEFER_ARGS{$_->{name}}) {
116 16         25 push @defer, $_;
117             } else {
118 56         74 push @args, $_;
119             }
120             }
121              
122 8         15 my @options = ();
123 8         15 for my $arg (@args, @defer) {
124 72 100 66     1496 my $help_array = ref $arg->{help} && ref $arg->{help} eq 'ARRAY' ? $arg->{help} : [ $arg->{help} ];
125 72 100 66     274 my $label_array = $arg->{label} && ref $arg->{label} && ref $arg->{label} eq 'ARRAY' ? $arg->{label} : [ $arg->{label} ];
126 72         797 my $help_string = '';
127 72         210 for (my $i = 0; $i <= $#$help_array; $i++) {
128 78         132 my $help = $help_array->[$i];
129             # Add spec arguments to help if not already there
130 78 100       198 if ($help =~ m/^\s*-/) {
131 52         162 $help_string .= $help;
132             }
133             else {
134 26         69 $help_string .= $self->_spec_to_help($arg->{spec}, $label_array->[$i]) . $help;
135 26 100       111 $help_string .= "\n " if $i < $#$help_array;
136             }
137             }
138              
139             # Add help_string to @options
140 72 100       147 if ($help_string =~ m/%s/) {
141 12 50       60 my $default = defined $arg->{default} ? $arg->{default} : '';
142             # We only handle '%s' formats here, so escape everything else
143 12         38 $help_string =~ s/%(?!s)/%%/g;
144 12         56 push @options, sprintf($help_string, $default, $default, $default, $default);
145             } else {
146 60         140 push @options, $help_string;
147             }
148             }
149              
150 8         108 return ' ' . join("\n ", @options);
151             }
152              
153             # Output for plugin -? (or missing/invalid args)
154             sub _usage
155             {
156 12     12   19 my $self = shift;
157 12         30 sprintf $self->_attr('usage'), $self->{_attr}->{plugin};
158             }
159              
160             # Output for plugin -V
161             sub _revision
162             {
163 6     6   14 my $self = shift;
164 6         42 my $revision = sprintf "%s %s", $self->{_attr}->{plugin}, $self->{_attr}->{version};
165 6 100       38 $revision .= sprintf " [%s]", $self->{_attr}->{url} if $self->{_attr}->{url};
166 6         11 $revision .= "\n";
167 6         23 $revision;
168             }
169              
170             # Output for plugin -h
171             sub _help
172             {
173 4     4   9 my $self = shift;
174 4         9 my $help = '';
175 4         17 $help .= $self->_revision . "\n";
176 4         18 $help .= $self->_attr('license', "\n");
177 4         11 $help .= $self->_attr('blurb', "\n");
178 4 50       17 $help .= $self->_usage ? $self->_usage . "\n" : '';
179 4 50       18 $help .= $self->_options ? $self->_options . "\n" : '';
180 4         15 $help .= $self->_attr('extra', "\n");
181 4         20 return $help;
182             }
183              
184             # Return a Getopt::Long-compatible option array from the current set of specs
185             sub _process_specs_getopt_long
186             {
187 33     33   57 my $self = shift;
188              
189 33         63 my @opts = ();
190 33         48 for my $arg (@{$self->{_args}}) {
  33         95  
191 372         628 push @opts, $arg->{spec};
192             # Setup names and defaults
193 372         509 my $spec = $arg->{spec};
194             # Use first arg as name (like Getopt::Long does)
195 372         1194 $spec =~ s/[=:].*$//;
196 372         1354 my $name = (split /\s*\|\s*/, $spec)[0];
197 372         888 $arg->{name} = $name;
198 372 100       788 if (defined $self->{$name}) {
199 33         83 $arg->{default} = $self->{$name};
200             } else {
201 339         915 $self->{$name} = $arg->{default};
202             }
203             }
204              
205 33         233 return @opts;
206             }
207              
208             # Check for existence of required arguments
209             sub _check_required_opts
210             {
211 23     23   42 my $self = shift;
212              
213 23         64 my @missing = ();
214 23         35 for my $arg (@{$self->{_args}}) {
  23         73  
215 272 100 100     752 if ($arg->{required} && ! defined $self->{$arg->{name}}) {
216 2         7 push @missing, $arg->{name};
217             }
218             }
219 23 100       80 if (@missing) {
220 2         20 $self->_die($self->_usage . "\n" .
221 2         8 join("\n", map { sprintf "Missing argument: %s", $_ } @missing) . "\n");
222             }
223             }
224              
225             # Process and handle any immediate options
226             sub _process_opts
227             {
228 31     31   53 my $self = shift;
229              
230             # Print message and exit for usage, version, help
231 31 100       103 $self->_die($self->_usage) if $self->{usage};
232 29 100       93 $self->_die($self->_revision) if $self->{version};
233 27 100       97 $self->_die($self->_help) if $self->{help};
234             }
235              
236             # -------------------------------------------------------------------------
237             # Default opts methods
238              
239             sub _load_config_section
240             {
241 13     13   19 my $self = shift;
242 13         24 my ($section, $file, $flags) = @_;
243 13   33     24 $section ||= $self->{_attr}->{plugin};
244              
245 13         28 my $Config;
246 13         18 eval { $Config = Nagios::Plugin::Config->read($file); };
  13         107  
247 13 50       51 $self->_die($@) if ($@); #TODO: add test?
248              
249             # TODO: is this check sane? Does --extra-opts=foo require a [foo] section?
250             ## Nevertheless, if we die as UNKNOWN here we should do the same on default
251             ## file *added eval/_die above*.
252 13 100       48 $self->_die("Invalid section '$section' in config file '$file'")
253             unless exists $Config->{$section};
254              
255 11         124 return $Config->{$section};
256             }
257              
258             # Helper method to setup a hash of spec definitions for _cmdline
259             sub _setup_spec_index
260             {
261 23     23   31 my $self = shift;
262 23 100       168 return if defined $self->{_spec};
263 12         19 $self->{_spec} = { map { $_->{name} => $_->{spec} } @{$self->{_args}} };
  192         4291  
  12         30  
264             }
265              
266             # Quote values that require it
267             sub _cmdline_value
268             {
269 58     58   67 my $self = shift;
270 58         245 local $_ = shift;
271 58 50 33     265 if (m/\s/ && (m/^[^"']/ || m/[^"']$/)) {
    100 66        
272 0         0 return qq("$_");
273             }
274             elsif ($_ eq '') {
275 1         3 return q("");
276             }
277             else {
278 57         138 return $_;
279             }
280             }
281              
282             # Helper method to format key/values in $hash in a quasi-commandline format
283             sub _cmdline
284             {
285 23     23   422 my $self = shift;
286 23         30 my ($hash) = @_;
287 23   66     96 $hash ||= $self;
288              
289 23         99 $self->_setup_spec_index;
290              
291 23         97 my @args = ();
292 23         313 for my $key (sort keys %$hash) {
293             # Skip internal keys
294 248 100       540 next if $key =~ m/^_/;
295              
296             # Skip defaults and internals
297 212 100 66     555 next if exists $DEFAULT{$key} && $hash->{$key} eq $DEFAULT{$key};
298 188 100       263 next if grep { $key eq $_ } qw(help usage version extra-opts);
  752         1647  
299 140 100       357 next unless defined $hash->{$key};
300              
301             # Render arg
302 46   50     122 my $spec = $self->{_spec}->{$key} || '';
303 46 100       173 if ($spec =~ m/[=:].+$/) {
304             # Arg takes value - may be a scalar or an arrayref
305 44 100       133 for my $value (ref $hash->{$key} eq 'ARRAY' ? @{$hash->{$key}} : ( $hash->{$key} )) {
  27         63  
306 58         122 $value = $self->_cmdline_value($value);
307 58 100       105 if (length($key) > 1) {
308 32         153 push @args, sprintf "--%s=%s", $key, $value;
309             }
310             else {
311 26         95 push @args, "-$key", $value;
312             }
313             }
314             }
315              
316             else {
317             # Flag - render long or short based on option length
318 2 50       9 push @args, (length($key) > 1 ? '--' : '-') . $key;
319             }
320             }
321              
322 23 100       224 return wantarray ? @args : join(' ', @args);
323             }
324              
325             # Process and load extra-opts sections
326             sub _process_extra_opts
327             {
328 33     33   65 my $self = shift;
329 33         47 my ($args) = @_;
330              
331 33         111 my $extopts_list = $args->{'extra-opts'};
332              
333 33         60 my @sargs = ();
334 33         81 for my $extopts (@$extopts_list) {
335 13   66     53 $extopts ||= $self->{_attr}->{plugin};
336 13         28 my $section = $extopts;
337 13         24 my $file = '';
338              
339             # Parse section@file
340 13 50       48 if ($extopts =~ m/^(\w*)@(.*?)\s*$/) {
341 0         0 $section = $1;
342 0         0 $file = $2;
343             }
344              
345             # Load section args
346 13         40 my $shash = $self->_load_config_section($section, $file);
347              
348             # Turn $shash into a series of commandline-like arguments
349 11         44 push @sargs, $self->_cmdline($shash);
350             }
351              
352             # Reset ARGV to extra-opts + original
353 31         63 @ARGV = ( @sargs, @{$self->{_attr}->{argv}} );
  31         132  
354              
355 31 100 100     644 printf "[extra-opts] %s %s\n", $self->{_attr}->{plugin}, join(' ', @ARGV)
356             if $args->{verbose} && $args->{verbose} >= 3;
357             }
358              
359             # -------------------------------------------------------------------------
360             # Public methods
361              
362             # Define plugin argument
363             sub arg
364             {
365 174     174 0 7302 my $self = shift;
366 174         191 my %args;
367              
368             # Named args
369 174 100 66     1068 if ($_[0] =~ m/^(spec|help|required|default)$/ && scalar(@_) % 2 == 0) {
370 157         3062 %args = validate( @_, {
371             spec => 1,
372             help => 1,
373             default => 0,
374             required => 0,
375             label => 0,
376             });
377             }
378              
379             # Positional args
380             else {
381 17         193 my @args = validate_pos(@_, 1, 1, 0, 0, 0);
382 17         119 %args = (
383             spec => $args[0],
384             help => $args[1],
385             default => $args[2],
386             required => $args[3],
387             label => $args[4],
388             );
389             }
390              
391             # Add to private args arrayref
392 174         976 push @{$self->{_args}}, \%args;
  174         807  
393             }
394              
395             # Process the @ARGV array using the current _args list (possibly exiting)
396             sub getopts
397             {
398 33     33 0 9170 my $self = shift;
399              
400             # Collate spec arguments for Getopt::Long
401 33         111 my @opt_array = $self->_process_specs_getopt_long;
402              
403             # Capture original @ARGV (for extra-opts games)
404 33         149 $self->{_attr}->{argv} = [ @ARGV ];
405              
406             # Call GetOptions using @opt_array
407 33         156 my $args1 = {};
408 33         391 my $ok = GetOptions($args1, @opt_array);
409             # Invalid options - give usage message and exit
410 33 50       38014 $self->_die($self->_usage) unless $ok;
411              
412             # Process extra-opts
413 33         169 $self->_process_extra_opts($args1);
414              
415             # Call GetOptions again, this time including extra-opts
416 31         300 $ok = GetOptions($self, @opt_array);
417             # Invalid options - give usage message and exit
418 31 50       32324 $self->_die($self->_usage) unless $ok;
419              
420             # Process immediate options (possibly exiting)
421 31         145 $self->_process_opts;
422              
423             # Required options (possibly exiting)
424 23         65 $self->_check_required_opts;
425              
426             # Setup accessors for options
427 21         337 $self->mk_ro_accessors(grep ! /^_/, keys %$self);
428              
429             # Setup default alarm handler for alarm($ng->timeout) in plugin
430             $SIG{ALRM} = sub {
431 1     1   2000671 my $plugin = uc $self->{_attr}->{plugin};
432 1         7 $plugin =~ s/^check_//;
433 1         9 $self->_die(
434             sprintf("%s UNKNOWN - plugin timed out (timeout %ss)",
435             $plugin, $self->timeout));
436 21         14561 };
437             }
438              
439             # -------------------------------------------------------------------------
440             # Constructor
441              
442             sub _init
443             {
444 33     33   49 my $self = shift;
445              
446             # Check params
447 33   33     1711 my $plugin = basename($ENV{NAGIOS_PLUGIN} || $0);
448 33         1670 my %attr = validate( @_, {
449             usage => 1,
450             version => 0,
451             url => 0,
452             plugin => { default => $plugin },
453             blurb => 0,
454             extra => 0,
455             'extra-opts' => 0,
456             license => { default => $DEFAULT{license} },
457             timeout => { default => $DEFAULT{timeout} },
458             });
459              
460             # Add attr to private _attr hash (except timeout)
461 33         410 $self->{timeout} = delete $attr{timeout};
462 33         176 $self->{_attr} = { %attr };
463             # Chomp _attr values
464 33         74 chomp foreach values %{$self->{_attr}};
  33         292  
465              
466             # Setup initial args list
467 33         149 $self->{_args} = [ @ARGS ];
468              
469 33         295 $self
470             }
471              
472             sub new
473             {
474 33     33 1 26024 my $class = shift;
475 33         119 my $self = bless {}, $class;
476 33         130 $self->_init(@_);
477             }
478              
479             # -------------------------------------------------------------------------
480              
481             1;
482              
483             __END__