mostly done minus readme stuff

This commit is contained in:
Zane C. B-H 2022-04-24 16:13:50 -05:00
parent 893682e547
commit 395e1c45d1
4 changed files with 333 additions and 59 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
*.o
*.pm.tdy
*.bs
bin/.exists
# Devel::Cover
cover_db/

View File

@ -10,6 +10,7 @@ my %WriteMakefileArgs = (
ABSTRACT_FROM => 'lib/Monitoring/Sneck.pm',
LICENSE => 'artistic_2',
MIN_PERL_VERSION => '5.006',
INST_SCRIPT => 'bin',
CONFIGURE_REQUIRES => {
'ExtUtils::MakeMaker' => '0',
},
@ -17,8 +18,9 @@ my %WriteMakefileArgs = (
'Test::More' => '0',
},
PREREQ_PM => {
'JSON' => '0',
'File::Slurp' => '0',
'JSON' => '0',
'File::Slurp' => '0',
'Sys::Hostname' => '0',
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'Monitoring-Sneck-*' },

160
bin/sneck Normal file → Executable file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env perl
=head1 NAME
sneck - a boopable LibreNMS JSON style SNMP extend for remotely running nagios style checks
=head1 SYNOPSIS
sneck -u [B<-C> <cache file] [B<-f> <config file>] [B<-p>] [B<-i>]
sneck -c [B<-C> <cache file]
sneck [B<-f> <config file>] [B<-p>] [B<-i>]
=head1 DESCRIPTION
For a description of the config file format and output,
please see L<Monitoring::Sneck>.
=head1 FLAGS
=head2 -f <config file>
The config file to use.
Defaults to '/usr/local/etc/sneck.conf'.
=head2 -p
Pretty it in a nicely formatted format.
=head2 -C <cache file>
The cache file to use.
Defaults to '/var/cache/sneck.cache'.
=head2 -u
Update the cache file. Will also print the was written to it.
=head2 -c
Print the cache file. Please note that B<-p> or B<-i> won't affect
this as this flag only reads/prints the cache file.
=head2 -i
Includes the config file used.
=cut
use strict;
use warnings;
use Getopt::Long;
use File::Slurp;
use JSON;
use Monitoring::Sneck;
sub version {
print "sneck v. 0.0.1\n";
}
sub help {
&version;
print '
-f <config> Config file to use.
Default: /usr/local/etc/sneck.conf
-c Print the cache and exit. Requires -u being used previously.
-C Cache file.
Default: /var/cache/sneck.cache
-u Run and write to cache.
-p Pretty print. Does not affect -c.
-i Include the raw config in the JSON.
-h Print help info.
--help Print help info.
-v Print version info.
--version Print version info.
';
}
my $cache_file = '/var/cache/sneck.cache';
my $config_file = '/usr/local/etc/sneck.conf';
my $update;
my $print_cache;
my $fallback;
my $help;
my $version;
my $pretty;
my $include;
Getopt::Long::Configure('no_ignore_case');
Getopt::Long::Configure('bundling');
GetOptions(
'version' => \$version,
'v' => \$version,
'help' => \$help,
'h' => \$help,
'c' => \$print_cache,
'f=s' => \$config_file,
'C=s' => \$cache_file,
'p' => \$pretty,
'u' => \$update,
'i' => \$include,
);
# print version or help if requested
if ($help) {
&help;
exit 42;
}
if ($version) {
&version;
exit 42;
}
# prints the cache and exit if requested
if ($print_cache) {
if ( !-f $cache_file || !-r $cache_file ) {
my $error = 'Cache file does not exist or is not readable "' . $cache_file . '"';
my $possible_error
= { error => 1, version => 1, errorString => $error, data => { alert => 1, alertString => $error } };
print encode_json($possible_error) . "\n";
exit 3;
}
my $cache = read_file($cache_file);
print $cache;
exit;
}
my $sneck = Monitoring::Sneck->new( { config => $config_file, include=>$include } );
my $returned = $sneck->run;
# encode it and print it
my $json = JSON->new->utf8->canonical(1);
if ($pretty) {
$json->pretty;
}
my $raw_json = $json->encode($returned);
# non-pretty does not include a new line, so add it
if ( !$pretty ) {
$raw_json = $raw_json . "\n";
}
print $raw_json;
if ($update) {
my $fh;
open( $fh, '>', $cache_file );
print $fh $raw_json;
close($fh);
}

View File

@ -4,6 +4,7 @@ use 5.006;
use strict;
use warnings;
use File::Slurp;
use Sys::Hostname;
=head1 NAME
@ -11,11 +12,11 @@ Monitoring::Sneck - a boopable LibreNMS JSON style SNMP extend for remotely runn
=head1 VERSION
Version 0.0.0
Version 0.0.1
=cut
our $VERSION = '0.0.0';
our $VERSION = '0.0.1';
=head1 SYNOPSIS
@ -25,6 +26,114 @@ our $VERSION = '0.0.0';
my $sneck=Monitoring::Sneck->new({config=>$file});
=head1 USAGE
Not really meant to be used as a library. The library is more of
to support the script.
=head1 CONFIG FORMAT
White space is always cleared from the start of lines via /^[\t ]*/ for
each file line that is read in.
Blank lines are ignored.
Lines starting with /\#/ are comments lines.
Lines matching /^[A-Za-z0-9\_]+\=/ are variables. Anything before the the
/\=/ is used as the name with everything after being the value.
Lines matching /^[A-Za-z0-9\_]+\|/ are checks to run. Anything before the
/\|/ is the name with everything after command to run.
Any other sort of lines are considered an error.
Variables in the checks are in the form of %%%varaible_name%%%.
Variable names and check names may not be redefined once defined in the config.
=head2 EXAMPLE CONFIG
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
# this is a comment
geom_foo|/usr/bin/env PATH=%%%PATH%%% /usr/local/libexec/nagios/check_geom mirror foo
does_not_exist|/bin/this_will_error yup... that it will
does_not_exist_2|/usr/bin/env /bin/this_will_also_error
The first line creates a variable named path.
The second is ignored as it is a comment.
The third creates a check named geom_foo that calls env with and sets the PATH to the
the variable defined on line 1 and calls check_geom_mirror.
The fourth is a example of an error that will show what will happen when you call to a
file that does not exit.
The fifth line will be ignored as it is blank.
The sixth is a example of another command erroring.
When you run it, you will notice that errors for lines 4 and 5 are printed to STDERR.
For this reason you should use '2> /dev/null' when calling it from snmpd or
'2> /dev/null > /dev/null' when calling from cron.
=head1 USAGE
snmpd should be configured as below.
extend sneck /usr/bin/env PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin /usr/local/bin/sneck -c
Then just setup a entry in like cron such as below.
*/5 * * * * /usr/bin/env PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin /usr/local/bin/sneck -u 2> /dev/null > /dev/null
Most likely want to run it once per polling interval.
You can use it in a non-cached manner with out cron, but this will result in a
longer polling time for LibreNMS or the like when it queries it.
=head1 RETURN HASH/JSON
The data section of the return hash/JSON is as below.
- $hash{data}{alert} :: 0/1 boolean for if there is a aloert or not.
- $hash{data}{ok} :: Count of the number of ok checks.
- $hash{data}{warning} :: Count of the number of warning checks.
- $hash{data}{critical} :: Count of the number of critical checks.
- $hash{data}{unknown} :: Count of the number of unkown checks.
- $hash{data}{errored} :: Count of the number of errored checks.
- $hash{data}{alertString} :: The cumulative outputs of anything
that returned a warning, critical, or unknown.
- $hash{data}{vars} :: A hash with the variables to use.
- $hash{data}{time} :: Time since epoch.
- $hash{data}{time} :: The hostname the check was ran on.
- $hash{data}{config} :: The raw config file if told to include it.
- $hash{data}[checks}{$name} :: A hash with info on the checks ran.
- $hash{data}[checks}{$name}{check} :: The command pre-variable substitution.
- $hash{data}[checks}{$name}{ran} :: The command ran.
- $hash{data}[checks}{$name}{output} :: The output of the check.
- $hash{data}[checks}{$name}{exit} :: The exit code.
- $hash{data}[checks}{$name}{error} :: Only present it died on a
signal or could not be executed. Provides a brief description.
=head1 METHODS
=head2 new
@ -33,9 +142,20 @@ Initiates the object.
One argument is taken and that is a hash ref. If the key 'config'
is present, that will be the config file used. Otherwise
'/usr/local/etc/sneck.conf' is used.
'/usr/local/etc/sneck.conf' is used. The key 'include' is a Perl
boolean for if the raw config should be included in the JSON.
my $sneck=Monitoring::Sneck->new({config=>$file});
This function should always work. If there is an error with
parsing or the like, it will be reported in the expected format
when $sneck->run is called.
This is meant to be rock solid and always work, meaning LibreNMS
style JSON is always returned(provided Perl and the other modules
are working).
my $sneck=Monitoring::Sneck->new({config=>$file}, include=>0);
=cut
@ -51,7 +171,17 @@ sub new {
to_return => {
error => 0,
errorString => '',
data => { ok => 0, warning => 0, critical => 0, unknown => 0, erroed=>0, alert => 0, alertString => '', checks => {} },
data => {
hostname => hostname,
ok => 0,
warning => 0,
critical => 0,
unknown => 0,
errored => 0,
alert => 0,
alertString => '',
checks => {}
},
version => 1,
},
checks => {},
@ -61,25 +191,32 @@ sub new {
bless $self;
my $config_raw;
eval { my $config_raw = read_file( $self->{config} ); };
eval { $config_raw = read_file( $self->{config} ); };
if ($@) {
$self->{good} = 0;
$self->{to_return}{error} = 1;
$self->{to_return}{errorString} = 'Failed to read in the config file "' . $self->{conffig} . '"... ' . $@;
$self->{to_return}{errorString} = 'Failed to read in the config file "' . $self->{config} . '"... ' . $@;
$self->{checks} = {};
return $self;
}
# include the config file if requested
if ( defined( $args{include} )
&& $args{include} )
{
$self->{to_return}{data}{config} = $config_raw;
}
# split the file and ignore any comments
my @config_split = grep( !/^[\t\ ]*#/, split( /\n/, $config_raw ) );
my $found_items = 0;
foreach my $line (@config_split) {
$line =~ s/^[\ \t]*//;
if ( $line =~ /^[A-Za-z0-9\_]+]\=/ ) {
if ( $line =~ /^[A-Za-z0-9\_]+\=/ ) {
# we found a variable
my ( $name, $value ) = split( /\|=/, $line, 2 );
my ( $name, $value ) = split( /\=/, $line, 2 );
# make sure we have a value
if ( !defined($value) ) {
@ -94,7 +231,7 @@ sub new {
$name =~ s/[\t\ ]*$//;
# check to make sure it is not already defined
if ( defiend( $self->{vars}{$name} ) ) {
if ( defined( $self->{vars}{$name} ) ) {
$self->{good} = 0;
$self->{to_return}{error} = 1;
$self->{to_return}{errorString} = 'variable "' . $name . '" is redefined on the line "' . $line . '"';
@ -103,9 +240,9 @@ sub new {
$self->{vars}{$name} = $value;
}
elsif ( $line =~ /^[A-Za-z0-9\_]+]\|/ ) {
elsif ( $line =~ /^[A-Za-z0-9\_]+\|/ ) {
# we found a item to add
# we found a check to add
my ( $name, $check ) = split( /\|/, $line, 2 );
# make sure we have a check
@ -121,7 +258,7 @@ sub new {
$name =~ s/[\t\ ]*$//;
# check to make sure it is not already defined
if ( defiend( $self->{checks}{$name} ) ) {
if ( defined( $self->{checks}{$name} ) ) {
$self->{good} = 0;
$self->{to_return}{error} = 1;
$self->{to_return}{errorString} = 'check "' . $name . '" is defined on the line "' . $line . '"';
@ -167,33 +304,41 @@ sub run {
return $self->{to_return};
}
# set the time it ran
$self->{to_return}{data}{time} = time;
my @vars = keys( %{ $self->{vars} } );
my @checks = keys( %{ $self->{checks} } );
foreach my $name (@checks) {
my $check = $self->{checks}{$name};
$self->{to_return}{checks}{$name} = { check => $check };
$self->{to_return}{data}{checks}{$name} = { check => $check };
# put the variables in place
foreach my $var_name (@vars) {
my $value = $self->{vars}{$var_name};
$check =~ s/%%%$var_name%%%/$value/g;
}
$self->{to_return}{checks}{$name}{ran} = $check;
$self->{to_return}{data}{checks}{$name}{ran} = $check;
$self->{to_return}{data}{checks}{$name}{output} = system($check);
$self->{to_return}{data}{checks}{$name}{output} = `$check`;
my $exit_code = $?;
chomp( $self->{to_return}{data}{checks}{$name}{output} );
if ( defined( $self->{to_return}{data}{checks}{$name}{output} ) ) {
chomp( $self->{to_return}{data}{checks}{$name}{output} );
}
# handle the exit code
if ( $? == -1 ) {
if ( $exit_code == -1 ) {
$self->{to_return}{data}{checks}{$name}{error} = 'failed to execute';
}
elsif ( $? & 127 ) {
$self->{to_return}{data}{checks}{$name}{error} = sprintf( "child died with signal %d, %s coredump\n",
( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
elsif ( $exit_code & 127 ) {
$self->{to_return}{data}{checks}{$name}{error} = sprintf(
"child died with signal %d, %s coredump\n",
( $exit_code & 127 ),
( $exit_code & 128 ) ? 'with' : 'without'
);
}
else {
$exit_code = $? >> 8;
$exit_code = $exit_code >> 8;
}
$self->{to_return}{data}{checks}{$name}{exit} = $exit_code;
@ -225,45 +370,11 @@ sub run {
}
}
$self->{to_return}{data}{vars}=$self->{vars};
$self->{to_return}{data}{vars} = $self->{vars};
return $self->{to_return};
}
=head1 RETURN HASH
The data section of the return hash is as below.
- $hash{data}{alert} :: 0/1 boolean for if there is a aloert or not.
- $hash{data}{ok} :: Count of the number of ok checks.
- $hash{data}{warning} :: Count of the number of warning checks.
- $hash{data}{critical} :: Count of the number of critical checks.
- $hash{data}{unknown} :: Count of the number of unkown checks.
- $hash{data}{errored} :: Count of the number of errored checks.
- $hash{data}{alertString} :: The cumulative outputs of anything
that returned a warning, critical, or unknown.
- $hash{data}{vars} :: A hash with the variables to use.
- $hash{data}[checks}{$name} :: A hash with info on the checks ran.
- $hash{data}[checks}{$name}{check} :: The command pre-variable substitution.
- $hash{data}[checks}{$name}{ran} :: The command ran.
- $hash{data}[checks}{$name}{output} :: The output of the check.
- $hash{data}[checks}{$name}{exit} :: The exit code.
- $hash{data}[checks}{$name}{error} :: Only present it died on a
signal or could not be executed. Provides a brief description.
=head1 AUTHOR
Zane C. Bowers-Hadley, C<< <vvelox at vvelox.net> >>