diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Nagios/Plugin/Getopt.pm | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/lib/Nagios/Plugin/Getopt.pm b/lib/Nagios/Plugin/Getopt.pm new file mode 100644 index 0000000..dedf92c --- /dev/null +++ b/lib/Nagios/Plugin/Getopt.pm | |||
@@ -0,0 +1,603 @@ | |||
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 | use 5.005003; | ||
9 | use strict; | ||
10 | use File::Basename; | ||
11 | use Getopt::Long qw(:config no_ignore_case bundling); | ||
12 | use Carp; | ||
13 | use Params::Validate qw(:all); | ||
14 | use base qw(Class::Accessor); | ||
15 | |||
16 | use vars qw($VERSION); | ||
17 | $VERSION = '0.02'; | ||
18 | |||
19 | # Standard defaults | ||
20 | my %DEFAULT = ( | ||
21 | timeout => 15, | ||
22 | verbose => 0, | ||
23 | licence => | ||
24 | "This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. | ||
25 | It may be used, redistributed and/or modified under the terms of the GNU | ||
26 | General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).", | ||
27 | ); | ||
28 | # Standard arguments | ||
29 | my @ARGS = ({ | ||
30 | spec => 'usage|?', | ||
31 | help => "-?, --usage\n Print usage information", | ||
32 | }, { | ||
33 | spec => 'help|h', | ||
34 | help => "-h, --help\n Print detailed help screen", | ||
35 | }, { | ||
36 | spec => 'version|V', | ||
37 | help => "-V, --version\n Print version information", | ||
38 | }, { | ||
39 | spec => 'timeout|t=i', | ||
40 | help => "-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)", | ||
41 | default => $DEFAULT{timeout}, | ||
42 | }, { | ||
43 | spec => 'verbose|v', | ||
44 | help => "-v, --verbose\n Show details for command-line debugging", | ||
45 | default => $DEFAULT{verbose}, | ||
46 | }, | ||
47 | ); | ||
48 | # Standard arguments we traditionally display last in the help output | ||
49 | my %DEFER_ARGS = map { $_ => 1 } qw(timeout verbose); | ||
50 | |||
51 | # ------------------------------------------------------------------------- | ||
52 | # Private methods | ||
53 | |||
54 | sub _die | ||
55 | { | ||
56 | my $self = shift; | ||
57 | my ($msg) = @_; | ||
58 | $msg .= "\n" unless substr($msg, -1) eq "\n"; | ||
59 | # Set errno to UNKNOWN for die return code | ||
60 | local $! = 3; | ||
61 | die $msg; | ||
62 | } | ||
63 | |||
64 | # Return the given attribute, if set, including a final newline | ||
65 | sub _attr | ||
66 | { | ||
67 | my $self = shift; | ||
68 | my ($item, $extra) = @_; | ||
69 | $extra = '' unless defined $extra; | ||
70 | return '' unless $self->{_attr}->{$item}; | ||
71 | $self->{_attr}->{$item} . "\n" . $extra; | ||
72 | } | ||
73 | |||
74 | # Options output for plugin -h | ||
75 | sub _options | ||
76 | { | ||
77 | my $self = shift; | ||
78 | |||
79 | my @args = (); | ||
80 | my @defer = (); | ||
81 | for (@{$self->{_args}}) { | ||
82 | if (exists $DEFER_ARGS{$_->{name}}) { | ||
83 | push @defer, $_; | ||
84 | } else { | ||
85 | push @args, $_; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | my @options = (); | ||
90 | for my $arg (@args, @defer) { | ||
91 | if ($arg->{help} =~ m/%s/) { | ||
92 | push @options, sprintf($arg->{help}, $arg->{default} || ''); | ||
93 | } else { | ||
94 | push @options, $arg->{help}; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | return ' ' . join("\n ", @options); | ||
99 | } | ||
100 | |||
101 | # Output for plugin -? (or missing/invalid args) | ||
102 | sub _usage | ||
103 | { | ||
104 | my $self = shift; | ||
105 | sprintf $self->_attr('usage'), $self->{_attr}->{plugin}; | ||
106 | } | ||
107 | |||
108 | # Output for plugin -V | ||
109 | sub _revision | ||
110 | { | ||
111 | my $self = shift; | ||
112 | my $revision = sprintf "%s %s", $self->{_attr}->{plugin}, $self->{_attr}->{version}; | ||
113 | $revision .= sprintf " [%s]", $self->{_attr}->{url} if $self->{_attr}->{url}; | ||
114 | $revision .= "\n"; | ||
115 | $revision; | ||
116 | } | ||
117 | |||
118 | # Output for plugin -h | ||
119 | sub _help | ||
120 | { | ||
121 | my $self = shift; | ||
122 | my $help = ''; | ||
123 | $help .= $self->_revision . "\n"; | ||
124 | $help .= $self->_attr('license', "\n"); | ||
125 | $help .= $self->_attr('blurb', "\n"); | ||
126 | $help .= $self->_usage ? $self->_usage . "\n" : ''; | ||
127 | $help .= $self->_options ? $self->_options . "\n" : ''; | ||
128 | $help .= $self->_attr('extra', "\n"); | ||
129 | return $help; | ||
130 | } | ||
131 | |||
132 | # Return a Getopt::Long-compatible option array from the current set of specs | ||
133 | sub _process_specs_getopt_long | ||
134 | { | ||
135 | my $self = shift; | ||
136 | |||
137 | my @opts = (); | ||
138 | for my $arg (@{$self->{_args}}) { | ||
139 | push @opts, $arg->{spec}; | ||
140 | # Setup names and defaults | ||
141 | my $spec = $arg->{spec}; | ||
142 | # Use first arg as name (like Getopt::Long does) | ||
143 | $spec =~ s/=\w+$//; | ||
144 | my $name = (split /\s*\|\s*/, $spec)[0]; | ||
145 | $arg->{name} = $name; | ||
146 | if (defined $self->{$name}) { | ||
147 | $arg->{default} = $self->{$name}; | ||
148 | } else { | ||
149 | $self->{$name} = $arg->{default}; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | return @opts; | ||
154 | } | ||
155 | |||
156 | # Check for existence of required arguments | ||
157 | sub _check_required_opts | ||
158 | { | ||
159 | my $self = shift; | ||
160 | |||
161 | my @missing = (); | ||
162 | for my $arg (@{$self->{_args}}) { | ||
163 | if ($arg->{required} && ! defined $self->{$arg->{name}}) { | ||
164 | push @missing, $arg->{name}; | ||
165 | } | ||
166 | } | ||
167 | if (@missing) { | ||
168 | $self->_die($self->_usage . "\n" . | ||
169 | join("\n", map { sprintf "Missing argument: %s", $_ } @missing) . "\n"); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | # Process and handle any immediate options | ||
174 | sub _process_opts | ||
175 | { | ||
176 | my $self = shift; | ||
177 | |||
178 | # Print message and exit for usage, version, help | ||
179 | $self->_die($self->_usage) if $self->{usage}; | ||
180 | $self->_die($self->_revision) if $self->{version}; | ||
181 | $self->_die($self->_help) if $self->{help}; | ||
182 | } | ||
183 | |||
184 | # ------------------------------------------------------------------------- | ||
185 | # Public methods | ||
186 | |||
187 | # Define plugin argument | ||
188 | sub arg | ||
189 | { | ||
190 | my $self = shift; | ||
191 | my %args; | ||
192 | |||
193 | # Named args | ||
194 | if ($_[0] =~ m/^(spec|help|required|default)$/ && scalar(@_) % 2 == 0) { | ||
195 | %args = validate( @_, { | ||
196 | spec => 1, | ||
197 | help => 1, | ||
198 | default => 0, | ||
199 | required => 0, | ||
200 | }); | ||
201 | } | ||
202 | |||
203 | # Positional args | ||
204 | else { | ||
205 | my @args = validate_pos(@_, 1, 1, 0, 0); | ||
206 | %args = ( | ||
207 | spec => $args[0], | ||
208 | help => $args[1], | ||
209 | default => $args[2], | ||
210 | required => $args[3], | ||
211 | ); | ||
212 | } | ||
213 | |||
214 | # Add to private args arrayref | ||
215 | push @{$self->{_args}}, \%args; | ||
216 | } | ||
217 | |||
218 | # Process the @ARGV array using the current _args list (possibly exiting) | ||
219 | sub getopts | ||
220 | { | ||
221 | my $self = shift; | ||
222 | |||
223 | # Collate spec arguments for Getopt::Long | ||
224 | my @opt_array = $self->_process_specs_getopt_long; | ||
225 | |||
226 | # Call GetOptions using @opt_array | ||
227 | my $ok = GetOptions($self, @opt_array); | ||
228 | |||
229 | # Invalid options - given usage message and exit | ||
230 | $self->_die($self->_usage) unless $ok; | ||
231 | |||
232 | # Process immediate options (possibly exiting) | ||
233 | $self->_process_opts; | ||
234 | |||
235 | # Required options (possibly exiting) | ||
236 | $self->_check_required_opts; | ||
237 | |||
238 | # Setup accessors for options | ||
239 | $self->mk_ro_accessors(grep ! /^_/, keys %$self); | ||
240 | |||
241 | # Setup default alarm handler for alarm($ng->timeout) in plugin | ||
242 | $SIG{ALRM} = sub { | ||
243 | my $plugin = uc $self->{_attr}->{plugin}; | ||
244 | $plugin =~ s/^check_//; | ||
245 | $self->_die( | ||
246 | sprintf("%s UNKNOWN - plugin timed out (timeout %ss)", | ||
247 | $plugin, $self->timeout)); | ||
248 | }; | ||
249 | } | ||
250 | |||
251 | # ------------------------------------------------------------------------- | ||
252 | # Constructor | ||
253 | |||
254 | sub _init | ||
255 | { | ||
256 | my $self = shift; | ||
257 | |||
258 | # Check params | ||
259 | my $plugin = basename($ENV{NAGIOS_PLUGIN} || $0); | ||
260 | my %attr = validate( @_, { | ||
261 | usage => 1, | ||
262 | version => 0, | ||
263 | url => 0, | ||
264 | plugin => { default => $plugin }, | ||
265 | blurb => 0, | ||
266 | extra => 0, | ||
267 | license => { default => $DEFAULT{licence} }, | ||
268 | timeout => { default => $DEFAULT{timeout} }, | ||
269 | }); | ||
270 | |||
271 | # Add attr to private _attr hash (except timeout) | ||
272 | $self->{timeout} = delete $attr{timeout}; | ||
273 | $self->{_attr} = { %attr }; | ||
274 | # Chomp _attr values | ||
275 | chomp foreach values %{$self->{_attr}}; | ||
276 | |||
277 | # Setup initial args list | ||
278 | $self->{_args} = [ @ARGS ]; | ||
279 | |||
280 | $self | ||
281 | } | ||
282 | |||
283 | sub new | ||
284 | { | ||
285 | my $class = shift; | ||
286 | my $self = bless {}, $class; | ||
287 | $self->_init(@_); | ||
288 | } | ||
289 | |||
290 | # ------------------------------------------------------------------------- | ||
291 | |||
292 | 1; | ||
293 | |||
294 | __END__ | ||
295 | |||
296 | =head1 NAME | ||
297 | |||
298 | Nagios::Plugin::Getopt - OO perl module providing standardised argument | ||
299 | processing for Nagios plugins | ||
300 | |||
301 | |||
302 | =head1 VERSION | ||
303 | |||
304 | This documentation applies to version 0.01 of Nagios::Plugin::Getopt. | ||
305 | |||
306 | |||
307 | =head1 SYNOPSIS | ||
308 | |||
309 | use Nagios::Plugin::Getopt; | ||
310 | |||
311 | # Instantiate object (usage and version are mandatory) | ||
312 | $ng = Nagios::Plugin::Getopt->new( | ||
313 | usage => "Usage: %s -H <host> -w <warning_threshold> | ||
314 | -c <critical threshold>", | ||
315 | version => '0.01', | ||
316 | url => 'http://www.openfusion.com.au/labs/nagios/', | ||
317 | blurb => 'This plugin tests various stuff.', | ||
318 | ); | ||
319 | |||
320 | # Add argument - named parameters (spec and help are mandatory) | ||
321 | $ng->arg( | ||
322 | spec => 'critical|c=s', | ||
323 | help => qq(-c, --critical=INTEGER\n Exit with CRITICAL status if fewer than INTEGER foobars are free), | ||
324 | required => 1, | ||
325 | default => 10, | ||
326 | ); | ||
327 | |||
328 | # Add argument - positional parameters - arg spec, help text, | ||
329 | # default value, required? (first two mandatory) | ||
330 | $ng->arg( | ||
331 | 'warning|w=s', | ||
332 | qq(-w, --warning=INTEGER\n Exit with WARNING status if fewer than INTEGER foobars are free), | ||
333 | 5, | ||
334 | 1); | ||
335 | |||
336 | # Parse arguments and process standard ones (e.g. usage, help, version) | ||
337 | $ng->getopts; | ||
338 | |||
339 | # Access arguments using named accessors or or via the generic get() | ||
340 | print $ng->warning; | ||
341 | print $ng->get('critical'); | ||
342 | |||
343 | |||
344 | |||
345 | =head1 DESCRIPTION | ||
346 | |||
347 | Nagios::Plugin::Getopt is an OO perl module providing standardised and | ||
348 | simplified argument processing for Nagios plugins. It implements | ||
349 | a number of standard arguments itself (--help, --version, | ||
350 | --usage, --timeout, --verbose, and their short form counterparts), | ||
351 | produces standardised nagios plugin help output, and allows | ||
352 | additional arguments to be easily defined. | ||
353 | |||
354 | |||
355 | =head2 CONSTRUCTOR | ||
356 | |||
357 | # Instantiate object (usage and version are mandatory) | ||
358 | $ng = Nagios::Plugin::Getopt->new( | ||
359 | usage => 'Usage: %s --hello', | ||
360 | version => '0.01', | ||
361 | ); | ||
362 | |||
363 | The Nagios::Plugin::Getopt constructor accepts the following named | ||
364 | arguments: | ||
365 | |||
366 | =over 4 | ||
367 | |||
368 | =item usage (required) | ||
369 | |||
370 | Short usage message used with --usage/-? and with missing required | ||
371 | arguments, and included in the longer --help output. Can include | ||
372 | a '%s' sprintf placeholder which will be replaced with the plugin | ||
373 | name e.g. | ||
374 | |||
375 | usage => qq(Usage: %s -H <hostname> -p <ports> [-v]), | ||
376 | |||
377 | might be displayed as: | ||
378 | |||
379 | $ ./check_tcp_range --usage | ||
380 | Usage: check_tcp_range -H <hostname> -p <ports> [-v] | ||
381 | |||
382 | =item version (required) | ||
383 | |||
384 | Plugin version number, included in the --version/-V output, and in | ||
385 | the longer --help output. e.g. | ||
386 | |||
387 | $ ./check_tcp_range --version | ||
388 | check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/] | ||
389 | |||
390 | =item url | ||
391 | |||
392 | URL for info about this plugin, included in the --version/-V output, | ||
393 | and in the longer --help output (see preceding 'version' example). | ||
394 | |||
395 | =item blurb | ||
396 | |||
397 | Short plugin description, included in the longer --help output | ||
398 | (see below for an example). | ||
399 | |||
400 | =item license | ||
401 | |||
402 | License text, included in the longer --help output (see below for an | ||
403 | example). By default, this is set to the standard nagios plugins | ||
404 | GPL licence text: | ||
405 | |||
406 | This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. | ||
407 | It may be used, redistributed and/or modified under the terms of the GNU | ||
408 | General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt). | ||
409 | |||
410 | Provide your own to replace this text in the help output. | ||
411 | |||
412 | =item extra | ||
413 | |||
414 | Extra text to be appended at the end of the longer --help output. | ||
415 | |||
416 | =item plugin | ||
417 | |||
418 | Plugin name. This defaults to the basename of your plugin, which is | ||
419 | usually correct, but you can set it explicitly if not. | ||
420 | |||
421 | =item timeout | ||
422 | |||
423 | Timeout period in seconds, overriding the standard timeout default | ||
424 | (15 seconds). | ||
425 | |||
426 | =back | ||
427 | |||
428 | The full --help output has the following form: | ||
429 | |||
430 | version string | ||
431 | |||
432 | license string | ||
433 | |||
434 | blurb | ||
435 | |||
436 | usage string | ||
437 | |||
438 | options list | ||
439 | |||
440 | extra text | ||
441 | |||
442 | The 'blurb' and 'extra text' sections are omitted if not supplied. For | ||
443 | example: | ||
444 | |||
445 | $ ./check_tcp_range -h | ||
446 | check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/] | ||
447 | |||
448 | This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. | ||
449 | It may be used, redistributed and/or modified under the terms of the GNU | ||
450 | General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt). | ||
451 | |||
452 | This plugin tests arbitrary ranges/sets of tcp ports for a host. | ||
453 | |||
454 | Usage: check_tcp_range -H <hostname> -p <ports> [-v] | ||
455 | |||
456 | Options: | ||
457 | -h, --help | ||
458 | Print detailed help screen | ||
459 | -V, --version | ||
460 | Print version information | ||
461 | -H, --hostname=ADDRESS | ||
462 | Host name or IP address | ||
463 | -p, --ports=STRING | ||
464 | Port numbers to check. Format: comma-separated, colons or hyphens for ranges, | ||
465 | no spaces e.g. 8700:8705,8710-8715,8760 | ||
466 | -t, --timeout=INTEGER | ||
467 | Seconds before plugin times out (default: 15) | ||
468 | -v, --verbose | ||
469 | Show details for command-line debugging | ||
470 | |||
471 | |||
472 | =head2 ARGUMENTS | ||
473 | |||
474 | You can define arguments for your plugin using the arg() method, which | ||
475 | supports both named and positional arguments. In both cases | ||
476 | the 'spec' and 'help' arguments are required, while the 'default' | ||
477 | and 'required' arguments are optional: | ||
478 | |||
479 | # Define --hello argument (named parameters) | ||
480 | $ng->arg( | ||
481 | spec => 'hello=s', | ||
482 | help => "--hello\n Hello string", | ||
483 | required => 1, | ||
484 | ); | ||
485 | |||
486 | # Define --hello argument (positional parameters) | ||
487 | # Parameter order is 'spec', 'help', 'default', 'required?' | ||
488 | $ng->arg('hello=s', "--hello\n Hello string", undef, 1); | ||
489 | |||
490 | The 'spec' argument (the first argument in the positional variant) is a | ||
491 | L<Getopt::Long> argument specification. See L<Getopt::Long> for the details, | ||
492 | but basically it is a series of one or more argument names for this argument | ||
493 | (separated by '|'), suffixed with an '=<type>' indicator if the argument | ||
494 | takes a value. '=s' indicates a string argument; '=i' indicates an integer | ||
495 | argument; appending an '@' indicates multiple such arguments are accepted; | ||
496 | and so on. The following are some examples: | ||
497 | |||
498 | =over 4 | ||
499 | |||
500 | =item hello=s | ||
501 | |||
502 | =item hello|h=s | ||
503 | |||
504 | =item ports|port|p=i | ||
505 | |||
506 | =item exclude|X=s@ | ||
507 | |||
508 | =item verbose|v | ||
509 | |||
510 | =back | ||
511 | |||
512 | The 'help' argument is a string displayed in the --help option list output. | ||
513 | If the string contains a '%s' it will be formatted via L<sprintf> with the | ||
514 | 'default' as the argument i.e. | ||
515 | |||
516 | sprintf($help, $default) | ||
517 | |||
518 | A gotcha is that standard percentage signs also need to be escaped | ||
519 | (i.e. '%%') in this case. | ||
520 | |||
521 | The 'default' argument is the default value to be given to this parameter | ||
522 | if none is explicitly supplied. | ||
523 | |||
524 | The 'required' argument is a boolean used to indicate that this argument | ||
525 | is mandatory (Nagios::Plugin::Getopt will exit with your usage message and | ||
526 | a 'Missing argument' indicator if any required arguments are not supplied). | ||
527 | |||
528 | Note that --help lists your arguments in the order they are defined, so | ||
529 | you might want to order your arg() calls accordingly. | ||
530 | |||
531 | |||
532 | =head2 GETOPTS | ||
533 | |||
534 | The main parsing and processing functionality is provided by the getopts() | ||
535 | method, which takes no arguments: | ||
536 | |||
537 | # Parse and process arguments | ||
538 | $ng->getopts; | ||
539 | |||
540 | This parses the command line arguments passed to your plugin using | ||
541 | Getopt::Long and the builtin and provided argument specifications. | ||
542 | Flags and argument values are recorded within the object, and can | ||
543 | be accessed either using the generic get() accessor, or using named | ||
544 | accessors corresponding to your argument names. For example: | ||
545 | |||
546 | print $ng->get('hello'); | ||
547 | print $ng->hello(); | ||
548 | |||
549 | if ($ng->verbose) { | ||
550 | # ... | ||
551 | } | ||
552 | |||
553 | if ($ng->get('ports') =~ m/:/) { | ||
554 | # ... | ||
555 | } | ||
556 | |||
557 | Note that where you have defined alternate argument names, the first is | ||
558 | considered the citation form. All the builtin arguments are available | ||
559 | using their long variant names. | ||
560 | |||
561 | |||
562 | =head2 BUILTIN PROCESSING | ||
563 | |||
564 | The getopts() method also handles processing of the immediate builtin | ||
565 | arguments, namely --usage, --version, --help, as well as checking all | ||
566 | required arguments have been supplied, so you don't have to handle | ||
567 | those yourself. This means that your plugin will exit from the getopts() | ||
568 | call in these cases - if you want to catch that you can run getopts() | ||
569 | within an eval{}. | ||
570 | |||
571 | getopts() also sets up a default ALRM timeout handler so you can use an | ||
572 | |||
573 | alarm $ng->timeout; | ||
574 | |||
575 | around any blocking operations within your plugin (which you are free | ||
576 | to override if you want to use a custom timeout message). | ||
577 | |||
578 | |||
579 | =head1 SEE ALSO | ||
580 | |||
581 | Nagios::Plugin, Getopt::Long | ||
582 | |||
583 | |||
584 | =head1 AUTHOR | ||
585 | |||
586 | Gavin Carr <gavin@openfusion.com.au> | ||
587 | |||
588 | |||
589 | =head1 COPYRIGHT AND LICENSE | ||
590 | |||
591 | Copyright 2005-2006 Gavin Carr. All Rights Reserved. | ||
592 | |||
593 | This module is free software. It may be used, redistributed | ||
594 | and/or modified under either the terms of the Perl Artistic | ||
595 | License (see http://www.perl.com/perl/misc/Artistic.html) | ||
596 | or the GNU General Public Licence (see | ||
597 | http://www.fsf.org/licensing/licenses/gpl.txt). | ||
598 | |||
599 | =cut | ||
600 | |||
601 | # arch-tag: c917effc-7400-4ee5-a5d6-baa9316a3abf | ||
602 | # vim:smartindent:sw=2:et | ||
603 | |||