From b418181dfe80dd75169b6e8a619ac1932155dea2 Mon Sep 17 00:00:00 2001 From: Sven Nierlein Date: Mon, 20 Jan 2014 00:54:34 +0100 Subject: renamed module into Monitoring::Plugin since the complete monitoring team has been renamed, we also rename this module. Signed-off-by: Sven Nierlein diff --git a/.gitignore b/.gitignore index 6cf3bbc..93cc521 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,8 @@ /pm_to_blib /.bzr /.bzrignore +MYMETA.json +MYMETA.yml +Makefile.old +inc/ +MANIFEST.bak diff --git a/Changes b/Changes index 2034b6c..b85c2ac 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,7 @@ -Revision history for Perl module Nagios::Plugin. +Revision history for Perl module Monitoring::Plugin. + +0.37 20nd January 2014 + - renamed module due to trademark issues 0.36 22nd December 2011 - Updated check_threshold to allow multiple check values to be checked at once diff --git a/MANIFEST b/MANIFEST index 39be2c5..8ccb51c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,33 +1,47 @@ Changes -lib/Nagios/Plugin.pm -lib/Nagios/Plugin/Config.pm -lib/Nagios/Plugin/ExitResult.pm -lib/Nagios/Plugin/Functions.pm -lib/Nagios/Plugin/Getopt.pm -lib/Nagios/Plugin/Performance.pm -lib/Nagios/Plugin/Range.pm -lib/Nagios/Plugin/Threshold.pm +inc/Module/AutoInstall.pm +inc/Module/Install.pm +inc/Module/Install/AutoInstall.pm +inc/Module/Install/Base.pm +inc/Module/Install/Can.pm +inc/Module/Install/Fetch.pm +inc/Module/Install/Include.pm +inc/Module/Install/Makefile.pm +inc/Module/Install/Metadata.pm +inc/Module/Install/Win32.pm +inc/Module/Install/WriteAll.pm +lib/Monitoring/Plugin.pm +lib/Monitoring/Plugin/Config.pm +lib/Monitoring/Plugin/ExitResult.pm +lib/Monitoring/Plugin/Functions.pm +lib/Monitoring/Plugin/Getopt.pm +lib/Monitoring/Plugin/Performance.pm +lib/Monitoring/Plugin/Range.pm +lib/Monitoring/Plugin/Threshold.pm Makefile.PL MANIFEST This list of files META.yml +notes README t/check_stuff.pl t/check_stuff.t -t/Nagios-Plugin-01.t -t/Nagios-Plugin-02.t -t/Nagios-Plugin-03.t -t/Nagios-Plugin-04.t -t/Nagios-Plugin-Functions-01.t -t/Nagios-Plugin-Functions-02.t -t/Nagios-Plugin-Functions-03.t -t/Nagios-Plugin-Getopt-01.t -t/Nagios-Plugin-Getopt-02.t -t/Nagios-Plugin-Getopt-03.t -t/Nagios-Plugin-Getopt-04.t -t/Nagios-Plugin-Performance-02.t -t/Nagios-Plugin-Performance.t -t/Nagios-Plugin-Range.t -t/Nagios-Plugin-Threshold.t +t/Monitoring-Plugin-01.t +t/Monitoring-Plugin-02.t +t/Monitoring-Plugin-03.t +t/Monitoring-Plugin-04.t +t/Monitoring-Plugin-05.t +t/Monitoring-Plugin-Functions-01.t +t/Monitoring-Plugin-Functions-02.t +t/Monitoring-Plugin-Functions-03.t +t/Monitoring-Plugin-Functions-04.t +t/Monitoring-Plugin-Getopt-01.t +t/Monitoring-Plugin-Getopt-02.t +t/Monitoring-Plugin-Getopt-03.t +t/Monitoring-Plugin-Getopt-04.t +t/Monitoring-Plugin-Performance-02.t +t/Monitoring-Plugin-Performance.t +t/Monitoring-Plugin-Range.t +t/Monitoring-Plugin-Threshold.t t/npg03/expected/00_basic t/npg03/expected/00_noextra t/npg03/expected/01_override1 @@ -38,8 +52,10 @@ t/npg03/expected/05_disk3 t/npg03/expected/05_disk4 t/npg03/expected/05_disk5 t/npg03/expected/05_disk6 +t/npg03/expected/05_disk7 t/npg03/expected/09_funnystuff t/npg03/expected/12_nosection_implicit +t/npg03/expected/15_badsection_catch t/npg03/input/00_basic t/npg03/input/00_noextra t/npg03/input/01_override1 @@ -50,9 +66,11 @@ t/npg03/input/05_disk3 t/npg03/input/05_disk4 t/npg03/input/05_disk5 t/npg03/input/05_disk6 +t/npg03/input/05_disk7 t/npg03/input/09_funnystuff t/npg03/input/12_nosection_implicit t/npg03/input/13_nosection_explicit_dies t/npg03/input/14_badsection_dies +t/npg03/input/15_badsection_catch t/npg03/plugins.ini t/npg03/README diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP new file mode 100644 index 0000000..134a92b --- /dev/null +++ b/MANIFEST.SKIP @@ -0,0 +1,21 @@ +.DS_Store +\.swp +blib +tmp +\.git +^Makefile$ +Makefile.old +MANIFEST.bak +MANIFEST.SKIP +MYMETA.yml +MYMETA.json +pm_to_blib +.*\.gz +# does not work for centos without F::C::R installed +inc/File/Copy/Recursive.pm +TODO +build-stamp +configure-stamp +nytprof/ +nytprof.out +.~ko-6.1.3-perllint~ diff --git a/Makefile.PL b/Makefile.PL index c043edb..d1f0c2a 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,22 +1,23 @@ -use 5.006; -use ExtUtils::MakeMaker; -# See lib/ExtUtils/MakeMaker.pm for details of how to influence -# the contents of the Makefile that is written. -WriteMakefile( - NAME => 'Nagios::Plugin', - VERSION_FROM => 'lib/Nagios/Plugin/Functions.pm', # finds $VERSION - PREREQ_PM => { - Params::Validate => 0, - Class::Accessor => 0, - Test::More => 0.62, - Carp => 0, - Config::Tiny => 0, - File::Spec => 0, - File::Basename => 0, - IO::File => 0, - Math::Calc::Units => 0, # used in N::P::Performance - }, # e.g., Module::Name => 1.1 - ($] >= 5.005 ? ## Add these new keywords supported since 5.005 - (ABSTRACT_FROM => 'lib/Nagios/Plugin.pm', # retrieve abstract from module - AUTHOR => 'Nagios Plugin Development Team ') : ()), -); +use inc::Module::Install; + +name 'Monitoring-Plugin'; +all_from 'lib/Monitoring/Plugin/Functions.pm'; +author q{Monitoring Plugin Team }; +license 'perl'; +repository 'https://github.com/monitoring-plugins/monitoring-plugin-perl'; + +requires 'Params::Validate' => 0; +requires 'Class::Accessor' => 0; +requires 'Carp' => 0; +requires 'Config::Tiny' => 0; +requires 'File::Spec' => 0; +requires 'File::Basename' => 0; +requires 'IO::File' => 0; +requires 'Math::Calc::Units' => 0; # used in M::P::Performance + + +build_requires 'Test::More' => 0.62; + +auto_install; + +WriteAll; diff --git a/README b/README index 939cc36..b0550d6 100644 --- a/README +++ b/README @@ -1,13 +1,12 @@ -Nagios::Plugin -============== +Monitoring::Plugin +================== -These modules are meant for perl developers of plugins for Nagios -(http://nagiosplug.sourceforge.net). It is meant to simplify a lot -of the common functions required to do checking of a particular -service. - -The modules are still in an experimental stage and will be considered -stable when it reaches version 1.0. +These modules are meant for perl developers of plugins for Naemon, Nagios, +Icinga, Shinken and other compatible products. It is meant to +simplify a lot of the common functions required to do checking of a +particular service. +This module is maintained by the Monitoring-Plugins team +(https:://monitoring-plugins.org) INSTALLATION @@ -23,16 +22,14 @@ EXAMPLE SCRIPT "Enough talk! Show me where to start!" -See the file 'check_stuff.pl' in the 't' directory for a complete +See the file 'check_stuff.pl' in the 't' directory for a complete working example of a plugin script. COPYRIGHT AND LICENCE -Copyright (C) 2006 by Nagios Plugin Development Team +Copyright (C) 2006-2014 by Monitoring Plugin Team This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.4 or, at your option, any later version of Perl 5 you may have available. - - diff --git a/lib/Monitoring/Plugin.pm b/lib/Monitoring/Plugin.pm new file mode 100644 index 0000000..f9eb49e --- /dev/null +++ b/lib/Monitoring/Plugin.pm @@ -0,0 +1,712 @@ + +package Monitoring::Plugin; + +use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT @STATUS_CODES); +use Params::Validate qw(:all); + +use strict; +use warnings; + +use Carp; +use base qw(Class::Accessor::Fast); + +Monitoring::Plugin->mk_accessors(qw( + shortname + perfdata + messages + opts + threshold + )); + +use Exporter; +our @ISA = qw(Exporter); +our @EXPORT = (@STATUS_CODES); +our @EXPORT_OK = qw(%ERRORS %STATUS_TEXT); + +# CPAN stupidly won't index this module without a literal $VERSION here, +# so we're forced to duplicate it explicitly +# Make sure you update $Monitoring::Plugin::Functions::VERSION too +our $VERSION = "0.37"; + +sub new { + my $class = shift; +# my %args = @_; + + my %args = validate( @_, + { + shortname => 0, + usage => 0, + version => 0, + url => 0, + plugin => 0, + blurb => 0, + extra => 0, + license => 0, + timeout => 0 + }, + ); + + my $shortname = Monitoring::Plugin::Functions::get_shortname(\%args); + delete $args{shortname} if (exists $args{shortname}); + my $self = { + shortname => $shortname, + perfdata => [], # to be added later + messages => { + warning => [], + critical => [], + ok => [] + }, + opts => undef, # see below + threshold => undef, # defined later + }; + bless $self, $class; + if (exists $args{usage}) { + require Monitoring::Plugin::Getopt; + $self->opts( new Monitoring::Plugin::Getopt(%args) ); + } + return $self; +} + +sub add_perfdata { + my ($self, %args) = @_; + require Monitoring::Plugin::Performance; + my $perf = Monitoring::Plugin::Performance->new(%args); + push @{$self->perfdata}, $perf; +} +sub all_perfoutput { + my $self = shift; + return join(" ", map {$_->perfoutput} (@{$self->perfdata})); +} + +sub set_thresholds { + my $self = shift; + require Monitoring::Plugin::Threshold; + return $self->threshold( Monitoring::Plugin::Threshold->set_thresholds(@_)); +} + +# MP::Functions wrappers +sub plugin_exit { + my $self = shift; + Monitoring::Plugin::Functions::plugin_exit(@_, { plugin => $self }); +} +sub plugin_die { + my $self = shift; + Monitoring::Plugin::Functions::plugin_die(@_, { plugin => $self }); +} +sub nagios_exit { + my $self = shift; + Monitoring::Plugin::Functions::plugin_exit(@_, { plugin => $self }); +} +sub nagios_die { + my $self = shift; + Monitoring::Plugin::Functions::plugin_die(@_, { plugin => $self }); +} +sub die { + my $self = shift; + Monitoring::Plugin::Functions::plugin_die(@_, { plugin => $self }); +} +sub max_state { + Monitoring::Plugin::Functions::max_state(@_); +} +sub max_state_alt { + Monitoring::Plugin::Functions::max_state_alt(@_); +} + +# top level interface to Monitoring::Plugin::Threshold +sub check_threshold { + my $self = shift; + + my %args; + + if ( $#_ == 0 && (! ref $_[0] || ref $_[0] eq "ARRAY" )) { # one positional param + %args = (check => shift); + } + else { + %args = validate ( @_, { # named params + check => 1, + warning => 0, + critical => 0, + } ); + } + + # in order of preference, get warning and critical from + # 1. explicit arguments to check_threshold + # 2. previously explicitly set threshold object + # 3. implicit options from Getopts object + if ( exists $args{warning} || exists $args{critical} ) { + $self->set_thresholds( + warning => $args{warning}, + critical => $args{critical}, + ); + } + elsif ( defined $self->threshold ) { + # noop + } + elsif ( defined $self->opts ) { + $self->set_thresholds( + warning => $self->opts->warning, + critical => $self->opts->critical, + ); + } + else { + return UNKNOWN; + } + + return $self->threshold->get_status($args{check}); +} + +# top level interface to my Monitoring::Plugin::Getopt object +sub add_arg { + my $self = shift; + $self->opts->arg(@_) if $self->_check_for_opts; +} +sub getopts { + my $self = shift; + $self->opts->getopts(@_) if $self->_check_for_opts; +} + +sub _check_for_opts { + my $self = shift; + croak + "You have to supply a 'usage' param to Monitoring::Plugin::new() if you want to use Getopts from your Monitoring::Plugin object." + unless ref $self->opts() eq 'Monitoring::Plugin::Getopt'; + return $self; +} + + + +# ------------------------------------------------------------------------- +# MP::Functions::check_messages helpers and wrappers + +sub add_message { + my $self = shift; + my ($code, @messages) = @_; + + croak "Invalid error code '$code'" + unless defined($ERRORS{uc $code}) || defined($STATUS_TEXT{$code}); + + # Store messages using strings rather than numeric codes + $code = $STATUS_TEXT{$code} if $STATUS_TEXT{$code}; + $code = lc $code; + croak "Error code '$code' not supported by add_message" + if $code eq 'unknown' || $code eq 'dependent'; + + $self->messages($code, []) unless $self->messages->{$code}; + push @{$self->messages->{$code}}, @messages; +} + +sub check_messages { + my $self = shift; + my %args = @_; + + # Add object messages to any passed in as args + for my $code (qw(critical warning ok)) { + my $messages = $self->messages->{$code} || []; + if ($args{$code}) { + unless (ref $args{$code} eq 'ARRAY') { + if ($code eq 'ok') { + $args{$code} = [ $args{$code} ]; + } else { + croak "Invalid argument '$code'" + } + } + push @{$args{$code}}, @$messages; + } + else { + $args{$code} = $messages; + } + } + + Monitoring::Plugin::Functions::check_messages(%args); +} + +# ------------------------------------------------------------------------- + +1; + +#vim:et:sw=4 + +__END__ + +=head1 NAME + +Monitoring::Plugin - A family of perl modules to streamline writing Naemon, Nagios, +Icinga or Shinken (and compatible) plugins. + +=head1 SYNOPSIS + + # Constants OK, WARNING, CRITICAL, and UNKNOWN are exported by default + # See also Monitoring::Plugin::Functions for a functional interface + use Monitoring::Plugin; + + # Constructor + $np = Monitoring::Plugin->new; # OR + $np = Monitoring::Plugin->new( shortname => "PAGESIZE" ); # OR + + + # use Monitoring::Plugin::Getopt to process the @ARGV command line options: + # --verbose, --help, --usage, --timeout and --host are defined automatically. + $np = Monitoring::Plugin->new( + usage => "Usage: %s [ -v|--verbose ] [-H ] [-t ] " + . "[ -c|--critical= ] [ -w|--warning= ]", + ); + + # add valid command line options and build them into your usage/help documentation. + $np->add_arg( + spec => 'warning|w=s', + help => '-w, --warning=INTEGER:INTEGER . See ' + . 'https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT ' + . 'for the threshold format. ', + ); + + # Parse @ARGV and process standard arguments (e.g. usage, help, version) + $np->getopts; + + + # Exit/return value methods - plugin_exit( CODE, MESSAGE ), + # plugin_die( MESSAGE, [CODE]) + $page = retrieve_page($page1) + or $np->plugin_exit( UNKNOWN, "Could not retrieve page" ); + # Return code: 3; + # output: PAGESIZE UNKNOWN - Could not retrieve page + test_page($page) + or $np->plugin_exit( CRITICAL, "Bad page found" ); + + # plugin_die() is just like plugin_exit(), but return code defaults + # to UNKNOWN + $page = retrieve_page($page2) + or $np->plugin_die( "Could not retrieve page" ); + # Return code: 3; + # output: PAGESIZE UNKNOWN - Could not retrieve page + + # Threshold methods + $code = $np->check_threshold( + check => $value, + warning => $warning_threshold, + critical => $critical_threshold, + ); + $np->plugin_exit( $code, "Threshold check failed" ) if $code != OK; + + + # Message methods (EXPERIMENTAL AND SUBJECT TO CHANGE) - + # add_message( CODE, $message ); check_messages() + for (@collection) { + if (m/Error/) { + $np->add_message( CRITICAL, $_ ); + } else { + $np->add_message( OK, $_ ); + } + } + ($code, $message) = $np->check_messages(); + plugin_exit( $code, $message ); + # If any items in collection matched m/Error/, returns CRITICAL and + # the joined set of Error messages; otherwise returns OK and the + # joined set of ok messages + + + # Perfdata methods + $np->add_perfdata( + label => "size", + value => $value, + uom => "kB", + threshold => $threshold, + ); + $np->add_perfdata( label => "time", ... ); + $np->plugin_exit( OK, "page size at http://... was ${value}kB" ); + # Return code: 0; + # output: PAGESIZE OK - page size at http://... was 36kB \ + # | size=36kB;10:25;25: time=... + + +=head1 DESCRIPTION + +Monitoring::Plugin and its associated Monitoring::Plugin::* modules are a +family of perl modules to streamline writing Monitoring plugins. The main +end user modules are Monitoring::Plugin, providing an object-oriented +interface to the entire Monitoring::Plugin::* collection, and +Monitoring::Plugin::Functions, providing a simpler functional interface to +a useful subset of the available functionality. + +The purpose of the collection is to make it as simple as possible for +developers to create plugins that conform the Monitoring Plugin guidelines +(https://www.monitoring-plugins.org/doc/guidelines.html). + + +=head2 EXPORTS + +Nagios status code constants are exported by default: + + OK + WARNING + CRITICAL + UNKNOWN + DEPENDENT + +The following variables are also exported on request: + +=over 4 + +=item %ERRORS + +A hash mapping error strings ("CRITICAL", "UNKNOWN", etc.) to the +corresponding status code. + +=item %STATUS_TEXT + +A hash mapping status code constants (OK, WARNING, CRITICAL, etc.) to the +corresponding error string ("OK", "WARNING, "CRITICAL", etc.) i.e. the +reverse of %ERRORS. + +=back + + +=head2 CONSTRUCTOR + + Monitoring::Plugin->new; + + Monitoring::Plugin->new( shortname => 'PAGESIZE' ); + + Monitoring::Plugin->new( + usage => "Usage: %s [ -v|--verbose ] [-H ] [-t ] + [ -c|--critical= ] [ -w|--warning= ] ", + version => $VERSION, + blurb => $blurb, + extra => $extra, + url => $url, + license => $license, + plugin => basename $0, + timeout => 15, + ); + +Instantiates a new Monitoring::Plugin object. Accepts the following named +arguments: + +=over 4 + +=item shortname + +The 'shortname' for this plugin, used as the first token in the plugin +output by the various exit methods. Default: uc basename $0. + +=item usage ("Usage: %s --foo --bar") + +Passing a value for the usage() argument makes Monitoring::Plugin +instantiate its own C object so you can start +doing command line argument processing. See +L for more about "usage" and the +following options: + +=item version + +=item url + +=item blurb + +=item license + +=item extra + +=item plugin + +=item timeout + +=back + +=head2 OPTION HANDLING METHODS + +C provides these methods for accessing the functionality in C. + +=over 4 + +=item add_arg + +Examples: + + # Define --hello argument (named parameters) + $plugin->add_arg( + spec => 'hello=s', + help => "--hello\n Hello string", + required => 1, + ); + + # Define --hello argument (positional parameters) + # Parameter order is 'spec', 'help', 'default', 'required?' + $plugin->add_arg('hello=s', "--hello\n Hello string", undef, 1); + +See L for more details. + +=item getopts() + +Parses and processes the command line options you've defined, +automatically doing the right thing with help/usage/version arguments. + +See L for more details. + +=item opts() + +Assuming you've instantiated it by passing 'usage' to new(), opts() +returns the Monitoring::Plugin object's C object, +with which you can do lots of great things. + +E.g. + + if ( $plugin->opts->verbose ) { + print "yah yah YAH YAH YAH!!!"; + } + + # start counting down to timeout + alarm $plugin->opts->timeout; + your_long_check_step_that_might_time_out(); + + # access any of your custom command line options, + # assuming you've done these steps above: + # $plugin->add_arg('my_argument=s', '--my_argument [STRING]'); + # $plugin->getopts; + print $plugin->opts->my_argument; + +Again, see L. + +=back + +=head2 EXIT METHODS + +=over 4 + +=item plugin_exit( , $message ) + +Exit with return code CODE, and a standard nagios message of the +form "SHORTNAME CODE - $message". + +=item plugin_die( $message, [] ) + +Same as plugin_exit(), except that CODE is optional, defaulting +to UNKNOWN. NOTE: exceptions are not raised by default to calling code. +Set C<$_use_die> flag if this functionality is required (see test code). + +=item nagios_exit( , $message ) + +Alias for plugin_die(). Deprecated. + +=item nagios_die( $message, [] ) + +Alias for plugin_die(). Deprecated. + +=item die( $message, [] ) + +Alias for plugin_die(). Deprecated. + +=item max_state, max_state_alt + +These are wrapper function for Monitoring::Plugin::Functions::max_state and +Monitoring::Plugin::Functions::max_state_alt. + +=back + +=head2 THRESHOLD METHODS + +These provide a top level interface to the +C module; for more details, see +L and L. + +=over 4 + +=item check_threshold( $value ) + +=item check_threshold( check => $value, warning => $warn, critical => $crit ) + +Evaluates $value against the thresholds and returns OK, CRITICAL, or +WARNING constant. The thresholds may be: + +1. explicitly set by passing 'warning' and/or 'critical' parameters to + C, or, + +2. explicitly set by calling C before C, or, + +3. implicitly set by command-line parameters -w, -c, --critical or + --warning, if you have run C<< $plugin->getopts() >>. + +You can specify $value as an array of values and each will be checked against +the thresholds. + +The return value is ready to pass to C , e . g ., + + $p->plugin_exit( + return_code => $p->check_threshold($result), + message => " sample result was $result" + ); + + +=item set_thresholds(warning => "10:25", critical => "~:25") + +Sets the acceptable ranges and creates the plugin's +Monitoring::Plugins::Threshold object. See +https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT +for details and examples of the threshold format. + +=item threshold() + +Returns the object's C object, if it has +been defined by calling set_thresholds(). You can pass a new +Threshold object to it to replace the old one too, but you shouldn't +need to do that from a plugin script. + +=back + +=head2 MESSAGE METHODS + +EXPERIMENTAL AND SUBJECT TO CHANGE + +add_messages and check_messages are higher-level convenience methods to add +and then check a set of messages, returning an appropriate return code +and/or result message. They are equivalent to maintaining a set of @critical, +@warning, and and @ok message arrays (add_message), and then doing a final +if test (check_messages) like this: + + if (@critical) { + plugin_exit( CRITICAL, join(' ', @critical) ); + } + elsif (@warning) { + plugin_exit( WARNING, join(' ', @warning) ); + } + else { + plugin_exit( OK, join(' ', @ok) ); + } + +=over 4 + +=item add_message( , $message ) + +Add a message with CODE status to the object. May be called multiple times. +The messages added are checked by check_messages, following. + +Only CRITICAL, WARNING, and OK are accepted as valid codes. + + +=item check_messages() + +Check the current set of messages and return an appropriate nagios return +code and/or a result message. In scalar context, returns only a return +code; in list context returns both a return code and an output message, +suitable for passing directly to plugin_exit() e.g. + + $code = $np->check_messages; + ($code, $message) = $np->check_messages; + +check_messages returns CRITICAL if any critical messages are found, WARNING +if any warning messages are found, and OK otherwise. The message returned +in list context defaults to the joined set of error messages; this may be +customised using the arguments below. + +check_messages accepts the following named arguments (none are required): + +=over 4 + +=item join => SCALAR + +A string used to join the relevant array to generate the message +string returned in list context i.e. if the 'critical' array @crit +is non-empty, check_messages would return: + + join( $join, @crit ) + +as the result message. Default: ' ' (space). + +=item join_all => SCALAR + +By default, only one set of messages are joined and returned in the +result message i.e. if the result is CRITICAL, only the 'critical' +messages are included in the result; if WARNING, only the 'warning' +messages are included; if OK, the 'ok' messages are included (if +supplied) i.e. the default is to return an 'errors-only' type +message. + +If join_all is supplied, however, it will be used as a string to +join the resultant critical, warning, and ok messages together i.e. +all messages are joined and returned. + +=item critical => ARRAYREF + +Additional critical messages to supplement any passed in via add_message(). + +=item warning => ARRAYREF + +Additional warning messages to supplement any passed in via add_message(). + +=item ok => ARRAYREF | SCALAR + +Additional ok messages to supplement any passed in via add_message(). + +=back + +=back + + +=head2 PERFORMANCE DATA METHODS + +=over 4 + +=item add_perfdata( label => "size", value => $value, uom => "kB", threshold => $threshold ) + +Add a set of performance data to the object. May be called multiple times. +The performance data is included in the standard plugin output messages by +the various exit methods. + +See the Monitoring::Plugin::Performance documentation for more information on +performance data and the various field definitions, as well as the relevant +section of the Monitoring Plugin guidelines +(https://www.monitoring-plugins.org/doc/guidelines.html#AEN202). + +=back + + +=head1 EXAMPLES + +"Enough talk! Show me some examples!" + +See the file 'check_stuff.pl' in the 't' directory included with the +Monitoring::Plugin distribution for a complete working example of a plugin +script. + + +=head1 VERSIONING + +The Monitoring::Plugin::* modules are currently experimental and so the +interfaces may change up until Monitoring::Plugin hits version 1.0, although +every attempt will be made to keep them as backwards compatible as +possible. + + +=head1 SEE ALSO + +See L for a simple functional interface to a subset +of the available Monitoring::Plugin functionality. + +See also L, L, +L, L, and +L. + +The Monitoring Plugin project page is at http://monitoring-plugins.org. + + +=head1 BUGS + +Please report bugs in these modules to the Monitoring Plugin development team: +devel@monitoring-plugins.org. + + +=head1 AUTHOR + +Maintained by the Monitoring Plugin development team - +https://www.monitoring-plugins.org. + +Originally by Ton Voon, Eton.voon@altinity.comE. + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006-2014 by Monitoring Plugin Team + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself, either Perl version 5.8.4 or, at your +option, any later version of Perl 5 you may have available. + +=cut diff --git a/lib/Monitoring/Plugin/Config.pm b/lib/Monitoring/Plugin/Config.pm new file mode 100644 index 0000000..5e941d4 --- /dev/null +++ b/lib/Monitoring/Plugin/Config.pm @@ -0,0 +1,177 @@ +package Monitoring::Plugin::Config; + +use strict; +use Carp; +use File::Spec; +use base qw(Config::Tiny); + +my $FILENAME1 = 'plugins.ini'; +my $FILENAME2 = 'nagios-plugins.ini'; +my $FILENAME3 = 'monitoring-plugins.ini'; +my $CURRENT_FILE = undef; + +# Config paths ending in nagios (search for $FILENAME1) +my @MONITORING_CONFIG_PATH = qw(/etc/nagios /usr/local/nagios/etc /usr/local/etc/nagios /etc/opt/nagios); +# Config paths not ending in nagios (search for $FILENAME2) +my @CONFIG_PATH = qw(/etc /usr/local/etc /etc/opt); + +# Override Config::Tiny::read to default the filename, if not given +sub read +{ + my $class = shift; + + unless ($_[0]) { + SEARCH: { + if ($ENV{MONITORING_CONFIG_PATH} || $ENV{NAGIOS_CONFIG_PATH}) { + for (split /:/, ($ENV{MONITORING_CONFIG_PATH} || $ENV{NAGIOS_CONFIG_PATH})) { + my $file = File::Spec->catfile($_, $FILENAME1); + unshift(@_, $file), last SEARCH if -f $file; + $file = File::Spec->catfile($_, $FILENAME2); + unshift(@_, $file), last SEARCH if -f $file; + $file = File::Spec->catfile($_, $FILENAME3); + unshift(@_, $file), last SEARCH if -f $file; + } + } + for (@MONITORING_CONFIG_PATH) { + my $file = File::Spec->catfile($_, $FILENAME1); + unshift(@_, $file), last SEARCH if -f $file; + } + for (@CONFIG_PATH) { + my $file = File::Spec->catfile($_, $FILENAME2); + unshift(@_, $file), last SEARCH if -f $file; + $file = File::Spec->catfile($_, $FILENAME3); + unshift(@_, $file), last SEARCH if -f $file; + } + } + + # Use die instead of croak, so we can pass a clean message downstream + die "Cannot find '$FILENAME1', '$FILENAME2' or '$FILENAME3' in any standard location.\n" unless $_[0]; + } + + $CURRENT_FILE = $_[0]; + $class->SUPER::read( @_ ); +} + +# Straight from Config::Tiny - only changes are repeated property key support +# Would be nice if we could just override the per-line handling ... +sub read_string +{ + my $class = ref $_[0] ? ref shift : shift; + my $self = bless {}, $class; + return undef unless defined $_[0]; + + # Parse the file + my $ns = '_'; + my $counter = 0; + foreach ( split /(?:\015{1,2}\012|\015|\012)/, shift ) { + $counter++; + + # Skip comments and empty lines + next if /^\s*(?:\#|\;|$)/; + + # Handle section headers + if ( /^\s*\[\s*(.+?)\s*\]\s*$/ ) { + # Create the sub-hash if it doesn't exist. + # Without this sections without keys will not + # appear at all in the completed struct. + $self->{$ns = $1} ||= {}; + next; + } + + # Handle properties + if ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) { + push @{$self->{$ns}->{$1}}, $2; + next; + } + + return $self->_error( "Syntax error at line $counter: '$_'" ); + } + + $self; +} + +sub write { croak "Write access not permitted" } + +# Return last file used by read(); +sub np_getfile { return $CURRENT_FILE; } + +1; + +=head1 NAME + +Monitoring::Plugin::Config - read nagios plugin .ini style config files + +=head1 SYNOPSIS + + # Read given nagios plugin config file + $Config = Monitoring::Plugin::Config->read( '/etc/nagios/plugins.ini' ); + + # Search for and read default nagios plugin config file + $Config = Monitoring::Plugin::Config->read(); + + # Access sections and properties (returns scalars or arrayrefs) + $rootproperty = $Config->{_}->{rootproperty}; + $one = $Config->{section}->{one}; + $Foo = $Config->{section}->{Foo}; + +=head1 DESCRIPTION + +Monitoring::Plugin::Config is a subclass of the excellent Config::Tiny, +with the following changes: + +=over 4 + +=item + +Repeated keys are allowed within sections, returning lists instead of scalars + +=item + +Write functionality has been removed i.e. access is read only + +=item + +Monitoring::Plugin::Config searches for a default nagios plugins file if no explicit +filename is given to C. The current standard locations checked are: + +=over 4 + +=item /etc/nagios/plugins.ini + +=item /usr/local/nagios/etc/plugins.ini + +=item /usr/local/etc/nagios /etc/opt/nagios/plugins.ini + +=item /etc/nagios-plugins.ini + +=item /usr/local/etc/nagios-plugins.ini + +=item /etc/opt/nagios-plugins.ini + +=back + +To use a custom location, set a C environment variable +to the set of directories that should be checked. The first C or +C file found will be used. + +=back + + +=head1 SEE ALSO + +L, L + + +=head1 AUTHOR + +This code is maintained by the Monitoring Plugin Development Team: see +https://monitoring-plugins.org + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006-2014 Monitoring Plugin Development Team + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Monitoring/Plugin/ExitResult.pm b/lib/Monitoring/Plugin/ExitResult.pm new file mode 100644 index 0000000..aa9f5da --- /dev/null +++ b/lib/Monitoring/Plugin/ExitResult.pm @@ -0,0 +1,71 @@ +# Tiny helper class to return both output and return_code when testing + +package Monitoring::Plugin::ExitResult; + +use strict; + +# Stringify to message +use overload '""' => sub { shift->{message} }; + +# Constructor +sub new { + my $class = shift; + return bless { return_code => $_[0], message => $_[1] }, $class; +} + +# Accessors +sub message { shift->{message} } +sub return_code { shift->{return_code} } +sub code { shift->{return_code} } + +1; + +__END__ + +=head1 NAME + +Monitoring::Plugin::ExitResult - Helper class for returning both output and +return codes when testing. + +=head1 SYNOPSIS + + use Test::More; + use Monitoring::Plugin::Functions; + + # In a test file somewhere + Monitoring::Plugin::Functions::_fake_exit(1); + + # Later ... + $e = plugin_exit( CRITICAL, 'aiiii ...' ); + print $e->message; + print $e->return_code; + + # MP::ExitResult also stringifies to the message output + like(plugin_exit( WARNING, 'foobar'), qr/^foo/, 'matches!'); + + + +=head1 DESCRIPTION + +Monitoring::Plugin::ExitResult is a tiny helper class intended for use +when testing other Monitoring::Plugin modules. A Monitoring::Plugin::ExitResult +object is returned by plugin_exit() and friends when +Monitoring::Plugin::Functions::_fake_exit has been set, instead of doing a +conventional print + exit. + +=head1 AUTHOR + +This code is maintained by the Monitoring Plugin Development Team: see +https://monitoring-plugins.org + +Originally: + Gavin Carr , Egavin@openfusion.com.auE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006-2014 Monitoring Plugin Development Team + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Monitoring/Plugin/Functions.pm b/lib/Monitoring/Plugin/Functions.pm new file mode 100644 index 0000000..d2856e8 --- /dev/null +++ b/lib/Monitoring/Plugin/Functions.pm @@ -0,0 +1,445 @@ +# Functional interface to basic Monitoring::Plugin constants, exports, +# and functions + +package Monitoring::Plugin::Functions; + +use 5.006; + +use strict; +use warnings; +use File::Basename; +use Params::Validate qw(:types validate); +use Math::Calc::Units; + +# Remember to update Monitoring::Plugins as well +our $VERSION = "0.37"; + +our @STATUS_CODES = qw(OK WARNING CRITICAL UNKNOWN DEPENDENT); + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT = (@STATUS_CODES, qw(plugin_exit plugin_die check_messages)); +our @EXPORT_OK = qw(%ERRORS %STATUS_TEXT @STATUS_CODES get_shortname max_state max_state_alt convert $value_re); +our %EXPORT_TAGS = ( + all => [ @EXPORT, @EXPORT_OK ], + codes => [ @STATUS_CODES ], + functions => [ qw(plugin_exit plugin_die check_messages max_state max_state_alt convert) ], +); + +use constant OK => 0; +use constant WARNING => 1; +use constant CRITICAL => 2; +use constant UNKNOWN => 3; +use constant DEPENDENT => 4; + +our %ERRORS = ( + 'OK' => OK, + 'WARNING' => WARNING, + 'CRITICAL' => CRITICAL, + 'UNKNOWN' => UNKNOWN, + 'DEPENDENT' => DEPENDENT, +); + +our %STATUS_TEXT = reverse %ERRORS; + +my $value = qr/[-+]?[\d\.]+/; +our $value_re = qr/$value(?:e$value)?/; + +# _fake_exit flag and accessor/mutator, for testing +my $_fake_exit = 0; +sub _fake_exit { @_ ? $_fake_exit = shift : $_fake_exit }; + +# _use_die flag and accessor/mutator, so exceptions can be raised correctly +my $_use_die = 0; +sub _use_die { @_ ? $_use_die = shift : $_use_die }; + +sub get_shortname { + my $arg = shift; + + my $shortname = undef; + + return $arg->{shortname} if (defined($arg->{shortname})); + $shortname = $arg->{plugin} if (defined( $arg->{plugin})); + + $shortname = uc basename($shortname || $ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0); + $shortname =~ s/^CHECK_(?:BY_)?//; # Remove any leading CHECK_[BY_] + $shortname =~ s/\..*$//; # Remove any trailing suffix + return $shortname; +} + +sub max_state { + return CRITICAL if grep { $_ == CRITICAL } @_; + return WARNING if grep { $_ == WARNING } @_; + return OK if grep { $_ == OK } @_; + return UNKNOWN if grep { $_ == UNKNOWN } @_; + return DEPENDENT if grep { $_ == DEPENDENT } @_; + return UNKNOWN; +} + +sub max_state_alt { + return CRITICAL if grep { $_ == CRITICAL } @_; + return WARNING if grep { $_ == WARNING } @_; + return UNKNOWN if grep { $_ == UNKNOWN } @_; + return DEPENDENT if grep { $_ == DEPENDENT } @_; + return OK if grep { $_ == OK } @_; + return UNKNOWN; +} + +# plugin_exit( $code, $message ) +sub plugin_exit { + my ($code, $message, $arg) = @_; + + # Handle named parameters + if (defined $code && ($code eq 'return_code' || $code eq 'message')) { + # Remove last argument if odd no and last is ref + if (int(@_ / 2) != @_ / 2 && ref $_[$#_]) { + $arg = pop @_; + } else { + undef $arg; + } + my %arg = @_; + $code = $arg{return_code}; + $message = $arg{message}; + } + $arg ||= {}; + + # Handle string codes + $code = $ERRORS{$code} if defined $code && exists $ERRORS{$code}; + + # Set defaults + $code = UNKNOWN unless defined $code && exists $STATUS_TEXT{$code}; + $message = '' unless defined $message; + if (ref $message && ref $message eq 'ARRAY') { + $message = join(' ', map { chomp; $_ } @$message); + } + else { + chomp $message; + } + + # Setup output + my $output = "$STATUS_TEXT{$code}"; + $output .= " - $message" if defined $message && $message ne ''; + my $shortname = ($arg->{plugin} ? $arg->{plugin}->shortname : undef); + $shortname ||= get_shortname(); # Should happen only if funnctions are called directly + $output = "$shortname $output" if $shortname; + if ($arg->{plugin}) { + my $plugin = $arg->{plugin}; + $output .= " | ". $plugin->all_perfoutput + if $plugin->perfdata && $plugin->all_perfoutput; + } + $output .= "\n"; + + # Don't actually exit if _fake_exit set + if ($_fake_exit) { + require Monitoring::Plugin::ExitResult; + return Monitoring::Plugin::ExitResult->new($code, $output); + } + + _plugin_exit($code, $output); +} + +sub _plugin_exit { + my ($code, $output) = @_; + # Print output and exit; die if flag set and called via a die in stack backtrace + if ($_use_die) { + for (my $i = 0;; $i++) { + @_ = caller($i); + last unless @_; + if ($_[3] =~ m/die/) { + $! = $code; + die($output); + } + } + } + print $output; + exit $code; +} + +# plugin_die( $message, [ $code ]) OR plugin_die( $code, $message ) +# Default $code: UNKNOWN +sub plugin_die { + my ($arg1, $arg2, $rest) = @_; + + # Named parameters + if (defined $arg1 && ($arg1 eq 'return_code' || $arg1 eq 'message')) { + return plugin_exit(@_); + } + + # ($code, $message) + elsif (defined $arg1 && (exists $ERRORS{$arg1} || exists $STATUS_TEXT{$arg1})) { + return plugin_exit(@_); + } + + # ($message, $code) + elsif (defined $arg2 && (exists $ERRORS{$arg2} || exists $STATUS_TEXT{$arg2})) { + return plugin_exit($arg2, $arg1, $rest); + } + + # Else just assume $arg1 is the message and hope for the best + else { + return plugin_exit( UNKNOWN, $arg1, $arg2 ); + } +} + +# For backwards compatibility +sub die { plugin_die(@_); } + + +# ------------------------------------------------------------------------ +# Utility functions + +# Simple wrapper around Math::Calc::Units::convert +sub convert +{ + my ($value, $from, $to) = @_; + my ($newval) = Math::Calc::Units::convert("$value $from", $to, 'exact'); + return $newval; +} + +# ------------------------------------------------------------------------ +# check_messages - return a status and/or message based on a set of +# message arrays. +# Returns a nagios status code in scalar context. +# Returns a code and a message in list context. +# The message is join($join, @array) for the relevant array for the code, +# or join($join_all, $message) for all arrays if $join_all is set. +sub check_messages { + my %arg = validate( @_, { + critical => { type => ARRAYREF }, + warning => { type => ARRAYREF }, + ok => { type => ARRAYREF | SCALAR, optional => 1 }, + 'join' => { default => ' ' }, + join_all => 0, + }); + $arg{join} = ' ' unless defined $arg{join}; + + # Decide $code + my $code = OK; + $code ||= CRITICAL if @{$arg{critical}}; + $code ||= WARNING if @{$arg{warning}}; + return $code unless wantarray; + + # Compose message + my $message = ''; + if ($arg{join_all}) { + $message = join( $arg{join_all}, + map { @$_ ? join( $arg{'join'}, @$_) : () } + $arg{critical}, + $arg{warning}, + $arg{ok} ? (ref $arg{ok} ? $arg{ok} : [ $arg{ok} ]) : [] + ); + } + + else { + $message ||= join( $arg{'join'}, @{$arg{critical}} ) + if $code == CRITICAL; + $message ||= join( $arg{'join'}, @{$arg{warning}} ) + if $code == WARNING; + $message ||= ref $arg{ok} ? join( $arg{'join'}, @{$arg{ok}} ) : $arg{ok} + if $arg{ok}; + } + + return ($code, $message); +} + +# ------------------------------------------------------------------------ + +1; + +# vim:sw=4:sm:et + +__END__ + +=head1 NAME + +Monitoring::Plugin::Functions - functions to simplify the creation of +Nagios plugins + +=head1 SYNOPSIS + + # Constants OK, WARNING, CRITICAL, and UNKNOWN exported by default + use Monitoring::Plugin::Functions; + + # plugin_exit( CODE, $message ) - exit with error code CODE, + # and message "PLUGIN CODE - $message" + plugin_exit( CRITICAL, $critical_error ) if $critical_error; + plugin_exit( WARNING, $warning_error ) if $warning_error; + plugin_exit( OK, $result ); + + # plugin_die( $message, [$CODE] ) - just like plugin_exit(), + # but CODE is optional, defaulting to UNKNOWN + do_something() + or plugin_die("do_something() failed horribly"); + do_something_critical() + or plugin_die("do_something_critical() failed", CRITICAL); + + # check_messages - check a set of message arrays, returning a + # CODE and/or a result message + $code = check_messages(critical => \@crit, warning => \@warn); + ($code, $message) = check_messages( + critical => \@crit, warning => \@warn, + ok => \@ok ); + + # get_shortname - return the default short name for this plugin + # (as used by plugin_exit/die; not exported by default) + $shortname = get_shortname(); + + +=head1 DESCRIPTION + +This module is part of the Monitoring::Plugin family, a set of modules +for simplifying the creation of Nagios plugins. This module exports +convenience functions for the class methods provided by +Monitoring::Plugin. It is intended for those who prefer a simpler +functional interface, and who do not need the additional +functionality of Monitoring::Plugin. + +=head2 EXPORTS + +Nagios status code constants are exported by default: + + OK + WARNING + CRITICAL + UNKNOWN + DEPENDENT + +as are the following functions: + + plugin_exit + plugin_die + check_messages + +The following variables and functions are exported only on request: + + %ERRORS + %STATUS_TEXT + get_shortname + max_state + max_state_alt + + +=head2 FUNCTIONS + +The following functions are supported: + +=over 4 + +=item plugin_exit( , $message ) + +Exit with return code CODE, and a standard nagios message of the +form "PLUGIN CODE - $message". + +=item plugin_die( $message, [CODE] ) + +Same as plugin_exit(), except that CODE is optional, defaulting +to UNKNOWN. NOTE: exceptions are not raised by default to calling code. +Set C<$_use_die> flag if this functionality is required (see test code). + +=item check_messages( critical => \@crit, warning => \@warn ) + +Convenience function to check a set of message arrays and return +an appropriate nagios return code and/or a result message. Returns +only a return code in scalar context; returns a return code and an +error message in list context i.e. + + # Scalar context + $code = check_messages(critical => \@crit, warning => \@warn); + # List context + ($code, $msg) = check_messages(critical => \@crit, warning => \@warn); + +check_messages() accepts the following named arguments: + +=over 4 + +=item critical => ARRAYREF + +An arrayref of critical error messages - check_messages() returns +CRITICAL if this arrayref is non-empty. Mandatory. + +=item warning => ARRAYREF + +An arrayref of warning error messages - check_messages() returns +WARNING if this arrayref is non-empty ('critical' is checked +first). Mandatory. + +=item ok => ARRAYREF | SCALAR + +An arrayref of informational messages (or a single scalar message), +used in list context if both the 'critical' and 'warning' arrayrefs +are empty. Optional. + +=item join => SCALAR + +A string used to join the relevant array to generate the message +string returned in list context i.e. if the 'critical' array @crit +is non-empty, check_messages would return: + + join( $join, @crit ) + +as the result message. Optional; default: ' ' (space). + +=item join_all => SCALAR + +By default, only one set of messages are joined and returned in the +result message i.e. if the result is CRITICAL, only the 'critical' +messages are included in the result; if WARNING, only the 'warning' +messages are included; if OK, the 'ok' messages are included (if +supplied) i.e. the default is to return an 'errors-only' type +message. + +If join_all is supplied, however, it will be used as a string to +join the resultant critical, warning, and ok messages together i.e. +all messages are joined and returned. + +=back + +=item get_shortname + +Return the default shortname used for this plugin i.e. the first +token reported by plugin_exit/plugin_die. The default is basically + + uc basename( $ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0 ) + +with any leading 'CHECK_' and trailing file suffixes removed. + +get_shortname is not exported by default, so must be explicitly +imported. + +=item max_state(@a) + +Returns the worst state in the array. Order is: CRITICAL, WARNING, OK, UNKNOWN, +DEPENDENT + +The typical usage of max_state is to initialise the state as UNKNOWN and use +it on the result of various test. If no test were performed successfully the +state will still be UNKNOWN. + +=item max_state_alt(@a) + +Returns the worst state in the array. Order is: CRITICAL, WARNING, UNKNOWN, +DEPENDENT, OK + +This is a true definition of a max state (OK last) and should be used if the +internal tests performed can return UNKNOWN. + +=back + +=head1 SEE ALSO + +Monitoring::Plugin; the nagios plugin developer guidelines at +https://www.monitoring-plugins.org/doc/guidelines.html. + +=head1 AUTHOR + +This code is maintained by the Monitoring Plugin Development Team: see +https://monitoring-plugins.org + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2006-2014 Monitoring Plugin Development Team + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Monitoring/Plugin/Getopt.pm b/lib/Monitoring/Plugin/Getopt.pm new file mode 100644 index 0000000..ce1c0f9 --- /dev/null +++ b/lib/Monitoring/Plugin/Getopt.pm @@ -0,0 +1,869 @@ +# +# Monitoring::Plugin::Getopt - OO perl module providing standardised argument +# processing for nagios plugins +# + +package Monitoring::Plugin::Getopt; + +use strict; +use File::Basename; +use Getopt::Long qw(:config no_ignore_case bundling); +use Carp; +use Params::Validate qw(:all); +use base qw(Class::Accessor); + +use Monitoring::Plugin::Functions; +use Monitoring::Plugin::Config; +use vars qw($VERSION); +$VERSION = $Monitoring::Plugin::Functions::VERSION; + +# Standard defaults +my %DEFAULT = ( + timeout => 15, + verbose => 0, + license => +"This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. +It may be used, redistributed and/or modified under the terms of the GNU +General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).", +); +# Standard arguments +my @ARGS = ({ + spec => 'usage|?', + help => "-?, --usage\n Print usage information", + }, { + spec => 'help|h', + help => "-h, --help\n Print detailed help screen", + }, { + spec => 'version|V', + help => "-V, --version\n Print version information", + }, { + spec => 'extra-opts:s@', + help => "--extra-opts=[section][\@file]\n Read options from an ini file. See http://nagiosplugins.org/extra-opts\n for usage and examples.", + }, { + spec => 'timeout|t=i', + help => "-t, --timeout=INTEGER\n Seconds before plugin times out (default: %s)", + default => $DEFAULT{timeout}, + }, { + spec => 'verbose|v+', + help => "-v, --verbose\n Show details for command-line debugging (can repeat up to 3 times)", + default => $DEFAULT{verbose}, + }, +); +# Standard arguments we traditionally display last in the help output +my %DEFER_ARGS = map { $_ => 1 } qw(timeout verbose); + +# ------------------------------------------------------------------------- +# Private methods + +sub _die +{ + my $self = shift; + my ($msg) = @_; + $msg .= "\n" unless substr($msg, -1) eq "\n"; + Monitoring::Plugin::Functions::_plugin_exit(3, $msg); +} + +# Return the given attribute, if set, including a final newline +sub _attr +{ + my $self = shift; + my ($item, $extra) = @_; + $extra = '' unless defined $extra; + return '' unless $self->{_attr}->{$item}; + $self->{_attr}->{$item} . "\n" . $extra; +} + +# Turn argument spec into help-style output +sub _spec_to_help +{ + my ($self, $spec, $label) = @_; + + my ($opts, $type) = split /=/, $spec, 2; + my (@short, @long); + for (split /\|/, $opts) { + if (length $_ == 1) { + push @short, "-$_"; + } else { + push @long, "--$_"; + } + } + + my $help = join(', ', @short, @long); + if ($type) { + if ($label) { + $help .= '=' . $label; + } + else { + $help .= $type eq 'i' ? '=INTEGER' : '=STRING'; + } + } + elsif ($label) { + carp "Label specified, but there's no type in spec '$spec'"; + } + $help .= "\n "; + return $help; +} + +# Options output for plugin -h +sub _options +{ + my $self = shift; + + my @args = (); + my @defer = (); + for (@{$self->{_args}}) { + if (exists $DEFER_ARGS{$_->{name}}) { + push @defer, $_; + } else { + push @args, $_; + } + } + + my @options = (); + for my $arg (@args, @defer) { + my $help_array = ref $arg->{help} && ref $arg->{help} eq 'ARRAY' ? $arg->{help} : [ $arg->{help} ]; + my $label_array = $arg->{label} && ref $arg->{label} && ref $arg->{label} eq 'ARRAY' ? $arg->{label} : [ $arg->{label} ]; + my $help_string = ''; + for (my $i = 0; $i <= $#$help_array; $i++) { + my $help = $help_array->[$i]; + # Add spec arguments to help if not already there + if ($help =~ m/^\s*-/) { + $help_string .= $help; + } + else { + $help_string .= $self->_spec_to_help($arg->{spec}, $label_array->[$i]) . $help; + $help_string .= "\n " if $i < $#$help_array; + } + } + + # Add help_string to @options + if ($help_string =~ m/%s/) { + my $default = defined $arg->{default} ? $arg->{default} : ''; + # We only handle '%s' formats here, so escape everything else + $help_string =~ s/%(?!s)/%%/g; + push @options, sprintf($help_string, $default, $default, $default, $default); + } else { + push @options, $help_string; + } + } + + return ' ' . join("\n ", @options); +} + +# Output for plugin -? (or missing/invalid args) +sub _usage +{ + my $self = shift; + sprintf $self->_attr('usage'), $self->{_attr}->{plugin}; +} + +# Output for plugin -V +sub _revision +{ + my $self = shift; + my $revision = sprintf "%s %s", $self->{_attr}->{plugin}, $self->{_attr}->{version}; + $revision .= sprintf " [%s]", $self->{_attr}->{url} if $self->{_attr}->{url}; + $revision .= "\n"; + $revision; +} + +# Output for plugin -h +sub _help +{ + my $self = shift; + my $help = ''; + $help .= $self->_revision . "\n"; + $help .= $self->_attr('license', "\n"); + $help .= $self->_attr('blurb', "\n"); + $help .= $self->_usage ? $self->_usage . "\n" : ''; + $help .= $self->_options ? $self->_options . "\n" : ''; + $help .= $self->_attr('extra', "\n"); + return $help; +} + +# Return a Getopt::Long-compatible option array from the current set of specs +sub _process_specs_getopt_long +{ + my $self = shift; + + my @opts = (); + for my $arg (@{$self->{_args}}) { + push @opts, $arg->{spec}; + # Setup names and defaults + my $spec = $arg->{spec}; + # Use first arg as name (like Getopt::Long does) + $spec =~ s/[=:].*$//; + my $name = (split /\s*\|\s*/, $spec)[0]; + $arg->{name} = $name; + if (defined $self->{$name}) { + $arg->{default} = $self->{$name}; + } else { + $self->{$name} = $arg->{default}; + } + } + + return @opts; +} + +# Check for existence of required arguments +sub _check_required_opts +{ + my $self = shift; + + my @missing = (); + for my $arg (@{$self->{_args}}) { + if ($arg->{required} && ! defined $self->{$arg->{name}}) { + push @missing, $arg->{name}; + } + } + if (@missing) { + $self->_die($self->_usage . "\n" . + join("\n", map { sprintf "Missing argument: %s", $_ } @missing) . "\n"); + } +} + +# Process and handle any immediate options +sub _process_opts +{ + my $self = shift; + + # Print message and exit for usage, version, help + $self->_die($self->_usage) if $self->{usage}; + $self->_die($self->_revision) if $self->{version}; + $self->_die($self->_help) if $self->{help}; +} + +# ------------------------------------------------------------------------- +# Default opts methods + +sub _load_config_section +{ + my $self = shift; + my ($section, $file, $flags) = @_; + $section ||= $self->{_attr}->{plugin}; + + my $Config; + eval { $Config = Monitoring::Plugin::Config->read($file); }; + $self->_die($@) if ($@); #TODO: add test? + + # TODO: is this check sane? Does --extra-opts=foo require a [foo] section? + ## Nevertheless, if we die as UNKNOWN here we should do the same on default + ## file *added eval/_die above*. + $file ||= $Config->np_getfile(); + $self->_die("Invalid section '$section' in config file '$file'") + unless exists $Config->{$section}; + + return $Config->{$section}; +} + +# Helper method to setup a hash of spec definitions for _cmdline +sub _setup_spec_index +{ + my $self = shift; + return if defined $self->{_spec}; + $self->{_spec} = { map { $_->{name} => $_->{spec} } @{$self->{_args}} }; +} + +# Quote values that require it +sub _cmdline_value +{ + my $self = shift; + local $_ = shift; + if (m/\s/ && (m/^[^"']/ || m/[^"']$/)) { + return qq("$_"); + } + elsif ($_ eq '') { + return q(""); + } + else { + return $_; + } +} + +# Helper method to format key/values in $hash in a quasi-commandline format +sub _cmdline +{ + my $self = shift; + my ($hash) = @_; + $hash ||= $self; + + $self->_setup_spec_index; + + my @args = (); + for my $key (sort keys %$hash) { + # Skip internal keys + next if $key =~ m/^_/; + + # Skip defaults and internals + next if exists $DEFAULT{$key} && $hash->{$key} eq $DEFAULT{$key}; + next if grep { $key eq $_ } qw(help usage version extra-opts); + next unless defined $hash->{$key}; + + # Render arg + my $spec = $self->{_spec}->{$key} || ''; + if ($spec =~ m/[=:].+$/) { + # Arg takes value - may be a scalar or an arrayref + for my $value (ref $hash->{$key} eq 'ARRAY' ? @{$hash->{$key}} : ( $hash->{$key} )) { + $value = $self->_cmdline_value($value); + if (length($key) > 1) { + push @args, sprintf "--%s=%s", $key, $value; + } + else { + push @args, "-$key", $value; + } + } + } + + else { + # Flag - render long or short based on option length + push @args, (length($key) > 1 ? '--' : '-') . $key; + } + } + + return wantarray ? @args : join(' ', @args); +} + +# Process and load extra-opts sections +sub _process_extra_opts +{ + my $self = shift; + my ($args) = @_; + + my $extopts_list = $args->{'extra-opts'}; + + my @sargs = (); + for my $extopts (@$extopts_list) { + $extopts ||= $self->{_attr}->{plugin}; + my $section = $extopts; + my $file = ''; + + # Parse section@file + if ($extopts =~ m/^([^@]*)@(.*?)\s*$/) { + $section = $1; + $file = $2; + } + + # Load section args + my $shash = $self->_load_config_section($section, $file); + + # Turn $shash into a series of commandline-like arguments + push @sargs, $self->_cmdline($shash); + } + + # Reset ARGV to extra-opts + original + @ARGV = ( @sargs, @{$self->{_attr}->{argv}} ); + + printf "[extra-opts] %s %s\n", $self->{_attr}->{plugin}, join(' ', @ARGV) + if $args->{verbose} && $args->{verbose} >= 3; +} + +# ------------------------------------------------------------------------- +# Public methods + +# Define plugin argument +sub arg +{ + my $self = shift; + my %args; + + # Named args + if ($_[0] =~ m/^(spec|help|required|default)$/ && scalar(@_) % 2 == 0) { + %args = validate( @_, { + spec => 1, + help => 1, + default => 0, + required => 0, + label => 0, + }); + } + + # Positional args + else { + my @args = validate_pos(@_, 1, 1, 0, 0, 0); + %args = ( + spec => $args[0], + help => $args[1], + default => $args[2], + required => $args[3], + label => $args[4], + ); + } + + # Add to private args arrayref + push @{$self->{_args}}, \%args; +} + +# Process the @ARGV array using the current _args list (possibly exiting) +sub getopts +{ + my $self = shift; + + # Collate spec arguments for Getopt::Long + my @opt_array = $self->_process_specs_getopt_long; + + # Capture original @ARGV (for extra-opts games) + $self->{_attr}->{argv} = [ @ARGV ]; + + # Call GetOptions using @opt_array + my $args1 = {}; + my $ok = GetOptions($args1, @opt_array); + # Invalid options - give usage message and exit + $self->_die($self->_usage) unless $ok; + + # Process extra-opts + $self->_process_extra_opts($args1); + + # Call GetOptions again, this time including extra-opts + $ok = GetOptions($self, @opt_array); + # Invalid options - give usage message and exit + $self->_die($self->_usage) unless $ok; + + # Process immediate options (possibly exiting) + $self->_process_opts; + + # Required options (possibly exiting) + $self->_check_required_opts; + + # Setup accessors for options + $self->mk_ro_accessors(grep ! /^_/, keys %$self); + + # Setup default alarm handler for alarm($ng->timeout) in plugin + $SIG{ALRM} = sub { + my $plugin = uc $self->{_attr}->{plugin}; + $plugin =~ s/^check_//; + $self->_die( + sprintf("%s UNKNOWN - plugin timed out (timeout %ss)", + $plugin, $self->timeout)); + }; +} + +# ------------------------------------------------------------------------- +# Constructor + +sub _init +{ + my $self = shift; + + # Check params + my $plugin = basename($ENV{PLUGIN_NAME} || $ENV{NAGIOS_PLUGIN} || $0); + my %attr = validate( @_, { + usage => 1, + version => 0, + url => 0, + plugin => { default => $plugin }, + blurb => 0, + extra => 0, + 'extra-opts' => 0, + license => { default => $DEFAULT{license} }, + timeout => { default => $DEFAULT{timeout} }, + }); + + # Add attr to private _attr hash (except timeout) + $self->{timeout} = delete $attr{timeout}; + $self->{_attr} = { %attr }; + # Chomp _attr values + chomp foreach values %{$self->{_attr}}; + + # Setup initial args list + $self->{_args} = [ @ARGS ]; + + $self +} + +sub new +{ + my $class = shift; + my $self = bless {}, $class; + $self->_init(@_); +} + +# ------------------------------------------------------------------------- + +1; + +__END__ + +=head1 NAME + +Monitoring::Plugin::Getopt - OO perl module providing standardised argument +processing for Nagios plugins + + +=head1 SYNOPSIS + + use Monitoring::Plugin::Getopt; + + # Instantiate object (usage is mandatory) + $ng = Monitoring::Plugin::Getopt->new( + usage => "Usage: %s -H -w -c ", + version => '0.1', + url => 'http://www.openfusion.com.au/labs/nagios/', + blurb => 'This plugin tests various stuff.', + ); + + # Add argument - named parameters (spec and help are mandatory) + $ng->arg( + spec => 'critical|c=i', + help => q(Exit with CRITICAL status if fewer than INTEGER foobars are free), + required => 1, + default => 10, + ); + + # Add argument - positional parameters - arg spec, help text, + # default value, required? (first two mandatory) + $ng->arg( + 'warning|w=i', + q(Exit with WARNING status if fewer than INTEGER foobars are free), + 5, + 1); + + # Parse arguments and process standard ones (e.g. usage, help, version) + $ng->getopts; + + # Access arguments using named accessors or or via the generic get() + print $ng->warning; + print $ng->get('critical'); + + + +=head1 DESCRIPTION + +Monitoring::Plugin::Getopt is an OO perl module providing standardised and +simplified argument processing for Nagios plugins. It implements +a number of standard arguments itself (--help, --version, +--usage, --timeout, --verbose, and their short form counterparts), +produces standardised nagios plugin help output, and allows +additional arguments to be easily defined. + + +=head2 CONSTRUCTOR + + # Instantiate object (usage is mandatory) + $ng = Monitoring::Plugin::Getopt->new( + usage => 'Usage: %s --hello', + version => '0.01', + ); + +The Monitoring::Plugin::Getopt constructor accepts the following named +arguments: + +=over 4 + +=item usage (required) + +Short usage message used with --usage/-? and with missing required +arguments, and included in the longer --help output. Can include +a '%s' sprintf placeholder which will be replaced with the plugin +name e.g. + + usage => qq(Usage: %s -H -p [-v]), + +might be displayed as: + + $ ./check_tcp_range --usage + Usage: check_tcp_range -H -p [-v] + +=item version (required) + +Plugin version number, included in the --version/-V output, and in +the longer --help output. e.g. + + $ ./check_tcp_range --version + check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/] + +=item url + +URL for info about this plugin, included in the --version/-V output, +and in the longer --help output (see preceding 'version' example). + +=item blurb + +Short plugin description, included in the longer --help output +(see below for an example). + +=item license + +License text, included in the longer --help output (see below for an +example). By default, this is set to the standard nagios plugins +GPL license text: + + This nagios plugin is free software, and comes with ABSOLUTELY + NO WARRANTY. It may be used, redistributed and/or modified under + the terms of the GNU General Public Licence (see + http://www.fsf.org/licensing/licenses/gpl.txt). + +Provide your own to replace this text in the help output. + +=item extra + +Extra text to be appended at the end of the longer --help output. + +=item plugin + +Plugin name. This defaults to the basename of your plugin, which is +usually correct, but you can set it explicitly if not. + +=item timeout + +Timeout period in seconds, overriding the standard timeout default +(15 seconds). + +=back + +The full --help output has the following form: + + version string + + license string + + blurb + + usage string + + options list + + extra text + +The 'blurb' and 'extra text' sections are omitted if not supplied. For +example: + + $ ./check_tcp_range -h + check_tcp_range 0.2 [http://www.openfusion.com.au/labs/nagios/] + + This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. + It may be used, redistributed and/or modified under the terms of the GNU + General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt). + + This plugin tests arbitrary ranges/sets of tcp ports for a host. + + Usage: check_tcp_range -H -p [-v] + + Options: + -h, --help + Print detailed help screen + -V, --version + Print version information + -H, --hostname=ADDRESS + Host name or IP address + -p, --ports=STRING + Port numbers to check. Format: comma-separated, colons for ranges, + no spaces e.g. 8700:8705,8710:8715,8760 + -t, --timeout=INTEGER + Seconds before plugin times out (default: 15) + -v, --verbose + Show details for command-line debugging (can repeat up to 3 times) + + +=head2 ARGUMENTS + +You can define arguments for your plugin using the arg() method, which +supports both named and positional arguments. In both cases +the C and C arguments are required, while the C