#!/usr/bin/env perl ## Copyright © 2010 by Daniel Friesel ## License: WTFPL ## 0. You just DO WHAT THE FUCK YOU WANT TO. use autodie; use strict; use warnings; use 5.010; use Date::Format; use Getopt::Long qw/:config bundling/; use Term::ANSIColor; my $VERSION = '0.3'; my ($cache, $config, $data, $extra); my $config_file = '/var/cache/icinga/objects.cache'; my $status_file = '/var/lib/icinga/status.dat'; my $rw_file = '/var/lib/icinga/rw/icinga.cmd'; my $context; my $colours = 1; my $list_type = 's'; my $verbosity = 1; my $recheck = 0; my $acknowledge = undef; my (@for_hosts, @for_groups, @for_services, @list_hosts, @list_services); 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) = @_; if ($colours) { return colored($text, $colour); } else { return $text; } } sub pretty_date { my ($unix) = @_; if ($unix == 0) { return 'never'; } return time2str('%Y-%m-%d %H:%M:%S', $unix); } sub pretty_duration { my ($since) = @_; my $now = time(); my $dif = $now - $since; return sprintf( "%dd %dh %dm %ds", int($dif / (24 * 3600)), int(($dif / 3600) % 24), int(($dif / 60) % 60), $dif % 60, ); } sub pretty_noyes { my ($bool) = @_; return ($bool ? colored('YES', 'white on_red') : colored('NO', 'black on_green') ); } sub pretty_yesno { my ($bool) = @_; return ($bool ? colored('YES', 'black on_green') : colored('NO', 'white on_red') ); } sub check_is_soft { my ($x) = @_; return ($x->{'last_hard_state'} != $x->{'current_state'}); } sub check_is_host_down { my ($s) = @_; return ( $data->{'hosts'}->{ $s->{'host_name'} }->{'current_state'} != 0 ); } sub filter_generic { my ($x) = @_; my $filters_unfulfilled = @filters; foreach my $f (@filters) { if ( ($f eq 'A' and $x->{'problem_has_been_acknowledged'}) or ($f eq '!A' and not $x->{'problem_has_been_acknowledged'}) or ($f eq 'D' and check_is_host_down($x)) or ($f eq '!D' and not check_is_host_down($x)) or ($f eq 'F' and $x->{'is_flapping'}) or ($f eq '!F' and not $x->{'is_flapping'}) or ($f eq 'P' and $x->{'passive_checks_enabled'} and not $x->{'active_checks_enabled'}) or ($f eq '!P' and $x->{'active_checks_enabled'}) or ($f eq 'S' and check_is_soft($x)) or ($f eq '!S' and not check_is_soft($x)) or ($f eq 'o' and $x->{'current_state'} == 0) or ($f eq '!o' and $x->{'current_state'} != 0) or ($f eq 'w' and $x->{'current_state'} == 1) or ($f eq '!w' and $x->{'current_state'} != 1) or ($f eq 'c' and $x->{'current_state'} == 2) or ($f eq '!c' and $x->{'current_state'} != 2) or ($f eq 'u' and $x->{'current_state'} == 3) or ($f eq '!u' and $x->{'current_state'} != 3) or ($f eq 'd' and $x->{'current_state'} == 1) or ($f eq '!d' and $x->{'current_state'} != 1) or ($f eq 'x' and $x->{'current_state'} == 2) or ($f eq '!x' and $x->{'current_state'} != 2) or 0 # Terminator to ease adding new lines ) { $filters_unfulfilled--; } } if ($filters_unfulfilled) { return 0; } return 1; } sub filter_host { my ($h) = @_; if (not filter_generic($h)) { return 0; } return 1; } sub filter_service { my ($s) = @_; if (not filter_generic($s)) { return 0; } if (@list_services and not ($s->{'service_description'} ~~ [@list_services])) { return 0; } return 1; } sub read_objects_line { my ($line, $ref) = @_; if ($line =~ / ^ (?:define \s )? (? \w+) \s+ { /x) { $context = $+{context}; } elsif ($line =~ / ^ \t (? [^=\t]+ ) [=\t] (? .*) $ /x) { $cache->{$+{key}} = $+{value}; } elsif ($line =~ / ^ \t } $ /x) { given ($context) { when (['info', 'programstatus']) { ${$ref}->{$context} = $cache; } when ('hoststatus') { ${$ref}->{hosts}->{$cache->{host_name}} = $cache; } when ('servicestatus') { push(@{${$ref}->{services}->{$cache->{host_name}}}, $cache); } when ('contactstatus') { push(@{${$ref}->{contacts}}, $cache); } when ('hostdowntime') { push(@{${$ref}->{downtimes}}, $cache); } when ('hostgroup') { ${$ref}->{hostgroups}->{$cache->{hostgroup_name}} = $cache; } when('servicegroup') { ${$ref}->{servicegroups}->{$cache->{servicegroup_name}} = $cache; } when ('hostcomment') { # TODO } when ('servicecomment') { # TODO } when ([qw[ timeperiod command contactgroup contact host service servicedependency ]]) { # skipped for now } default { warn("Unknown field in $status_file: $context\n"); } } $cache = undef; } } sub read_objects { my ($file, $ref) = @_; open(my $fh, '<', $file); while (my $line = <$fh>) { chomp($line); read_objects_line($line, $ref); } close($fh); } sub enhance_status { HOST: for my $h (keys %{$data->{services}}) { for my $s (@{$data->{services}->{$h}}) { if ($s->{current_state} != 0) { $extra->{$h}->{service_problem} = 1; next HOST; } } } } sub service_state { my ($checked, $digit) = @_; if (not $checked) { return 'PENDING '; } given ($digit) { when(0) { return with_colour(' OK ', 'black on_green' ) } when(1) { return with_colour(' WARNING', 'black on_yellow') } when(2) { return with_colour('CRITICAL', 'white on_red' ) } when(3) { return with_colour(' UNKNOWN', 'white on_blue' ) } default { die("Unknown service state: $digit\n") } } } sub host_state { my ($checked, $digit) = @_; if (not $checked) { return ' PENDING '; } given($digit) { when(0) { return with_colour(' OK ', 'black on_green') } when(1) { return with_colour(' DOWN ', 'white on_red' ) } when(2) { return with_colour('UNREACHABLE', 'white on_blue' ) } default { die("Unknown host state: $digit\n") } } } sub display_queue { my @queue = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, $_->{next_check}] } (values %{$data->{hosts}}, map { @{$_} } values %{$data->{services}}); @queue = grep { $_->{host_name} ~~ \@list_hosts } @queue; if (@list_services) { @queue = grep { $_->{service_description} ~~ \@list_services } @queue; } printf( "%-25.25s %-20.20s %-19s %-19s\n", 'Host', 'Service', 'Last Check', 'Next Check', ); for my $e (@queue) { if ($e->{next_check} == 0) { next; } printf( "%-25.25s %-20.20s %-19s %-19s\n", $e->{host_name}, $e->{service_description} // q{}, pretty_date($e->{last_check}), pretty_date($e->{next_check}), ); } } sub display_downtime { my ($d) = @_; my $v = $verbosity; printf('%-27.27s', $d->{'host_name'}); if ($v >= 3) { printf( ' %s %-10.10s', pretty_date($d->{'entry_time'}), $d->{'author'}, ); } if ($v >= 2) { printf(' %-30.30s', $d->{'comment'}); } printf( ' %s %s', pretty_date($d->{'start_time'}), pretty_date($d->{'end_time'}), ); if ($v >= 2) { print ($d->{'fixed'} ? ' Fixed' : ' Flexi'); } print "\n"; } sub display_service { my ($s) = @_; my $v = $verbosity; my $flags = q{}; my $format = "%-16s : %s\n"; if ($v < 3) { printf("%-20.20s", $s->{service_description}); if ($v >= 2) { if ($s->{'problem_has_been_acknowledged'}) { $flags .= 'A'; } if ($s->{'is_flapping'}) { $flags .= 'F'; } if ($s->{'notifications_enabled'} == 0) { $flags .= 'N'; } if ($s->{'active_checks_enabled'} == 0 and $s->{'passive_checks_enabled'} == 1) { $flags .= 'P'; } if (not ($s->{'active_checks_enabled'} or $s->{'passive_checks_enabled'})) { $flags .= '!'; } printf(" %s%-3s%s", color('bold'), $flags, color('reset')); } printf(" %s" , service_state($s->{'has_been_checked'}, $s->{'current_state'})); if ($v >= 2) { printf(' %d/%d', $s->{'current_attempt'}, $s->{'max_attempts'}); } printf(" %s", $s->{'plugin_output'}); } else { printf( $format, 'Host', $s->{'host_name'}, ); printf( $format, 'Service', $s->{'service_description'}, ); printf( "%-16s : %s (for %s)%s\n", 'Status', service_state($s->{'has_been_checked'}, $s->{'current_state'}), pretty_duration($s->{'last_state_change'}), ($s->{'problem_has_been_acknowledged'} ? ' (Acknowledged)' : q{}), ); printf( $format, 'Plugin Output', $s->{'plugin_output'}, ); printf( $format, 'Performance Data', $s->{'performance_data'}, ); printf( "%-16s : %d/%d\n", 'Current Attempt', $s->{'current_attempt'}, $s->{'max_attempts'}, ); printf( $format, 'Last Check Time', pretty_date($s->{'last_check'}), ); printf( $format, 'Next Check', pretty_date($s->{'next_check'}), ); printf( "%-16s : %s (%.1f%% state change)\n", 'Flapping', pretty_noyes($s->{'is_flapping'}), $s->{'percent_state_change'}, ); } if ($v > 3) { printf( $format, 'Check Type', ($s->{'check_type'} ? 'PASSIVE' : 'ACTIVE'), ); printf( "%-16s : %5.3fs\n%-16s : %5.3fs\n", 'Check Latency', $s->{'check_latency'}, 'Check Duration', $s->{'check_execution_time'}, ); printf( "%-16s : o %s w %s c %s u %s\n", 'Last State Times', pretty_date($s->{'last_time_ok'}), pretty_date($s->{'last_time_warning'}), pretty_date($s->{'last_time_critical'}), pretty_date($s->{'last_time_unknown'}), ); printf( $format, 'In Downtime', 'FIXME' ); printf( $format, 'Active Checks', pretty_yesno($s->{'active_checks_enabled'}), ); printf( $format, 'Passive Checks', pretty_yesno($s->{'passive_checks_enabled'}), ); printf( $format, 'Obsessing', pretty_yesno($s->{'obsess_over_service'}), ); printf( $format, 'Notifications', pretty_yesno($s->{'notifications_enabled'}), ); printf( $format, 'Event Handler', pretty_yesno($s->{'event_handler_enabled'}), ); printf( $format, 'Flap Detection', pretty_yesno($s->{'flap_detection_enabled'}), ); } print "\n"; } sub display_host_services { my ($host, $all) = @_; my @services; my $h = $data->{'hosts'}->{$host}; @services = grep { filter_service($_) } @{$data->{'services'}->{$host}}; if ($all and @services and $verbosity < 3) { print "\n$host"; if ($h->{'current_state'}) { print q{ }; } if ($h->{'current_state'} == 1) { print colored('DOWN', 'white on_red'); } elsif ($h->{'current_state'} == 2) { print colored('UNREACHABLE', 'white on_blue'); } print "\n"; } foreach my $service (@services) { if ($all and $verbosity < 3) { print "\t"; } elsif ($all) { print "\n"; } display_service($service); } } sub display_host_single { my ($host) = @_; my $format = "%-16s : %s\n"; my $h = $data->{hosts}->{$host}; my $v = $verbosity; if ($v < 3) { printf("%-32.32s", $h->{'host_name'}); printf(" %s", host_state($h->{'has_been_checked'}, $h->{'current_state'})); if ($v >= 2) { printf(" %d/%d", $h->{'current_attempt'}, $h->{'max_attempts'}); } printf(" %s", $h->{'plugin_output'}); } else { printf( $format, 'Host', $h->{'host_name'}, ); printf( "%-16s : %s (for %s)%s\n", 'Status', host_state($h->{'has_been_checked'}, $h->{'current_state'}), pretty_duration($h->{'last_state_change'}), ($h->{'problem_has_been_acknowledged'} ? ' (Acknowledged)' : q{}), ); printf( $format, 'Plugin Output', $h->{'plugin_output'}, ); printf( $format, 'Performance Data', $h->{'performance_data'}, ); printf( "%-16s : %d/%d\n", 'Current Attempt', $h->{'current_attempt'}, $h->{'max_attempts'}, ); printf( $format, 'Last Check Time', pretty_date($h->{'last_check'}), ); printf( $format, 'Next Check', pretty_date($h->{'next_check'}), ); printf( "%-16s : %s (%.1f%% state change)\n", 'Flapping', ($h->{'is_flapping'} ? colored('YES', 'white on_red') : colored('NO', 'black on_green') ), $h->{'percent_state_change'}, ); } if ($v > 3) { printf( $format, 'Check Type', ($h->{'check_type'} ? 'PASSIVE' : 'ACTIVE'), ); printf( "%-16s : %5.3fs\n%-16s : %5.3fs\n", 'Check Latency', $h->{'check_latency'}, 'Check Duration', $h->{'check_execution_time'}, ); printf( "%-16s : o %s d %s u %s\n", 'Last State Times', pretty_date($h->{'last_time_up'}), pretty_date($h->{'last_time_down'}), pretty_date($h->{'last_time_unreachable'}), ); printf( $format, 'In Downtime', 'FIXME' ); printf( $format, 'Active Checks', pretty_yesno($h->{'active_checks_enabled'}), ); printf( $format, 'Passive Checks', pretty_yesno($h->{'passive_checks_enabled'}), ); printf( $format, 'Obsessing', pretty_yesno($h->{'obsess_over_host'}), ); printf( $format, 'Notifications', pretty_yesno($h->{'notifications_enabled'}), ); printf( $format, 'Event Handler', pretty_yesno($h->{'event_handler_enabled'}), ); printf( $format, 'Flap Detection', pretty_yesno($h->{'flap_detection_enabled'}), ); } print "\n"; } sub display_host { my ($host, $all) = @_; if ($list_type eq 'h') { display_host_single($host); } else { display_host_services($host, $all); } } sub dispatch_command { my $str = join(';', @_); open(my $cmd_fh, '>', $rw_file); printf $cmd_fh ( "[%d] %s", time(), $str, ); close($cmd_fh); } sub recheck_host_all { my ($host) = @_; dispatch_command('SCHEDULE_HOST_SVC_CHECKS', $host, time()); say "Scheduled check of * on '$host'"; } sub recheck_service { my ($host, $service) = @_; dispatch_command('SCHEDULE_SVC_CHECK', $host, $service, time()); say "Scheduled check of '$service' on '$host'"; } sub acknowledge_service { my ($host, $service) = @_; dispatch_command('ACKNOWLEDGE_SVC_PROBLEM', $host, $service, 2, 1, 1, 'cli', $acknowledge); say "Acknowledged $host/$service: $acknowledge"; } sub action_on_host { my ($h) = @_; if ($recheck) { recheck_host_all($h); } } sub action_on_service { my ($h, $s) = @_; if (not have_service($h, $s)) { return; } if ($recheck) { recheck_service($h, $s); } if ($acknowledge) { acknowledge_service($h, $s); } } GetOptions( 'a|acknowledge=s' => sub { $acknowledge = $_[1]; $list_type = q{} }, '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) }, 'r|recheck' => sub { $recheck = 1; $list_type = q{} }, 's|service=s' => sub { push(@for_services, split(/,/, $_[1])) }, 'v|verbose+' => \$verbosity, 'V|version' => sub { say "icli version $VERSION"; exit 0 }, 'z|filter=s' => sub { push(@filters, split(/,/, $_[1])) }, ) or die("Please see perldoc -F $0 for help\n"); read_objects($status_file, \$data); read_objects($config_file, \$config); enhance_status(); foreach my $host (@for_hosts) { if (not exists $data->{services}->{$host}) { die("Unknown host: ${host}\n"); } } @list_hosts = @for_hosts; @list_services = @for_services; foreach my $group (@for_groups) { if (not exists $config->{'hostgroups'}->{$group}) { die("Unknown hostgroup: ${group}\n"); } foreach my $host (split/,/, $config->{'hostgroups'}->{$group}->{'members'}) { if (not grep { $_ eq $host } @list_hosts) { push(@list_hosts, $host); } } } if (@list_hosts == 0) { @list_hosts = sort keys %{$data->{services}}; } if (@list_services) { @list_hosts = grep { have_service_multi($_, @list_services) } @list_hosts; } if ($list_type eq 'h') { @list_hosts = grep { filter_host($data->{'hosts'}->{$_}) } @list_hosts; } if ($list_type ~~ [qw[s h]]) { foreach my $host (@list_hosts) { display_host($host, (@list_hosts > 1)); } } elsif ($list_type eq 'q') { display_queue(); } elsif ($list_type eq 'd') { foreach my $downtime (@{$data->{downtimes}}) { display_downtime($downtime); } } elsif ($recheck or $acknowledge) { foreach my $host (@list_hosts) { if (not @list_services and not @filters) { action_on_host($host); } elsif (not @list_services and @filters) { foreach my $service ( grep { filter_service($_) } @{$data->{'services'}->{$host}} ) { action_on_service($host, $service->{'service_description'}); } } else { foreach my $service (@list_services) { action_on_service($host, $service); } } } } else { die("See perldoc -F $0\n"); } __END__ =head1 NAME B - Icinga Command Line Interface =head1 SYNOPSIS B [B<-v>|B<-vv>] [B<-z> I] [B<-h> I] [B<-g> I] [B<-s> I] [B<-c> I] [B<-C>] [B<-f> I] [B<-F> I] [B<-r>|B<-lh>|B<-ls>|B<-lq>|B<-ld>] =head1 DESCRIPTION B is a command line interface to B. By default it lists all services and their states. Note that when supplying custom config and status file paths, B also works with B. 100% compatibility is not guranteed, however. =head1 OPTIONS =over =item B<-c>|B<--config> I Read config from I instead of the default F =item B<-C>|B<--no-colours> Disable colours in output =item B<-f>|B<--status-file> I Read the status from I instead of the default F =item B<-F>|B<--rw-file> I Use I as external commands file. Default: F =item B<-g>|B<--hostgroup> I Limit selection to hosts in I (comma separated list) =item B<-h>|B<--host> I Limit selection to I (comma separated list) =item B<-l>|B<--list> B|B|B|B List either services (the default) or hosts. Note that only the first character of the argument is checked, so C<< icli -lh >>, C<< icli -ls >> etc. are also fine. =item B<-r>|B<--recheck> Schedule an immediate recheck of all selected services =item B<-s>|B<--service> I Limit selection to I (comma separated lists). Can be combined with B<-h>/B<-g> to further narrow down the selection, but may also be used stand-alone. =item B<-v>|B<--verbose> Increase output verbosity =item B<-V>|B<--version> Show version information =item B<-z>|B<--filter> I Limit selection to hosts/services passing the filter. I is a comma separated list of filters, only hosts/services to which all filters apply are selected. See also L. =back =head1 OUTPUT =head2 SERVICE LISTING This is the standard output method. It contains the following: =over =item * Service description =item * -v: Service Flags (Bcknowledged, Blapping, B

assive, Bno checks) =item * Service state (ok / warning / critical / unknown) =item * -v: Current attempt / Max attempts =item * Plugin output =back =head2 HOST LISTING Enabled with -ld =over =item * Host name =item * Host state (ok / down / unreachable) =item * -v: Current attempt / Max attempts =item * Plugin output =back =head2 QUEUE LISTING Enabled with -lq =over =item * Host name =item * Service name =item * Last check =item * Next check =back =head1 FILTER EXPRESSIONS Each expression can be negated with an exclamation mark, e.g. "!A" for all non-acknowledged services. =over =item B Check state has been acknowledged =item B The host this service belongs to is Down or Unreachable =item B Service is flapping between states =item B Notifications for this service are disabled =item B

Only passive checks are enabled. Note that B simply means that active checks are enabled, no matter the status of passive checks =item B 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 Host/Service state is OK =item B Service state is Warning =item B Service state is Critical =item B Service state is Unknown =item B Host state is Down =item B Host state is Unreachable =back =head1 EXIT STATUS Zero, unless errors occured. =head1 CONFIGURATION None. =head1 DEPENDENCIES None, so far. =head1 BUGS AND LIMITATIONS This software is in early development stages. So there will probably be quite a lot. =head2 REPORTING BUGS Either via mail to Ederf@finalrewind.orgE or on Ehttp://github.com/derf/icinga-cli/issuesE. =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 Ederf@finalrewind.orgE =head1 LICENSE 0. You just DO WHAT THE FUCK YOU WANT TO.