App-Icli
view release on metacpan or search on metacpan
#!/usr/bin/env perl
## Copyright © 2010-2012 by Daniel Friesel <derf@finalrewind.org>
## License: WTFPL <http://sam.zoy.org/wtfpl>
## 0. You just DO WHAT THE FUCK YOU WANT TO.
use strict;
use warnings;
use 5.010;
no if $] >= 5.018, warnings => 'experimental::smartmatch';
use App::Icli::ConfigData;
use Carp qw(croak);
use DateTime;
use DateTime::Format::Strptime;
use DateTime::TimeZone;
use Getopt::Long qw/:config bundling/;
use List::MoreUtils qw(any firstval);
use POSIX qw(strftime);
use Term::ANSIColor;
use Term::Size;
our $VERSION = '0.48';
my ( $cache, $config, $data, $extra );
my $config_file = App::Icli::ConfigData->config('object_file');
my $status_file = App::Icli::ConfigData->config('status_file');
my $rw_file = App::Icli::ConfigData->config('command_file');
my $context;
my $colours = 1;
my $list_type = 's';
my $verbosity = 1;
my $overview = 0;
my $match_output = undef;
my $action = undef;
my $as_contact = undef;
my $term_width = Term::Size::chars();
my $cut_mode = 'b';
my ( @for_hosts, @for_groups, @for_services, @list_hosts, @list_services );
my @action_args;
my @filters;
sub have_host {
my ($host) = @_;
if ( $list_type eq 's' ) {
return exists $data->{services}->{$host};
}
else {
return exists $data->{hosts}->{$host};
}
}
sub have_service {
my ( $host, $service ) = @_;
foreach my $s ( @{ $data->{services}->{$host} } ) {
if ( $s->{service_description} eq $service ) {
return 1;
}
}
return 0;
}
sub have_service_multi {
my ( $host, @services ) = @_;
foreach my $s (@services) {
if ( have_service( $host, $s ) ) {
return 1;
}
}
return 0;
}
sub with_colour {
my ( $text, $colour ) = @_;
print "\n";
}
sub display_overview {
my ( $h_ok, $h_d, $h_u, $h_p, $s_ok, $s_w, $s_c, $s_u, $s_p ) = (0) x 9;
for my $h (@list_hosts) {
if ( $data->{hosts}{$h}{has_been_checked} == 0 ) {
$h_p++;
}
else {
given ( $data->{hosts}{$h}{current_state} ) {
when (0) { $h_ok++ }
when (1) { $h_d++ }
when (2) { $h_u++ }
}
}
for my $s ( grep { filter_service($_) } @{ $data->{services}{$h} } ) {
if ( $s->{has_been_checked} == 0 ) {
$s_p++;
}
else {
given ( $s->{current_state} ) {
when (0) { $s_ok++ }
when (1) { $s_w++ }
when (2) { $s_c++ }
when (3) { $s_u++ }
}
}
}
}
printf( "%-16.16s %4s\n", 'total hosts', $h_ok + $h_d + $h_u );
printf( "%-16.16s %s\n", 'up', pretty_state( $h_ok, 'ok' ) );
printf( "%-16.16s %s\n", 'down', pretty_state( $h_d, 'critical' ) );
printf( "%-16.16s %s\n", 'unreachable', pretty_state( $h_u, 'unknown' ) );
printf( "%-16.16s %s\n", 'pending', pretty_state( $h_p, 'pending' ) );
print "\n";
printf( "%-16.16s %4s\n", 'total services', $s_ok + $s_w + $s_c + $s_u );
printf( "%-16.16s %s\n", 'ok', pretty_state( $s_ok, 'ok' ) );
printf( "%-16.16s %s\n", 'warning', pretty_state( $s_w, 'warning' ) );
printf( "%-16.16s %s\n", 'critical', pretty_state( $s_c, 'critical' ) );
printf( "%-16.16s %s\n", 'unknown', pretty_state( $s_u, 'unknown' ) );
printf( "%-16.16s %s\n", 'pending', pretty_state( $s_p, 'pending' ) );
}
sub dispatch_command {
my $str = join( ';', @_ );
open( my $cmd_fh, '>', $rw_file )
or die( "Failed to open icinga command file ($rw_file): $!\n"
. "Set --rw-file to change it\n" );
printf $cmd_fh ( '[%d] %s', time(), $str, );
close($cmd_fh)
or warn("Failed to close $rw_file: $!\n");
}
sub action_on_host {
my ($host) = @_;
my $tz = DateTime::TimeZone->new( name => 'local' );
given ($action) {
when ('downtime') {
my ( $start, $end, $duration, $comment, @opts ) = @action_args;
my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S',
time_zone => $tz->name,
);
my $dt_start = $strp->parse_datetime($start);
my $dt_end = $strp->parse_datetime($end);
my $fixed = $duration ? 0 : 1;
my $command = 'SCHEDULE_HOST_DOWNTIME';
my $addendum = q{};
$duration = parse_duration($duration);
if ( 'children' ~~ \@opts ) {
$command = 'SCHEDULE_AND_PROPAGATE_HOST_DOWNTIME';
$addendum = ' and its children';
}
if ( 'trigger_children' ~~ \@opts ) {
$command = 'SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME';
$addendum = ' and its children (triggered)';
}
dispatch_command( $command, $host, $dt_start->epoch, $dt_end->epoch,
$fixed, 0, $duration, 'cli', $comment );
say "Scheduled host downtime for '$host'$addendum";
}
when ('recheck') {
dispatch_command( 'SCHEDULE_HOST_SVC_CHECKS', $host, time() );
say "Scheduled check of * on '$host'";
}
when ('force_recheck') {
dispatch_command( 'SCHEDULE_FORCED_HOST_SVC_CHECKS', $host,
time() );
say "Scheduled forced check of * on '$host'";
}
default {
say STDERR "Cannot run action '${action}' on a host"
}
}
}
sub action_on_service {
my ( $host, $service ) = @_;
if ( not have_service( $host, $service ) ) {
return;
}
my $tz = DateTime::TimeZone->new( name => 'local' );
given ($action) {
when ('downtime') {
my ( $start, $end, $duration, $comment, @opts ) = @action_args;
my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S',
time_zone => $tz->name,
);
my $dt_start = $strp->parse_datetime($start);
my $dt_end = $strp->parse_datetime($end);
my $fixed = $duration ? 0 : 1;
$duration = parse_duration($duration);
dispatch_command( 'SCHEDULE_SVC_DOWNTIME', $host, $service,
$dt_start->epoch, $dt_end->epoch,
$fixed, 0, $duration, 'cli', $comment );
say "Scheduled service downtime for '$service' on '$host'";
}
when ('recheck') {
dispatch_command( 'SCHEDULE_SVC_CHECK', $host, $service, time() );
say "Scheduled check of '$service' on '$host'";
}
when ('force_recheck') {
dispatch_command( 'SCHEDULE_FORCED_SVC_CHECK', $host, $service,
time() );
say "Scheduled forced check of '$service' on '$host'";
}
when ('Acknowledge') {
dispatch_command( 'ACKNOWLEDGE_SVC_PROBLEM', $host, $service, 2, 1,
1, 'cli', $action_args[0] );
say "Acknowledged $host/$service: $action_args[0]";
}
default {
say STDERR "Cannot run action '${action}' on a service"
}
}
}
GetOptions(
'a|action=s' => \$action,
'c|config=s' => \$config_file,
'C|no-colours' => sub { $colours = 0 },
'f|status-file=s' => \$status_file,
'F|rw-file=s' => \$rw_file,
'g|hostgroup=s' => sub { push( @for_groups, split( /,/, $_[1] ) ) },
'h|host=s' => sub { push( @for_hosts, split( /,/, $_[1] ) ) },
'l|list=s' => sub { $list_type = substr( $_[1], 0, 1 ) },
'm|match=s' => sub { $match_output = qr{$_[1]}i },
'o|overview' => \$overview,
's|service=s' => sub { push( @for_services, split( /,/, $_[1] ) ) },
'U|as-contact=s' => \$as_contact,
'v|verbose+' => \$verbosity,
'V|version' => sub { say "icli version $VERSION"; exit 0 },
'x|cut-mode=s' => sub { $cut_mode = substr( $_[1], 0, 1 ) },
'z|filter=s' => sub { push( @filters, split( /,/, $_[1] ) ) },
) or die("Please see perldoc -F $0 for help\n");
read_objects( $status_file, \$data, 'icinga status_file', '--status-file' );
read_objects( $config_file, \$config, 'icinga object_cache_file', '--config' );
enhance_status();
parse_action();
compute_hostlist();
=item B<N>
Notifications for this service are disabled
=item B<P>
Only passive checks are enabled. Note that B<!P> simply means that active
checks are enabled, no matter the status of passive checks
=item B<S>
Check state is soft. For instance, it used to be OK and is now critical, but
has not reached its maximum number and caused a notification yet. Good to
find (or ignore) service problems which might just be temporary, non-critical
glitches.
=item B<o>
Host/Service state is OK
=item B<w>
Service state is Warning
=item B<c>
Service state is Critical
=item B<u>
Service state is Unknown
=item B<p>
Host or service state is Pending
=item B<d>
Host state is Down
=item B<x>
Host state is Unreachable
=back
=head1 EXIT STATUS
Zero, unless errors occured.
=head1 CONFIGURATION
None.
=head1 DEPENDENCIES
=over
=item * autodie (included with perl >= 5.10.1)
=item * DateTime
=item * DateTime::Format::Strptime
=item * DateTime::TimeZone
=item * Term::Size
=back
=head1 BUGS AND LIMITATIONS
It is probably not clear from the documentation when an action will operate
on hosts and when on services.
Note that this software is not yet stable. Command line options may be changed
/ removed and thus break backwards compatibility at any time.
=head2 REPORTING BUGS
Either via mail to E<lt>derf@finalrewind.orgE<gt> or on
E<lt>http://github.com/derf/icinga-cli/issuesE<gt>.
=head1 EXAMPLES
=over
=item C<< icli -r -s 'APT Updates' >>
Schedule a check of the "APT Updates" service on all hosts having it
=item C<< icli -lq -h aneurysm -g chaosdorf-hosts >>
List check queue for all hosts in the hostgroup "chaosdorf-hosts", plus the
host aneurysm
=item C<< icli -z!o,!A,!S,!D >>
Show all service problems which are already hard states and have not yet been
acknowledged. Also weed out problem services on hosts which are down anyways
=back
=head1 AUTHOR
Copyright (C) 2010 by Daniel Friesel E<lt>derf@finalrewind.orgE<gt>
=head1 LICENSE
0. You just DO WHAT THE FUCK YOU WANT TO.
( run in 0.848 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )