File Coverage

blib/lib/Monitoring/TT.pm
Criterion Covered Total %
statement 95 471 20.1
branch 21 204 10.2
condition 2 36 5.5
subroutine 16 36 44.4
pod 3 3 100.0
total 137 750 18.2


line stmt bran cond sub pod time code
1             package Monitoring::TT;
2              
3 4     4   2970 use strict;
  4         8  
  4         122  
4 4     4   17 use warnings;
  4         7  
  4         107  
5 4     4   2124 use utf8;
  4         49  
  4         18  
6 4     4   1901 use Pod::Usage;
  4         163074  
  4         540  
7 4     4   2717 use Getopt::Long;
  4         33891  
  4         18  
8 4     4   2537 use Template;
  4         63417  
  4         126  
9 4     4   1752 use Monitoring::TT::Identifier;
  4         11  
  4         120  
10 4     4   1370 use Monitoring::TT::Log qw/error warn info debug trace log/;
  4         12  
  4         283  
11 4     4   1768 use Monitoring::TT::Object;
  4         10  
  4         97  
12 4     4   1522 use Monitoring::TT::Render;
  4         9  
  4         120  
13 4     4   1405 use Monitoring::TT::Utils;
  4         9  
  4         18718  
14              
15             our $VERSION = '1.0.2';
16              
17             #####################################################################
18              
19             =head1 NAME
20              
21             Monitoring::TT - Generic Monitoring Config based on Template Toolkit Templates
22              
23             =head1 DESCRIPTION
24              
25             Generic Monitoring Config based on Template Toolkit Templates
26              
27             =cut
28              
29             #####################################################################
30              
31             =head1 CONSTRUCTOR
32              
33             =head2 new
34              
35             new(%options)
36              
37             =cut
38              
39             sub new {
40 3     3 1 1957 my($class, %options) = @_;
41 3         77 my $self = {
42             tt_opts => {
43             TRIM => 1,
44             RELATIVE => 1,
45             STAT_TTL => 60,
46             STRICT => 1,
47             }
48             };
49 3         9 bless $self, $class;
50              
51 3 50       13 $self->{'tt_opts'}->{'STRICT'} = 1 if $ENV{'TEST_AUTHOR'};
52 3 50       54 $self->{'tt_opts'}->{'STRICT'} = 1 if -f '.author';
53 3         8 for my $s (@{Monitoring::TT::Identifier::functions('Monitoring::TT::Render')}) {
  3         15  
54 69         73 $self->{'tt_opts'}->{'PRE_DEFINE'}->{$s} = \&{'Monitoring::TT::Render::'.$s};
  69         192  
55             }
56 3         10 $Monitoring::TT::Render::tt = $self;
57              
58 3         32 return $self;
59             }
60              
61             #####################################################################
62              
63             =head1 METHODS
64              
65             =head2 run
66              
67             run config generator and write it to the output folder
68              
69             =cut
70              
71             sub run {
72 0     0 1 0 my( $self ) = @_;
73 0 0       0 return unless $self->_get_options();
74 0         0 info('generating config from '.join(', ', @{$self->{'in'}}));
  0         0  
75 0         0 info('into '.$self->{'out'});
76 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
77 0 0       0 if(! -d $in.'/.') {
78 0         0 error($in.': '.$!);
79 0         0 exit 1;
80             }
81             }
82 0         0 $self->_run_hook('pre', join(',', @{$self->{'in'}}));
  0         0  
83              
84             # die if output directory already exists
85 0 0 0     0 if(-e $self->{'out'} and !$self->{'opt'}->{'force'}) {
86 0         0 my @files = glob($self->{'out'}.'/*');
87 0 0       0 if(scalar @files > 0) {
88 0         0 error($self->{'out'}.' does already exist and is not empty. (use --force to overwrite contents)');
89 0         0 exit 1;
90             }
91             }
92 0         0 $self->_mkdir_r($self->{'out'});
93              
94 0 0       0 info('using template filter: '.$self->{'opt'}->{'templatefilter'}) if $self->{'opt'}->{'templatefilter'};
95 0 0       0 info('using contact filter: '.$self->{'opt'}->{'contactfilter'}) if $self->{'opt'}->{'contactfilter'};
96 0 0       0 info('using host filter: '.$self->{'opt'}->{'hostfilter'}) if $self->{'opt'}->{'hostfilter'};
97              
98             # reset counter
99 0         0 $self->{'possible_types'} = {};
100 0         0 $self->{'possible_tags'} = {};
101 0         0 $self->{'possible_apps'} = {};
102              
103 0         0 $self->_copy_static_files();
104 0         0 $self->_build_dynamic_config();
105 0 0       0 $self->_check_typos() unless $self->{'opt'}->{'templatefilter'};
106 0         0 $self->_post_process();
107 0 0       0 $self->_print_stats() if $Monitoring::TT::Log::Verbose >= 2;
108 0         0 $self->_run_hook('post', join(',', @{$self->{'in'}}));
  0         0  
109 0         0 info('done');
110 0         0 return 0;
111             }
112              
113             #####################################################################
114              
115             =head2 tt
116              
117             return template toolkit object
118              
119             =cut
120              
121             sub tt {
122 6     6 1 15 my($self) = @_;
123              
124 6 100       39 return $self->{'_tt'} if $self->{'_tt'};
125              
126             # make some globals available in TT stash
127 2         5 $self->{'tt_opts'}->{'PRE_DEFINE'}->{'src'} = $self->{'in'};
128              
129 2         28 $self->{'_tt'} = Template->new($self->{'tt_opts'});
130 2         39495 $Template::Stash::PRIVATE = undef;
131              
132 2         12 return $self->{'_tt'};
133             }
134              
135             #####################################################################
136             # INTERNAL SUBS
137             #####################################################################
138             sub _get_options {
139 0     0   0 my($self) = @_;
140 0         0 Getopt::Long::Configure('no_ignore_case');
141 0         0 Getopt::Long::Configure('bundling');
142 0         0 $self->{'opt'} = {
143             files => [],
144             verbose => 1,
145             force => 0,
146             dryrun => 0,
147             };
148             GetOptions (
149             'h|help' => \$self->{'opt'}->{'help'},
150 0     0   0 'v|verbose' => sub { $self->{'opt'}->{'verbose'}++ },
151             'q|quiet' => \$self->{'opt'}->{'quiet'},
152             'V|version' => \$self->{'opt'}->{'version'},
153             'f|force' => \$self->{'opt'}->{'force'},
154             'cf|contactfilter=s' => \$self->{'opt'}->{'contactfilter'},
155             'hf|hostfilter=s' => \$self->{'opt'}->{'hostfilter'},
156             'tf|templatefilter=s' => \$self->{'opt'}->{'templatefilter'},
157             'n|dry-run' => \$self->{'opt'}->{'dryrun'},
158 0     0   0 '<>' => sub { push @{$self->{'opt'}->{'files'}}, $_[0] },
  0         0  
159 0 0       0 ) or $self->_usage();
160 0 0       0 if($self->{'opt'}->{'version'}) { print 'Version ', $VERSION,"\n"; exit 0; }
  0         0  
  0         0  
161 0 0       0 pod2usage({ -verbose => 2, -exit => 3 } ) if $self->{'opt'}->{'help'};
162 0 0       0 $self->_usage('please specify at least one input and output folder!') if scalar @{$self->{'opt'}->{'files'}} <= 1;
  0         0  
163 0         0 for my $f (@{$self->{'opt'}->{'files'}}) { $f =~ s/\/*$//gmx; }
  0         0  
  0         0  
164 0         0 $self->{'out'} = pop @{$self->{'opt'}->{'files'}};
  0         0  
165 0         0 $self->{'in'} = $self->{'opt'}->{'files'};
166 0 0       0 $self->{'opt'}->{'verbose'} = 0 if $self->{'opt'}->{'quiet'};
167 0 0       0 $self->{'opt'}->{'dryrun'} = 1 if $self->{'opt'}->{'contactfilter'};
168 0 0       0 $self->{'opt'}->{'dryrun'} = 1 if $self->{'opt'}->{'hostfilter'};
169 0 0       0 $self->{'opt'}->{'dryrun'} = 1 if $self->{'opt'}->{'templatefilter'};
170 0         0 $Monitoring::TT::Log::Verbose = $self->{'opt'}->{'verbose'};
171 0 0       0 info('Dry Run, Hooks won\'t be executed') if $self->{'opt'}->{'dryrun'};
172 0         0 return 1;
173             }
174              
175             #####################################################################
176             sub _usage {
177 0     0   0 my($self, $msg) = @_;
178 0 0       0 print $msg, "\n\n" if $msg;
179 0         0 print "usage: $0 [options] [...] \ndetailed help available with --help\n";
180 0         0 exit 3;
181             }
182              
183             #####################################################################
184             sub _copy_static_files {
185 0     0   0 my($self) = @_;
186 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
187 0 0       0 if(-d $in.'/static/.') {
188 0         0 my $cmd = 'cp -LR '.$in.'/static/* '.$self->{'out'}.'/';
189 0         0 debug($cmd);
190 0         0 `$cmd`;
191             }
192             }
193 0         0 return;
194             }
195              
196             #####################################################################
197             sub _build_dynamic_config {
198 0     0   0 my($self) = @_;
199             # main work block, dynamic object configuration
200 0         0 $self->_build_dynamic_object_config();
201              
202             # other files
203 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
204 0         0 for my $file (sort glob($in.'/*.cfg')) {
205 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $file !~ m/$self->{'opt'}->{'templatefilter'}/mx;
206 0         0 info('processing non object: '.$file);
207 0         0 my $outfile = $file;
208 0         0 $outfile =~ s/.*\///mx;
209 0 0       0 next if $outfile =~ m/^hosts.*\.cfg/gmx;
210 0 0       0 next if $outfile =~ m/^contacts.*\.cfg/gmx;
211 0         0 $outfile = $self->{'out'}.'/'.$outfile;
212 0         0 debug('writing: '.$outfile);
213 0 0       0 open(my $fh, '>', $outfile) or die('cannot write '.$outfile.': '.$!);
214 0         0 print $fh $self->_process_template($self->_read_replaced_template($file), {});
215 0         0 print $fh "\n";
216 0         0 close($fh);
217             }
218             }
219              
220 0         0 return;
221             }
222              
223             #####################################################################
224             # do the main work, this block is essential for maximum performance
225             sub _build_dynamic_object_config {
226 0     0   0 my($self) = @_;
227              
228             # detect input type
229 0         0 my $input_types = $self->_get_input_types($self->{'in'});
230              
231             # no dynamic config at all?
232 0 0       0 return unless scalar keys %{$input_types} > 0;
  0         0  
233              
234             # build templates
235 0         0 my $templates = {
236             contacts => $self->_build_template('conf.d', 'contacts'),
237             hosts => $self->_build_template('conf.d', 'hosts', [ 'conf.d/apps', 'conf.d/apps.cfg' ]),
238             };
239              
240 0         0 mkdir($self->{'out'}.'/conf.d');
241              
242 0         0 $self->{'data'} = { hosts => [], contacts => [] };
243 0         0 for my $type (keys %{$input_types}) {
  0         0  
244 0         0 my $typefilter = $self->{'opt'}->{substr($type,0,-1).'filter'};
245 0         0 my $obj_list = [];
246 0 0       0 trace('fetching data for '.$type) if $Monitoring::TT::Log::Verbose >= 4;
247 0         0 for my $cls (@{$input_types->{$type}}) {
  0         0  
248 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
249 0         0 my $data = $cls->read($in, $type);
250 0         0 for my $d (@{$data}) {
  0         0  
251 0         0 $d->{'montt'} = $self;
252 0         0 my $o = Monitoring::TT::Object->new($type, $d);
253 0 0       0 die('got no object') unless defined $o;
254 0 0 0     0 next if defined $typefilter and join(',', values %{$o}) !~ m/$typefilter/mx;
  0         0  
255 0 0       0 trace($o) if $Monitoring::TT::Log::Verbose >= 5;
256 0         0 push @{$obj_list}, $o;
  0         0  
257             }
258             }
259             }
260             # sort objects by name
261 0         0 @{$obj_list} = sort {$a->{'name'} cmp $b->{'name'}} @{$obj_list};
  0         0  
  0         0  
  0         0  
262 0         0 $self->{'data'}->{$type} = $obj_list;
263              
264 0         0 my $outfile = $self->{'out'}.'/conf.d/'.$type.'.cfg';
265 0         0 info('writing: '.$outfile);
266 0 0       0 open(my $fh, '>', $outfile) or die('cannot write '.$outfile.': '.$!);
267 0         0 print $fh $self->_process_template($templates->{$type}, { type => $type, data => $obj_list });
268 0         0 print $fh "\n";
269 0         0 close($fh);
270             }
271              
272 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
273 0         0 for my $file (reverse sort @{$self->_get_files($in.'/conf.d', '\.cfg')}) {
  0         0  
274 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $file !~ m/$self->{'opt'}->{'templatefilter'}/mx;
275 0 0       0 next if $file =~ m/^$in\/conf\.d\/apps/mx;
276 0 0       0 next if $file =~ m/^$in\/conf\.d\/contacts/mx;
277 0 0       0 next if $file =~ m/^$in\/conf\.d\/hosts/mx;
278 0         0 info('processing object file: '.$file);
279 0         0 my $outfile = $file;
280 0         0 $outfile =~ s/.*\///mx;
281 0         0 $outfile = $self->{'out'}.'/conf.d/'.$outfile;
282 0         0 debug('writing: '.$outfile);
283 0 0       0 open(my $fh, '>', $outfile) or die('cannot write '.$outfile.': '.$!);
284 0         0 print $fh $self->_process_template($self->_read_replaced_template($file), $self->{'data'});
285 0         0 print $fh "\n";
286 0         0 close($fh);
287             }
288             }
289              
290 0         0 return;
291             }
292              
293             #####################################################################
294             sub _print_stats {
295 0     0   0 my($self) = @_;
296 0         0 my $out = $self->{'out'};
297 0         0 info('written:');
298 0         0 for my $type (qw/host hostgroup hostdependency hostextinfo hostescalation
299             service servicegroup servicedependency serviceextinfo serviceescalation
300             contact contactgroup command timeperiod
301             /) {
302 0         0 my $num = $self->_grep_count($out, '^\s*define\s*'.$type.'\( \|{\)');
303 0 0       0 next if $num == 0;
304 0         0 info(sprintf('# %-15s %6s', $type, $num));
305             }
306 0         0 return;
307             }
308              
309             #####################################################################
310             sub _grep_count {
311 0     0   0 my($self, $dir, $pattern) = @_;
312 0         0 my $txt = `grep -r -c '$pattern' $dir 2>&1`;
313 0         0 my $total = 0;
314 0         0 for my $line (split/\n/mx, $txt) {
315 0 0       0 if($line =~ m/:(\d+)$/mx) {
316 0         0 $total += $1;
317             }
318             }
319 0         0 return $total;
320             }
321              
322             #####################################################################
323             sub _build_template {
324 0     0   0 my($self, $dir, $type, $appdirs) = @_;
325 0         0 my $shorttype = substr($type, 0, -1);
326 0         0 my $template = "[% FOREACH d = data %][% ".$shorttype." = d %]\n";
327 0         0 my $found = 0;
328 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
329 0         0 for my $path (glob($in.'/'.$dir.'/'.$type.'/ '.
330             $in.'/'.$dir.'/'.$type.'*.cfg')
331             ) {
332 0 0       0 trace('looking for '.$type.' templates in '.$path) if $Monitoring::TT::Log::Verbose >= 4;
333 0 0       0 if(-e $path) {
334 0         0 my $templates = $self->_get_files($path, '\.cfg');
335 0         0 for my $t (reverse sort @{$templates}) {
  0         0  
336 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $t !~ m|$self->{'opt'}->{'templatefilter'}|mx;
337 0         0 my $tags = $self->_get_tags_for_path($t, $path);
338 0         0 my $required_type = shift @{$tags};
  0         0  
339 0 0       0 info('adding '.$type.' template: '.$t.($required_type ? ' for type '.$required_type : '').(scalar @{$tags} > 0 ? ' with tags: '.join(' & ', @{$tags}) : ''));
  0 0       0  
  0         0  
340 0 0       0 if($required_type) {
341 0         0 $self->{$type.'possible_types'}->{$required_type} = 1;
342 0         0 $template .= "[% IF d.type == '$required_type' %]";
343             }
344 0         0 for my $tag (@{$tags}) {
  0         0  
345 0         0 $self->{$type.'possible_tags'}->{$tag} = 1;
346 0         0 $template .= "[% IF d.has_tag('$tag') %]";
347             }
348 0         0 $template .= $self->_read_replaced_template($t);
349 0         0 for my $tag (@{$tags}) {
  0         0  
350 0         0 $template .= "[% END %]";
351             }
352 0 0       0 $template .= "[% END %]" if $required_type;
353 0         0 $found++;
354 0         0 $template .= "\n";
355             }
356             }
357             }
358             }
359              
360             # add apps for hosts
361 0 0 0     0 if(defined $appdirs and scalar @{$appdirs} > 0) {
  0         0  
362 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
363 0         0 for my $path (@{$appdirs}) {
  0         0  
364 0         0 $path = $in.'/'.$path;
365 0 0       0 trace('looking for '.$type.' apps in '.$path) if $Monitoring::TT::Log::Verbose >= 4;
366 0 0       0 if(-e $path) {
367 0         0 my $templates = $self->_get_files($path, '\.cfg');
368 0         0 for my $t (reverse sort @{$templates}) {
  0         0  
369 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $t !~ m|$self->{'opt'}->{'templatefilter'}|mx;
370 0         0 my $apps = $self->_get_tags_for_path($t, $path);
371 0 0       0 info('adding apps template: '.$t.(scalar @{$apps} > 0 ? ' for apps: '.join(' & ', @{$apps}) : ''));
  0         0  
  0         0  
372 0         0 for my $app (@{$apps}) {
  0         0  
373 0         0 $self->{'possible_apps'}->{$app} = 1;
374 0         0 $template .= "[% IF d.has_app('$app') %]";
375             }
376 0         0 $template .= $self->_read_replaced_template($t);
377 0         0 for my $app (@{$apps}) {
  0         0  
378 0         0 $template .= "[% END %]";
379             }
380 0         0 $found++;
381 0         0 $template .= "\n";
382             }
383             }
384             }
385             }
386             }
387              
388 0 0       0 if($found == 0) {
389 0         0 debug('no templates for type '.$type.' found');
390 0         0 return '';
391             }
392 0         0 $template .= "[% END %]\n";
393 0 0       0 if($Monitoring::TT::Log::Verbose >= 4) {
394 0         0 trace('created template:');
395 0         0 trace($template);
396             }
397 0         0 return $template;
398             }
399              
400             #####################################################################
401             sub _get_files {
402 0     0   0 my($self, $dir, $pattern) = @_;
403 0 0 0     0 if(!-d $dir and $dir =~ m/$pattern/mx) {
404 0         0 return([$dir]);
405             }
406 0         0 my @files;
407 0 0       0 return \@files unless -d $dir;
408 0 0       0 opendir(my $d, $dir) or die("cannot read directory $dir: $!");
409 0         0 while(my $file = readdir($d)) {
410 0 0       0 next if substr($file,0,1) eq '.';
411 0 0       0 if(-d $dir.'/'.$file.'/.') {
412 0         0 push @files, @{$self->_get_files($dir.'/'.$file, $pattern)};
  0         0  
413             } else {
414 0 0       0 next if $file !~ m/$pattern/mx;
415 0         0 push @files, $dir."/".$file;
416             }
417             }
418 0         0 return \@files;
419             }
420              
421             #####################################################################
422             sub _process_template {
423 3     3   9 my($self, $template, $data) = @_;
424              
425 3 100       10 if(!defined $self->{'_config_template'}) {
426 2         5 $self->{'_config_template'} = "";
427 2         2 for my $in (@{$self->{'in'}}) {
  2         7  
428 0         0 debug('looking for a '.$in.'/config.cfg');
429 0 0       0 if(-e $in.'/config.cfg') {
430 0         0 debug('added config template '.$in.'/config.cfg');
431 0         0 $self->{'_config_template'} .= $self->_read_replaced_template($in.'/config.cfg')."\n";
432             }
433             }
434             }
435 3         8 $template = $self->{'_config_template'}.$template;
436              
437 3 50       10 if($Monitoring::TT::Log::Verbose >= 4) {
438 0         0 trace('template:');
439 0         0 trace('==========================');
440 0         0 trace($template);
441 0         0 trace('==========================');
442             }
443              
444 3         4 my $output;
445 3 50       9 $self->tt->process(\$template, $data, \$output) or $self->_template_process_die($template, $data);
446              
447             # clean up result
448 3         52387 $output =~ s/^\s*$//sgmxo;
449 3         11 $output =~ s/^\n//gmxo;
450              
451 3         11 return $output;
452             }
453              
454             #####################################################################
455             sub _post_process {
456 0     0   0 my($self) = @_;
457 0         0 my $out = $self->{'out'};
458 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
459 0         0 for my $processor (sort glob($in.'/post_process*')) {
460 0         0 info('postprocessing with '.$processor);
461 0 0       0 if(!-x $processor) {
462 0         0 error("post processor ".$processor." must be executable");
463 0         0 next;
464             }
465 0         0 my $res = `$processor $out`;
466 0         0 my $rc = $?;
467 0 0       0 if($rc != 0) {
468 0         0 warn($res);
469             }
470             }
471             }
472 0         0 return;
473             }
474              
475             #####################################################################
476             sub _get_input_classes {
477 1     1   6 my($self, $folders) = @_;
478 1         1 my $types = [];
479              
480 1         2 for my $dir (@{$folders}) {
  1         2  
481 0 0       0 next unless -d $dir.'/lib/.';
482 0         0 unshift @INC, "$dir/lib";
483 0 0       0 trace('added '.$dir.'/lib to @INC') if $Monitoring::TT::Log::Verbose >= 4;
484             }
485              
486 1 50       3 if($Monitoring::TT::Log::Verbose >= 4) {
487 0         0 trace('@INC:');
488 0         0 trace(\@INC);
489             }
490              
491 1         2 my $uniq_types = {};
492 1         1 my $uniq_libs = {};
493 1         2 for my $inc (@INC) {
494 12 100       29 next if defined $uniq_libs->{$inc};
495 10         17 $uniq_libs->{$inc} = 1;
496 10         306 my @files = glob($inc.'/Monitoring/TT/Input/*.pm');
497 10         230 for my $file (glob($inc.'/Monitoring/TT/Input/*.pm')) {
498 4 50       11 trace('found input class: '.$file) if $Monitoring::TT::Log::Verbose >= 4;
499 4         43 $file =~ s|^$inc/Monitoring/TT/Input/||mx;
500 4         12 $file =~ s|\.pm$||mx;
501 4 100       12 push @{$types}, $file unless defined $uniq_types->{$types}->{$file};
  2         4  
502 4         12 $uniq_types->{$types}->{$file} = 1;
503             }
504             }
505 1         6 return $types;
506             }
507              
508             #####################################################################
509             sub _get_input_types {
510 0     0   0 my($self, $folders) = @_;
511 0         0 my $input_types = {};
512 0         0 my $input_classes = $self->_get_input_classes($folders);
513 0         0 for my $t (@{$input_classes}) {
  0         0  
514 0         0 debug('requesting input files from: '.$t);
515 0         0 my $objclass = 'Monitoring::TT::Input::'.$t;
516             ## no critic
517 0         0 eval "require $objclass;";
518             ## use critic
519 0 0       0 error($@) if $@;
520 0         0 my $obj = \&{$objclass."::new"};
  0         0  
521 0         0 my $it = &$obj($objclass, montt => $self);
522 0         0 my $types = $it->get_types($folders);
523 0 0       0 trace('input \''.$t.'\' supports: '.join(', ', @{$types})) if $Monitoring::TT::Log::Verbose >= 4;
  0         0  
524 0         0 for my $type (@{$types}) {
  0         0  
525 0 0       0 $input_types->{$type} = [] unless defined $input_types->{$type};
526 0         0 push @{$input_types->{$type}}, $it;
  0         0  
527             }
528             }
529 0         0 return $input_types;
530             }
531              
532             #####################################################################
533             sub _run_hook {
534 0     0   0 my($self, $name, $args) = @_;
535 0 0       0 return if $self->{'opt'}->{'dryrun'};
536 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
537 0         0 my $hook = $in.'/hooks/'.$name;
538 0 0       0 trace("hook: looking for ".$hook) if $Monitoring::TT::Log::Verbose >= 4;
539 0 0       0 if(-x $hook) {
540 0         0 my $cmd = $hook;
541 0 0       0 $cmd = $cmd." ".$args if defined $args;
542 0         0 debug($cmd);
543 0         0 open(my $ph, '-|', $cmd);
544 0         0 while(my $line = <$ph>) {
545 0         0 log($line);
546             }
547 0         0 close($ph);
548 0         0 my $rc = $?>>8;
549 0         0 debug('hook returned: '.$rc);
550 0 0       0 if($rc) {
551 0         0 debug(' -> exiting');
552 0         0 exit $rc;
553             }
554             }
555             }
556 0         0 return;
557             }
558              
559             #####################################################################
560             sub _read_replaced_template {
561 3     3   8 my($self, $template) = @_;
562 3         8 $template =~ s|//|/|gmxo;
563 3         9 my $text = '[%# SRC '.$template.':1 #%]';
564 3 50       113 open(my $fh, '<', $template) or die("cannot read: ".$template.': '.$!);
565 3         69 while(my $line = <$fh>) {
566             # remove utf8 file bom
567 25 100       46 if($. == 1) {
568 3         7 my $bom = pack("CCC", 0xef, 0xbb, 0xbf);
569 3 50       11 if(substr($line,0,3) eq $bom) {
570 0         0 $line = substr($line, 3);
571             }
572             }
573 25         36 $text .= $line;
574 25 100       88 if($line =~ m/^define\s+(\w+)/mxo) {
575 3 50 33     30 if($1 eq 'service' or $1 eq 'host' or $1 eq 'contact') {
      33        
576 3         16 $text .= ' _SRC '.$template.':'.$.."\n";
577             } else {
578 0         0 $text .= '# SRC '.$template.':'.$.."\n";
579             }
580             }
581             }
582 3         24 close($fh);
583 3         26 return $text;
584             }
585              
586             #####################################################################
587             sub _get_tags_for_path {
588 0     0     my($self, $path, $basepath) = @_;
589 0           my $tmppath = lc $path;
590 0           $tmppath =~ s|^$basepath||mx;
591 0           $tmppath =~ s|\.cfg$||mx;
592 0           $tmppath =~ s|^/||mx;
593 0           my @tags = split(/\//mx, $tmppath);
594 0           return \@tags;
595             }
596              
597             #####################################################################
598             sub _template_process_die {
599 0     0     my($self, $template, $data) = @_;
600 0           my $tterror = "".$self->tt->error();
601 0           my $already_printed = 0;
602              
603             # try to find file / line
604 0 0         if($tterror =~ m/input\s+text\s+line\s+(\d+)/mx) {
605 0           my $linenr = $1;
606 0           my @lines = split/\n/mx, $template;
607 0           my($realfile, $realline) = $self->_get_file_and_line_for_error(\@lines, $linenr);
608 0 0         if($realfile) {
609 0           my $newloc = $realfile.' line '.$realline;
610 0           $tterror =~ s|input\s+text\s+line\s+\d+|$newloc|gmx;
611             }
612             }
613              
614             # var.undef error - undefined variable: host.tag('contact_groups')
615 0 0         if($tterror =~ m/var\.undef\ error\ -\ undefined\ variable:\s+(.*)$/mx) {
616 0           my $err = $1;
617 0           my $linenr = 0;
618 0           error($tterror);
619 0           $already_printed = 1;
620 0           my @lines = split/\n/mx, $template;
621 0           for my $line (@lines) {
622 0           $linenr++;
623 0 0         if($line =~ m/\Q$err\E/mx) {
624 0           my($realfile, $realline) = $self->_get_file_and_line_for_error(\@lines, $linenr);
625 0 0         if($realfile) {
626 0           error('occurs in: '.$realfile.':'.$realline);
627             }
628             }
629             }
630             }
631              
632 0 0         error($tterror) unless $already_printed;
633 0           debug('in template:');
634 0           debug($template);
635 0 0         trace($data) if $Monitoring::TT::Log::Verbose >= 4;
636 0           exit 1;
637             }
638              
639             #####################################################################
640             sub _get_file_and_line_for_error {
641 0     0     my($self, $lines, $linenr) = @_;
642 0           for(my $x = $linenr; $x >= 0; $x--) {
643 0 0 0       if(defined $lines->[$x] and $lines->[$x] =~ m/SRC\s+(.*):(\d+)/mx) {
644 0           my $diff = $x - $2 + 1;
645 0           return($1, ($linenr - $diff))
646             }
647             }
648 0           return(undef, undef);
649             }
650              
651             #####################################################################
652             sub _mkdir_r {
653 0     0     my($self, $dir) = @_;
654 0           my $path = '';
655 0           for my $part (split/(\/)/mx, $dir) {
656 0           $path .= $part;
657 0 0         next if $path eq '';
658 0 0         mkdir($path) unless -d $path;
659             }
660 0           return;
661             }
662              
663             #####################################################################
664             sub _check_typos {
665 0     0     my($self) = @_;
666 0           for my $type (qw/hosts contacts/) {
667 0           for my $o (@{$self->{'data'}->{$type}}) {
  0            
668 0 0 0       if($o->{'type'} and $o->{'type'} ne 'contact') {
669 0 0         warn('unused type \''.$o->{'type'}.'\' defined in '.$o->{'file'}.':'.$o->{'line'}) unless defined $self->{$type.'possible_types'}->{$o->{'type'}};
670             }
671 0 0         if($o->{'tags'}) {
672 0           for my $t (keys %{$o->{'tags'}}) {
  0            
673 0 0         next if substr($t,0,1) eq '_';
674 0 0         warn('unused tag \''.$t.'\' defined in '.$o->{'file'}.':'.$o->{'line'}) unless defined $self->{$type.'possible_tags'}->{$t};
675             }
676             }
677 0 0         if($o->{'apps'}) {
678 0           for my $a (keys %{$o->{'apps'}}) {
  0            
679 0 0         warn('unused app \''.$a.'\' defined in '.$o->{'file'}.':'.$o->{'line'}) unless defined $self->{$type.'possible_apps'}->{$a};
680             }
681             }
682             }
683             }
684 0           return;
685             }
686              
687             =head1 AUTHOR
688              
689             Sven Nierlein, 2013,
690              
691             =cut
692              
693             1;