NAME

Monitoring::Sneck::Config - parses and validates sneck config files

VERSION

Version 1.6.0

SYNOPSIS

use Monitoring::Sneck::Config;

my $config;
eval { $config = Monitoring::Sneck::Config->new( { file => '/usr/local/etc/sneck.conf' } ); };
if ($@) {
    die($@);
}

if ( !$config->is_valid ) {
    foreach my $error ( $config->errors ) {
        print $error->{where} . ': ' . $error->{message} . "\n";
    }
}

foreach my $name ( sort keys %{ $config->checks } ) {
    print $name . ' runs ' . $config->substitute( $config->checks->{$name} ) . "\n";
}

CONFIG FORMAT

Two formats are supported, the sneck format and YAML. Files ending in .yaml or .yml are read as YAML, anything else as the sneck format. This can be overridden via the format arg to new.

The following applies to both.

Names of variables, environment variables, checks, and debug checks are made up of A-Z, a-z, 0-9, and _.

Variables are used in commands in the form /%+variable_name%+/. A reference to a variable that is not defined is left as written and produces a warning, as it may just be part of the command, such as 'date +%Y%m%d'.

Debug checks are the same as checks, but are not counted towards any of the counts. They exist purely for debugging. A check and a debug check may share a name.

Commands may not be empty.

Environment variables are only set if the whole config is valid.

SNECK FORMAT

Each line has leading spaces and tabs removed before it is looked at. A trailing \r is also removed, so files with CRLF line endings work.

Blank lines are ignored.

Lines starting with # are comments and are ignored.

Lines matching /^[Ee][Nn][Vv]\ [A-Za-z0-9\_]+\=/ set environment variables. The name is between the space and the first =, the value is everything after it. The value may be empty. These may be set more than once, with the last one winning.

Lines matching /^\$[A-Za-z0-9\_]+\=/ are options. The name is between the $ and the first =, the value is everything after it. See OPTIONS.

Lines matching /^[A-Za-z0-9\_]+\=/ are variables. The name is before the first =, the value is everything after it. The value may be empty.

Lines matching /^[A-Za-z0-9\_]+\|/ are checks. The name is before the first |, the command is everything after it with leading whitespace removed.

Lines matching /^\%[A-Za-z0-9\_]+\|/ are debug checks. The leading % is not part of the name.

Lines matching /^\@[A-Za-z0-9\_]+\|[^\|]*\|/ are restarts. The name is between the @ and the first |, the options are between the first and second |, and the command is everything after the second |, with leading whitespace removed. Options are separated by spaces or tabs and are in the form key=value. Values containing spaces or tabs may be quoted with " or ', such as not_every="* 0-3 * * *". The checks and depends options are comma separated lists. See RESTARTS.

Any other sort of line is an error.

Option, variable, check, debug check, and restart names may not be redefined.

EXAMPLE SNECK CONFIG

env PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
# this is a comment
GEOM_DEV=foo
geom_foo|/usr/local/libexec/nagios/check_geom mirror %GEOM_DEV%
does_not_exist|/bin/this_will_error yup... that it will

does_not_exist_2|/usr/bin/env /bin/this_will_also_error

#includes route info
%routes|netstat -rn

php_check|/usr/local/libexec/nagios/check_procs -C php-fpm -c 1:
@php_fpm|checks=php_check|/usr/sbin/service php_fpm restart

The first line sets the environment variable PATH.

The second is ignored as it is a comment.

The third sets the variable GEOM_DEV to 'foo'.

The fourth creates a check named geom_foo that calls check_geom with the value of the variable GEOM_DEV.

The fifth is an example of a check that calls a file that does not exist.

The sixth line is ignored as it is blank.

The seventh is an example of another command erroring.

The eighth is ignored as it is blank.

The ninth is ignored as it is a comment.

The tenth creates a debug check named routes.

The eleventh is ignored as it is blank.

The twelfth creates a check named php_check.

The thirteenth creates a restart named php_fpm that runs the command when php_check is critical, if restarts are enabled.

When it is run, errors for the fifth and seventh lines are printed to STDERR. For this reason, use '2> /dev/null' when calling it from snmpd or '2> /dev/null > /dev/null' when calling it from cron.

YAML FORMAT

This needs YAML::XS, which is optional and only loaded when a YAML config is used.

The top level is a mapping with up to six keys, each of which is a mapping of names to values.

- options :: Options. See OPTIONS.

- env :: Environment variables to set. Set in sorted name order.

- vars :: Variables.

- checks :: Checks, with the command as the value.

- debugs :: Debug checks, with the command as the value.

- restarts :: Restarts. Each is a mapping of options plus command.
  See RESTARTS.

Any other top level key is an error. Any section may be left out or empty. An empty file is a valid config with nothing in it.

Values must be strings or numbers. An empty value, such as 'FOO:' or 'FOO: ~', is an empty string for env and vars and an error for checks and debugs.

YAML::XS turns some unquoted values into something else. 'true' becomes 1 and 'false' becomes a empty string. Quote values like those.

Values starting with % or containing ': ' or ending in ':' must be quoted, such as '-w 80 -c 1:'.

YAML::XS keeps the last of any duplicate keys without saying anything, so redefinitions can not be caught like they are in the sneck format.

EXAMPLE YAML CONFIG

env:
  PATH: /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
vars:
  GEOM_DEV: foo
checks:
  geom_foo: /usr/local/libexec/nagios/check_geom mirror %GEOM_DEV%
  does_not_exist: /bin/this_will_error yup... that it will
  php_check: '/usr/local/libexec/nagios/check_procs -C php-fpm -c 1:'
debugs:
  routes: netstat -rn
restarts:
  php_fpm:
    command: /usr/sbin/service php_fpm restart
    checks: [php_check]

OPTIONS

Options are settings for sneck itself. Options given on the command line override these. cache_file, pid_dir, and locking are only used by sneck, not Monitoring::Sneck.

- cache_file :: The cache file. The same as B<-C> for sneck.
  May not be empty.

- pid_dir :: The directory for the PID file used for locking. The
  same as B<-P> for sneck. May not be empty.

- locking :: If 1, locking is enabled. The same as B<-l> for sneck.
  B<-L> disables it. Takes true and false in YAML.

- check_timeout :: Seconds to wait on each check and debug check
  before giving up on it. Works the same as timeout for restarts.
  A timeout counts as errored, with a exit of -1. The same as B<-T>
  for sneck.
  Default :: 30

- check_timeout_signal :: Signal to send a check or debug check on
  timeout. Works the same as timeout_signal for restarts. The same
  as B<-s> for sneck.
  Default :: none

- check_kill_sub_pids :: If 1, the timeout signal is also sent to
  all child processes of the check or debug check. Works the same
  as kill_sub_pids for restarts. The same as B<-k> for sneck. B<-K>
  disables it. Takes true and false in YAML.
  Default :: 1

Any other option is an error.

$cache_file=/var/db/sneck/sneck.cache
$pid_dir=/var/run/sneck
$locking=1
$check_timeout=60
$check_timeout_signal=TERM

options:
  cache_file: /var/db/sneck/sneck.cache
  pid_dir: /var/run/sneck
  locking: true
  check_timeout: 60
  check_timeout_signal: TERM

RESTARTS

A restart is a command that is run when enough of the checks it watches fail. Restarts only run when asked for, via the restart option of Monitoring::Sneck or -r for sneck. Otherwise they are just reported.

Options are as below.

- checks :: Required. The checks to watch. Debug checks can not be
  watched.

- threshold :: How many of the watched checks must fail for it to
  trigger. Must be between 1 and the number of checks.
  Default :: 1

- depends :: Other restarts this one depends on. When both trigger,
  the ones depended on run first. A depend that did not trigger is
  assumed to be fine and is not run. If a depend runs and fails,
  or was itself skipped for that reason, this one is skipped.
  Dependency cycles are errors.
  Default :: none

- cascade :: If 1, this also runs when any of its depends ran
  without failing, even if its own threshold was not met.
  Default :: 0

- ignore_unknown :: If 0, unknown, exit 3, counts as failed.
  Default :: 1

- ignore_errored :: If 0, errored counts as failed. This is any
  exit other than 0 to 3, dying on a signal, or not being able to
  run the check.
  Default :: 1

- min_interval :: Minimum seconds from when this restart last
  finished to when it may run again. 0 turns it off.
  Default :: 180

- max_retries :: How many times in a row it will run for its
  threshold before giving up until its checks recover. 0 means
  always retry. Runs from cascade do not count towards this.
  Default :: 0

- timeout :: Seconds to wait on the command before giving up on it.
  On timeout its output pipes are closed and it is left running,
  unless timeout_signal is set. If it writes again it gets SIGPIPE,
  or whatever it does when the reader goes away. A timeout counts
  as failed, with a exit of -1.
  Default :: 30

- timeout_signal :: Signal to send the command on timeout, as a
  name such as TERM or SIGTERM, or a number. 0 is not allowed. It is
  sent to the PID of the command and it is not waited on afterwards.
  If the command has shell metacharacters in it, that PID is the
  /bin/sh running it, so use kill_sub_pids to reach the command.
  Default :: none

- kill_sub_pids :: If 1, the timeout signal is also sent to all
  child processes of the command, found via pgrep and signaled via
  pkill, deepest first. Does nothing without timeout_signal.
  Default :: 1

- check_restart :: If 1, its checks are rerun after the command
  finishes and their results replace the earlier ones in what is
  returned. If they still meet the threshold, the restart counts
  as failed, the same as the command failing.
  Default :: 0

- check_restart_delay :: Seconds to wait after the command finishes
  before rerunning its checks. Does nothing without check_restart.
  Default :: 5

- not_every :: A maintenance window, as a five field cron spec of
  minute, hour, day of month, month, and day of week. While the
  local time matches it, this restart does not run. Only numbers
  are allowed, no names such as sat or jan. Sunday is 0 or 7. As
  with cron, if both day of month and day of week are given, either
  one matching is enough. Restarts depending on one held back by
  this treat it as if it did not trigger. In the sneck format it
  must be quoted. In YAML it must be quoted if it starts with *.
  Needs L<DateTime::Event::Cron>.
  Default :: none

Critical always counts as failed. Ok and warning never do.

The 0/1 options also take true and false in YAML.

Restart commands get variables put in place the same as checks. A restart does not wait for its output to be closed once its command has exited, so a daemon started by it holding stdout open is fine.

The command is also given the environment variables below, describing why it is running.

- SNECK_RESTART :: The name of the restart.

- SNECK_REASON :: Either 'threshold' or 'cascade'.

- SNECK_CASCADE_FROM :: The depend it cascaded from, or empty.

- SNECK_FAILED_CHECKS :: Comma separated list of its checks that
  counted as failed. May be empty for a cascade.

- SNECK_CHECKS :: Comma separated list of the checks it watches.

- SNECK_THRESHOLD :: The threshold.

- SNECK_ATTEMPTS :: Runs for its threshold since its checks last
  recovered, this one included.

@httpd|checks=http_check,php_check threshold=2 depends=php_fpm cascade=1 timeout=60|/usr/sbin/service apache24 restart

restarts:
  httpd:
    command: /usr/sbin/service apache24 restart
    checks: [http_check, php_check]
    threshold: 2
    depends: [php_fpm]
    cascade: true
    timeout: 60

@php_fpm|checks=php_check timeout=60 timeout_signal=TERM check_restart=1|/usr/sbin/service php_fpm restart

restarts:
  php_fpm:
    command: /usr/sbin/service php_fpm restart
    checks: [php_check]
    timeout: 60
    timeout_signal: TERM
    check_restart: true

@php_fpm|checks=php_check not_every="* 2-3 * * 0"|/usr/sbin/service php_fpm restart

restarts:
  php_fpm:
    command: /usr/sbin/service php_fpm restart
    checks: [php_check]
    not_every: '* 2-3 * * 0'

The last two never restart between 02:00 and 03:59 on Sundays.

METHODS

new

Reads and parses a config.

One argument is taken and that is a hash ref.

- file :: Path to the config file to read.

- raw :: The config as a string. Used instead of file if both
  are given.

- format :: Either 'sneck' or 'yaml'. If not given, a file ending
  in .yaml or .yml is 'yaml' and anything else, including raw, is
  'sneck'.

Dies if neither file nor raw is given, the file can not be read, the format is unknown, or the format is 'yaml' and YAML::XS can not be loaded. Problems with the config itself never die. Check them via is_valid and errors.

my $config;
eval { $config = Monitoring::Sneck::Config->new( { file => $file } ); };
if ($@) {
    die($@);
}

my $config = Monitoring::Sneck::Config->new( { raw => "FOO=bar\nfoo_check|/bin/echo %FOO%\n" } );

my $config = Monitoring::Sneck::Config->new( { raw => "checks:\n  foo_check: /bin/true\n", format => 'yaml' } );

file

Returns the path to the config file read or undef if it was created from a string.

my $file = $config->file;

format

Returns the format the config was parsed as, either 'sneck' or 'yaml'.

my $format = $config->format;

raw

Returns the raw config as a string, exactly as read.

my $raw = $config->raw;

is_valid

Returns 1 if the config has no errors, otherwise 0. Warnings do not affect this.

if ( !$config->is_valid ) {
    warn('config has errors');
}

errors

Returns a list of errors found in the config. Each is a hash ref as below.

- where :: Where the problem is, for printing. For the sneck format
  this is 'line N'. For YAML this is the path to the problem, such
  as 'checks.foo', or 'YAML' for a YAML syntax error.

- line :: The line number, starting at 1. Undef for YAML.

- path :: The path to the problem, such as 'checks.foo'. Undef for
  the sneck format.

- text :: The line as it appears in the file. Undef for YAML.

- message :: A description of the problem.

For the sneck format these are in line order. For YAML they are in section order, options, env, vars, checks, debugs, then restarts, then by name.

foreach my $error ( $config->errors ) {
    print $error->{where} . ': ' . $error->{message} . "\n";
}

warnings

Returns a list of warnings found in the config. Same format and order as errors.

foreach my $warning ( $config->warnings ) {
    print $warning->{where} . ': ' . $warning->{message} . "\n";
}

options

Returns a hash ref of the options set in the config, with the names as keys. Options not set are left out, so the caller can fall back to its own defaults. Invalid ones are also left out. Values are in the standard form described under validate_option. This is a copy, so changing it does not change the config.

my $cache_file = $config->options->{cache_file};

validate_option

Validates a option value and puts it in a standard form, the same as is done for the config. May be called on the class or a object.

Two arguments are taken, the option name and the value.

Returns two values. The first is the value in standard form or undef if it is not valid. The second is undef if it is valid or a error message.

The standard forms are as below.

- locking, check_kill_sub_pids :: 0 or 1.

- check_timeout :: A number.

- check_timeout_signal :: The signal name without the SIG prefix,
  such as TERM. Numbers are turned into names.

my ( $signal, $error ) = Monitoring::Sneck::Config->validate_option( 'check_timeout_signal', 'sigterm' );
# $signal is 'TERM' and $error is undef

my ( $timeout, $error ) = Monitoring::Sneck::Config->validate_option( 'check_timeout', 0 );
# $timeout is undef and $error is 'option "check_timeout" must be a whole number of at least 1'

vars

Returns a hash ref of the variables, with the names as keys. This is a copy, so changing it does not change the config.

my $value = $config->vars->{GEOM_DEV};

env

Returns a array ref of environment variables to set, in the order they should be set. Each item is a array ref of the name and value. This is a copy, so changing it does not change the config.

For the sneck format, the order is the order they appear in the config. For YAML, it is sorted by name.

Nothing is set in %ENV by this module. That is left to the caller.

foreach my $env ( @{ $config->env } ) {
    $ENV{ $env->[0] } = $env->[1];
}

checks

Returns a hash ref of the checks, with the names as keys and the commands, before variable substitution, as values. This is a copy, so changing it does not change the config.

my $command = $config->checks->{geom_foo};

debugs

Returns a hash ref of the debug checks. Same format as checks. The names do not include the leading % used by the sneck format.

my $command = $config->debugs->{routes};

restarts

Returns a hash ref of the restarts, with the names as keys. Each value is a hash ref with every option filled in, defaults included, as below. This is a copy, so changing it does not change the config.

- command :: The command, before variable substitution.

- checks :: Array ref of the names of the checks watched.

- depends :: Array ref of the names of the restarts depended on.

- threshold, cascade, ignore_unknown, ignore_errored, min_interval,
  max_retries, timeout, kill_sub_pids, check_restart,
  check_restart_delay :: As described under
  RESTARTS. The 0/1 options are always 0 or 1.

- timeout_signal :: The signal name without the SIG prefix, such as
  TERM, or undef if not set. Numbers are turned into names.

- not_every :: The cron spec with surrounding whitespace removed and
  other runs of whitespace turned into a single space, or undef if
  not set.

my $threshold = $config->restarts->{httpd}{threshold};

substitute

Takes a string, usually a check command, and returns it with the variables put in place. Variables are put in place in sorted name order. Undefined variables are left as written.

my $ran = $config->substitute( $config->checks->{geom_foo} );

BUGS

Please report any bugs or feature requests to bug-monitoring-sneck at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Monitoring-Sneck.