File Coverage

blib/lib/App/MonM.pm
Criterion Covered Total %
statement 63 281 22.4
branch 0 92 0.0
condition 0 64 0.0
subroutine 21 36 58.3
pod 9 9 100.0
total 93 482 19.2


line stmt bran cond sub pod time code
1             package App::MonM; # $Id: MonM.pm 89 2019-07-15 05:19:11Z abalama $
2 1     1   53719 use warnings;
  1         9  
  1         29  
3 1     1   5 use strict;
  1         1  
  1         16  
4 1     1   525 use utf8;
  1         12  
  1         4  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             App::MonM - Simple monitoring tool
11              
12             =head1 VERSION
13              
14             Version 1.07
15              
16             =head1 SYNOPSIS
17              
18             # monm checkit
19             # monm report
20             # monm show
21              
22             =head1 DESCRIPTION
23              
24             Simple monitoring tool
25              
26             =head2 FEATURES
27              
28             =over 4
29              
30             =item Checking availability of sites (http/https)
31              
32             =item Checking of database health (DBI)
33              
34             =item Checking internal and external counters using system commands and tools (command)
35              
36             =item Supports SMTP, POP3, FTP, SSH protocols, and etc.
37              
38             =item Interface for SMS sending
39              
40             =item Easy installation and configuration
41              
42             =item A small number of system dependencies
43              
44             =back
45              
46             =head2 SYSTEM REQUIREMENTS
47              
48             =over 4
49              
50             =item Perl v5.16+
51              
52             =item libwww
53              
54             =item libnet
55              
56             =item L
57              
58             To use this module, you must have Net-SNMP installed on your system.
59             More specifically you need the Perl modules that come with it.
60              
61             DO NOT INSTALL SNMP or Net::SNMP from CPAN!
62              
63             The SNMP module is matched to an install of net-snmp, and must be installed
64             from the net-snmp source tree.
65              
66             The Perl module C is found inside the net-snmp distribution. Go to the
67             F directory of the distribution to install it, or run
68             C<./configure --with-perl-modules> from the top directory of the net-snmp
69             distribution.
70              
71             Net-SNMP can be found at http://net-snmp.sourceforge.net
72              
73             =back
74              
75             =head2 INSTALLATION
76              
77             # sudo cpan install App::MonM
78              
79             ...and then:
80              
81             # sudo monm configure
82              
83             =head2 CONFIGURATION
84              
85             By default configuration file located in C directory
86              
87             Every configuration directive detailed described in C file, also
88             see C file for MonM checkit configuration
89              
90             =head2 CRONTAB
91              
92             To automatically launch the program, we recommend using standard scheduling tools, such as crontab
93              
94             * * * * * monm -l checkit >/dev/null 2>>/var/log/monm-error.log
95              
96             For daily reporting:
97              
98             0 8 * * * monm -l report >/dev/null 2>>/var/log/monm-error.log
99              
100             =head1 INTERNAL METHODS
101              
102             =over 4
103              
104             =item B
105              
106             The CTK method for classes extension. For internal use only!
107              
108             See L
109              
110             =item B
111              
112             The internal method for initializing the project
113              
114             =item B
115              
116             my $dbi = $app->getdbi;
117              
118             Returns DBI object
119              
120             =item B, B, B, B
121              
122             my $status = $app->nope("Format %s", "text");
123              
124             Prints status message and returns status.
125              
126             For nope returns - 0; for skip, wow, yep - 1
127              
128             =item B
129              
130             $app->notify();
131              
132             Sends notifications
133              
134             =item B
135              
136             $app->trigger();
137              
138             Runs triggers
139              
140             =back
141              
142             =head1 HISTORY
143              
144             See C file
145              
146             =head1 DEPENDENCIES
147              
148             L
149              
150             =head1 TO DO
151              
152             See C file
153              
154             =head1 BUGS
155              
156             * none noted
157              
158             =head1 SEE ALSO
159              
160             L
161              
162             =head1 AUTHOR
163              
164             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
165              
166             =head1 COPYRIGHT
167              
168             Copyright (C) 1998-2019 D&D Corporation. All Rights Reserved
169              
170             =head1 LICENSE
171              
172             This program is free software; you can redistribute it and/or
173             modify it under the same terms as Perl itself.
174              
175             See C file and L
176              
177             =cut
178              
179 1     1   50 use vars qw/ $VERSION /;
  1         2  
  1         47  
180             $VERSION = '1.07';
181              
182 1     1   5 use feature qw/ say /;
  1         1  
  1         387  
183              
184 1     1   5 use Carp;
  1         2  
  1         43  
185 1     1   398 use Text::SimpleTable;
  1         1963  
  1         25  
186 1     1   5 use File::Spec;
  1         2  
  1         24  
187 1     1   5 use File::Path; # mkpath / rmtree
  1         2  
  1         57  
188 1     1   414 use Try::Tiny;
  1         1665  
  1         49  
189 1     1   359 use Text::ParseWords qw/shellwords/;
  1         1050  
  1         46  
190              
191 1     1   386 use CTK::Skel;
  1         160275  
  1         36  
192 1     1   8 use CTK::Util qw/ preparedir dformat execute dtf sendmail variant_stf /;
  1         2  
  1         55  
193 1     1   6 use CTK::ConfGenUtil;
  1         2  
  1         46  
194 1     1   5 use CTK::TFVals qw/ :ALL /;
  1         1  
  1         149  
195              
196 1     1   392 use App::MonM::Const;
  1         3  
  1         55  
197 1         69 use App::MonM::Util qw/
198             blue green red yellow cyan
199             getBit setBit
200             getExpireOffset explain
201 1     1   342 /;
  1         2  
202 1     1   361 use App::MonM::Store;
  1         3  
  1         28  
203 1     1   397 use App::MonM::Checkit;
  1         3  
  1         31  
204              
205 1     1   6 use base qw/ CTK::App /;
  1         2  
  1         397  
206              
207             use constant {
208 1         4840 TAB9 => " " x 9,
209             EXPIRES => 24*60*60, # 1 day
210             SMSSBJ => 'MONM CHECKIT REPORT',
211             DATE_FORMAT => '%YYYY-%MM-%DD %hh:%mm:%ss',
212             TABLE_HEADERS => [(
213             [32, 'NAME'],
214             [7, 'TYPE'],
215             [19, 'LAST CHECK DATE'],
216             [7, 'STATUS'],
217             )],
218 1     1   5333 };
  1         2  
219              
220             eval { require App::MonM::Notifier::Agent };
221             my $NOTIFIER_LOADED = 1 unless $@;
222              
223             sub again {
224 0     0 1   my $self = shift;
225              
226             # Datadir & Tempdir
227 0 0         if ($self->option("datadir")) {
228 0           preparedir( $self->datadir() );
229             } else {
230 0           $self->datadir($self->tempdir());
231             }
232 0           preparedir( $self->tempdir() );
233              
234             # Notifier agent init
235 0           $self->{notifier} = undef;
236              
237 0           return $self->SUPER::again;
238             }
239             sub configure {
240 0     0 1   my $self = shift;
241 0           my $config = $self->configobj;
242              
243             # DBI object
244 0           my $dbi_file = File::Spec->catfile($self->datadir, App::MonM::Store::DB_FILENAME());
245 0   0       my $dbi_conf = $self->config('dbi') || {file => $dbi_file};
246 0 0         $dbi_conf = {file => $dbi_file} unless is_hash($dbi_conf);
247 0           my $dbi = new App::MonM::Store(%$dbi_conf);
248 0           $self->{_dbi} = $dbi;
249 0 0         if ($config->status) {
250 0 0         $self->error($dbi->error) if $dbi->error;
251 0           return 1;
252             }
253              
254             # Creting DB
255 0 0         if ($dbi->is_sqlite) {
256 0           printf("Creating local database %s...\n", $dbi->{file});
257             } else {
258 0           printf("Checking database %s...\n", $dbi->dsn);
259             }
260 0 0         if ($dbi->error) {
261 0           say( IS_TTY ? red("Fail") : "Fail");
262 0           $self->error($dbi->error);
263             } else {
264 0           say( IS_TTY ? green("Done") : "Done");
265             }
266              
267             # Creating configuration
268 0           my $skel = new CTK::Skel (
269             -name => PROJECTNAME,
270             -root => $self->root,
271             -skels => {
272             config => 'App::MonM::ConfigSkel',
273             },
274             -vars => {
275             PROJECT => PROJECTNAME,
276             PROJECTNAME => PROJECTNAME,
277             PREFIX => PREFIX,
278             },
279             -debug => $self->debugmode,
280             );
281             #say("Skel object: ", explain($skel));
282 0           printf("Creating configuration to %s...\n", $self->root);
283 0 0         if ($skel->build("config")) {
284 0           $self->CTK::Plugin::Config::init;
285 0           $config = $self->configobj;
286 0 0         unless ($config->status) {
287 0           say( IS_TTY ? red("Fail") : "Fail");
288 0           return 0;
289             }
290 0           say( IS_TTY ? green("Done") : "Done");
291             } else {
292 0           say( IS_TTY ? red("Fail") : "Fail");
293 0           $self->error(sprintf("Can't %s initialize: %s", PREFIX, $self->root));
294 0           return 0;
295             }
296              
297 0           return 1;
298             }
299 0     0 1   sub getdbi {shift->{_dbi}}
300              
301             __PACKAGE__->register_handler(
302             handler => "configure",
303             description => sprintf("Configure %s", PROJECTNAME),
304             code => sub { shift->configure });
305              
306             __PACKAGE__->register_handler(
307             handler => "config",
308             description => "Alias for configure command",
309             code => sub { shift->configure });
310              
311             __PACKAGE__->register_handler(
312             handler => "checkit",
313             description => "Checkit",
314             code => sub {
315             ### CODE:
316             my ($self, $meta, @arguments) = @_;
317             $self->configure or return 0;
318             my $status = 1;
319              
320             printf("Start of checking for %s...\n", HOSTNAME);
321             $self->wow("Will checked: %s", join(", ", @arguments)) if @arguments;
322              
323             # Get DBI
324             my $dbi = $self->getdbi;
325             return 0 if $dbi->error;
326              
327             # Get checkits
328             my @checkits = $self->_getCheckits(@arguments);
329             unless (scalar(@checkits)) {
330             $self->log_warn("No enabled configuration section found");
331             return 1;
332             }
333              
334             # Create Checkit object
335             my $checker = new App::MonM::Checkit;
336              
337             # Get all records from DB
338             my %all;
339             foreach my $r ($dbi->getall) {
340             $all{$r->{name}} = $r;
341             }
342             if ($dbi->error) {
343             $self->error($dbi->error);
344             return 0;
345             }
346              
347             # Init notifier and sending messages
348             if ($NOTIFIER_LOADED) {
349             $self->{notifier} = App::MonM::Notifier::Agent->new(
350             configobj => $self->configobj,
351             );
352             my $agent = $self->{notifier};
353             unless ($agent->status) {
354             $self->error($agent->error);
355             return 0;
356             }
357              
358             # Run sending messages
359             $agent->trysend() or do {
360             $self->log_error($agent->error);
361             };
362             }
363              
364             # Start
365             foreach my $checkit (sort {$a->{name} cmp $b->{name}} @checkits) {
366             my $ostat = 1; # Operation status
367             my $name = $checkit->{name};
368             my $info = $all{$name} || {};
369             my $id = $info->{id} || 0;
370             my $old = $info->{status} || 0;
371             my $got = ($old << 1) & 15;
372              
373             # Check
374             $ostat = $checker->check($checkit);
375             $self->log_info("Checking %s (%s >>> %s): %s", $name, $checker->source, $checker->message, $ostat ? 'OK' : "FAIL");
376             if ($ostat) {
377             $self->yep("Checking %s (%s >>> %s)", $name, $checker->source, $checker->message);
378             $got = setBit($got, 0); # Set first bit
379             } else {
380             $self->nope("Checking %s (%s >>> %s)", $name, $checker->source, $checker->message);
381             say(TAB9, red($checker->error));
382             }
383              
384             # Save data to database
385             my %rec = (
386             id => $id,
387             name => $name,
388             type => $checker->type,
389             source => $checker->source,
390             status => $got,
391             message => $checker->message,
392             );
393             if ($id) {
394             $dbi->set(%rec) or do {
395             $self->error($dbi->error);
396             $status = 0;
397             next;
398             };
399             } else {
400             $dbi->add(%rec) or do {
401             $self->error($dbi->error);
402             $status = 0;
403             next;
404             };
405             }
406              
407             # Triggers, Sending and notifies
408             # [0-0-1-1] = 3 -- OK
409             # [1-1-0-0] = 12 -- PROBLEM
410             if ($got == 3 or $got == 12 or $self->testmode) {
411             my %data = (
412             name => $name,
413             type => $checker->type,
414             source => $checker->source,
415             status => $ostat,
416             error => $checker->error,
417             message => $checker->message,
418             sendto => array($checkit, "sendto"),
419             trigger => array($checkit, "trigger"),
420             );
421             $self->notify(%data);
422             $self->trigger(%data);
423             }
424              
425             # General status
426             $status = 0 unless $ostat;
427             }
428              
429             # Cleaning DB
430             my $expire = getExpireOffset($self->config("expires") || $self->config("expire") || EXPIRES);
431             $dbi->clean(period => $expire) or do {
432             $self->error($dbi->error);
433             return 0;
434             };
435              
436             # Finish
437             printf("Finish of checking for %s (%s)\n", HOSTNAME, $self->tms);
438              
439             return $status;
440             });
441              
442             __PACKAGE__->register_handler(
443             handler => "report",
444             description => "Checkit report",
445             code => sub {
446             ### CODE:
447             my ($self, $meta, @arguments) = @_;
448             $self->configure or return 0;
449             my (@header, @errors, @report, @table);
450             my $status = 1;
451              
452             # Init
453             my $tbl = Text::SimpleTable->new(@{(TABLE_HEADERS)});
454              
455             # Start reporting
456             printf("Start of the checkit reporting for %s...\n", HOSTNAME);
457             $self->wow("Will checked: %s", join(", ", @arguments)) if @arguments;
458             $self->log_info("Start of the checkit reporting for \"%s\"", HOSTNAME);
459              
460             # Header
461             push @header, ["Hostname", HOSTNAME];
462              
463             # Get DBI
464             my $dbi = $self->getdbi;
465             push @header, ["Database DSN", $dbi->dsn];
466             if ($dbi->error) {
467             push @errors, $dbi->dsn, $dbi->error, "";
468             $self->log_error("%s: %s", $dbi->dsn, $dbi->error);
469             $self->nope($dbi->dsn);
470             say(TAB9, red($dbi->error));
471             $status = 0;
472             }
473              
474             # Get checkits
475             my @checkits = $self->_getCheckits(@arguments);
476             my $noc = scalar(@checkits);
477             push @header, ["Number of checks", $noc ? $noc : "no checks"];
478             unless ($noc) {
479             $self->log_warn("No enabled configuration section found");
480             $status = 0;
481             }
482              
483             # Get all records from DB
484             my %all;
485             if ($status) {
486             foreach my $r ($dbi->getall) {
487             $all{$r->{name}} = $r;
488             }
489             if ($dbi->error) {
490             push @errors, $dbi->dsn, $dbi->error, "";
491             $self->log_error("%s: %s", $dbi->dsn, $dbi->error);
492             $self->nope($dbi->dsn);
493             say(TAB9, red($dbi->error));
494             $status = 0;
495             }
496             }
497              
498             #
499             # General cycle
500             #
501             foreach my $checkit (sort {$a->{name} cmp $b->{name}} @checkits) {
502             my $name = $checkit->{name};
503             my $info = $all{$name} || {};
504             my $last = $info->{"time"} || 0;
505             my $v = $info->{status} || 0;
506             my $ostat = -1;
507             if (getBit($v, 0) && getBit($v, 1) && getBit($v, 2)) { # Ok
508             $ostat = 1;
509             } elsif ((getBit($v, 0) + getBit($v, 1)) == 0) { # Problem
510             $ostat = 0;
511             $status = 0;
512             }
513             $tbl->row($name, $info->{type} || 'http',
514             $last ? dtf(DATE_FORMAT, $last) : "",
515             $ostat ? $ostat > 0 ? 'OK' : 'UNKNOWN' : 'PROBLEM',
516             );
517             unless ($ostat) {
518             push @errors, sprintf("%s (%s >>> %s)", $name, $info->{source} || '', $info->{message} || ''), "";
519             }
520             #say(explain($info));
521             }
522             $tbl->hr;
523             $tbl->row('SUMMARY', "", "", $noc ? $status ? 'OK' : 'PROBLEM' : 'UNKNOWN');
524              
525             # Get SendMail config
526             my $sendmail = hash($self->config('sendmail'));
527             my $to = uv2null(value($sendmail, "to"));
528             my $send_report = 1 if $to && $to !~ /\@example.com$/;
529             push @header, ["Send report to", $to] if $send_report;
530              
531             #
532             # Report generate
533             #
534             push @header, ["Summary status", $status ? 'OK' : 'PROBLEM'];
535             my $report_name = $status ? "checking report" : "error report";
536             push @report, $self->_report_common(@header); # Common information
537             push @report, $self->_report_summary($status ? "All last checks successful" : "Errors occurred while checking"); # Summary table
538             push @report, $tbl->draw(); # Report table
539             push @report, $self->_report_errors(@errors); # List of occurred errors
540             if (IS_TTY || $self->verbosemode) { # Draw to TTY
541             printf("%s\n\n", "~" x SCREENWIDTH);
542             printf("The %s for last checks on %s\n\n", $report_name, HOSTNAME);
543             print join("\n", @report, "");
544             }
545              
546             #
547             # SendMail (Send report)
548             #
549             if ($send_report) {
550             unshift @report, $self->_report_title($report_name, "last checks");
551             push @report, $self->_report_footer();
552             my %ma = (); foreach my $k (keys %$sendmail) { $ma{"-".$k} = $sendmail->{$k} };
553             $ma{"-subject"} = sprintf("%s %s (%s on %s)", PROJECTNAME, $report_name, "last checks", HOSTNAME);
554             $ma{"-message"} = join("\n", @report);
555              
556             # Send!
557             my $sent = sendmail(%ma);
558             if ($sent) {
559             my $msg = sprintf("Mail has been sent to: %s", $to);
560             $self->wow($msg);
561             $self->log_info($msg);
562             } else {
563             my $msg = sprintf("Mail was not sent to: %s", $to);
564             $self->skip($msg);
565             $self->log_warning($msg);
566             }
567             }
568              
569             # Finish reporting
570             printf("Finish of the checkit reporting for %s (%s)\n", HOSTNAME, $self->tms);
571             $self->log_info("Finish of the checkit reporting for \"%s\" (%s)", HOSTNAME, $self->tms);
572              
573             return $status;
574             });
575              
576             __PACKAGE__->register_handler(
577             handler => "show",
578             description => "Show table data",
579             code => sub {
580             ### CODE:
581             my ($self, $meta, @arguments) = @_;
582             $self->configure or return 0;
583             my (@header, @errors, @report, @table);
584             my $status = 1;
585              
586             # Start
587             printf("Getting checkit data for %s...\n", HOSTNAME);
588              
589             # Get DBI
590             my $dbi = $self->getdbi;
591             if ($dbi->error) {
592             $self->log_error("%s: %s", $dbi->dsn, $dbi->error);
593             $self->nope($dbi->dsn);
594             say(TAB9, red($dbi->error));
595             return 0;
596             }
597              
598             # Get all records from DB
599             my %all;
600             foreach my $r ($dbi->getall) {
601             $all{$r->{name}} = $r;
602             }
603             if ($dbi->error) {
604             $self->log_error("%s: %s", $dbi->dsn, $dbi->error);
605             $self->nope($dbi->dsn);
606             say(TAB9, red($dbi->error));
607             return 0
608             }
609             my $n = scalar(keys %all) || 0;
610             unless ($n) {
611             $self->skip("No data");
612             return 1;
613             }
614              
615             if ($self->verbosemode) {
616             print(explain(\%all));
617             $self->yep("Number of records: %d", $n);
618             return 1;
619             }
620              
621             # Show table
622             eval <<'FORMATTING';
623             my @arr;
624             my $total;
625             say "";
626             say "Actual table data:
627             ----------------------+----------------------------------+---------------------+---------";
628             format STDOUT_TOP =
629             Name | Source string | Date | Status
630             ----------------------+----------------------------------+---------------------+---------
631             .
632             format STDOUT =
633             @<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<<<<<<<<<< | @||||||
634             @arr
635             .
636             format STDOUTBOT =
637             ----------------------+----------------------------------+---------------------+---------
638             SUMMARY | @||||||
639             $total
640             .
641             foreach my $v (sort {$a->{name} cmp $b->{name}} values %all) {
642             @arr = ();
643             my $stv = $v->{status} || 0;
644             my $ostat = -1;
645             if (getBit($stv, 0) && getBit($stv, 1) && getBit($stv, 2)) { # Ok
646             $ostat = 1;
647             } elsif ((getBit($stv, 0) + getBit($stv, 1)) == 0) { # Problem
648             $ostat = 0;
649             $status = 0;
650             }
651             push @arr, variant_stf($v->{name} // '', 20);
652             push @arr, variant_stf($v->{source} // '', 32);
653             push @arr, $v->{"time"} ? dtf(DATE_FORMAT, $v->{"time"}) : '';
654             push @arr, $ostat ? $ostat > 0 ? 'OK' : 'UNKNOWN' : 'PROBLEM';
655             write;
656             }
657             local $~ = "STDOUTBOT";
658             $total = $status ? "OK" : "PROBLEM";
659             write;
660             FORMATTING
661             say "";
662              
663             $self->yep("Number of records: %d", $n);
664             return $status;
665             });
666              
667             sub notify {
668 0     0 1   my $self = shift;
669 0           my %args = @_;
670 0   0       my $name = $args{name} || 'virtual';
671 0           my @errors;
672 0 0         push @errors, $args{error} if $args{error};
673              
674             # Get SendMail config
675 0           my $sendmail = hash($self->config('sendmail'));
676             #say(explain($sendmail));
677              
678             # Get SMSGW
679 0           my $smsgw = $self->config('smsgw');
680             #say(explain($sendmail));
681              
682             #
683             # Sorting receivers
684             #
685 0   0       my $sendto = $args{sendto} || [];
686 0           my (@for_sendmail, @for_smsgw, @for_notifier);
687 0           foreach my $rec (@$sendto) {
688 0 0         next unless $rec;
689 0 0         if ($rec =~ /\@/) { push @for_sendmail, $rec }
  0 0          
690             elsif ($rec =~ /^[\(+]*\d+/) {
691 0           $rec =~ s/[^0-9]//g;
692 0           push @for_smsgw, $rec;
693             }
694 0           else { push @for_notifier, $rec }
695             }
696              
697             #
698             # Make subject and sms body
699             #
700             my $subject = sprintf("%s: Available %s [%s]",
701 0 0         $args{status} ? 'OK' : 'PROBLEM',
702             $name,
703             HOSTNAME,
704             );
705              
706             #
707             # Send SMS
708             #
709 0           foreach my $phone (@for_smsgw) {
710 0 0         unless ($smsgw) {
711 0           my $msg = sprintf("Can't send SMS to %s: SMSGW is not defined!", $phone);
712 0           $self->skip($msg);
713 0           $self->log_error($msg);
714 0           push @errors, $msg;
715 0           next;
716             }
717 0           my $cmd = dformat($smsgw, {
718             PHONE => $phone,
719             NUM => $phone,
720             TEL => $phone,
721             PHONE => $phone,
722             NUM => $phone,
723             NUMBER => $phone,
724             SUBJECT => SMSSBJ,
725             SUBJ => SMSSBJ,
726             MSG => $subject,
727             MESSAGE => $subject,
728             });
729 0           my $exe_err = '';
730 0           my $exe_out = execute($cmd, undef, \$exe_err);
731 0 0         my $exe_stt = ($? >> 8) ? 0 : 1;
732 0 0         if ($exe_stt) {
733 0           my $msg = sprintf("# %s", $cmd);
734 0           $self->wow($msg);
735 0           $self->log_info($msg);
736 0 0 0       if (defined($exe_out) && length($exe_out) && $self->verbosemode) {
      0        
737 0           say(TAB9, cyan($exe_out)) if IS_TTY;
738 0           $self->log_info($exe_out);
739             }
740             } else {
741 0           my $msg = sprintf("Can't send SMS: %s", $cmd);
742 0           $self->skip($msg);
743 0           $self->log_warning($msg);
744 0           push @errors, $msg;
745 0 0         if ($exe_err) {
746 0           chomp($exe_err);
747 0           IS_TTY ? say(TAB9, yellow($exe_err)) : say($exe_err);
748 0           $self->log_error($exe_err);
749 0           push @errors, $exe_err;
750             }
751 0           push @errors, "";
752             }
753             }
754              
755             #
756             # Make headers
757             #
758 0           my @header;
759             push @header, (
760             ["Checkit", $name],
761             ["Type", $args{type} || 'http'],
762             ["Status", $args{status} ? 'OK' : 'PROBLEM'],
763             ["Source", $args{source} || "UNKNOWN"],
764 0 0 0       ["Message", $args{message} // ""],
      0        
      0        
765             );
766              
767             #
768             # Make email report message
769             #
770 0           my @report;
771 0 0         my $report_name = $args{status} ? "checking report" : "error report";
772 0           push @report, $self->_report_common(@header); # Common information
773 0 0         push @report, $self->_report_summary($args{status} ? "All checks successful" : "Errors occurred while checking"); # Summary table
774 0           push @report, $self->_report_errors(@errors); # List of occurred errors
775              
776             # Data for Emails only
777 0           unshift @report, $self->_report_title($report_name, $name);
778 0           push @report, $self->_report_footer();
779              
780             #
781             # Send report to Notifier (if installed)
782             #
783 0           my $agent = $self->{notifier};
784 0 0 0       if ($NOTIFIER_LOADED && $agent && @for_notifier) {
      0        
785 0           foreach my $to (shellwords(@for_notifier)) {
786             $agent->create(
787             to => $to,
788             subject => $subject,
789             message => join("\n", @report),
790 0 0         ) or do {
791 0           my $msg = sprintf("Can't send message via notifier: %s", $agent->error);
792 0           $self->skip($msg);
793 0           $self->log_warning($msg);
794             };
795 0 0         if ($agent->status) {
796 0           my $msg = sprintf("The message has been successfully queued for sending to: %s", $to);
797 0           $self->wow($msg);
798 0           $self->log_info($msg);
799             }
800             }
801             }
802              
803             #
804             # SendMail (Send report)
805             #
806 0           my %ma = (); foreach my $k (keys %$sendmail) { $ma{"-".$k} = $sendmail->{$k} };
  0            
  0            
807 0           $ma{"-subject"} = $subject;
808 0           $ma{"-message"} = join("\n", @report);
809 0           foreach my $to (@for_sendmail) {
810 0           $ma{"-to"} = $to;
811 0 0         my $sent = sendmail(%ma) if $to !~ /\@example.com$/;
812 0 0         if ($sent) {
813 0           my $msg = sprintf("Mail has been sent to: %s", $to);
814 0           $self->wow($msg);
815 0           $self->log_info($msg);
816             } else {
817 0           my $msg = sprintf("Mail was not sent to: %s", $to);
818 0           $self->skip($msg);
819 0           $self->log_warning($msg);
820             }
821             }
822              
823 0           return 1;
824             }
825             sub trigger {
826 0     0 1   my $self = shift;
827 0           my %args = @_;
828 0   0       my $name = $args{name} || 'virtual';
829 0   0       my $message = $args{message} || "";
830 0   0       my $source = $args{source} || "";
831              
832             #
833             # Make subject and sms body
834             #
835             my $subject = sprintf("%s: Available %s [%s]",
836 0 0         $args{status} ? 'OK' : 'PROBLEM',
837             $name,
838             HOSTNAME,
839             );
840              
841             #
842             # Execute
843             #
844 0   0       my $triggers = $args{trigger} || [];
845 0           foreach my $trg (@$triggers) {
846 0 0         next unless $trg;
847             my $cmd = dformat($trg, {
848             SUBJECT => $subject,
849             SUBJ => $subject,
850             MSG => $message,
851             MESSAGE => $message,
852             SOURCE => $source,
853             NAME => $name,
854             TYPE => $args{type} || "http",
855 0 0 0       STATUS => $args{status} ? 1 : 0,
856             });
857 0           my $exe_err = '';
858 0           my $exe_out = execute($cmd, undef, \$exe_err);
859 0 0         my $exe_stt = ($? >> 8) ? 0 : 1;
860 0 0         if ($exe_stt) {
861 0           my $msg = sprintf("# %s", $cmd);
862 0           $self->yep($msg);
863 0           $self->log_info($msg);
864 0 0 0       if (defined($exe_out) && length($exe_out) && $self->verbosemode) {
      0        
865 0           say(TAB9, green($exe_out)) if IS_TTY;
866 0           $self->log_info($exe_out);
867             }
868             } else {
869 0           my $msg = sprintf("Can't execute trigger: %s", $cmd);
870 0           $self->nope($msg);
871 0           $self->log_error($msg);
872 0 0         if ($exe_err) {
873 0           chomp($exe_err);
874 0           IS_TTY ? say(TAB9, red($exe_err)) : say($exe_err);
875 0           $self->log_error($exe_err);
876             }
877             }
878             }
879              
880 0           return 1;
881             }
882              
883             #######################
884             # Colored says methods
885             #######################
886             sub yep {
887 0     0 1   my $self = shift;
888 0           print(IS_TTY ? green('[ OK ]') : '[ OK ]', ' ', IS_TTY ? green(shift, @_) : sprintf(shift, @_), "\n");
889 0           return 1;
890             }
891             sub nope {
892 0     0 1   my $self = shift;
893 0           print(IS_TTY ? red('[ FAIL ]') : '[ FAIL ]', ' ', IS_TTY ? red(shift, @_) : sprintf(shift, @_), "\n");
894 0           return 0;
895             }
896             sub skip {
897 0     0 1   my $self = shift;
898 0           print(IS_TTY ? yellow('[ SKIP ]') : '[ SKIP ]', ' ', IS_TTY ? yellow(shift, @_) : sprintf(shift, @_), "\n");
899 0           return 1;
900             }
901             sub wow {
902 0     0 1   my $self = shift;
903 0           print(IS_TTY ? blue('[ INFO ]') : '[ INFO ]', ' ', IS_TTY ? blue(shift, @_) : sprintf(shift, @_), "\n");
904 0           return 1;
905             }
906              
907             # Private methods
908             sub _getCheckits {
909 0     0     my $self = shift;
910 0           my @names = @_;
911 0           my $sects = $self->config("checkit");
912 0           my $i = 0;
913 0           my @j = ();
914 0 0 0       if (ref($sects) eq 'ARRAY') { # Array
    0          
    0          
915 0           foreach my $r (@$sects) {
916 0 0 0       if ((ref($r) eq 'HASH') && exists $r->{enable}) { # Anonymous
    0          
917 0           $r->{name} = sprintf("virtual%d", ++$i);
918 0 0 0       next unless (!@names || grep {$r->{name} eq lc($_)} @names);
  0            
919 0           push @j, $r;
920             } elsif (ref($r) eq 'HASH') { # Named
921 0           foreach my $k (keys %$r) {
922 0           my $v = $r->{$k};
923 0 0         next unless ref($v) eq 'HASH';
924 0           $v->{name} = lc($k);
925 0 0 0       next unless (!@names || grep {$v->{name} eq lc($_)} @names);
  0            
926 0           push @j, $v;
927             }
928             }
929             }
930             } elsif ((ref($sects) eq 'HASH') && !exists $sects->{enable}) { # Hash {name => {...}}
931 0           foreach my $k (keys %$sects) {
932 0           my $v = $sects->{$k};
933 0 0         next unless ref($v) eq 'HASH';
934 0           $v->{name} = lc($k);
935 0 0 0       next unless (!@names || grep {$v->{name} eq lc($_)} @names);
  0            
936 0           push @j, $v;
937             }
938             } elsif (ref($sects) eq 'HASH') { # Hash {...}
939 0           $sects->{name} = sprintf("virtual%d", ++$i);
940 0 0 0       push @j, $sects if (!@names || grep {$sects->{name} eq lc($_)} @names);
  0            
941             }
942 0           return grep {$_->{enable}} @j;
  0            
943             }
944             sub _report_title {
945 0     0     my $self = shift;
946 0   0       my $title = shift || "report";
947 0   0       my $name = shift || "virtual";
948             return (
949 0           sprintf("Dear %s user,", PROJECTNAME),"",
950             sprintf("This is a automatic-generated %s for %s\non %s, created by %s/%s",
951             $title, $name, HOSTNAME, __PACKAGE__, $VERSION),"",
952             "Sections of this report:","",
953             " * Common information",
954             " * Summary",
955             " * List of occurred errors","",
956             );
957             }
958             sub _report_common {
959 0     0     my $self = shift;
960 0           my @hdr = @_;
961 0           my @rep = (
962             "-"x32,
963             "COMMON INFORMATION",
964             "-"x32,"",
965             );
966 0           my $maxlen = 0;
967 0           foreach my $r (@hdr) {
968 0 0         $maxlen = length($r->[0]) if $maxlen < length($r->[0])
969             }
970 0           foreach my $r (@hdr) {
971 0           push @rep, sprintf("%s %s: %s", $r->[0], " "x($maxlen-length($r->[0])), $r->[1]);
972             }
973 0           push @rep, "";
974 0           return (@rep);
975             }
976             sub _report_summary {
977 0     0     my $self = shift;
978 0   0       my $summary = shift || "Ok";
979 0           my @rep = (
980             "-"x32,
981             "SUMMARY",
982             "-"x32,"",
983             );
984 0           push @rep, $summary, "";
985 0           return (@rep);
986             }
987             sub _report_errors {
988 0     0     my $self = shift;
989 0           my @errs = @_;
990 0           my @rep = (
991             "-"x32,
992             "LIST OF OCCURRED ERRORS",
993             "-"x32,"",
994             );
995 0 0         if (@errs) {
996 0           push @rep, @errs;
997             } else {
998 0           push @rep, "No errors occurred";
999             }
1000 0           return (@rep, "");
1001             }
1002             sub _report_footer {
1003 0     0     my $self = shift;
1004 0           return sprintf(join("\n",
1005             "",
1006             "---",
1007             "Hostname : %s",
1008             "Program : %s (%s, Perl %s)",
1009             "Version : %s/%s",
1010             "Config file : %s",
1011             "PID : %d",
1012             "Work time : %s",
1013             "Generated : %s"
1014             ),
1015             HOSTNAME, $0, $^O, $^V, PROJECTNAME, $VERSION, $self->configfile(),
1016             $$, $self->tms(), dtf("%w, %DD %MON %YYYY %hh:%mm:%ss"),
1017             );
1018             }
1019              
1020             1;
1021              
1022             __END__